@assistant-ui/react-generative-ui 0.0.24 → 0.1.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/README.md +2 -0
- package/dist/a2ui.d.ts +1 -4
- package/dist/a2ui.d.ts.map +1 -1
- package/dist/a2ui.js +1 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -10
- package/dist/index.server.d.ts +1 -2
- package/dist/index.server.d.ts.map +1 -1
- package/dist/index.server.js +1 -10
- package/dist/ir.d.ts +1 -111
- package/dist/ir.d.ts.map +1 -1
- package/dist/ir.js +1 -175
- package/dist/slack.d.ts +1 -4
- package/dist/slack.d.ts.map +1 -1
- package/dist/slack.js +1 -4
- package/dist/teams.d.ts +1 -4
- package/dist/teams.d.ts.map +1 -1
- package/dist/teams.js +1 -4
- package/package.json +4 -5
- package/src/a2ui.ts +1 -22
- package/src/index.server.ts +1 -2
- package/src/index.test.ts +75 -0
- package/src/index.ts +1 -2
- package/src/ir.ts +1 -279
- package/src/slack.ts +1 -39
- package/src/teams.ts +1 -35
- package/dist/JSONGenerativeUI.client.d.ts +0 -28
- package/dist/JSONGenerativeUI.client.d.ts.map +0 -1
- package/dist/JSONGenerativeUI.client.js +0 -101
- package/dist/JSONGenerativeUI.client.js.map +0 -1
- package/dist/JSONGenerativeUI.server.d.ts +0 -21
- package/dist/JSONGenerativeUI.server.d.ts.map +0 -1
- package/dist/JSONGenerativeUI.server.js +0 -32
- package/dist/JSONGenerativeUI.server.js.map +0 -1
- package/dist/JSONGenerativeUI.shared.d.ts +0 -63
- package/dist/JSONGenerativeUI.shared.d.ts.map +0 -1
- package/dist/JSONGenerativeUI.shared.js +0 -31
- package/dist/JSONGenerativeUI.shared.js.map +0 -1
- package/dist/RadioGroupScope.d.ts +0 -6
- package/dist/RadioGroupScope.d.ts.map +0 -1
- package/dist/RadioGroupScope.js +0 -25
- package/dist/RadioGroupScope.js.map +0 -1
- package/dist/a2ui/BindingContext.d.ts +0 -11
- package/dist/a2ui/BindingContext.d.ts.map +0 -1
- package/dist/a2ui/BindingContext.js +0 -12
- package/dist/a2ui/BindingContext.js.map +0 -1
- package/dist/a2ui/PresentRenderer.d.ts +0 -11
- package/dist/a2ui/PresentRenderer.d.ts.map +0 -1
- package/dist/a2ui/PresentRenderer.js +0 -39
- package/dist/a2ui/PresentRenderer.js.map +0 -1
- package/dist/a2ui/convert.d.ts +0 -15
- package/dist/a2ui/convert.d.ts.map +0 -1
- package/dist/a2ui/convert.js +0 -728
- package/dist/a2ui/convert.js.map +0 -1
- package/dist/a2ui/dataModel.d.ts +0 -9
- package/dist/a2ui/dataModel.d.ts.map +0 -1
- package/dist/a2ui/dataModel.js +0 -44
- package/dist/a2ui/dataModel.js.map +0 -1
- package/dist/a2ui/reducer.d.ts +0 -8
- package/dist/a2ui/reducer.d.ts.map +0 -1
- package/dist/a2ui/reducer.js +0 -242
- package/dist/a2ui/reducer.js.map +0 -1
- package/dist/a2ui/snapshot.d.ts +0 -3
- package/dist/a2ui/snapshot.d.ts.map +0 -1
- package/dist/a2ui/snapshot.js +0 -33
- package/dist/a2ui/snapshot.js.map +0 -1
- package/dist/a2ui/types.d.ts +0 -88
- package/dist/a2ui/types.d.ts.map +0 -1
- package/dist/a2ui/types.js +0 -6
- package/dist/a2ui/types.js.map +0 -1
- package/dist/a2ui/valueFunctions.d.ts +0 -16
- package/dist/a2ui/valueFunctions.d.ts.map +0 -1
- package/dist/a2ui/valueFunctions.js +0 -332
- package/dist/a2ui/valueFunctions.js.map +0 -1
- package/dist/actionRegistry.d.ts +0 -29
- package/dist/actionRegistry.d.ts.map +0 -1
- package/dist/actionRegistry.js +0 -41
- package/dist/actionRegistry.js.map +0 -1
- package/dist/answeredValues.d.ts +0 -16
- package/dist/answeredValues.d.ts.map +0 -1
- package/dist/answeredValues.js +0 -37
- package/dist/answeredValues.js.map +0 -1
- package/dist/buildPresentParameters.d.ts +0 -21
- package/dist/buildPresentParameters.d.ts.map +0 -1
- package/dist/buildPresentParameters.js +0 -95
- package/dist/buildPresentParameters.js.map +0 -1
- package/dist/constants.d.ts +0 -15
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -19
- package/dist/constants.js.map +0 -1
- package/dist/convert/boundSpec.d.ts +0 -48
- package/dist/convert/boundSpec.d.ts.map +0 -1
- package/dist/convert/boundSpec.js +0 -108
- package/dist/convert/boundSpec.js.map +0 -1
- package/dist/convert/copyBounded.d.ts +0 -25
- package/dist/convert/copyBounded.d.ts.map +0 -1
- package/dist/convert/copyBounded.js +0 -38
- package/dist/convert/copyBounded.js.map +0 -1
- package/dist/convert/isElement.d.ts +0 -3
- package/dist/convert/isElement.d.ts.map +0 -1
- package/dist/convert/isElement.js +0 -6
- package/dist/convert/isElement.js.map +0 -1
- package/dist/convert/takeRun.d.ts +0 -6
- package/dist/convert/takeRun.d.ts.map +0 -1
- package/dist/convert/takeRun.js +0 -20
- package/dist/convert/takeRun.js.map +0 -1
- package/dist/defineGenerativeComponents.d.ts +0 -29
- package/dist/defineGenerativeComponents.d.ts.map +0 -1
- package/dist/defineGenerativeComponents.js +0 -34
- package/dist/defineGenerativeComponents.js.map +0 -1
- package/dist/fieldReferences.d.ts +0 -9
- package/dist/fieldReferences.d.ts.map +0 -1
- package/dist/fieldReferences.js +0 -53
- package/dist/fieldReferences.js.map +0 -1
- package/dist/generativeUIToJSX.d.ts +0 -18
- package/dist/generativeUIToJSX.d.ts.map +0 -1
- package/dist/generativeUIToJSX.js +0 -76
- package/dist/generativeUIToJSX.js.map +0 -1
- package/dist/index.shared.d.ts +0 -15
- package/dist/index.shared.d.ts.map +0 -1
- package/dist/index.shared.js +0 -9
- package/dist/ir.js.map +0 -1
- package/dist/renderGenerativeUI.d.ts +0 -14
- package/dist/renderGenerativeUI.d.ts.map +0 -1
- package/dist/renderGenerativeUI.js +0 -83
- package/dist/renderGenerativeUI.js.map +0 -1
- package/dist/scopeSchema.d.ts +0 -6
- package/dist/scopeSchema.d.ts.map +0 -1
- package/dist/scopeSchema.js +0 -56
- package/dist/scopeSchema.js.map +0 -1
- package/dist/slack/constants.d.ts +0 -93
- package/dist/slack/constants.d.ts.map +0 -1
- package/dist/slack/constants.js +0 -146
- package/dist/slack/constants.js.map +0 -1
- package/dist/slack/decodeBlockAction.d.ts +0 -10
- package/dist/slack/decodeBlockAction.d.ts.map +0 -1
- package/dist/slack/decodeBlockAction.js +0 -129
- package/dist/slack/decodeBlockAction.js.map +0 -1
- package/dist/slack/fromSlackBlocks.d.ts +0 -27
- package/dist/slack/fromSlackBlocks.d.ts.map +0 -1
- package/dist/slack/fromSlackBlocks.js +0 -483
- package/dist/slack/fromSlackBlocks.js.map +0 -1
- package/dist/slack/toSlackBlocks.d.ts +0 -4
- package/dist/slack/toSlackBlocks.d.ts.map +0 -1
- package/dist/slack/toSlackBlocks.js +0 -901
- package/dist/slack/toSlackBlocks.js.map +0 -1
- package/dist/slack/types.d.ts +0 -204
- package/dist/slack/types.d.ts.map +0 -1
- package/dist/slack/types.js +0 -0
- package/dist/teams/constants.d.ts +0 -34
- package/dist/teams/constants.d.ts.map +0 -1
- package/dist/teams/constants.js +0 -68
- package/dist/teams/constants.js.map +0 -1
- package/dist/teams/decodeSubmitData.d.ts +0 -26
- package/dist/teams/decodeSubmitData.d.ts.map +0 -1
- package/dist/teams/decodeSubmitData.js +0 -70
- package/dist/teams/decodeSubmitData.js.map +0 -1
- package/dist/teams/temporalId.d.ts +0 -5
- package/dist/teams/temporalId.d.ts.map +0 -1
- package/dist/teams/temporalId.js +0 -28
- package/dist/teams/temporalId.js.map +0 -1
- package/dist/teams/toAdaptiveCard.d.ts +0 -45
- package/dist/teams/toAdaptiveCard.d.ts.map +0 -1
- package/dist/teams/toAdaptiveCard.js +0 -642
- package/dist/teams/toAdaptiveCard.js.map +0 -1
- package/dist/teams/toTeamsAttachments.d.ts +0 -10
- package/dist/teams/toTeamsAttachments.d.ts.map +0 -1
- package/dist/teams/toTeamsAttachments.js +0 -99
- package/dist/teams/toTeamsAttachments.js.map +0 -1
- package/dist/teams/types.d.ts +0 -224
- package/dist/teams/types.d.ts.map +0 -1
- package/dist/teams/types.js +0 -0
- package/dist/temporal.d.ts +0 -33
- package/dist/temporal.d.ts.map +0 -1
- package/dist/temporal.js +0 -153
- package/dist/temporal.js.map +0 -1
- package/dist/types.d.ts +0 -99
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -0
- package/dist/vocabulary/alert.d.ts +0 -32
- package/dist/vocabulary/alert.d.ts.map +0 -1
- package/dist/vocabulary/alert.js +0 -160
- package/dist/vocabulary/alert.js.map +0 -1
- package/dist/vocabulary/collectFormValues.d.ts +0 -23
- package/dist/vocabulary/collectFormValues.d.ts.map +0 -1
- package/dist/vocabulary/collectFormValues.js +0 -44
- package/dist/vocabulary/collectFormValues.js.map +0 -1
- package/dist/vocabulary/data.d.ts +0 -66
- package/dist/vocabulary/data.d.ts.map +0 -1
- package/dist/vocabulary/data.js +0 -503
- package/dist/vocabulary/data.js.map +0 -1
- package/dist/vocabulary/dispatch.d.ts +0 -9
- package/dist/vocabulary/dispatch.d.ts.map +0 -1
- package/dist/vocabulary/dispatch.js +0 -50
- package/dist/vocabulary/dispatch.js.map +0 -1
- package/dist/vocabulary/fact.d.ts +0 -19
- package/dist/vocabulary/fact.d.ts.map +0 -1
- package/dist/vocabulary/fact.js +0 -51
- package/dist/vocabulary/fact.js.map +0 -1
- package/dist/vocabulary/form.d.ts +0 -11
- package/dist/vocabulary/form.d.ts.map +0 -1
- package/dist/vocabulary/form.js +0 -23
- package/dist/vocabulary/form.js.map +0 -1
- package/dist/vocabulary/formatValue.d.ts +0 -7
- package/dist/vocabulary/formatValue.d.ts.map +0 -1
- package/dist/vocabulary/formatValue.js +0 -76
- package/dist/vocabulary/formatValue.js.map +0 -1
- package/dist/vocabulary/icon.d.ts +0 -41
- package/dist/vocabulary/icon.d.ts.map +0 -1
- package/dist/vocabulary/icon.js +0 -272
- package/dist/vocabulary/icon.js.map +0 -1
- package/dist/vocabulary/index.d.ts +0 -12
- package/dist/vocabulary/index.d.ts.map +0 -1
- package/dist/vocabulary/index.js +0 -36
- package/dist/vocabulary/index.js.map +0 -1
- package/dist/vocabulary/interactive.d.ts +0 -230
- package/dist/vocabulary/interactive.d.ts.map +0 -1
- package/dist/vocabulary/interactive.js +0 -584
- package/dist/vocabulary/interactive.js.map +0 -1
- package/dist/vocabulary/layout.d.ts +0 -79
- package/dist/vocabulary/layout.d.ts.map +0 -1
- package/dist/vocabulary/layout.js +0 -139
- package/dist/vocabulary/layout.js.map +0 -1
- package/dist/vocabulary/list.d.ts +0 -14
- package/dist/vocabulary/list.d.ts.map +0 -1
- package/dist/vocabulary/list.js +0 -50
- package/dist/vocabulary/list.js.map +0 -1
- package/dist/vocabulary/media.d.ts +0 -25
- package/dist/vocabulary/media.d.ts.map +0 -1
- package/dist/vocabulary/media.js +0 -42
- package/dist/vocabulary/media.js.map +0 -1
- package/dist/vocabulary/text.d.ts +0 -57
- package/dist/vocabulary/text.d.ts.map +0 -1
- package/dist/vocabulary/text.js +0 -49
- package/dist/vocabulary/text.js.map +0 -1
- package/dist/vocabulary/toTextContent.d.ts +0 -2
- package/dist/vocabulary/toTextContent.d.ts.map +0 -1
- package/dist/vocabulary/toTextContent.js +0 -6
- package/dist/vocabulary/toTextContent.js.map +0 -1
- package/src/JSONGenerativeUI.client.tsx +0 -168
- package/src/JSONGenerativeUI.server.tsx +0 -39
- package/src/JSONGenerativeUI.shared.ts +0 -90
- package/src/JSONGenerativeUI.test.tsx +0 -1298
- package/src/RadioGroupScope.server.test.tsx +0 -24
- package/src/RadioGroupScope.tsx +0 -34
- package/src/a2ui/BindingContext.ts +0 -26
- package/src/a2ui/PresentRenderer.tsx +0 -83
- package/src/a2ui/convert.test.ts +0 -2923
- package/src/a2ui/convert.ts +0 -1207
- package/src/a2ui/dataModel.ts +0 -69
- package/src/a2ui/reducer.test.ts +0 -422
- package/src/a2ui/reducer.ts +0 -363
- package/src/a2ui/snapshot.test.ts +0 -52
- package/src/a2ui/snapshot.ts +0 -52
- package/src/a2ui/types.ts +0 -114
- package/src/a2ui/valueFunctions.test.ts +0 -271
- package/src/a2ui/valueFunctions.ts +0 -523
- package/src/actionRegistry.test.ts +0 -90
- package/src/actionRegistry.ts +0 -88
- package/src/answeredValues.test.ts +0 -33
- package/src/answeredValues.tsx +0 -70
- package/src/buildPresentParameters.test.ts +0 -246
- package/src/buildPresentParameters.ts +0 -130
- package/src/constants.ts +0 -17
- package/src/convert/boundSpec.test.ts +0 -192
- package/src/convert/boundSpec.ts +0 -134
- package/src/convert/copyBounded.test.ts +0 -131
- package/src/convert/copyBounded.ts +0 -38
- package/src/convert/isElement.ts +0 -6
- package/src/convert/takeRun.test.ts +0 -64
- package/src/convert/takeRun.ts +0 -19
- package/src/defineGenerativeComponents.ts +0 -38
- package/src/fieldReferences.test.ts +0 -66
- package/src/fieldReferences.ts +0 -83
- package/src/generativeUIToJSX.test.ts +0 -268
- package/src/generativeUIToJSX.ts +0 -152
- package/src/index.server.test.ts +0 -106
- package/src/index.shared.ts +0 -61
- package/src/ir.test.ts +0 -203
- package/src/renderGenerativeUI.test.tsx +0 -373
- package/src/renderGenerativeUI.tsx +0 -165
- package/src/scopeSchema.test.ts +0 -72
- package/src/scopeSchema.ts +0 -71
- package/src/slack/a2uiSlack.test.ts +0 -198
- package/src/slack/constants.ts +0 -194
- package/src/slack/datePicker.test.ts +0 -612
- package/src/slack/decodeBlockAction.test.ts +0 -233
- package/src/slack/decodeBlockAction.ts +0 -232
- package/src/slack/fromSlackBlocks.test.ts +0 -1465
- package/src/slack/fromSlackBlocks.ts +0 -789
- package/src/slack/toSlackBlocks.test.ts +0 -3065
- package/src/slack/toSlackBlocks.ts +0 -1818
- package/src/slack/types.ts +0 -264
- package/src/teams/constants.ts +0 -93
- package/src/teams/decodeSubmitData.test.ts +0 -397
- package/src/teams/decodeSubmitData.ts +0 -110
- package/src/teams/temporal.test.ts +0 -766
- package/src/teams/temporalId.ts +0 -56
- package/src/teams/toAdaptiveCard.test.ts +0 -1897
- package/src/teams/toAdaptiveCard.ts +0 -1057
- package/src/teams/toTeamsAttachments.test.ts +0 -74
- package/src/teams/toTeamsAttachments.ts +0 -133
- package/src/teams/types.ts +0 -266
- package/src/temporal.test.ts +0 -337
- package/src/temporal.ts +0 -263
- package/src/types.ts +0 -119
- package/src/vocabulary/alert.test.tsx +0 -427
- package/src/vocabulary/alert.tsx +0 -217
- package/src/vocabulary/collectFormValues.test.ts +0 -389
- package/src/vocabulary/collectFormValues.ts +0 -116
- package/src/vocabulary/data.test.tsx +0 -858
- package/src/vocabulary/data.tsx +0 -819
- package/src/vocabulary/datepicker.dom.test.tsx +0 -1457
- package/src/vocabulary/dispatch.ts +0 -83
- package/src/vocabulary/fact.test.tsx +0 -94
- package/src/vocabulary/fact.tsx +0 -54
- package/src/vocabulary/form.test.tsx +0 -113
- package/src/vocabulary/form.tsx +0 -39
- package/src/vocabulary/formatValue.test.ts +0 -72
- package/src/vocabulary/formatValue.ts +0 -127
- package/src/vocabulary/icon.test.tsx +0 -64
- package/src/vocabulary/icon.tsx +0 -174
- package/src/vocabulary/index.test.ts +0 -65
- package/src/vocabulary/index.ts +0 -33
- package/src/vocabulary/interactive.dispatch.test.tsx +0 -454
- package/src/vocabulary/interactive.dom.test.tsx +0 -1747
- package/src/vocabulary/interactive.test.tsx +0 -377
- package/src/vocabulary/interactive.tsx +0 -1109
- package/src/vocabulary/layout.test.tsx +0 -323
- package/src/vocabulary/layout.tsx +0 -247
- package/src/vocabulary/list.test.tsx +0 -222
- package/src/vocabulary/list.tsx +0 -50
- package/src/vocabulary/media.test.tsx +0 -85
- package/src/vocabulary/media.tsx +0 -56
- package/src/vocabulary/text.test.tsx +0 -102
- package/src/vocabulary/text.tsx +0 -60
- package/src/vocabulary/toTextContent.ts +0 -2
package/src/convert/boundSpec.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { copyBounded } from "./copyBounded";
|
|
2
|
-
|
|
3
|
-
/** The maximum traversal depth, counted in visited values, not tree levels. */
|
|
4
|
-
export const MAX_TRAVERSAL_DEPTH = 64;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* The element-nesting ceiling a caller observes. `boundNode` visits an element
|
|
8
|
-
* and its `children` array separately, so one level of nesting spends two of
|
|
9
|
-
* {@link MAX_TRAVERSAL_DEPTH}; it runs before the conversion walk and is never
|
|
10
|
-
* the looser of the two. A root array costs one more level than a root object.
|
|
11
|
-
*/
|
|
12
|
-
export const MAX_ELEMENT_DEPTH = MAX_TRAVERSAL_DEPTH / 2;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* The maximum number of entries kept at any single level (root, or a
|
|
16
|
-
* `children` array at any depth) of the raw spec, before it ever reaches
|
|
17
|
-
* `normalizeSpec`. Bounds a hostile array's reported `length` so the pre-pass
|
|
18
|
-
* can never be made to walk further than this regardless of what the array
|
|
19
|
-
* claims about itself.
|
|
20
|
-
*/
|
|
21
|
-
export const CHILDREN_CAP = 200;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The total number of nodes {@link boundSpec} will visit across one walk,
|
|
25
|
-
* regardless of how many times a shared reference recurs. Bounds the
|
|
26
|
-
* combinatorial work a DAG of shared or self-referential nodes would
|
|
27
|
-
* otherwise force even though each individual array stays within
|
|
28
|
-
* {@link CHILDREN_CAP}.
|
|
29
|
-
*/
|
|
30
|
-
export const NODE_BUDGET = 5000;
|
|
31
|
-
|
|
32
|
-
export type ClampReason = "children" | "budget" | "cycle" | "depth";
|
|
33
|
-
|
|
34
|
-
/** Renders the warning detail a protocol reports for one {@link ClampReason}. */
|
|
35
|
-
export function clampReasonDetail(reason: ClampReason): string {
|
|
36
|
-
if (reason === "budget") {
|
|
37
|
-
return `the tree was truncated after ${NODE_BUDGET} nodes.`;
|
|
38
|
-
}
|
|
39
|
-
if (reason === "cycle") return "a self-referencing node was dropped.";
|
|
40
|
-
if (reason === "depth") {
|
|
41
|
-
return `nodes deeper than ${MAX_ELEMENT_DEPTH} levels were dropped.`;
|
|
42
|
-
}
|
|
43
|
-
return `children were clamped to ${CHILDREN_CAP} entries.`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface BoundState {
|
|
47
|
-
remaining: number;
|
|
48
|
-
exhausted: boolean;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function boundNode(
|
|
52
|
-
value: unknown,
|
|
53
|
-
depth: number,
|
|
54
|
-
onClamp: (reason: ClampReason) => void,
|
|
55
|
-
state: BoundState,
|
|
56
|
-
ancestors: WeakSet<object>,
|
|
57
|
-
): unknown {
|
|
58
|
-
if (state.remaining <= 0) {
|
|
59
|
-
if (!state.exhausted) {
|
|
60
|
-
state.exhausted = true;
|
|
61
|
-
onClamp("budget");
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
state.remaining -= 1;
|
|
66
|
-
if (depth > MAX_TRAVERSAL_DEPTH) {
|
|
67
|
-
onClamp("depth");
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
if (Array.isArray(value)) {
|
|
71
|
-
if (ancestors.has(value)) {
|
|
72
|
-
onClamp("cycle");
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
ancestors.add(value);
|
|
76
|
-
const { items: bounded, truncated } = copyBounded(value, CHILDREN_CAP);
|
|
77
|
-
if (truncated) onClamp("children");
|
|
78
|
-
const result = bounded.map((item) =>
|
|
79
|
-
boundNode(item, depth + 1, onClamp, state, ancestors),
|
|
80
|
-
);
|
|
81
|
-
ancestors.delete(value);
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
if (value !== null && typeof value === "object") {
|
|
85
|
-
const record = value as Record<string, unknown>;
|
|
86
|
-
const children = record["children"];
|
|
87
|
-
// The spread performs a `children` read of its own, so the copy states the
|
|
88
|
-
// bounded value even when there is none.
|
|
89
|
-
if (children === undefined) return { ...record, children: undefined };
|
|
90
|
-
if (ancestors.has(value)) {
|
|
91
|
-
onClamp("cycle");
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
ancestors.add(value);
|
|
95
|
-
const result = {
|
|
96
|
-
...record,
|
|
97
|
-
children: boundNode(children, depth + 1, onClamp, state, ancestors),
|
|
98
|
-
};
|
|
99
|
-
ancestors.delete(value);
|
|
100
|
-
return result;
|
|
101
|
-
}
|
|
102
|
-
return value;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Produces a bounded plain copy of a raw generative-ui spec before it
|
|
107
|
-
* reaches `normalizeSpec`, whose own traversal of the root array or any
|
|
108
|
-
* `children` array walks the full reported length of a hostile proxied
|
|
109
|
-
* array before any per-field cap downstream ever applies. Every array
|
|
110
|
-
* (root, or `children` at any depth) is capped to {@link CHILDREN_CAP}
|
|
111
|
-
* entries by index, which bounds a proxy however it fabricates `length` or
|
|
112
|
-
* replaces its array methods; recursion itself is capped at
|
|
113
|
-
* {@link MAX_TRAVERSAL_DEPTH}. Every object is returned as a plain copy whose
|
|
114
|
-
* `children` came from a single read, because a record that answers
|
|
115
|
-
* `[[HasProperty]]` and `[[Get]]`, or two `[[Get]]`s, differently would
|
|
116
|
-
* otherwise hand the bound one value and `normalizeSpec` another, skipping
|
|
117
|
-
* every bound here for that subtree. `onClamp` fires once per level that was
|
|
118
|
-
* truncated, receiving the reason for that truncation: `"children"`,
|
|
119
|
-
* `"depth"`, `"budget"`, or `"cycle"`. The walk also spends a total budget of
|
|
120
|
-
* {@link NODE_BUDGET} nodes, so shared references cannot multiply work
|
|
121
|
-
* exponentially, and a node that is its own ancestor is cut to `null`.
|
|
122
|
-
*/
|
|
123
|
-
export function boundSpec(
|
|
124
|
-
spec: unknown,
|
|
125
|
-
onClamp: (reason: ClampReason) => void,
|
|
126
|
-
): unknown {
|
|
127
|
-
return boundNode(
|
|
128
|
-
spec,
|
|
129
|
-
0,
|
|
130
|
-
onClamp,
|
|
131
|
-
{ remaining: NODE_BUDGET, exhausted: false },
|
|
132
|
-
new WeakSet(),
|
|
133
|
-
);
|
|
134
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { copyBounded } from "./copyBounded";
|
|
3
|
-
|
|
4
|
-
const hostileSpecies = (real: unknown[], injected: unknown[]) => {
|
|
5
|
-
function HostileCtor() {
|
|
6
|
-
return { map: () => injected, some: () => true, length: 0 };
|
|
7
|
-
}
|
|
8
|
-
const constructor = { [Symbol.species]: HostileCtor };
|
|
9
|
-
return new Proxy(real, {
|
|
10
|
-
get: (target, prop, receiver) =>
|
|
11
|
-
prop === "constructor"
|
|
12
|
-
? constructor
|
|
13
|
-
: Reflect.get(target, prop, receiver),
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
describe("copyBounded", () => {
|
|
18
|
-
it("copies every entry of a shorter array", () => {
|
|
19
|
-
expect(copyBounded(["a", "b"], 5).items).toEqual(["a", "b"]);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("stops at the cap", () => {
|
|
23
|
-
expect(copyBounded(["a", "b", "c"], 2).items).toEqual(["a", "b"]);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("returns a fresh array rather than the input", () => {
|
|
27
|
-
const input = ["a"];
|
|
28
|
-
expect(copyBounded(input, 5).items).not.toBe(input);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("leaves an absent index absent, as slice does", () => {
|
|
32
|
-
const sparse: unknown[] = [];
|
|
33
|
-
sparse[0] = "a";
|
|
34
|
-
sparse[2] = "c";
|
|
35
|
-
|
|
36
|
-
const { items: result } = copyBounded(sparse, 5);
|
|
37
|
-
|
|
38
|
-
expect(result).toHaveLength(3);
|
|
39
|
-
expect(1 in result).toBe(false);
|
|
40
|
-
expect(result).toEqual(sparse.slice(0, 5));
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("does not read an index the input reports as absent", () => {
|
|
44
|
-
const reads: string[] = [];
|
|
45
|
-
const probe = new Proxy(["a", "b"], {
|
|
46
|
-
has: (target, prop) => prop !== "1" && Reflect.has(target, prop),
|
|
47
|
-
get: (target, prop, receiver) => {
|
|
48
|
-
if (typeof prop === "string" && /^\d+$/.test(prop)) reads.push(prop);
|
|
49
|
-
return Reflect.get(target, prop, receiver);
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const { items: result } = copyBounded(probe, 2);
|
|
54
|
-
|
|
55
|
-
expect(reads).toEqual(["0"]);
|
|
56
|
-
expect(1 in result).toBe(false);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("reports truncation from the same length read that bounds the copy", () => {
|
|
60
|
-
let reads = 0;
|
|
61
|
-
const shifty = new Proxy(["a", "b", "c"], {
|
|
62
|
-
get: (target, prop, receiver) => {
|
|
63
|
-
if (prop === "length") {
|
|
64
|
-
reads += 1;
|
|
65
|
-
return reads === 1 ? 3 : 1;
|
|
66
|
-
}
|
|
67
|
-
return Reflect.get(target, prop, receiver);
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const { items, truncated } = copyBounded(shifty, 2);
|
|
72
|
-
|
|
73
|
-
expect(items).toHaveLength(2);
|
|
74
|
-
expect(truncated).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("matches slice for every shape of reported length", () => {
|
|
78
|
-
const reportedLengths = [2.5, Infinity, NaN, -1, "3", 2, 0, -0.5, 1e21];
|
|
79
|
-
const lying = (length: unknown) =>
|
|
80
|
-
new Proxy(["a", "b", "c"], {
|
|
81
|
-
get: (target, prop, receiver) =>
|
|
82
|
-
prop === "length" ? length : Reflect.get(target, prop, receiver),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
for (const cap of [0, 1, 2, 5]) {
|
|
86
|
-
for (const length of reportedLengths) {
|
|
87
|
-
const { items, truncated } = copyBounded(lying(length), cap);
|
|
88
|
-
|
|
89
|
-
expect(items).toEqual(
|
|
90
|
-
Array.prototype.slice.call(lying(length), 0, cap),
|
|
91
|
-
);
|
|
92
|
-
expect(truncated).toBe(
|
|
93
|
-
Array.prototype.slice.call(lying(length), 0, cap + 1).length > cap,
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("bounds an array whose reported length is fabricated", () => {
|
|
100
|
-
const hostile = new Proxy(["a", "b"], {
|
|
101
|
-
get: (target, prop, receiver) =>
|
|
102
|
-
prop === "length"
|
|
103
|
-
? Number.MAX_SAFE_INTEGER
|
|
104
|
-
: Reflect.get(target, prop, receiver),
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
expect(copyBounded(hostile, 3).items).toEqual(["a", "b", undefined]);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("bounds an array that replaces its slice", () => {
|
|
111
|
-
const hostile = new Proxy(["a", "b"], {
|
|
112
|
-
get: (target, prop, receiver) =>
|
|
113
|
-
prop === "slice"
|
|
114
|
-
? () => Array(500).fill("injected")
|
|
115
|
-
: Reflect.get(target, prop, receiver),
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
expect(copyBounded(hostile, 5).items).toEqual(["a", "b"]);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("bounds an array that redirects Symbol.species to a foreign object", () => {
|
|
122
|
-
const injected = Array(500).fill("injected");
|
|
123
|
-
const { items: result } = copyBounded(
|
|
124
|
-
hostileSpecies(["a", "b"], injected),
|
|
125
|
-
5,
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
expect(Array.isArray(result)).toBe(true);
|
|
129
|
-
expect(result).toEqual(["a", "b"]);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copies at most `cap` entries out of `value` by index.
|
|
3
|
-
*
|
|
4
|
-
* Every array method dispatches on the input: a replaced `slice` is called
|
|
5
|
-
* directly, and the intrinsic `Array.prototype.slice.call` still reaches a
|
|
6
|
-
* `constructor` with a `@@species` whose `[[Construct]]` returns an arbitrary
|
|
7
|
-
* object, which then supplies the `map` or `some` the caller runs next. Reading
|
|
8
|
-
* indices consults neither, so it is the only form that bounds a hostile array.
|
|
9
|
-
*
|
|
10
|
-
* Absent indices stay absent, matching what `slice` produces for a sparse
|
|
11
|
-
* input, so callers that skip holes still skip them. `truncated` comes from the
|
|
12
|
-
* same `length` read as the bound, so an input cannot report one figure to the
|
|
13
|
-
* cap and another to the caller's warning.
|
|
14
|
-
*
|
|
15
|
-
* A reported `length` is normalized the way `ToLength` does before it reaches
|
|
16
|
-
* `count`, because a fractional one would otherwise reach `items.length` and
|
|
17
|
-
* throw `RangeError` where `slice` returns a bounded result. `truncated` reads
|
|
18
|
-
* that same normalized figure, so a `length` whose `valueOf` answers differently
|
|
19
|
-
* on a second conversion cannot separate the warning from the bound.
|
|
20
|
-
*/
|
|
21
|
-
export const copyBounded = <T>(
|
|
22
|
-
value: readonly T[],
|
|
23
|
-
cap: number,
|
|
24
|
-
): { readonly items: T[]; readonly truncated: boolean } => {
|
|
25
|
-
const reported = value.length;
|
|
26
|
-
const integral = Math.trunc(Number(reported));
|
|
27
|
-
const usable =
|
|
28
|
-
Number.isNaN(integral) || integral <= 0
|
|
29
|
-
? 0
|
|
30
|
-
: Math.min(integral, Number.MAX_SAFE_INTEGER);
|
|
31
|
-
const count = Math.min(cap, usable);
|
|
32
|
-
const items: T[] = [];
|
|
33
|
-
for (let index = 0; index < count; index += 1) {
|
|
34
|
-
if (index in value) items[index] = value[index] as T;
|
|
35
|
-
}
|
|
36
|
-
items.length = count;
|
|
37
|
-
return { items, truncated: usable > cap };
|
|
38
|
-
};
|
package/src/convert/isElement.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import type { NormalizedUIElement, NormalizedUINode } from "../ir";
|
|
3
|
-
import { takeRun } from "./takeRun";
|
|
4
|
-
|
|
5
|
-
const element = (type: string): NormalizedUIElement => ({ type, props: {} });
|
|
6
|
-
|
|
7
|
-
const isFact = (candidate: NormalizedUIElement) => candidate.type === "Fact";
|
|
8
|
-
|
|
9
|
-
describe("takeRun", () => {
|
|
10
|
-
it("collects the contiguous matching run and reports the resume index", () => {
|
|
11
|
-
const fact = element("Fact");
|
|
12
|
-
const nodes = [fact, fact, element("Text"), fact];
|
|
13
|
-
|
|
14
|
-
expect(takeRun(nodes, 0, isFact)).toEqual({ run: [fact, fact], next: 2 });
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("starts at the given index rather than the head of the list", () => {
|
|
18
|
-
const fact = element("Fact");
|
|
19
|
-
const nodes = [element("Text"), fact, fact];
|
|
20
|
-
|
|
21
|
-
expect(takeRun(nodes, 1, isFact)).toEqual({ run: [fact, fact], next: 3 });
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("stops at the end of the list", () => {
|
|
25
|
-
const fact = element("Fact");
|
|
26
|
-
|
|
27
|
-
expect(takeRun([fact, fact], 0, isFact)).toEqual({
|
|
28
|
-
run: [fact, fact],
|
|
29
|
-
next: 2,
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("returns an empty run without advancing when the first node does not match", () => {
|
|
34
|
-
expect(takeRun([element("Text"), element("Fact")], 0, isFact)).toEqual({
|
|
35
|
-
run: [],
|
|
36
|
-
next: 0,
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("stops on every non-element node shape", () => {
|
|
41
|
-
const fact = element("Fact");
|
|
42
|
-
const nonElements: (NormalizedUINode | undefined)[] = [
|
|
43
|
-
undefined,
|
|
44
|
-
null,
|
|
45
|
-
"",
|
|
46
|
-
"text",
|
|
47
|
-
0,
|
|
48
|
-
1,
|
|
49
|
-
[],
|
|
50
|
-
[fact],
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
for (const nonElement of nonElements) {
|
|
54
|
-
expect(takeRun([fact, nonElement, fact], 0, isFact)).toEqual({
|
|
55
|
-
run: [fact],
|
|
56
|
-
next: 1,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("stops past the end of the list", () => {
|
|
62
|
-
expect(takeRun([element("Fact")], 1, isFact)).toEqual({ run: [], next: 1 });
|
|
63
|
-
});
|
|
64
|
-
});
|
package/src/convert/takeRun.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { NormalizedUIElement, NormalizedUINode } from "../ir";
|
|
2
|
-
import { isElement } from "./isElement";
|
|
3
|
-
|
|
4
|
-
export function takeRun(
|
|
5
|
-
nodes: readonly (NormalizedUINode | undefined)[],
|
|
6
|
-
index: number,
|
|
7
|
-
matches: (element: NormalizedUIElement) => boolean,
|
|
8
|
-
): { run: NormalizedUIElement[]; next: number } {
|
|
9
|
-
const run: NormalizedUIElement[] = [];
|
|
10
|
-
let next = index;
|
|
11
|
-
while (next < nodes.length) {
|
|
12
|
-
const candidate = nodes[next];
|
|
13
|
-
if (candidate === undefined || !isElement(candidate) || !matches(candidate))
|
|
14
|
-
break;
|
|
15
|
-
run.push(candidate);
|
|
16
|
-
next += 1;
|
|
17
|
-
}
|
|
18
|
-
return { run, next };
|
|
19
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { GenerativeUILibrary } from "./types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Authoring helper for a `"use generative"` generative-UI library — the set of
|
|
5
|
-
* components the model may render. Each component colocates its `properties`
|
|
6
|
-
* schema (kept on every build, drives the tool parameters) with its `render`
|
|
7
|
-
* (kept only on the client). Pass the result to {@link JSONGenerativeUI}:
|
|
8
|
-
*
|
|
9
|
-
* ```tsx
|
|
10
|
-
* "use generative";
|
|
11
|
-
* const generative = new JSONGenerativeUI({
|
|
12
|
-
* library: defineGenerativeComponents({
|
|
13
|
-
* Card: {
|
|
14
|
-
* description: "A card.",
|
|
15
|
-
* properties: z.object({ title: z.string() }),
|
|
16
|
-
* render: (props) => <Card {...props} />,
|
|
17
|
-
* },
|
|
18
|
-
* }),
|
|
19
|
-
* });
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* Unlike {@link defineToolkit}, it has **no runtime implementation**. A
|
|
23
|
-
* `"use generative"` compiler unwraps the `defineGenerativeComponents(...)` call
|
|
24
|
-
* per build, dropping each `render` (and its client-only imports) from the server
|
|
25
|
-
* build. Reaching it at runtime means the module wasn't compiled (the directive
|
|
26
|
-
* is missing, or it was used outside a `"use generative"` file), so it throws
|
|
27
|
-
* rather than shipping client `render` code to the server.
|
|
28
|
-
*/
|
|
29
|
-
export function defineGenerativeComponents(
|
|
30
|
-
_library: GenerativeUILibrary,
|
|
31
|
-
): GenerativeUILibrary {
|
|
32
|
-
throw new Error(
|
|
33
|
-
"[assistant-ui] defineGenerativeComponents() has no runtime implementation " +
|
|
34
|
-
"— it is stripped at build time by the use-generative compiler. Reaching " +
|
|
35
|
-
"it means this module was not compiled (e.g. used outside a " +
|
|
36
|
-
'"use generative" file). Add the directive, or do not use it here.',
|
|
37
|
-
);
|
|
38
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { hasFieldReference, resolveFieldReferences } from "./fieldReferences";
|
|
3
|
-
|
|
4
|
-
describe("field references", () => {
|
|
5
|
-
it("resolve from own fields only and drop what does not resolve", () => {
|
|
6
|
-
const fields = Object.assign(Object.create({ inherited: "no" }), {
|
|
7
|
-
note: "hi",
|
|
8
|
-
empty: undefined,
|
|
9
|
-
}) as Record<string, unknown>;
|
|
10
|
-
|
|
11
|
-
expect(
|
|
12
|
-
resolveFieldReferences(
|
|
13
|
-
{
|
|
14
|
-
note: { $field: "note" },
|
|
15
|
-
list: [
|
|
16
|
-
{ $field: "note" },
|
|
17
|
-
{ $field: "empty" },
|
|
18
|
-
{ $field: "inherited" },
|
|
19
|
-
],
|
|
20
|
-
literal: { $field: "note", other: 1 },
|
|
21
|
-
},
|
|
22
|
-
fields,
|
|
23
|
-
),
|
|
24
|
-
).toEqual({
|
|
25
|
-
note: "hi",
|
|
26
|
-
list: ["hi"],
|
|
27
|
-
literal: { $field: "note", other: 1 },
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("fall back to the reference's own fallback, and read only $field and fallback as a reference", () => {
|
|
32
|
-
expect(
|
|
33
|
-
resolveFieldReferences(
|
|
34
|
-
{
|
|
35
|
-
filled: { $field: "note", fallback: "old" },
|
|
36
|
-
missing: { $field: "gone", fallback: "kept" },
|
|
37
|
-
empty: { $field: "gone" },
|
|
38
|
-
list: [{ $field: "gone", fallback: 0 }],
|
|
39
|
-
data: { $field: "note", fallback: "old", extra: true },
|
|
40
|
-
},
|
|
41
|
-
{ note: "new" },
|
|
42
|
-
),
|
|
43
|
-
).toEqual({
|
|
44
|
-
filled: "new",
|
|
45
|
-
missing: "kept",
|
|
46
|
-
list: [0],
|
|
47
|
-
data: { $field: "note", fallback: "old", extra: true },
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("terminate on a cyclic value and keep the cycle", () => {
|
|
52
|
-
const cyclic: Record<string, unknown> = { note: { $field: "note" } };
|
|
53
|
-
cyclic["self"] = cyclic;
|
|
54
|
-
const acyclic: Record<string, unknown> = { a: 1 };
|
|
55
|
-
acyclic["self"] = { back: [acyclic] };
|
|
56
|
-
|
|
57
|
-
expect(hasFieldReference(cyclic)).toBe(true);
|
|
58
|
-
expect(hasFieldReference(acyclic)).toBe(false);
|
|
59
|
-
const resolved = resolveFieldReferences(cyclic, { note: "hi" }) as Record<
|
|
60
|
-
string,
|
|
61
|
-
unknown
|
|
62
|
-
>;
|
|
63
|
-
expect(resolved["note"]).toBe("hi");
|
|
64
|
-
expect(resolved["self"]).toBe(cyclic);
|
|
65
|
-
});
|
|
66
|
-
});
|
package/src/fieldReferences.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
2
|
-
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
-
|
|
4
|
-
const MAX_DEPTH = 64;
|
|
5
|
-
|
|
6
|
-
const isFieldReference = (
|
|
7
|
-
value: unknown,
|
|
8
|
-
): value is { readonly $field: string; readonly fallback?: unknown } =>
|
|
9
|
-
isRecord(value) &&
|
|
10
|
-
typeof value["$field"] === "string" &&
|
|
11
|
-
Object.keys(value).every((key) => key === "$field" || key === "fallback");
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Whether `value` contains a `{ "$field": name }` or `{ "$field": name, "fallback": value }` reference within 64 levels.
|
|
15
|
-
*/
|
|
16
|
-
export const hasFieldReference = (value: unknown): boolean => {
|
|
17
|
-
const seen = new Set<object>();
|
|
18
|
-
const visit = (entry: unknown, depth: number): boolean => {
|
|
19
|
-
if (
|
|
20
|
-
typeof entry !== "object" ||
|
|
21
|
-
entry === null ||
|
|
22
|
-
depth > MAX_DEPTH ||
|
|
23
|
-
seen.has(entry)
|
|
24
|
-
) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
if (isFieldReference(entry)) return true;
|
|
28
|
-
seen.add(entry);
|
|
29
|
-
return Object.values(entry).some((item) => visit(item, depth + 1));
|
|
30
|
-
};
|
|
31
|
-
return visit(value, 0);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Replaces each `{ "$field": name }` reference inside `value` with the own property `name` of `fields`, or with the reference's own `fallback` when that is `undefined`. A reference that still resolves to `undefined` is dropped from its object or array, and a cyclic reference or anything nested deeper than 64 levels is kept as is.
|
|
36
|
-
*/
|
|
37
|
-
export const resolveFieldReferences = (
|
|
38
|
-
value: unknown,
|
|
39
|
-
fields: Readonly<Record<string, unknown>>,
|
|
40
|
-
): unknown => {
|
|
41
|
-
const ancestors = new Set<object>();
|
|
42
|
-
const resolve = (entry: unknown, depth: number): unknown => {
|
|
43
|
-
if (isFieldReference(entry)) {
|
|
44
|
-
const current = Object.hasOwn(fields, entry.$field)
|
|
45
|
-
? fields[entry.$field]
|
|
46
|
-
: undefined;
|
|
47
|
-
if (current !== undefined) return current;
|
|
48
|
-
return Object.hasOwn(entry, "fallback") ? entry.fallback : undefined;
|
|
49
|
-
}
|
|
50
|
-
if (
|
|
51
|
-
typeof entry !== "object" ||
|
|
52
|
-
entry === null ||
|
|
53
|
-
depth > MAX_DEPTH ||
|
|
54
|
-
ancestors.has(entry)
|
|
55
|
-
) {
|
|
56
|
-
return entry;
|
|
57
|
-
}
|
|
58
|
-
ancestors.add(entry);
|
|
59
|
-
try {
|
|
60
|
-
if (Array.isArray(entry)) {
|
|
61
|
-
return entry
|
|
62
|
-
.map((item) => resolve(item, depth + 1))
|
|
63
|
-
.filter((item) => item !== undefined);
|
|
64
|
-
}
|
|
65
|
-
const result: Record<string, unknown> = {};
|
|
66
|
-
for (const [key, item] of Object.entries(entry)) {
|
|
67
|
-
const resolved = resolve(item, depth + 1);
|
|
68
|
-
if (resolved !== undefined) {
|
|
69
|
-
Object.defineProperty(result, key, {
|
|
70
|
-
value: resolved,
|
|
71
|
-
enumerable: true,
|
|
72
|
-
configurable: true,
|
|
73
|
-
writable: true,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return result;
|
|
78
|
-
} finally {
|
|
79
|
-
ancestors.delete(entry);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
return resolve(value, 0);
|
|
83
|
-
};
|