diff --git a/api/package.json b/api/package.json
index 7052ebc3..fc4f08c3 100644
--- a/api/package.json
+++ b/api/package.json
@@ -23,7 +23,7 @@
"dependencies": {
"@data-fair/lib-express": "^1.22.5",
"@data-fair/lib-node": "^2.12.1",
- "@data-fair/lib-node-registry": "^0.5.0",
+ "@data-fair/lib-node-registry": "^0.6.0",
"@data-fair/lib-utils": "^1.10.1",
"@data-fair/processings-shared": "*",
"ajv": "^8.17.1",
diff --git a/docker-compose.yml b/docker-compose.yml
index e084899d..36e598a3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -112,7 +112,7 @@ services:
registry:
profiles:
- dev
- image: ghcr.io/data-fair/registry:feat-npm-noarch
+ image: ghcr.io/data-fair/registry:main
network_mode: host
depends_on:
- mongo
diff --git a/package-lock.json b/package-lock.json
index 8375559a..a58324fc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,7 +47,7 @@
"dependencies": {
"@data-fair/lib-express": "^1.22.5",
"@data-fair/lib-node": "^2.12.1",
- "@data-fair/lib-node-registry": "^0.5.0",
+ "@data-fair/lib-node-registry": "^0.6.0",
"@data-fair/lib-utils": "^1.10.1",
"@data-fair/processings-shared": "*",
"ajv": "^8.17.1",
@@ -596,9 +596,9 @@
}
},
"node_modules/@data-fair/lib-node-registry": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/@data-fair/lib-node-registry/-/lib-node-registry-0.5.0.tgz",
- "integrity": "sha512-WFh2BFrzFiRFCzYO/x41Ax7RUop8Gs9DVcOr2wHtC7zsZiVpre13PsJvNwkL0L3pdiwtGSUuaHFv3C6XNfBwdQ==",
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@data-fair/lib-node-registry/-/lib-node-registry-0.6.0.tgz",
+ "integrity": "sha512-TT3IFJwOwksl6rGi1JIz7tBM0M3NWyDcHR3orsAtF2UGn64cvQti829lfuhTAmGOhWA/nWjWL4kxDYliOKsJNA==",
"license": "MIT",
"dependencies": {
"@types/resolve-path": "^1.4.3",
@@ -12221,7 +12221,7 @@
"worker": {
"dependencies": {
"@data-fair/lib-node": "^2.12.1",
- "@data-fair/lib-node-registry": "^0.5.0",
+ "@data-fair/lib-node-registry": "^0.6.0",
"@data-fair/processings-shared": "*",
"@mdi/js": "^7.4.47",
"axios": "^1.8.3",
diff --git a/ui/src/pages/processings/[id]/index.vue b/ui/src/pages/processings/[id]/index.vue
index cc9a09d6..dcbc4708 100644
--- a/ui/src/pages/processings/[id]/index.vue
+++ b/ui/src/pages/processings/[id]/index.vue
@@ -54,6 +54,10 @@
{{ timezoneLabel(node.data.timeZone) }}
+
import type { Processing } from '#api/types'
-import type { RegistryArtefact } from '~/composables/use-plugin-fetch'
-import { ofetch } from 'ofetch'
import cronstrue from 'cronstrue'
import 'cronstrue/locales/en'
import 'cronstrue/locales/fr'
+import type { RegistryArtefact } from '~/composables/use-plugin-fetch'
+
+import useFetch from '@data-fair/lib-vue/fetch.js'
import { resolvedSchema as contractProcessing } from '#api/types/processing/index.ts'
import timeZones from 'timezones.json'
import Vjsf, { type Options as VjsfOptions } from '@koumoul/vjsf'
@@ -111,9 +116,6 @@ const valid = ref(false)
const edited = ref(false)
const editProcessing: Ref = ref(null)
const processing: Ref = ref(null)
-const plugin: Ref = ref(null)
-const pluginBroken = ref(false)
-const configSchema: Ref | null> = ref(null)
const runs: Ref> = ref([])
/*
@@ -128,51 +130,58 @@ onMounted(async () => {
}, {
text: processing.value?.title || ''
}])
- await fetchPlugin()
})
async function fetchProcessing () {
processing.value = await $fetch(`/processings/${processingId}`)
if (processing.value) editProcessing.value = { ...processing.value }
}
-async function fetchPlugin () {
- pluginBroken.value = false
- if (!processing.value?.plugin) return
- // Display metadata comes from registry — `processing.plugin` is the artefact id.
- // The config schema is read out of the cached package.json by the
- // processings API — registry doesn't know or care what's inside packages.
- //
- // Registry returns 404 when the plugin has been deleted, 403 when the
- // owner has lost access. We collapse both into pluginBroken=true and
- // render a banner; the config-schema fetch's 404 (no schema for this
- // plugin) is a separate, narrower state that does NOT trigger the banner.
- //
- // Hit the registry with the bare `ofetch` — not the app's `$fetch`, whose
- // `/processings/api/v1` baseURL would rewrite this to
- // `/processings/api/v1/registry/...` and 404. Registry is mounted at
- // `/registry` of the current domain (same convention as use-plugin-fetch).
- const artefactResult = await ofetch(
- `/registry/api/v1/artefacts/${encodeURIComponent(processing.value.plugin)}`
- ).then(
- (data) => ({ ok: true as const, data }),
- (err) => {
- const status = err?.statusCode ?? err?.status
- if (status === 404 || status === 403) return { ok: false as const }
- throw err
- }
- )
- if (!artefactResult.ok) {
- pluginBroken.value = true
- return
- }
- plugin.value = artefactResult.data
- configSchema.value = await $fetch>(
- `/processings/${processingId}/plugin-config-schema`
- ).catch(err => {
- if (err?.statusCode === 404 || err?.status === 404) return null
- throw err
- })
-}
+
+/*
+ Plugin metadata + config schema fetches.
+
+ Both kick off in parallel as soon as `processing` resolves — useFetch
+ watches its reactive URL and waits while it's null.
+
+ - pluginBroken: 404 (deleted) or 403 (access revoked) on the artefact.
+ Surfaces the banner and suppresses the form.
+ - configSchema null: legitimate "this plugin ships no schema" — distinct
+ from a registry error and does NOT trigger the banner.
+
+ Same-domain assumption: registry is mounted at `/registry` of the current
+ domain, so an absolute path bypasses `$fetch`'s `/processings/api/v1`
+ baseURL (which would rewrite `/registry/...` to a 404).
+*/
+const pluginFetch = useFetch(
+ computed(() => processing.value?.plugin
+ ? `/registry/api/v1/artefacts/${encodeURIComponent(processing.value.plugin)}`
+ : null),
+ { notifError: false }
+)
+const plugin = computed(() => pluginFetch.data.value)
+const pluginBroken = computed(() => {
+ const status = pluginFetch.error.value?.statusCode
+ return status === 404 || status === 403
+})
+
+const configSchemaFetch = useFetch>(
+ computed(() => processing.value?.plugin
+ ? `${$apiPath}/processings/${processingId}/plugin-config-schema`
+ : null),
+ { notifError: false }
+)
+const configSchema = computed | null>(() => {
+ const err = configSchemaFetch.error.value
+ // The endpoint returns 404 to mean "this plugin doesn't ship a schema"; we
+ // collapse that to null so the page degrades gracefully (no form, no
+ // banner). Other errors are left to surface through the data binding.
+ if (err && (err.statusCode === 404 || err.status === 404)) return null
+ return configSchemaFetch.data.value
+})
+
+const pluginFetchPending = computed(() =>
+ pluginFetch.loading.value || configSchemaFetch.loading.value
+)
/*
Permissions
diff --git a/worker/package.json b/worker/package.json
index c72cf119..aaefe973 100644
--- a/worker/package.json
+++ b/worker/package.json
@@ -20,7 +20,7 @@
},
"dependencies": {
"@data-fair/lib-node": "^2.12.1",
- "@data-fair/lib-node-registry": "^0.5.0",
+ "@data-fair/lib-node-registry": "^0.6.0",
"@data-fair/processings-shared": "*",
"@mdi/js": "^7.4.47",
"axios": "^1.8.3",