@narrative.io/jsonforms-provider-protocols 1.1.0-beta.4 → 1.1.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/dist/protocols/rest_api.d.ts +1 -0
- package/dist/protocols/rest_api.d.ts.map +1 -1
- package/dist/protocols/rest_api.js +6 -1
- package/dist/protocols/rest_api.js.map +1 -1
- package/dist/vue/composables/useDerive.d.ts.map +1 -1
- package/dist/vue/composables/useDerive.js +16 -4
- package/dist/vue/composables/useDerive.js.map +1 -1
- package/package.json +1 -1
- package/src/protocols/rest_api.ts +8 -1
- package/src/vue/composables/useDerive.ts +26 -6
package/README.md
CHANGED
|
@@ -158,6 +158,67 @@ Control when data is fetched:
|
|
|
158
158
|
- `onFocus` - Load when field receives focus
|
|
159
159
|
- `query` - Load when user types (autocomplete)
|
|
160
160
|
|
|
161
|
+
### Derive Functionality
|
|
162
|
+
Auto-populate fields from form data or external sources:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"type": "Control",
|
|
167
|
+
"scope": "#/properties/derived_field",
|
|
168
|
+
"options": {
|
|
169
|
+
"derive": "country",
|
|
170
|
+
"mode": "follow",
|
|
171
|
+
"readonly": true
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### External Data Support
|
|
177
|
+
Access external data sources separate from form data using the `externalData()` syntax:
|
|
178
|
+
|
|
179
|
+
```vue
|
|
180
|
+
<script setup>
|
|
181
|
+
// Provide external data to the form
|
|
182
|
+
const externalData = ref({
|
|
183
|
+
user: { preferences: { theme: "dark" } },
|
|
184
|
+
tree: { name: "Oak", type: "Deciduous" }
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
provide('externalData', externalData)
|
|
188
|
+
</script>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"type": "Control",
|
|
194
|
+
"scope": "#/properties/tree_preference",
|
|
195
|
+
"options": {
|
|
196
|
+
"derive": "externalData(tree.name)",
|
|
197
|
+
"mode": "follow",
|
|
198
|
+
"readonly": true
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Error Handling
|
|
204
|
+
Control error display behavior with the `showError` property:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"provider": {
|
|
209
|
+
"protocol": "rest_api",
|
|
210
|
+
"config": {
|
|
211
|
+
"url": "https://api.example.com/data",
|
|
212
|
+
"showError": false,
|
|
213
|
+
"items": "$.data[*]",
|
|
214
|
+
"map": { "label": "$.name", "value": "$.id" }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
When `showError` is `false`, failed requests return empty results instead of throwing errors. Defaults to `true`.
|
|
221
|
+
|
|
161
222
|
### Composables
|
|
162
223
|
Use providers directly in your components:
|
|
163
224
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest_api.d.ts","sourceRoot":"","sources":["../../src/protocols/rest_api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,QAAQ,EAGR,UAAU,EACX,MAAM,eAAe,CAAC;AAEvB,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IACrE,QAAQ,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,IAAI,CAAC,EAAE,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"rest_api.d.ts","sourceRoot":"","sources":["../../src/protocols/rest_api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,QAAQ,EAGR,UAAU,EACX,MAAM,eAAe,CAAC;AAEvB,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IACrE,QAAQ,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AA0DF,eAAO,MAAM,eAAe,QAAO,QAAQ,CAAC,UAAU,CAuEpD,CAAC"}
|
|
@@ -73,7 +73,12 @@ const RestApiProtocol = () => ({
|
|
|
73
73
|
);
|
|
74
74
|
}
|
|
75
75
|
const res = await fetch(url.toString(), requestInit);
|
|
76
|
-
if (!res.ok)
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
if (cfg.showError !== false) {
|
|
78
|
+
throw new Error(`REST ${res.status}`);
|
|
79
|
+
}
|
|
80
|
+
return { items: [], ttl: 0 };
|
|
81
|
+
}
|
|
77
82
|
const json = await res.json();
|
|
78
83
|
const items = jp(json, cfg.items);
|
|
79
84
|
for (const it of items) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rest_api.js","sources":["../../src/protocols/rest_api.ts"],"sourcesContent":["import { jp } from \"../core/jsonpath\";\nimport { renderObj, renderTpl } from \"../core/templating\";\nimport type {\n Protocol,\n ProviderItem,\n ProviderOutput,\n AuthConfig,\n} from \"../core/types\";\n\nexport type RestApiCfg = {\n url: string;\n method?: \"GET\" | \"POST\";\n headers?: Record<string, string>;\n query?: Record<string, unknown>;\n body?: unknown;\n items: string; // JSONPath to array; e.g. \"$.items[*]\"\n map: { label: string; value: string; meta?: Record<string, string> }; // relative to each item\n paginate?: { cursorPath: string; param: string; maxPages?: number };\n auth?: AuthConfig;\n};\n\nfunction buildAuthHeaders(\n auth?: AuthConfig,\n globalAuth?: Record<string, unknown>,\n): Record<string, string> {\n const headers: Record<string, string> = {};\n\n if (!auth) return headers;\n\n // Handle \"use\" reference to global auth\n if (auth.use && globalAuth?.[auth.use]) {\n const globalValue = globalAuth[auth.use];\n const value =\n typeof globalValue === \"function\" ? globalValue() : globalValue;\n\n if (auth.use === \"apiKey\") {\n headers[\"X-API-Key\"] = String(value);\n } else if (auth.use === \"bearer\") {\n headers[\"Authorization\"] = `Bearer ${value}`;\n } else if (auth.use === \"token\") {\n headers[\"Authorization\"] = `Token ${value}`;\n }\n return headers;\n }\n\n // Handle direct auth values\n if (auth.apiKey) {\n const value =\n typeof auth.apiKey === \"function\" ? auth.apiKey() : auth.apiKey;\n headers[\"X-API-Key\"] = String(value);\n }\n\n if (auth.bearer) {\n const value =\n typeof auth.bearer === \"function\" ? auth.bearer() : auth.bearer;\n headers[\"Authorization\"] = `Bearer ${value}`;\n }\n\n if (auth.token) {\n const value = typeof auth.token === \"function\" ? auth.token() : auth.token;\n headers[\"Authorization\"] = `Token ${value}`;\n }\n\n // Handle custom auth fields\n for (const [key, value] of Object.entries(auth)) {\n if (\n ![\"use\", \"apiKey\", \"bearer\", \"token\"].includes(key) &&\n value !== undefined\n ) {\n const authValue = typeof value === \"function\" ? value() : value;\n headers[key] = String(authValue);\n }\n }\n\n return headers;\n}\n\nexport const RestApiProtocol = (): Protocol<RestApiCfg> => ({\n protocol: \"rest_api\",\n async resolve(cfg, ctx): Promise<ProviderOutput> {\n const ac = ctx.signal;\n const out: ProviderItem[] = [];\n let cursor: unknown = null;\n let page = 0;\n do {\n const renderedUrl = renderTpl(cfg.url, { data: ctx.data, ui: ctx.ui });\n\n // Check if URL has unresolved template variables (empty string after template rendering)\n if (cfg.url.includes(\"{{\") && renderedUrl.endsWith(\"/\")) {\n return { items: [], ttl: 0 };\n }\n\n const url = new URL(renderedUrl);\n const q = renderObj(cfg.query ?? {}, {\n data: ctx.data,\n ui: ctx.ui,\n }) as Record<string, unknown>;\n for (const [k, v] of Object.entries(q))\n if (v !== undefined && v !== \"\") url.searchParams.set(k, String(v));\n if (cursor && cfg.paginate)\n url.searchParams.set(cfg.paginate.param, String(cursor));\n\n // Build headers with auth\n const baseHeaders = renderObj(cfg.headers ?? {}, {\n data: ctx.data,\n }) as Record<string, string>;\n const authHeaders = buildAuthHeaders(cfg.auth, ctx.auth);\n const headers = { ...baseHeaders, ...authHeaders };\n\n const method = cfg.method ?? \"GET\";\n const requestInit: RequestInit = {\n method,\n headers,\n signal: ac,\n };\n\n // Only include body for non-GET requests\n if (method !== \"GET\" && cfg.body) {\n requestInit.body = JSON.stringify(\n renderObj(cfg.body, { data: ctx.data }),\n );\n }\n\n const res = await fetch(url.toString(), requestInit);\n if (!res.ok) throw new Error(`REST ${res.status}`);\n const json = await res.json();\n const items = jp(json, cfg.items);\n for (const it of items) {\n const label = jp(it, cfg.map.label)[0];\n const value = jp(it, cfg.map.value)[0];\n const meta = cfg.map.meta\n ? Object.fromEntries(\n Object.entries(cfg.map.meta).map(([k, p]) => [k, jp(it, p)[0]]),\n )\n : undefined;\n out.push({ label: String(label ?? \"\"), value, meta });\n }\n cursor = cfg.paginate ? jp(json, cfg.paginate.cursorPath)[0] : null;\n page += 1;\n } while (cfg.paginate && cursor && page < (cfg.paginate.maxPages ?? 5));\n return { items: out, ttl: 300 };\n },\n});\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"rest_api.js","sources":["../../src/protocols/rest_api.ts"],"sourcesContent":["import { jp } from \"../core/jsonpath\";\nimport { renderObj, renderTpl } from \"../core/templating\";\nimport type {\n Protocol,\n ProviderItem,\n ProviderOutput,\n AuthConfig,\n} from \"../core/types\";\n\nexport type RestApiCfg = {\n url: string;\n method?: \"GET\" | \"POST\";\n headers?: Record<string, string>;\n query?: Record<string, unknown>;\n body?: unknown;\n items: string; // JSONPath to array; e.g. \"$.items[*]\"\n map: { label: string; value: string; meta?: Record<string, string> }; // relative to each item\n paginate?: { cursorPath: string; param: string; maxPages?: number };\n auth?: AuthConfig;\n showError?: boolean; // Whether to show error messages, defaults to true\n};\n\nfunction buildAuthHeaders(\n auth?: AuthConfig,\n globalAuth?: Record<string, unknown>,\n): Record<string, string> {\n const headers: Record<string, string> = {};\n\n if (!auth) return headers;\n\n // Handle \"use\" reference to global auth\n if (auth.use && globalAuth?.[auth.use]) {\n const globalValue = globalAuth[auth.use];\n const value =\n typeof globalValue === \"function\" ? globalValue() : globalValue;\n\n if (auth.use === \"apiKey\") {\n headers[\"X-API-Key\"] = String(value);\n } else if (auth.use === \"bearer\") {\n headers[\"Authorization\"] = `Bearer ${value}`;\n } else if (auth.use === \"token\") {\n headers[\"Authorization\"] = `Token ${value}`;\n }\n return headers;\n }\n\n // Handle direct auth values\n if (auth.apiKey) {\n const value =\n typeof auth.apiKey === \"function\" ? auth.apiKey() : auth.apiKey;\n headers[\"X-API-Key\"] = String(value);\n }\n\n if (auth.bearer) {\n const value =\n typeof auth.bearer === \"function\" ? auth.bearer() : auth.bearer;\n headers[\"Authorization\"] = `Bearer ${value}`;\n }\n\n if (auth.token) {\n const value = typeof auth.token === \"function\" ? auth.token() : auth.token;\n headers[\"Authorization\"] = `Token ${value}`;\n }\n\n // Handle custom auth fields\n for (const [key, value] of Object.entries(auth)) {\n if (\n ![\"use\", \"apiKey\", \"bearer\", \"token\"].includes(key) &&\n value !== undefined\n ) {\n const authValue = typeof value === \"function\" ? value() : value;\n headers[key] = String(authValue);\n }\n }\n\n return headers;\n}\n\nexport const RestApiProtocol = (): Protocol<RestApiCfg> => ({\n protocol: \"rest_api\",\n async resolve(cfg, ctx): Promise<ProviderOutput> {\n const ac = ctx.signal;\n const out: ProviderItem[] = [];\n let cursor: unknown = null;\n let page = 0;\n do {\n const renderedUrl = renderTpl(cfg.url, { data: ctx.data, ui: ctx.ui });\n\n // Check if URL has unresolved template variables (empty string after template rendering)\n if (cfg.url.includes(\"{{\") && renderedUrl.endsWith(\"/\")) {\n return { items: [], ttl: 0 };\n }\n\n const url = new URL(renderedUrl);\n const q = renderObj(cfg.query ?? {}, {\n data: ctx.data,\n ui: ctx.ui,\n }) as Record<string, unknown>;\n for (const [k, v] of Object.entries(q))\n if (v !== undefined && v !== \"\") url.searchParams.set(k, String(v));\n if (cursor && cfg.paginate)\n url.searchParams.set(cfg.paginate.param, String(cursor));\n\n // Build headers with auth\n const baseHeaders = renderObj(cfg.headers ?? {}, {\n data: ctx.data,\n }) as Record<string, string>;\n const authHeaders = buildAuthHeaders(cfg.auth, ctx.auth);\n const headers = { ...baseHeaders, ...authHeaders };\n\n const method = cfg.method ?? \"GET\";\n const requestInit: RequestInit = {\n method,\n headers,\n signal: ac,\n };\n\n // Only include body for non-GET requests\n if (method !== \"GET\" && cfg.body) {\n requestInit.body = JSON.stringify(\n renderObj(cfg.body, { data: ctx.data }),\n );\n }\n\n const res = await fetch(url.toString(), requestInit);\n if (!res.ok) {\n if (cfg.showError !== false) {\n throw new Error(`REST ${res.status}`);\n }\n // If showError is false, return empty items instead of throwing\n return { items: [], ttl: 0 };\n }\n const json = await res.json();\n const items = jp(json, cfg.items);\n for (const it of items) {\n const label = jp(it, cfg.map.label)[0];\n const value = jp(it, cfg.map.value)[0];\n const meta = cfg.map.meta\n ? Object.fromEntries(\n Object.entries(cfg.map.meta).map(([k, p]) => [k, jp(it, p)[0]]),\n )\n : undefined;\n out.push({ label: String(label ?? \"\"), value, meta });\n }\n cursor = cfg.paginate ? jp(json, cfg.paginate.cursorPath)[0] : null;\n page += 1;\n } while (cfg.paginate && cursor && page < (cfg.paginate.maxPages ?? 5));\n return { items: out, ttl: 300 };\n },\n});\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDerive.d.ts","sourceRoot":"","sources":["../../../src/vue/composables/useDerive.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AACxD,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,UAAU,aAAa;IACrB,OAAO,EAAE,GAAG,CAAC;QACX,QAAQ,EAAE,cAAc,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,OAAO,CAAC;KACf,CAAC,CAAC;IACH,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACtD;AAED,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"useDerive.d.ts","sourceRoot":"","sources":["../../../src/vue/composables/useDerive.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AACxD,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,UAAU,aAAa;IACrB,OAAO,EAAE,GAAG,CAAC;QACX,QAAQ,EAAE,cAAc,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,OAAO,CAAC;KACf,CAAC,CAAC;IACH,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACtD;AAED,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,aAAa,QAkDjE"}
|
|
@@ -5,6 +5,10 @@ function useDerive({ control, handleChange }) {
|
|
|
5
5
|
value: {}
|
|
6
6
|
});
|
|
7
7
|
const rootData = computed(() => injectedFormData.value || {});
|
|
8
|
+
const injectedExternalData = inject("externalData", {
|
|
9
|
+
value: {}
|
|
10
|
+
});
|
|
11
|
+
const externalData = computed(() => injectedExternalData.value || {});
|
|
8
12
|
const deriveConfig = computed(() => {
|
|
9
13
|
const options = control.value.uischema?.options;
|
|
10
14
|
return {
|
|
@@ -14,13 +18,17 @@ function useDerive({ control, handleChange }) {
|
|
|
14
18
|
};
|
|
15
19
|
});
|
|
16
20
|
watch(
|
|
17
|
-
[rootData, deriveConfig],
|
|
18
|
-
([data, config]) => {
|
|
21
|
+
[rootData, externalData, deriveConfig],
|
|
22
|
+
([data, extData, config]) => {
|
|
19
23
|
if (!config.expression || config.mode !== "follow") {
|
|
20
24
|
return;
|
|
21
25
|
}
|
|
22
26
|
try {
|
|
23
|
-
const derivedValue = resolveDeriveExpression(
|
|
27
|
+
const derivedValue = resolveDeriveExpression(
|
|
28
|
+
config.expression,
|
|
29
|
+
data,
|
|
30
|
+
extData
|
|
31
|
+
);
|
|
24
32
|
if (derivedValue !== control.value.data) {
|
|
25
33
|
handleChange(control.value.path, derivedValue);
|
|
26
34
|
}
|
|
@@ -34,7 +42,11 @@ function useDerive({ control, handleChange }) {
|
|
|
34
42
|
{ deep: true, immediate: true }
|
|
35
43
|
);
|
|
36
44
|
}
|
|
37
|
-
function resolveDeriveExpression(expression, data) {
|
|
45
|
+
function resolveDeriveExpression(expression, data, externalData) {
|
|
46
|
+
if (expression.startsWith("externalData(") && expression.endsWith(")")) {
|
|
47
|
+
const propertyPath = expression.slice(13, -1);
|
|
48
|
+
return resolvePropertyPath(propertyPath, externalData);
|
|
49
|
+
}
|
|
38
50
|
if (!expression.includes("(") && !expression.includes("+") && !expression.includes("?")) {
|
|
39
51
|
return resolvePropertyPath(expression, data);
|
|
40
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDerive.js","sources":["../../../src/vue/composables/useDerive.ts"],"sourcesContent":["import { computed, watch, inject, type Ref } from \"vue\";\nimport { type ControlElement } from \"@jsonforms/core\";\n\ninterface DeriveOptions {\n control: Ref<{\n uischema: ControlElement;\n path: string;\n data: unknown;\n }>;\n handleChange: (path: string, value: unknown) => void;\n}\n\nexport function useDerive({ control, handleChange }: DeriveOptions) {\n // Get the root form data from JSONForms context\n const injectedFormData = inject<{ value: unknown }>(\"formData\", {\n value: {},\n });\n const rootData = computed(() => injectedFormData.value || {});\n\n // Extract derive configuration from uischema options\n const deriveConfig = computed(() => {\n const options = control.value.uischema?.options as\n | { derive?: string; mode?: string }\n | undefined;\n return {\n expression: options?.derive,\n mode: options?.mode || \"follow\", // 'follow' = auto-update, 'manual' = user controlled\n };\n });\n\n // Watch for changes in form data and update derived field\n watch(\n [rootData, deriveConfig],\n ([data, config]) => {\n if (!config.expression || config.mode !== \"follow\") {\n return;\n }\n\n try {\n const derivedValue = resolveDeriveExpression(config.expression
|
|
1
|
+
{"version":3,"file":"useDerive.js","sources":["../../../src/vue/composables/useDerive.ts"],"sourcesContent":["import { computed, watch, inject, type Ref } from \"vue\";\nimport { type ControlElement } from \"@jsonforms/core\";\n\ninterface DeriveOptions {\n control: Ref<{\n uischema: ControlElement;\n path: string;\n data: unknown;\n }>;\n handleChange: (path: string, value: unknown) => void;\n}\n\nexport function useDerive({ control, handleChange }: DeriveOptions) {\n // Get the root form data from JSONForms context\n const injectedFormData = inject<{ value: unknown }>(\"formData\", {\n value: {},\n });\n const rootData = computed(() => injectedFormData.value || {});\n\n // Get external data from context if available\n const injectedExternalData = inject<{ value: unknown }>(\"externalData\", {\n value: {},\n });\n const externalData = computed(() => injectedExternalData.value || {});\n\n // Extract derive configuration from uischema options\n const deriveConfig = computed(() => {\n const options = control.value.uischema?.options as\n | { derive?: string; mode?: string }\n | undefined;\n return {\n expression: options?.derive,\n mode: options?.mode || \"follow\", // 'follow' = auto-update, 'manual' = user controlled\n };\n });\n\n // Watch for changes in form data and external data and update derived field\n watch(\n [rootData, externalData, deriveConfig],\n ([data, extData, config]) => {\n if (!config.expression || config.mode !== \"follow\") {\n return;\n }\n\n try {\n const derivedValue = resolveDeriveExpression(\n config.expression,\n data,\n extData,\n );\n if (derivedValue !== control.value.data) {\n handleChange(control.value.path, derivedValue);\n }\n } catch (error) {\n console.warn(\n `Failed to derive value for ${control.value.path}:`,\n error,\n );\n }\n },\n { deep: true, immediate: true },\n );\n}\n\nfunction resolveDeriveExpression(\n expression: string,\n data: unknown,\n externalData?: unknown,\n): unknown {\n // Handle externalData() syntax\n if (expression.startsWith(\"externalData(\") && expression.endsWith(\")\")) {\n const propertyPath = expression.slice(13, -1); // Remove \"externalData(\" and \")\"\n return resolvePropertyPath(propertyPath, externalData);\n }\n\n // Handle simple property paths like \"country.name\"\n if (\n !expression.includes(\"(\") &&\n !expression.includes(\"+\") &&\n !expression.includes(\"?\")\n ) {\n return resolvePropertyPath(expression, data);\n }\n\n // For now, we'll only support simple property paths and externalData() calls\n // Complex expressions would require a safe expression evaluator\n return resolvePropertyPath(expression, data);\n}\n\nfunction resolvePropertyPath(path: string, data: unknown): unknown {\n if (!path || !data) return \"\";\n\n const keys = path.split(\".\");\n let value: unknown = data;\n\n for (const key of keys) {\n if (value && typeof value === \"object\" && key in value) {\n value = (value as Record<string, unknown>)[key];\n } else {\n return null;\n }\n }\n\n return value;\n}\n"],"names":[],"mappings":";;AAYO,SAAS,UAAU,EAAE,SAAS,gBAA+B;AAElE,QAAM,mBAAmB,OAA2B,YAAY;AAAA,IAC9D,OAAO,CAAA;AAAA,EAAC,CACT;AACD,QAAM,WAAW,SAAS,MAAM,iBAAiB,SAAS,CAAA,CAAE;AAG5D,QAAM,uBAAuB,OAA2B,gBAAgB;AAAA,IACtE,OAAO,CAAA;AAAA,EAAC,CACT;AACD,QAAM,eAAe,SAAS,MAAM,qBAAqB,SAAS,CAAA,CAAE;AAGpE,QAAM,eAAe,SAAS,MAAM;AAClC,UAAM,UAAU,QAAQ,MAAM,UAAU;AAGxC,WAAO;AAAA,MACL,YAAY,SAAS;AAAA,MACrB,MAAM,SAAS,QAAQ;AAAA;AAAA,IAAA;AAAA,EAE3B,CAAC;AAGD;AAAA,IACE,CAAC,UAAU,cAAc,YAAY;AAAA,IACrC,CAAC,CAAC,MAAM,SAAS,MAAM,MAAM;AAC3B,UAAI,CAAC,OAAO,cAAc,OAAO,SAAS,UAAU;AAClD;AAAA,MACF;AAEA,UAAI;AACF,cAAM,eAAe;AAAA,UACnB,OAAO;AAAA,UACP;AAAA,UACA;AAAA,QAAA;AAEF,YAAI,iBAAiB,QAAQ,MAAM,MAAM;AACvC,uBAAa,QAAQ,MAAM,MAAM,YAAY;AAAA,QAC/C;AAAA,MACF,SAAS,OAAO;AACd,gBAAQ;AAAA,UACN,8BAA8B,QAAQ,MAAM,IAAI;AAAA,UAChD;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,EAAE,MAAM,MAAM,WAAW,KAAA;AAAA,EAAK;AAElC;AAEA,SAAS,wBACP,YACA,MACA,cACS;AAET,MAAI,WAAW,WAAW,eAAe,KAAK,WAAW,SAAS,GAAG,GAAG;AACtE,UAAM,eAAe,WAAW,MAAM,IAAI,EAAE;AAC5C,WAAO,oBAAoB,cAAc,YAAY;AAAA,EACvD;AAGA,MACE,CAAC,WAAW,SAAS,GAAG,KACxB,CAAC,WAAW,SAAS,GAAG,KACxB,CAAC,WAAW,SAAS,GAAG,GACxB;AACA,WAAO,oBAAoB,YAAY,IAAI;AAAA,EAC7C;AAIA,SAAO,oBAAoB,YAAY,IAAI;AAC7C;AAEA,SAAS,oBAAoB,MAAc,MAAwB;AACjE,MAAI,CAAC,QAAQ,CAAC,KAAM,QAAO;AAE3B,QAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,MAAI,QAAiB;AAErB,aAAW,OAAO,MAAM;AACtB,QAAI,SAAS,OAAO,UAAU,YAAY,OAAO,OAAO;AACtD,cAAS,MAAkC,GAAG;AAAA,IAChD,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;"}
|
package/package.json
CHANGED
|
@@ -17,6 +17,7 @@ export type RestApiCfg = {
|
|
|
17
17
|
map: { label: string; value: string; meta?: Record<string, string> }; // relative to each item
|
|
18
18
|
paginate?: { cursorPath: string; param: string; maxPages?: number };
|
|
19
19
|
auth?: AuthConfig;
|
|
20
|
+
showError?: boolean; // Whether to show error messages, defaults to true
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
function buildAuthHeaders(
|
|
@@ -122,7 +123,13 @@ export const RestApiProtocol = (): Protocol<RestApiCfg> => ({
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
const res = await fetch(url.toString(), requestInit);
|
|
125
|
-
if (!res.ok)
|
|
126
|
+
if (!res.ok) {
|
|
127
|
+
if (cfg.showError !== false) {
|
|
128
|
+
throw new Error(`REST ${res.status}`);
|
|
129
|
+
}
|
|
130
|
+
// If showError is false, return empty items instead of throwing
|
|
131
|
+
return { items: [], ttl: 0 };
|
|
132
|
+
}
|
|
126
133
|
const json = await res.json();
|
|
127
134
|
const items = jp(json, cfg.items);
|
|
128
135
|
for (const it of items) {
|
|
@@ -17,6 +17,12 @@ export function useDerive({ control, handleChange }: DeriveOptions) {
|
|
|
17
17
|
});
|
|
18
18
|
const rootData = computed(() => injectedFormData.value || {});
|
|
19
19
|
|
|
20
|
+
// Get external data from context if available
|
|
21
|
+
const injectedExternalData = inject<{ value: unknown }>("externalData", {
|
|
22
|
+
value: {},
|
|
23
|
+
});
|
|
24
|
+
const externalData = computed(() => injectedExternalData.value || {});
|
|
25
|
+
|
|
20
26
|
// Extract derive configuration from uischema options
|
|
21
27
|
const deriveConfig = computed(() => {
|
|
22
28
|
const options = control.value.uischema?.options as
|
|
@@ -28,16 +34,20 @@ export function useDerive({ control, handleChange }: DeriveOptions) {
|
|
|
28
34
|
};
|
|
29
35
|
});
|
|
30
36
|
|
|
31
|
-
// Watch for changes in form data and update derived field
|
|
37
|
+
// Watch for changes in form data and external data and update derived field
|
|
32
38
|
watch(
|
|
33
|
-
[rootData, deriveConfig],
|
|
34
|
-
([data, config]) => {
|
|
39
|
+
[rootData, externalData, deriveConfig],
|
|
40
|
+
([data, extData, config]) => {
|
|
35
41
|
if (!config.expression || config.mode !== "follow") {
|
|
36
42
|
return;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
try {
|
|
40
|
-
const derivedValue = resolveDeriveExpression(
|
|
46
|
+
const derivedValue = resolveDeriveExpression(
|
|
47
|
+
config.expression,
|
|
48
|
+
data,
|
|
49
|
+
extData,
|
|
50
|
+
);
|
|
41
51
|
if (derivedValue !== control.value.data) {
|
|
42
52
|
handleChange(control.value.path, derivedValue);
|
|
43
53
|
}
|
|
@@ -52,7 +62,17 @@ export function useDerive({ control, handleChange }: DeriveOptions) {
|
|
|
52
62
|
);
|
|
53
63
|
}
|
|
54
64
|
|
|
55
|
-
function resolveDeriveExpression(
|
|
65
|
+
function resolveDeriveExpression(
|
|
66
|
+
expression: string,
|
|
67
|
+
data: unknown,
|
|
68
|
+
externalData?: unknown,
|
|
69
|
+
): unknown {
|
|
70
|
+
// Handle externalData() syntax
|
|
71
|
+
if (expression.startsWith("externalData(") && expression.endsWith(")")) {
|
|
72
|
+
const propertyPath = expression.slice(13, -1); // Remove "externalData(" and ")"
|
|
73
|
+
return resolvePropertyPath(propertyPath, externalData);
|
|
74
|
+
}
|
|
75
|
+
|
|
56
76
|
// Handle simple property paths like "country.name"
|
|
57
77
|
if (
|
|
58
78
|
!expression.includes("(") &&
|
|
@@ -62,7 +82,7 @@ function resolveDeriveExpression(expression: string, data: unknown): unknown {
|
|
|
62
82
|
return resolvePropertyPath(expression, data);
|
|
63
83
|
}
|
|
64
84
|
|
|
65
|
-
// For now, we'll only support simple property paths
|
|
85
|
+
// For now, we'll only support simple property paths and externalData() calls
|
|
66
86
|
// Complex expressions would require a safe expression evaluator
|
|
67
87
|
return resolvePropertyPath(expression, data);
|
|
68
88
|
}
|