@object-ui/react 17.6.0 → 18.0.0
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/CHANGELOG.md +4263 -0
- package/README.md +383 -42
- package/dist/SchemaRenderer.d.ts +64 -7
- package/dist/SchemaRenderer.js +1326 -106
- package/dist/context/ActionContext.js +40 -1
- package/dist/context/AppShellContext.d.ts +27 -0
- package/dist/context/DiscussionContext.d.ts +27 -0
- package/dist/context/DiscussionContext.js +4 -0
- package/dist/context/DrillNavigationContext.d.ts +13 -0
- package/dist/context/InlineEditContext.d.ts +151 -1
- package/dist/context/InlineEditContext.js +36 -2
- package/dist/context/PrintPageContext.d.ts +60 -0
- package/dist/context/PrintPageContext.js +32 -0
- package/dist/context/RecordContext.d.ts +48 -5
- package/dist/context/RecordContext.js +10 -1
- package/dist/context/RelatedRecordActionsContext.d.ts +72 -2
- package/dist/context/RelatedRecordActionsContext.js +4 -0
- package/dist/context/SchemaRendererContext.d.ts +61 -6
- package/dist/context/SchemaRendererContext.js +39 -6
- package/dist/context/ThemeContext.js +1 -1
- package/dist/context/actionRunnerTranslator.d.ts +27 -0
- package/dist/context/actionRunnerTranslator.js +37 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +1 -0
- package/dist/element-data-source/ElementApiDataSourcePanel.d.ts +28 -0
- package/dist/element-data-source/ElementApiDataSourcePanel.js +10 -0
- package/dist/element-data-source/ElementDataSourceGate.d.ts +118 -3
- package/dist/element-data-source/ElementDataSourceGate.js +401 -19
- package/dist/element-data-source/useElementApiDataSource.d.ts +119 -0
- package/dist/element-data-source/useElementApiDataSource.js +198 -0
- package/dist/hooks/index.d.ts +3 -1
- package/dist/hooks/index.js +10 -1
- package/dist/hooks/useActionEngine.js +46 -3
- package/dist/hooks/useActionRunner.js +4 -0
- package/dist/hooks/useActionTextLocalizer.d.ts +35 -9
- package/dist/hooks/useActionTextLocalizer.js +70 -14
- package/dist/hooks/useConfigBagEvaluator.d.ts +31 -0
- package/dist/hooks/useConfigBagEvaluator.js +40 -0
- package/dist/hooks/useDatasetDimensionLabels.js +25 -10
- package/dist/hooks/useDensityMode.d.ts +8 -2
- package/dist/hooks/useDensityMode.js +8 -2
- package/dist/hooks/useDiscovery.d.ts +1 -10
- package/dist/hooks/useETagCache.js +25 -4
- package/dist/hooks/useElementDataSource.d.ts +15 -2
- package/dist/hooks/useElementDataSource.js +24 -7
- package/dist/hooks/useExpression.d.ts +52 -59
- package/dist/hooks/useExpression.js +58 -90
- package/dist/hooks/useFilterScope.d.ts +22 -2
- package/dist/hooks/useFilterScope.js +29 -4
- package/dist/hooks/useGlobalUndo.d.ts +10 -0
- package/dist/hooks/useGlobalUndo.js +112 -46
- package/dist/hooks/useNavigationOverlay.d.ts +76 -11
- package/dist/hooks/useNavigationOverlay.js +119 -9
- package/dist/hooks/useOffline.js +65 -11
- package/dist/hooks/usePageAssignment.d.ts +33 -7
- package/dist/hooks/usePageAssignment.js +85 -21
- package/dist/hooks/useRecordSearch.d.ts +34 -1
- package/dist/hooks/useRecordSearch.js +42 -7
- package/dist/hooks/useResolvedFilter.d.ts +46 -0
- package/dist/hooks/useResolvedFilter.js +130 -0
- package/dist/hooks/useSchemaPersistence.d.ts +7 -0
- package/dist/hooks/useSchemaPersistence.js +144 -5
- package/dist/hooks/useSettledSchema.d.ts +123 -0
- package/dist/hooks/useSettledSchema.js +186 -0
- package/dist/hooks/useViewData.d.ts +10 -3
- package/dist/hooks/useViewData.js +5 -14
- package/dist/index.d.ts +9 -1
- package/dist/index.js +48 -1
- package/dist/schema-input.d.ts +38 -8
- package/dist/schema-input.js +39 -7
- package/dist/utils/configBag.d.ts +68 -0
- package/dist/utils/configBag.js +70 -0
- package/dist/utils/error-message.d.ts +1 -1
- package/dist/utils/error-message.js +1 -1
- package/dist/utils/inlineAria.d.ts +61 -0
- package/dist/utils/inlineAria.js +70 -0
- package/dist/utils/localeMap.d.ts +27 -0
- package/dist/utils/localeMap.js +106 -0
- package/dist/utils/nonGridRowCeiling.d.ts +44 -0
- package/dist/utils/nonGridRowCeiling.js +78 -0
- package/dist/utils/paramsBag.d.ts +28 -0
- package/dist/utils/paramsBag.js +146 -0
- package/dist/utils/propsBagDiagnostic.d.ts +196 -0
- package/dist/utils/propsBagDiagnostic.js +422 -0
- package/dist/utils/refusedDataPropDiagnostic.d.ts +44 -0
- package/dist/utils/refusedDataPropDiagnostic.js +94 -0
- package/dist/utils/unevaluatedExpression.d.ts +44 -1
- package/dist/utils/unevaluatedExpression.js +92 -11
- package/dist/utils/visibilityDiagnostic.d.ts +282 -21
- package/dist/utils/visibilityDiagnostic.js +321 -46
- package/package.json +10 -10
- package/dist/hooks/useClientNotifications.d.ts +0 -47
- package/dist/hooks/useClientNotifications.js +0 -162
- package/dist/spec-bridge/SpecBridge.d.ts +0 -28
- package/dist/spec-bridge/SpecBridge.js +0 -56
- package/dist/spec-bridge/bridges/form-view.d.ts +0 -158
- package/dist/spec-bridge/bridges/form-view.js +0 -170
- package/dist/spec-bridge/bridges/list-view.d.ts +0 -20
- package/dist/spec-bridge/bridges/list-view.js +0 -268
- package/dist/spec-bridge/index.d.ts +0 -11
- package/dist/spec-bridge/index.js +0 -10
- package/dist/spec-bridge/types.d.ts +0 -55
- package/dist/spec-bridge/types.js +0 -8
|
@@ -51,6 +51,27 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
51
51
|
* designer emits for an unconfigured column list, and supplying the columns is
|
|
52
52
|
* exactly why a view was named.
|
|
53
53
|
*
|
|
54
|
+
* The row cap answers "did the component author one?" the same way, and for the
|
|
55
|
+
* same reason (objectui#9899). A cap the contract REFUSES — `pageSize: 0`,
|
|
56
|
+
* a negative, a fraction — is not a cap this relay may treat as the author's
|
|
57
|
+
* intent, so it does not suppress the one the bound view supplies. Deciding
|
|
58
|
+
* that question by PRESENCE is how a view's legitimate cap was dropped in
|
|
59
|
+
* favour of the consuming renderer's own default: measured end to end on
|
|
60
|
+
* `list-view` before the repair, a component carrying `pagination.pageSize: 0`
|
|
61
|
+
* under a view supplying `7` put `$top: 100` on the wire — the view's cap never
|
|
62
|
+
* reached it, so the read was WIDER than the view asked for.
|
|
63
|
+
*
|
|
64
|
+
* The VIEW's own cap can be refused too, and `@object-ui/core` drops it before
|
|
65
|
+
* it reaches this relay (objectui#9928). The drop is right, but it left the
|
|
66
|
+
* consuming renderer nothing to refuse, so it went silent — and this relay
|
|
67
|
+
* reports the view's refusal instead (objectui#10015). See
|
|
68
|
+
* {@link describeRefusedViewRowLimit} for when that message applies.
|
|
69
|
+
*
|
|
70
|
+
* The BINDING's own cap follows the same rule (objectui#10016): a `limit` the
|
|
71
|
+
* contract refuses is not authored, so `@object-ui/core` lets the view's cap
|
|
72
|
+
* through in its place, and that cap is a baseline here like any other
|
|
73
|
+
* view-sourced value. See {@link describeRefusedBindingRowLimit}.
|
|
74
|
+
*
|
|
54
75
|
* ## What a mapping may NOT do
|
|
55
76
|
*
|
|
56
77
|
* {@link ElementDataSourceMapping} names only keys the target block genuinely
|
|
@@ -80,9 +101,135 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
80
101
|
* like it works.
|
|
81
102
|
*/
|
|
82
103
|
import * as React from 'react';
|
|
83
|
-
import { isElementDataSourceConfig, mergeFilterNodes, } from '@object-ui/core';
|
|
104
|
+
import { elementDataSourceRefusedLimitMessage, filterRefusalSubject, isElementDataSourceConfig, mergeFilterNodes, toFilterNodeSafely, } from '@object-ui/core';
|
|
105
|
+
import { useObjectTranslation } from '@object-ui/i18n';
|
|
84
106
|
import { useElementDataSource, } from '../hooks/useElementDataSource.js';
|
|
85
107
|
import { SchemaRendererContext } from '../context/SchemaRendererContext.js';
|
|
108
|
+
/**
|
|
109
|
+
* What the contract admits as a row cap (objectui#9899).
|
|
110
|
+
*
|
|
111
|
+
* `@objectstack/spec` has already answered what `0` means for the keys this
|
|
112
|
+
* branch writes. Its view pagination config declares `pageSize` a positive
|
|
113
|
+
* integer with a default; every component row cap its component props map
|
|
114
|
+
* declares is `z.number().int().positive()`; and that flat `limit` is the key a
|
|
115
|
+
* view's `pagination.pageSize` is LOWERED INTO for the blocks that read it. So
|
|
116
|
+
* `0`, a negative and a fraction are not spellings this relay may assign a
|
|
117
|
+
* meaning to — they are values the contract refuses.
|
|
118
|
+
*
|
|
119
|
+
* ⚠️ Said precisely, because it is easy to overstate: the `object-grid` props
|
|
120
|
+
* face declares its OWN `pagination` key `z.unknown()`, so a save gate does not
|
|
121
|
+
* refuse a component-level `pagination.pageSize: 0` on that face, and the flat
|
|
122
|
+
* `pageSize` shorthand beside it is a bare `z.number()`. The refusal rests on
|
|
123
|
+
* the pagination CONFIG contract and on the `limit` the value is lowered into —
|
|
124
|
+
* the same ground objectui#9853, objectui#9897 and objectui#9925 already stood
|
|
125
|
+
* on at the renderer sites.
|
|
126
|
+
*
|
|
127
|
+
* ⛔ Deliberately a LOCAL restatement of the predicate objectui#9925 landed at
|
|
128
|
+
* the renderer sites, not an import and not a shared helper: hoisting it would
|
|
129
|
+
* be a cross-package extraction this card may not make. The cost is stated
|
|
130
|
+
* rather than hidden — one rule now has a spelling at every site that enforces
|
|
131
|
+
* it, and `git grep 'isUsableRowLimit\|isUsablePageSize'` is what enumerates
|
|
132
|
+
* them; the day one is meant to change, all of them are.
|
|
133
|
+
*/
|
|
134
|
+
function isUsableRowLimit(value) {
|
|
135
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The loud half, and the reason a RELAY owes one at all.
|
|
139
|
+
*
|
|
140
|
+
* A relay that merely declines to treat a value as authored could reasonably
|
|
141
|
+
* say nothing — the renderer it forwards to has its own refusal and its own
|
|
142
|
+
* message. Measured rather than assumed: BEFORE this repair, a `list-view`
|
|
143
|
+
* carrying `pagination.pageSize: 0` under a bound view was told about it, once,
|
|
144
|
+
* by `ListView`'s own diagnostic. AFTER it, the gate writes the view's usable
|
|
145
|
+
* cap, that renderer sees a value it accepts, and its message correctly goes
|
|
146
|
+
* silent — so the repair REMOVES the only place the author was being told.
|
|
147
|
+
* This restores that one message at the layer that now makes the decision.
|
|
148
|
+
*
|
|
149
|
+
* ⛔ NOT a second guard: the predicate lives once, in {@link isUsableRowLimit},
|
|
150
|
+
* and this reads it. `null` means "nothing to say" — an absent cap is not a
|
|
151
|
+
* mistake and a usable one is not either, which is what keeps this from
|
|
152
|
+
* becoming an always-on marker that states nothing.
|
|
153
|
+
*/
|
|
154
|
+
const describeDisplacedRowLimit = (authored, key, used, componentType, objectName) => {
|
|
155
|
+
if (authored === undefined || authored === null)
|
|
156
|
+
return null;
|
|
157
|
+
if (isUsableRowLimit(authored))
|
|
158
|
+
return null;
|
|
159
|
+
const type = typeof componentType === 'string' && componentType ? componentType : 'component';
|
|
160
|
+
const where = typeof objectName === 'string' && objectName ? `${type} on ${objectName}` : type;
|
|
161
|
+
return (`[ObjectUI] ElementDataSourceGate row cap: ${where} declared ${key}: ${String(authored)}, `
|
|
162
|
+
+ 'which is not a positive integer. A row cap must be a positive integer '
|
|
163
|
+
+ '(the spec refuses zero and negative values), so it was ignored and the cap '
|
|
164
|
+
+ `from this component's bound data source (${used}) was used instead.`);
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* The loud half for a cap the SAVED VIEW declared and the contract refuses
|
|
168
|
+
* (objectui#10015).
|
|
169
|
+
*
|
|
170
|
+
* `@object-ui/core` drops such a cap while composing, so `composed.limit`
|
|
171
|
+
* never carries it and the renderer below never sees it. Its own diagnostic
|
|
172
|
+
* treats an absent key as "not a mistake", so after that drop nothing said
|
|
173
|
+
* anything. The wording is the core builder's, not this file's: it is the one
|
|
174
|
+
* sentence both consumers of the composed binding use, and `ViewDataProvider`
|
|
175
|
+
* already reports it on the path with no renderer. The builder is pure because
|
|
176
|
+
* the composer runs inside a `useMemo`; this relay reports its result from an
|
|
177
|
+
* effect, the same way it reports {@link describeDisplacedRowLimit}.
|
|
178
|
+
*
|
|
179
|
+
* It speaks only when the refusal CHANGED what this block receives — when a
|
|
180
|
+
* usable view cap would have been written here:
|
|
181
|
+
*
|
|
182
|
+
* - the binding declared no usable `limit` of its own (if it did, the
|
|
183
|
+
* binding's cap is what the block gets whatever the view carries). The
|
|
184
|
+
* builder applies this condition itself, because `ViewDataProvider` needs
|
|
185
|
+
* it too (objectui#10016), so this relay hands it the binding; and
|
|
186
|
+
* - the component's own cap is not a usable one (a usable one wins over any
|
|
187
|
+
* view cap).
|
|
188
|
+
*
|
|
189
|
+
* In both of those excluded cases the builder's sentence — the block falls
|
|
190
|
+
* back to its own default — would not be true, so silence is the honest
|
|
191
|
+
* answer, not a gap. The caller adds the third condition: the block reads a
|
|
192
|
+
* row cap at all.
|
|
193
|
+
*
|
|
194
|
+
* ## Never together with {@link describeDisplacedRowLimit}
|
|
195
|
+
*
|
|
196
|
+
* That message needs a usable cap to have been WRITTEN. With a refused view
|
|
197
|
+
* cap, the only usable cap left is a binding's usable `limit`, and that is
|
|
198
|
+
* excluded above. So the two are exclusive by construction. When the component
|
|
199
|
+
* AND the view both carry a refused cap, this message names the view and the
|
|
200
|
+
* renderer names the component: two declarations, one message each.
|
|
201
|
+
*/
|
|
202
|
+
const describeRefusedViewRowLimit = (view, config, authored) => {
|
|
203
|
+
if (!config)
|
|
204
|
+
return null;
|
|
205
|
+
if (isUsableRowLimit(authored))
|
|
206
|
+
return null;
|
|
207
|
+
return elementDataSourceRefusedLimitMessage(view, config.view, config.object, config);
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* The loud half for a cap the BINDING declared and the contract refuses
|
|
211
|
+
* (objectui#10016), in the core builder's words for that operand.
|
|
212
|
+
*
|
|
213
|
+
* `@object-ui/core` treats such a cap as not authored: it lets the view's
|
|
214
|
+
* usable cap through in its place, or none. Nothing downstream can see the
|
|
215
|
+
* refused value any more, so this relay reports it, from an effect, the same
|
|
216
|
+
* way it reports the view's refusal.
|
|
217
|
+
*
|
|
218
|
+
* It needs no condition beyond the caller's (the block reads a row cap at
|
|
219
|
+
* all). A usable binding cap is written whatever the component or the view
|
|
220
|
+
* carries, so a refused one is always a cap this block did not get.
|
|
221
|
+
*
|
|
222
|
+
* It can fire beside the other two, and that is one message per declaration,
|
|
223
|
+
* not two per mistake: beside {@link describeRefusedViewRowLimit} when the view
|
|
224
|
+
* ALSO carries a refused cap, and beside {@link describeDisplacedRowLimit} when
|
|
225
|
+
* the component ALSO carries a refused cap and the view's usable one displaced
|
|
226
|
+
* it.
|
|
227
|
+
*/
|
|
228
|
+
const describeRefusedBindingRowLimit = (view, config) => {
|
|
229
|
+
if (!config)
|
|
230
|
+
return null;
|
|
231
|
+
return elementDataSourceRefusedLimitMessage(view, config.view, config.object, config, 'binding');
|
|
232
|
+
};
|
|
86
233
|
const readLimit = (base, key) => {
|
|
87
234
|
if (key === 'pagination.pageSize')
|
|
88
235
|
return base.pagination?.pageSize;
|
|
@@ -122,10 +269,16 @@ export function useElementDataSourceSchema(schema, mapping = {}, dataSource) {
|
|
|
122
269
|
const { object: objectKey = 'objectName', columns, filter, sort, limit, viewType } = mapping;
|
|
123
270
|
const mapped = React.useMemo(() => {
|
|
124
271
|
const composed = binding.composed;
|
|
272
|
+
// BY REFERENCE when there is nothing to apply — a fresh object every render
|
|
273
|
+
// would remount the block and refetch. The wrapper is memoised alongside it,
|
|
274
|
+
// so the identity this carries is the one the caller sees.
|
|
125
275
|
if (!composed)
|
|
126
|
-
return schema;
|
|
276
|
+
return { schema, capMessage: null, viewCapMessage: null, bindingCapMessage: null };
|
|
127
277
|
const base = (schema ?? {});
|
|
128
278
|
const next = { ...base };
|
|
279
|
+
let capMessage = null;
|
|
280
|
+
let viewCapMessage = null;
|
|
281
|
+
let bindingCapMessage = null;
|
|
129
282
|
if (objectKey !== false)
|
|
130
283
|
next[objectKey] = composed.object;
|
|
131
284
|
if (columns) {
|
|
@@ -139,7 +292,23 @@ export function useElementDataSourceSchema(schema, mapping = {}, dataSource) {
|
|
|
139
292
|
// Component filter AND (view filter AND binding filter). `composed.filter`
|
|
140
293
|
// already carries the latter pair; a single surviving source comes back
|
|
141
294
|
// unwrapped, so the common "only the view filters" case stays flat.
|
|
142
|
-
|
|
295
|
+
//
|
|
296
|
+
// ⚠️ Each source is lowered through `toFilterNodeSafely` first, the way
|
|
297
|
+
// `RelatedList` and `LineItemsPanel` lower their own filter before they
|
|
298
|
+
// merge it — objectui#10789. This is a RENDER-time `useMemo`, and the
|
|
299
|
+
// lowering refuses malformed authored shapes with a `FilterOperatorError`:
|
|
300
|
+
// uncaught, that was a render error, thrown BEFORE the wrapped block
|
|
301
|
+
// reached its own safe lowering and its malformed-filter state. The
|
|
302
|
+
// refusal is returned as a VALUE and the block is not rendered at all —
|
|
303
|
+
// never merged as "no filter", which would run it unconstrained. The
|
|
304
|
+
// merge below then only re-reads nodes the lowering already produced.
|
|
305
|
+
const own = toFilterNodeSafely(base.filter);
|
|
306
|
+
if (!own.ok)
|
|
307
|
+
return { schema, capMessage: null, viewCapMessage: null, bindingCapMessage: null, filterRefusal: own.refusal };
|
|
308
|
+
const bound = toFilterNodeSafely(composed.filter);
|
|
309
|
+
if (!bound.ok)
|
|
310
|
+
return { schema, capMessage: null, viewCapMessage: null, bindingCapMessage: null, filterRefusal: bound.refusal };
|
|
311
|
+
const merged = mergeFilterNodes(own.node, bound.node);
|
|
143
312
|
if (merged !== undefined)
|
|
144
313
|
next.filter = merged;
|
|
145
314
|
else
|
|
@@ -153,38 +322,172 @@ export function useElementDataSourceSchema(schema, mapping = {}, dataSource) {
|
|
|
153
322
|
next.sort = composed.sort;
|
|
154
323
|
}
|
|
155
324
|
if (limit && composed.limit !== undefined) {
|
|
156
|
-
|
|
157
|
-
|
|
325
|
+
// For the row cap, "declared one" means a cap the contract admits
|
|
326
|
+
// (objectui#10016): a refused binding `limit` is not authored, so the
|
|
327
|
+
// composer let the view's cap through in its place, and that cap is a
|
|
328
|
+
// baseline the component's usable cap still wins over.
|
|
329
|
+
const fromView = !isUsableRowLimit(binding.config?.limit);
|
|
330
|
+
// PRESENCE is not authorship (objectui#9899) — the same question the
|
|
331
|
+
// `columns` branch above answers by CONTENT, answered the same way here.
|
|
332
|
+
const authored = readLimit(base, limit);
|
|
333
|
+
if (!fromView || !isUsableRowLimit(authored)) {
|
|
158
334
|
writeLimit(next, base, limit, composed.limit);
|
|
335
|
+
capMessage = describeDisplacedRowLimit(authored, limit, composed.limit, base.type, composed.object);
|
|
159
336
|
}
|
|
160
337
|
}
|
|
338
|
+
// Outside the branch above on purpose: a refused cap never reaches
|
|
339
|
+
// `composed.limit`, so that branch does not run for it (objectui#10015,
|
|
340
|
+
// and objectui#10016 for the binding's).
|
|
341
|
+
if (limit) {
|
|
342
|
+
viewCapMessage = describeRefusedViewRowLimit(binding.view, binding.config, readLimit(base, limit));
|
|
343
|
+
bindingCapMessage = describeRefusedBindingRowLimit(binding.view, binding.config);
|
|
344
|
+
}
|
|
161
345
|
if (viewType && composed.viewType !== undefined && base.viewType === undefined) {
|
|
162
346
|
next.viewType = composed.viewType;
|
|
163
347
|
}
|
|
164
|
-
return next;
|
|
165
|
-
}, [schema, binding.composed, binding.config, objectKey, columns, filter, sort, limit, viewType]);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
348
|
+
return { schema: next, capMessage, viewCapMessage, bindingCapMessage };
|
|
349
|
+
}, [schema, binding.composed, binding.config, binding.view, objectKey, columns, filter, sort, limit, viewType]);
|
|
350
|
+
// Keyed on the MESSAGE, so it is one warning per declaration rather than one
|
|
351
|
+
// per render — and it fires from an effect, never from render, which is the
|
|
352
|
+
// same shape the renderer sites use for "you declared it, we dropped it".
|
|
353
|
+
// One effect per message, so a change to one never re-emits another.
|
|
354
|
+
const { schema: boundSchema, capMessage, viewCapMessage, bindingCapMessage, filterRefusal: mergeRefusal } = mapped;
|
|
355
|
+
React.useEffect(() => {
|
|
356
|
+
if (capMessage)
|
|
357
|
+
console.warn(capMessage);
|
|
358
|
+
}, [capMessage]);
|
|
359
|
+
React.useEffect(() => {
|
|
360
|
+
if (viewCapMessage)
|
|
361
|
+
console.warn(viewCapMessage);
|
|
362
|
+
}, [viewCapMessage]);
|
|
363
|
+
React.useEffect(() => {
|
|
364
|
+
if (bindingCapMessage)
|
|
365
|
+
console.warn(bindingCapMessage);
|
|
366
|
+
}, [bindingCapMessage]);
|
|
367
|
+
// A refused filter — from either merge — is `missing`, the one state every
|
|
368
|
+
// caller already answers by withholding the block (objectui#10789).
|
|
369
|
+
const filterRefusal = binding.filterRefusal ?? mergeRefusal;
|
|
370
|
+
return React.useMemo(() => filterRefusal
|
|
371
|
+
? {
|
|
372
|
+
status: 'missing',
|
|
373
|
+
schema: boundSchema,
|
|
374
|
+
config: binding.config,
|
|
375
|
+
error: filterRefusal.message,
|
|
376
|
+
filterRefusal,
|
|
377
|
+
}
|
|
378
|
+
: {
|
|
379
|
+
status: binding.status,
|
|
380
|
+
schema: boundSchema,
|
|
381
|
+
config: binding.config,
|
|
382
|
+
error: binding.error,
|
|
383
|
+
}, [binding.status, binding.config, binding.error, boundSchema, filterRefusal]);
|
|
172
384
|
}
|
|
173
385
|
/**
|
|
174
386
|
* The "named view does not resolve" panel. Same posture (and shape) as
|
|
175
387
|
* `SchemaRenderer`'s "Unknown component type": authored metadata pointing at
|
|
176
388
|
* something that is not there, reported where the author can see it.
|
|
177
389
|
*/
|
|
178
|
-
export function ElementDataSourceErrorPanel({ testId, title
|
|
179
|
-
|
|
390
|
+
export function ElementDataSourceErrorPanel({ testId, title, message, }) {
|
|
391
|
+
// objectui#12920 — the default reads the bundle; it was an English literal.
|
|
392
|
+
// Only an ABSENT `title` takes it, as the parameter default did. The hook
|
|
393
|
+
// runs on this error path only, for the reason
|
|
394
|
+
// {@link ElementDataSourceMalformedFilterPanel} gives, and the inline
|
|
395
|
+
// `defaultValue` is the `en` pack's value (`pnpm check:i18n-keys`).
|
|
396
|
+
const { t } = useObjectTranslation();
|
|
397
|
+
const heading = title === undefined
|
|
398
|
+
? t('element.component.sourceUnresolved', {
|
|
399
|
+
defaultValue: 'This component’s data source could not be resolved',
|
|
400
|
+
})
|
|
401
|
+
: title;
|
|
402
|
+
return (_jsxs("div", { className: "p-4 border border-red-500 rounded text-red-500 bg-red-50 my-2", role: "alert", "data-testid": `${testId}-datasource-error`, children: [_jsx("p", { className: "font-medium", children: heading }), message ? _jsx("p", { className: "text-sm mt-1", children: message }) : null] }));
|
|
180
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* The malformed-filter notice, drawn IN PLACE OF the block when a filter the
|
|
406
|
+
* gate had to merge is refused (objectui#10789).
|
|
407
|
+
*
|
|
408
|
+
* It is the notice the wrapped blocks draw themselves (objectui#9050 step 2 —
|
|
409
|
+
* `record:related_list`, the line-items panel, `object-grid`): the same
|
|
410
|
+
* sentence (`view.malformedFilter`), naming the refused operator or field in a
|
|
411
|
+
* separately addressable headline, with the refusal's own message beneath it.
|
|
412
|
+
* The gate draws it because its merge refuses FIRST — before the block's own
|
|
413
|
+
* safe lowering ever runs — so without it the author would get the error
|
|
414
|
+
* boundary's "failed to render" instead of the sentence those blocks give.
|
|
415
|
+
*
|
|
416
|
+
* ⚠️ A separate component, so `useObjectTranslation` runs only on this path:
|
|
417
|
+
* the gate wraps every object-bound block, and a translation hook in the
|
|
418
|
+
* gate's own body would run for all of them, including under the many suites
|
|
419
|
+
* that mock `@object-ui/i18n` by hand.
|
|
420
|
+
*
|
|
421
|
+
* The provider-less default is the sentence itself, written the way the
|
|
422
|
+
* sibling blocks carry it: a LITERAL copy of the `en` pack's value, never a
|
|
423
|
+
* read of the pack object. The siblings keep theirs in a module-scope
|
|
424
|
+
* `createSafeTranslation` table, which this module cannot have: it is
|
|
425
|
+
* re-exported from the package entry, so a factory call at module scope would
|
|
426
|
+
* run on import for every consumer (`NonGridRowCeilingNote` records that
|
|
427
|
+
* hazard). So the copy sits in the call's inline `defaultValue`, where
|
|
428
|
+
* `pnpm check:i18n-keys` holds it byte-identical to the pack, as the siblings'
|
|
429
|
+
* tables are held. With an `I18nProvider` the pack's value wins, and without
|
|
430
|
+
* one this literal is interpolated (objectui#6219).
|
|
431
|
+
*/
|
|
432
|
+
function ElementDataSourceMalformedFilterPanel({ testId, refusal, }) {
|
|
433
|
+
const { t } = useObjectTranslation();
|
|
434
|
+
return (_jsxs("div", { role: "alert", className: "rounded-md border border-amber-300 bg-amber-50 p-3 my-2 text-sm text-amber-800", "data-testid": `${testId}-malformed-filter`, children: [_jsx("p", { className: "font-medium", "data-testid": `${testId}-malformed-filter-subject`, children: t('view.malformedFilter', {
|
|
435
|
+
subject: filterRefusalSubject(refusal) ?? '',
|
|
436
|
+
defaultValue: 'This view’s filter is malformed, so no records are shown: the {{subject}} condition cannot be applied.',
|
|
437
|
+
}) }), _jsx("p", { className: "mt-1 text-xs opacity-80", children: refusal.message })] }));
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* The "no object named" hint, drawn IN PLACE OF the block when a placement
|
|
441
|
+
* that opted in with `requiresObject` names its object in neither place: not
|
|
442
|
+
* on the block's own object key, and not through `dataSource.object`
|
|
443
|
+
* (objectui#11605).
|
|
444
|
+
*
|
|
445
|
+
* Why it exists: the object-bound registrations no longer declare their object
|
|
446
|
+
* key `required`, because the binding can supply it and the manifest has no
|
|
447
|
+
* "this key or that binding" form. So the page compile accepts a node that
|
|
448
|
+
* names no object at all, and this hint is the one signal left for it. Before
|
|
449
|
+
* it, those blocks drew an empty board, a field-less form, a dash or a "no
|
|
450
|
+
* data yet" state, which reads as an empty query rather than a missing object.
|
|
451
|
+
*
|
|
452
|
+
* The wording is `element:number`'s own no-object notice (objectui#10951) with
|
|
453
|
+
* the property as a hole, so the two blocks that answer this question say it
|
|
454
|
+
* the same way. The property name is interpolated, never translated.
|
|
455
|
+
*
|
|
456
|
+
* A separate component for the reason {@link ElementDataSourceMalformedFilterPanel}
|
|
457
|
+
* gives: `useObjectTranslation` runs only on this path, and the provider-less
|
|
458
|
+
* default is a literal copy of the `en` pack's value, held byte-identical to it
|
|
459
|
+
* by `pnpm check:i18n-keys`.
|
|
460
|
+
*/
|
|
461
|
+
function ElementDataSourceNoObjectPanel({ testId, property, }) {
|
|
462
|
+
const { t } = useObjectTranslation();
|
|
463
|
+
return (_jsx("div", { className: "p-4 text-sm text-muted-foreground my-2", "data-testid": `${testId}-no-object`, children: t('view.noObject', {
|
|
464
|
+
property,
|
|
465
|
+
defaultValue: 'No object named: set {{property}} or dataSource.object.',
|
|
466
|
+
}) }));
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Does the bound schema name an object on `key`? A blank string names nothing,
|
|
470
|
+
* the same reading `noDataSourceMessage` gives an object name.
|
|
471
|
+
*/
|
|
472
|
+
const namesObject = (schema, key) => {
|
|
473
|
+
const value = schema?.[key];
|
|
474
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
475
|
+
};
|
|
181
476
|
/**
|
|
182
477
|
* The "saved views are still being fetched" placeholder. Distinct from the error
|
|
183
478
|
* panel because a component that treated "not resolved yet" as "does not exist"
|
|
184
479
|
* would flash a configuration error on every mount.
|
|
480
|
+
*
|
|
481
|
+
* Its copy reads `common.loading` (objectui#12889), the key the rest of the
|
|
482
|
+
* tree reads for a bare "Loading…": it printed an English "Loading view…" on
|
|
483
|
+
* every locale before. `useObjectTranslation` runs only while a view resolves,
|
|
484
|
+
* for the reason {@link ElementDataSourceMalformedFilterPanel} gives, and the
|
|
485
|
+
* inline `defaultValue` is the `en` pack's value, held byte-identical to it by
|
|
486
|
+
* `pnpm check:i18n-keys`.
|
|
185
487
|
*/
|
|
186
488
|
export function ElementDataSourceLoadingPanel({ testId, }) {
|
|
187
|
-
|
|
489
|
+
const { t } = useObjectTranslation();
|
|
490
|
+
return (_jsx("div", { className: "p-2 text-sm text-muted-foreground animate-pulse", role: "status", "aria-live": "polite", "data-testid": `${testId}-resolving-view`, children: t('common.loading', { defaultValue: 'Loading…' }) }));
|
|
188
491
|
}
|
|
189
492
|
/**
|
|
190
493
|
* The adapter this block would use, resolved the one way every block in the
|
|
@@ -203,6 +506,9 @@ export function ElementDataSourceLoadingPanel({ testId, }) {
|
|
|
203
506
|
* it off and you get `unknown`, which is the honest default.
|
|
204
507
|
*/
|
|
205
508
|
export function useResolvedDataSource(explicit) {
|
|
509
|
+
// Read AS DECLARED (objectui#7209): the only cast in this hook is the
|
|
510
|
+
// caller's `T` on the way out, never one erasing the context on the way in.
|
|
511
|
+
// Pinned by `useResolvedDataSource.schemaRendererContextRead-7209.test.ts`.
|
|
206
512
|
const context = React.useContext(SchemaRendererContext);
|
|
207
513
|
const named = isElementDataSourceConfig(explicit) ? undefined : explicit;
|
|
208
514
|
return (named ?? context?.dataSource ?? undefined);
|
|
@@ -241,9 +547,72 @@ export function noDataSourceMessage(blockKey, objectName) {
|
|
|
241
547
|
* WIRING one level up. Same posture as {@link ElementDataSourceErrorPanel}: the
|
|
242
548
|
* metadata is answerable, so the answer is rendered where the author can see it.
|
|
243
549
|
*/
|
|
244
|
-
export function NoDataSourcePanel({ testId, title
|
|
245
|
-
|
|
550
|
+
export function NoDataSourcePanel({ testId, title, message, }) {
|
|
551
|
+
// objectui#12920 — same reading as {@link ElementDataSourceErrorPanel}: an
|
|
552
|
+
// absent `title` reads the bundle, a passed one wins.
|
|
553
|
+
const { t } = useObjectTranslation();
|
|
554
|
+
const heading = title === undefined
|
|
555
|
+
? t('element.component.noDataSource', { defaultValue: 'No data source resolved' })
|
|
556
|
+
: title;
|
|
557
|
+
return (_jsxs("div", { className: "p-4 border border-red-500 rounded text-red-500 bg-red-50 my-2", role: "alert", "data-testid": `${testId}-no-data-source`, children: [_jsx("p", { className: "font-medium", children: heading }), message ? _jsx("p", { className: "text-sm mt-1", children: message }) : null] }));
|
|
246
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* The wrapping seam — mark `renderer` as one that wraps {@link
|
|
561
|
+
* ElementDataSourceGate}, so its registrations DECLARE the `dataSource` key this
|
|
562
|
+
* gate READS (objectui#6678).
|
|
563
|
+
*
|
|
564
|
+
* ```tsx
|
|
565
|
+
* export const ObjectMapRenderer = elementDataSourceBlock<React.FC<any>>(({ schema }) => (
|
|
566
|
+
* <ElementDataSourceGate schema={schema} testId="object-map" …>
|
|
567
|
+
* {(bound) => <ObjectMap schema={bound} />}
|
|
568
|
+
* </ElementDataSourceGate>
|
|
569
|
+
* ));
|
|
570
|
+
* ```
|
|
571
|
+
*
|
|
572
|
+
* ## What this closes
|
|
573
|
+
*
|
|
574
|
+
* `PageComponentSchema.dataSource` is the one spelling that resolves a saved
|
|
575
|
+
* view for an object-bound block. It works — and because no registration
|
|
576
|
+
* declared it, `sdui-parser`'s `validateTree` reported it with the SAME
|
|
577
|
+
* `unknown-prop` warning it gives the spellings that do nothing. On the tier
|
|
578
|
+
* built to accept AI-authored pages the diagnostic IS the contract, so the
|
|
579
|
+
* tier's only signal pointed away from the one key that works.
|
|
580
|
+
*
|
|
581
|
+
* ## Why a seam and not nine declarations
|
|
582
|
+
*
|
|
583
|
+
* Maintainer ruling, 2026-08-29: option B **in the injection form**. Nine
|
|
584
|
+
* hand-written copies across nine packages is the shape that ruling refused —
|
|
585
|
+
* they drift, and the tenth block forgets. Passing through here is the only
|
|
586
|
+
* thing a block does; `Registry.register` emits the declaration
|
|
587
|
+
* (`withElementDataSourceInput`), and `ELEMENT_DATA_SOURCE_INPUT` in
|
|
588
|
+
* `@object-ui/core` is its single copy, beside the binding's own semantics.
|
|
589
|
+
*
|
|
590
|
+
* ## And it cannot be forgotten
|
|
591
|
+
*
|
|
592
|
+
* `scripts/check-element-data-source-declaration.mjs` fails any source that
|
|
593
|
+
* renders `ElementDataSourceGate` for a registration without passing that
|
|
594
|
+
* registration's renderer through this function. That is the mechanical half of
|
|
595
|
+
* "a tenth block gets it automatically"; this function is the seam it enforces.
|
|
596
|
+
*
|
|
597
|
+
* Returns the renderer UNCHANGED (the mark is held in a `WeakSet`), so it is safe
|
|
598
|
+
* over a `React.forwardRef` object, over `React.memo`, and over a component that
|
|
599
|
+
* something else already re-exports by reference.
|
|
600
|
+
*
|
|
601
|
+
* ⚠️ Re-exported from `@object-ui/core` — where the marker and the declaration
|
|
602
|
+
* live — rather than wrapped here: ONE function under ONE name. This export
|
|
603
|
+
* exists for DISCOVERABILITY, so the seam is findable beside the gate it is
|
|
604
|
+
* about; **call sites must import it from `@object-ui/core`**, and
|
|
605
|
+
* `check:element-data-source-declaration` enforces that.
|
|
606
|
+
*
|
|
607
|
+
* The rule is measured, not stylistic. A registration runs at MODULE SCOPE, 101
|
|
608
|
+
* suites in this repo partially mock `@object-ui/react` by hand-listing the
|
|
609
|
+
* exports they return, and a module-scope read of a name absent from such a list
|
|
610
|
+
* throws at COLLECTION time — the importing test file dies before running one
|
|
611
|
+
* assertion. Taking the seam from here reddened 17 files across all four CI
|
|
612
|
+
* shards with zero failed assertions among them. Nothing mocks `@object-ui/core`,
|
|
613
|
+
* and every registration module already imports `ComponentRegistry` from it.
|
|
614
|
+
*/
|
|
615
|
+
export { elementDataSourceBlock } from '@object-ui/core';
|
|
247
616
|
/**
|
|
248
617
|
* Wrap an object-bound block so its `dataSource` binding reaches the keys it
|
|
249
618
|
* reads, and so the two non-final resolution states render once, the same way,
|
|
@@ -253,7 +622,7 @@ export function NoDataSourcePanel({ testId, title = 'No data source resolved', m
|
|
|
253
622
|
* against a half-resolved query is how a page shows a wider answer than the one
|
|
254
623
|
* that was authored.
|
|
255
624
|
*/
|
|
256
|
-
export function ElementDataSourceGate({ schema, mapping, dataSource, testId, errorTitle, requiresDataSource, noDataSourceMessage: noDataSourceText, children, }) {
|
|
625
|
+
export function ElementDataSourceGate({ schema, mapping, dataSource, testId, errorTitle, requiresDataSource, noDataSourceMessage: noDataSourceText, requiresObject, children, }) {
|
|
257
626
|
const bound = useElementDataSourceSchema(schema, mapping, dataSource);
|
|
258
627
|
const adapter = useResolvedDataSource(dataSource);
|
|
259
628
|
// Reported BEFORE the view state, because with no adapter every downstream
|
|
@@ -264,11 +633,24 @@ export function ElementDataSourceGate({ schema, mapping, dataSource, testId, err
|
|
|
264
633
|
if (requiresDataSource && adapter == null) {
|
|
265
634
|
return _jsx(NoDataSourcePanel, { testId: testId, message: noDataSourceText });
|
|
266
635
|
}
|
|
636
|
+
// A refused filter is `missing` too, but it gets the malformed-filter notice
|
|
637
|
+
// rather than the "could not be resolved" panel: the binding resolved, and
|
|
638
|
+
// what the author has to fix is a filter condition (objectui#10789).
|
|
639
|
+
if (bound.filterRefusal) {
|
|
640
|
+
return _jsx(ElementDataSourceMalformedFilterPanel, { testId: testId, refusal: bound.filterRefusal });
|
|
641
|
+
}
|
|
267
642
|
if (bound.status === 'missing') {
|
|
268
643
|
return _jsx(ElementDataSourceErrorPanel, { testId: testId, title: errorTitle, message: bound.error });
|
|
269
644
|
}
|
|
270
645
|
if (bound.status === 'loading') {
|
|
271
646
|
return _jsx(ElementDataSourceLoadingPanel, { testId: testId });
|
|
272
647
|
}
|
|
648
|
+
// Named in neither place: not on the block's own key, and not by a binding
|
|
649
|
+
// (whose `object` the hook above has already landed on that key). See
|
|
650
|
+
// `requiresObject` for why the call site, not this gate, decides it applies.
|
|
651
|
+
const objectKey = mapping?.object ?? 'objectName';
|
|
652
|
+
if (requiresObject && objectKey !== false && !namesObject(bound.schema, objectKey)) {
|
|
653
|
+
return _jsx(ElementDataSourceNoObjectPanel, { testId: testId, property: objectKey });
|
|
654
|
+
}
|
|
273
655
|
return children(bound.schema);
|
|
274
656
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* The renderer half of the spec's API binding of a page element (objectui#12197):
|
|
9
|
+
* `PageComponentSchema.dataSource` as `ElementApiDataSourceSchema`,
|
|
10
|
+
*
|
|
11
|
+
* ```json
|
|
12
|
+
* { "provider": "api", "read": { "url": "/api/v1/cloud/home" }, "path": "data" }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* The spec states what a renderer does with it: the element reads ONE
|
|
16
|
+
* same-origin endpoint with the console's signed-in fetch and binds the selected
|
|
17
|
+
* value, "which the element reads as `data` in its expression holes and its
|
|
18
|
+
* `visibleWhen`". `SchemaRenderer` is the one place both of those are evaluated,
|
|
19
|
+
* so it calls {@link useElementApiDataSource} for every node and binds the
|
|
20
|
+
* answer as the `data` root (see "The API binding" there). This module owns the
|
|
21
|
+
* read itself.
|
|
22
|
+
*
|
|
23
|
+
* ## The binding is judged by the spec's own schema
|
|
24
|
+
*
|
|
25
|
+
* A node's `dataSource` reaches the renderer as stored metadata, unparsed. The
|
|
26
|
+
* hook parses it with `ElementApiDataSourceSchema` before it reads anything,
|
|
27
|
+
* so a binding the spec refuses — an absolute or protocol-relative `url`, a
|
|
28
|
+
* credential key, a `method` other than `GET`, a malformed `path` — is
|
|
29
|
+
* `refused` here with the spec's own message, and no request is sent. There is
|
|
30
|
+
* no second, hand-kept same-origin rule in this repo to drift from the
|
|
31
|
+
* contract's.
|
|
32
|
+
*
|
|
33
|
+
* A `dataSource` that is not an API binding (absent, or the object binding
|
|
34
|
+
* `useElementDataSource` reads) is `absent` here: the two shapes are told apart
|
|
35
|
+
* by `provider: 'api'`, the spec's discriminator.
|
|
36
|
+
*
|
|
37
|
+
* ## The read
|
|
38
|
+
*
|
|
39
|
+
* Through `SchemaRendererContext.apiFetch`, the host's authenticated fetch the
|
|
40
|
+
* console hands every renderer (`ConsoleShell`), falling back to the global
|
|
41
|
+
* fetch only when no host supplies one — the rule every `apiFetch` reader in
|
|
42
|
+
* the tree follows. `GET`, `Accept: application/json`, `credentials: 'include'`.
|
|
43
|
+
* A non-2xx answer, a body that is not JSON, or a `path` that selects nothing
|
|
44
|
+
* is `error`, with a sentence naming which; the value is never guessed.
|
|
45
|
+
*
|
|
46
|
+
* Concurrent elements that bind the same URL share ONE request: {@link
|
|
47
|
+
* readElementApi} keeps a pending request per URL and forgets it the moment it
|
|
48
|
+
* settles, so it caches no answer (the shape `readStorageUsage` uses).
|
|
49
|
+
*
|
|
50
|
+
* @module
|
|
51
|
+
*/
|
|
52
|
+
import { type ElementApiDataSource } from '@objectstack/spec/ui';
|
|
53
|
+
import { type ApiFetch } from '../context/SchemaRendererContext.js';
|
|
54
|
+
/**
|
|
55
|
+
* Where an API binding stands.
|
|
56
|
+
*
|
|
57
|
+
* - `absent` — the node carries no API binding; nothing is read.
|
|
58
|
+
* - `refused` — the node carries one the spec refuses; nothing is read.
|
|
59
|
+
* - `loading` — the read is in flight.
|
|
60
|
+
* - `ready` — the read answered and `path` selected a value.
|
|
61
|
+
* - `error` — the read failed, or `path` selected nothing.
|
|
62
|
+
*/
|
|
63
|
+
export type ElementApiDataSourceStatus = 'absent' | 'refused' | 'loading' | 'ready' | 'error';
|
|
64
|
+
export interface UseElementApiDataSourceResult {
|
|
65
|
+
status: ElementApiDataSourceStatus;
|
|
66
|
+
/** The parsed binding, for every status but `absent` and `refused`. */
|
|
67
|
+
config?: ElementApiDataSource;
|
|
68
|
+
/** The selected value, set only for `ready`. */
|
|
69
|
+
data?: unknown;
|
|
70
|
+
/** Author-facing explanation, set for `refused` and `error`. */
|
|
71
|
+
error?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Is this `dataSource` value the API binding — whatever it says beyond
|
|
75
|
+
* `provider`? `provider: 'api'` is the spec's discriminator between the two
|
|
76
|
+
* shapes of `PageComponentSchema.dataSource`; the object binding carries no
|
|
77
|
+
* `provider`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function isElementApiDataSourceShape(value: unknown): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Judge a node's `dataSource` as an API binding: `absent` when it is not one,
|
|
82
|
+
* `refused` with the spec's message when the spec refuses it, otherwise the
|
|
83
|
+
* parsed binding.
|
|
84
|
+
*/
|
|
85
|
+
export declare function judgeElementApiDataSource(value: unknown): {
|
|
86
|
+
status: 'absent';
|
|
87
|
+
} | {
|
|
88
|
+
status: 'refused';
|
|
89
|
+
error: string;
|
|
90
|
+
} | {
|
|
91
|
+
status: 'parsed';
|
|
92
|
+
config: ElementApiDataSource;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Select `path` (dotted segments; a numeric segment indexes an array) from a
|
|
96
|
+
* response body. `found: false` when any segment has nothing under it — an
|
|
97
|
+
* absent key, not a key whose value is `undefined`, is what "selects nothing"
|
|
98
|
+
* means.
|
|
99
|
+
*/
|
|
100
|
+
export declare function selectElementApiPath(body: unknown, path: string | undefined): {
|
|
101
|
+
found: true;
|
|
102
|
+
value: unknown;
|
|
103
|
+
} | {
|
|
104
|
+
found: false;
|
|
105
|
+
segment: string;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* `GET url` through `doFetch`, resolving with the parsed JSON body. Rejects on
|
|
109
|
+
* a network failure, a non-2xx answer or a body that is not JSON. Callers that
|
|
110
|
+
* ask while a read of the same URL is pending share it.
|
|
111
|
+
*/
|
|
112
|
+
export declare function readElementApi(url: string, doFetch: ApiFetch): Promise<unknown>;
|
|
113
|
+
/**
|
|
114
|
+
* Resolve a node's API binding: read its endpoint and select its `path`.
|
|
115
|
+
*
|
|
116
|
+
* Called unconditionally by `SchemaRenderer` for every node; a node without an
|
|
117
|
+
* API binding costs one `isPlainObject` test and issues no request.
|
|
118
|
+
*/
|
|
119
|
+
export declare function useElementApiDataSource(schema: unknown): UseElementApiDataSourceResult;
|