@object-ui/core 17.6.0 → 17.7.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 +6610 -0
- package/README.md +101 -14
- package/dist/actions/ActionEngine.d.ts +4 -2
- package/dist/actions/ActionEngine.js +13 -8
- package/dist/actions/ActionRunner.d.ts +288 -27
- package/dist/actions/ActionRunner.js +443 -63
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/actionKeys.d.ts +9 -7
- package/dist/actions/actionKeys.js +57 -14
- package/dist/actions/actionResponse.js +2 -2
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/recordDelete.d.ts +118 -0
- package/dist/actions/recordDelete.js +112 -0
- package/dist/adapters/ValueDataSource.d.ts +6 -1
- package/dist/adapters/ValueDataSource.js +1090 -98
- package/dist/adapters/batchTransaction.d.ts +1 -1
- package/dist/adapters/batchTransaction.js +1 -1
- package/dist/builder/schema-builder.d.ts +38 -81
- package/dist/builder/schema-builder.js +33 -152
- package/dist/data-scope/ViewDataProvider.d.ts +1 -1
- package/dist/data-scope/ViewDataProvider.js +21 -1
- package/dist/data-scope/element-data-source.d.ts +261 -7
- package/dist/data-scope/element-data-source.js +342 -52
- package/dist/data-scope/index.d.ts +9 -4
- package/dist/data-scope/index.js +9 -4
- package/dist/evaluator/ExpressionEvaluator.d.ts +86 -0
- package/dist/evaluator/ExpressionEvaluator.js +184 -12
- package/dist/evaluator/FormulaFunctions.d.ts +0 -10
- package/dist/evaluator/FormulaFunctions.js +166 -27
- package/dist/evaluator/declaredPredicate.d.ts +93 -9
- package/dist/evaluator/declaredPredicate.js +117 -16
- package/dist/evaluator/fieldRules.d.ts +88 -4
- package/dist/evaluator/fieldRules.js +203 -31
- package/dist/evaluator/index.d.ts +1 -0
- package/dist/evaluator/index.js +1 -0
- package/dist/evaluator/listConditional.d.ts +103 -34
- package/dist/evaluator/listConditional.js +127 -50
- package/dist/evaluator/optionRules.d.ts +77 -6
- package/dist/evaluator/optionRules.js +87 -10
- package/dist/evaluator/predicateInput.d.ts +15 -4
- package/dist/evaluator/predicateInput.js +35 -2
- package/dist/evaluator/rowPredicateCanon.d.ts +7 -18
- package/dist/evaluator/rowPredicateCanon.js +65 -115
- package/dist/evaluator/subjectPermissions.d.ts +76 -0
- package/dist/evaluator/subjectPermissions.js +44 -0
- package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
- package/dist/evaluator/unevaluablePredicate.js +70 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +80 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +358 -31
- package/dist/registry/Registry.js +263 -11
- package/dist/registry/WidgetRegistry.js +7 -3
- package/dist/registry/html-tier-intrinsics.d.ts +90 -0
- package/dist/registry/html-tier-intrinsics.js +110 -0
- package/dist/registry/public-blocks.d.ts +8 -0
- package/dist/registry/public-blocks.js +57 -0
- package/dist/theme/ThemeEngine.js +1 -1
- package/dist/utils/chart-category-key.d.ts +82 -0
- package/dist/utils/chart-category-key.js +84 -0
- package/dist/utils/chart-measure-key.d.ts +64 -0
- package/dist/utils/chart-measure-key.js +65 -0
- package/dist/utils/chart-presentation.d.ts +95 -112
- package/dist/utils/chart-presentation.js +169 -104
- package/dist/utils/chart-series.d.ts +131 -5
- package/dist/utils/chart-series.js +98 -16
- package/dist/utils/column-sortability.d.ts +157 -0
- package/dist/utils/column-sortability.js +217 -0
- package/dist/utils/dashboard-filters.d.ts +38 -6
- package/dist/utils/dashboard-filters.js +107 -61
- package/dist/utils/dataset-format.d.ts +211 -30
- package/dist/utils/dataset-format.js +389 -46
- package/dist/utils/date-display.d.ts +354 -0
- package/dist/utils/date-display.js +463 -0
- package/dist/utils/date-macros.js +7 -0
- package/dist/utils/drill-down.d.ts +78 -0
- package/dist/utils/drill-down.js +93 -0
- package/dist/utils/emptiness.d.ts +96 -0
- package/dist/utils/emptiness.js +101 -0
- package/dist/utils/expand-fields.d.ts +76 -6
- package/dist/utils/expand-fields.js +76 -6
- package/dist/utils/extract-records.d.ts +65 -5
- package/dist/utils/extract-records.js +65 -8
- package/dist/utils/filter-converter.d.ts +152 -4
- package/dist/utils/filter-converter.js +1092 -22
- package/dist/utils/filter-tokens.d.ts +49 -5
- package/dist/utils/filter-tokens.js +105 -33
- package/dist/utils/grouping-fields.d.ts +61 -0
- package/dist/utils/grouping-fields.js +84 -0
- package/dist/utils/managedBy.d.ts +3 -3
- package/dist/utils/managedBy.js +5 -5
- package/dist/utils/native-date-value.d.ts +48 -0
- package/dist/utils/native-date-value.js +167 -0
- package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
- package/dist/utils/non-grid-row-ceiling.js +141 -0
- package/dist/utils/normalize-list-view.d.ts +121 -1
- package/dist/utils/normalize-list-view.js +186 -6
- package/dist/utils/number-display.d.ts +63 -14
- package/dist/utils/number-display.js +34 -12
- package/dist/utils/object-aggregate-query.d.ts +113 -0
- package/dist/utils/object-aggregate-query.js +50 -0
- package/dist/utils/parent-scope.d.ts +123 -0
- package/dist/utils/parent-scope.js +140 -0
- package/dist/utils/predicate-fields.d.ts +45 -12
- package/dist/utils/predicate-fields.js +137 -12
- package/dist/utils/record-color.d.ts +75 -0
- package/dist/utils/record-color.js +65 -0
- package/dist/utils/record-source.d.ts +298 -0
- package/dist/utils/record-source.js +331 -0
- package/dist/utils/record-title.d.ts +148 -8
- package/dist/utils/record-title.js +220 -26
- package/dist/utils/reference-keys.d.ts +21 -22
- package/dist/utils/reference-keys.js +532 -29
- package/dist/utils/sort-query.d.ts +115 -15
- package/dist/utils/sort-query.js +240 -13
- package/dist/utils/text-comparand.d.ts +92 -0
- package/dist/utils/text-comparand.js +99 -0
- package/dist/utils/without-denied-fields.d.ts +49 -0
- package/dist/utils/without-denied-fields.js +52 -0
- package/dist/validation/index.d.ts +8 -4
- package/dist/validation/index.js +8 -4
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.js +200 -82
- package/dist/validation/validators/object-validation-engine.d.ts +1 -1
- package/dist/validation/validators/object-validation-engine.js +14 -3
- package/package.json +8 -9
- package/dist/data-scope/DataScopeManager.d.ts +0 -127
- package/dist/data-scope/DataScopeManager.js +0 -229
- package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
- package/dist/protocols/ResponsiveProtocol.js +0 -158
- package/dist/validation/validation-engine.d.ts +0 -88
- package/dist/validation/validation-engine.js +0 -434
|
@@ -1,56 +1,559 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ObjectUI —
|
|
2
|
+
* ObjectUI — object-schema key canonicalization at ingestion
|
|
3
3
|
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
4
|
*
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ The FILE and its exports still say `reference`; the pass no longer does
|
|
9
|
+
* only that. The `reference` / `reference_to` pair was the first tenant, and
|
|
10
|
+
* objectui#7650 added the retired-dialect arm below, which folds ANY undeclared
|
|
11
|
+
* snake twin of a declared `FieldSchema` key. Renaming the exports would be a
|
|
12
|
+
* published-export change across ~20 in-repo citations and belongs to its own
|
|
13
|
+
* card; the two arms are marked so the name cannot mislead a reader who got
|
|
14
|
+
* here from a grep.
|
|
7
15
|
*/
|
|
16
|
+
import { FIELD_KEY_GUIDANCE, FieldSchema } from '@objectstack/spec/data';
|
|
8
17
|
/**
|
|
9
18
|
* Backend object schemas follow the ObjectStack convention and name a
|
|
10
19
|
* relational field's target object `reference`
|
|
11
|
-
* (e.g. `{ type: 'lookup', reference: 'showcase_account' }`)
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* `normalizeFieldReferenceKeys`
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
* (e.g. `{ type: 'lookup', reference: 'showcase_account' }`) — the only
|
|
21
|
+
* spelling `@objectstack/spec`'s `FieldSchema` declares. Since objectui#11070
|
|
22
|
+
* round 4 it is also the only spelling ObjectUI writes or reads: its types
|
|
23
|
+
* (`LookupFieldMetadata`, `MasterDetailFieldMetadata`, `DetailViewField`), its
|
|
24
|
+
* in-repo emitters, and its readers (`LookupField`, `UserField`, the lookup
|
|
25
|
+
* and user read cells) all spell it `reference`.
|
|
26
|
+
*
|
|
27
|
+
* `normalizeFieldReferenceKeys` FOLDS a foreign `reference_to` (or camelCase
|
|
28
|
+
* `referenceTo`) onto `reference` when — and only when — `reference` is
|
|
29
|
+
* absent, so a stored legacy def still reaches every reader. It never writes
|
|
30
|
+
* `reference_to`. Until round 4 it stamped BOTH snake_case keys onto every
|
|
31
|
+
* relational def, and that stamp was the producer that kept a second spelling
|
|
32
|
+
* alive inside ObjectUI: the readers read `reference_to` because the stamp and
|
|
33
|
+
* the emitters wrote it, and the emitters wrote it because the readers read it.
|
|
34
|
+
* It mutates the field in place — the ObjectStack adapter caches the schema
|
|
35
|
+
* object and re-serves it, so the one pass must stick — and is idempotent. A
|
|
36
|
+
* `reference` that is already set is never overwritten, and the foreign key is
|
|
37
|
+
* left on the def exactly as served: nothing here drops a key (see "Leave arm"
|
|
38
|
+
* below).
|
|
39
|
+
*
|
|
40
|
+
* ## ⭐ Why this file also WARNS — objectui#6837 half 2
|
|
41
|
+
*
|
|
42
|
+
* Maintainer ruling, 2026-08-31 (第 6 场总监席决裁批 #14), 原文照录:
|
|
43
|
+
*
|
|
44
|
+
* > objectui不是前端的项目吗?后端的元数据只要对,前端按协议执行就行了呀
|
|
45
|
+
*
|
|
46
|
+
* Protocol normalization belongs on the SERVER; the front end just executes
|
|
47
|
+
* the protocol. objectstack#13847 landed that half: a `field-reference-to-alias`
|
|
48
|
+
* conversion rewrites stored `reference_to` → `reference` on the serve path and
|
|
49
|
+
* in `os migrate meta`, so a spec-compliant backend now emits `reference` only.
|
|
50
|
+
* This repo's half deleted the per-reader `reference_to` fallback arms that
|
|
51
|
+
* existed to absorb the un-normalized shape.
|
|
52
|
+
*
|
|
53
|
+
* The ruling asked for that deletion to be AUDIBLE rather than silent, and this
|
|
54
|
+
* is the cheap defence-in-depth pin it named, placed here as dispatched.
|
|
55
|
+
* `@objectstack/spec` 17.2.0's `FieldSchema` is strict and refuses
|
|
56
|
+
* `reference_to`, `referenceTo` and `target` by name with `unrecognized_keys`,
|
|
57
|
+
* each carrying its own "did you mean → `reference`" rename (measured on the
|
|
58
|
+
* installed package, with `reference` as the positive control and a nonsense
|
|
59
|
+
* key as the negative one — the nonsense key gets no rename hint, so the hint
|
|
60
|
+
* is alias-table membership, not boilerplate). So a def that reaches this choke
|
|
61
|
+
* point spelling ONLY a legacy key came from a producer the spec would reject,
|
|
62
|
+
* and the producer is where it gets fixed.
|
|
63
|
+
*
|
|
64
|
+
* ## ⛔ WHAT THIS WARNING DOES NOT COVER — state it, do not overclaim it
|
|
65
|
+
*
|
|
66
|
+
* This warning fires ONLY where this file runs, and this file runs at exactly
|
|
67
|
+
* three production call sites, all of them ingestion choke points:
|
|
68
|
+
*
|
|
69
|
+
* packages/app-shell/src/providers/MetadataProvider.tsx (`ensureType`, metadata type `object`)
|
|
70
|
+
* packages/app-shell/src/providers/MetadataProvider.tsx (`getItem`, metadata type `object`)
|
|
71
|
+
* packages/data-objectstack/src/index.ts (ObjectStackAdapter.getObjectSchema)
|
|
72
|
+
*
|
|
73
|
+
* All three FOLD the def onto `reference`, so a def that triggers this warning
|
|
74
|
+
* is also a def that still resolves. ⇒ The warning fires precisely where
|
|
75
|
+
* nothing is broken.
|
|
76
|
+
*
|
|
77
|
+
* ⚠️ The third of those is new in objectui#7650 and the count above used to
|
|
78
|
+
* read TWO. The old count was true about where this file RAN and false about
|
|
79
|
+
* the serve surface it was cited for: `MetadataProvider` normalized on its
|
|
80
|
+
* LIST path only, so an object def fetched BY NAME — the published
|
|
81
|
+
* `useMetadataItem` hook, and any cold-cache read behind it — was served with
|
|
82
|
+
* whichever single spelling the producer stored. ⛔ Do not re-derive this list
|
|
83
|
+
* by grepping for the call: derive it from the serve paths that hand an object
|
|
84
|
+
* schema to a reader, and check each one calls in.
|
|
85
|
+
*
|
|
86
|
+
* ⚠️ The BREAK surface is the complement of that: `getObjectSchema` is a
|
|
87
|
+
* required member of the published `DataSource` interface and the readers call
|
|
88
|
+
* it on the generic `dataSource`, so a hand-written schema served through ANY
|
|
89
|
+
* other `DataSource` reaches a reader RAW — it neither passes through here nor
|
|
90
|
+
* warns. Since objectui#11070 round 4 no reader carries a `reference_to` leg,
|
|
91
|
+
* so on that path a def that spells ONLY a legacy key resolves no target: the
|
|
92
|
+
* picker has no object to query and the read cell names nothing. That round's
|
|
93
|
+
* changeset states the break for such a host. Reader-side or shared-resolver
|
|
94
|
+
* diagnostics, which would make it audible, remain an open question on
|
|
95
|
+
* objectui#6837 (options B and C of its table §5).
|
|
96
|
+
*
|
|
97
|
+
* ⛔ Do not describe this pin as making the BYO break audible. It does not.
|
|
98
|
+
*
|
|
99
|
+
* ⭐ The `reference_to` STAMP is retired (objectui#11070 round 4, the seat's
|
|
100
|
+
* answer A to that card's Q1 under the objectui#6837 ruling). An earlier
|
|
101
|
+
* revision of this header kept it as "the only thing standing between a BYO
|
|
102
|
+
* `DataSource` and the break". It stood there only because the readers still
|
|
103
|
+
* read `reference_to`, and they read it because this stamp and ObjectUI's own
|
|
104
|
+
* emitters wrote it; with both ends moved to `reference` it protected nothing.
|
|
105
|
+
* The FOLD onto `reference` is what a served legacy def needs, and it stays.
|
|
106
|
+
*/
|
|
107
|
+
/**
|
|
108
|
+
* ## ⭐ THE RETIRED-DIALECT ARM — objectui#7650
|
|
109
|
+
*
|
|
110
|
+
* The `reference` pair above is one instance of a general problem this file is
|
|
111
|
+
* now the choke point for. objectui#7650 measured the shape of it:
|
|
112
|
+
*
|
|
113
|
+
* > The strict schema gates the WRITE door — `PUT /api/v1/meta/object/:name`
|
|
114
|
+
* > refuses a document carrying an undeclared key. It does not gate the READ
|
|
115
|
+
* > door.
|
|
116
|
+
*
|
|
117
|
+
* `ObjectStackAdapter.getObjectSchema` fetches the object document and returns
|
|
118
|
+
* it with exactly two mutations applied and NO `ObjectSchema.parse` anywhere on
|
|
119
|
+
* the path. So a document stored BEFORE a key was tightened is served, verbatim,
|
|
120
|
+
* to every consumer, forever — it cannot be re-saved through the strict door,
|
|
121
|
+
* but nothing ever asks it to be.
|
|
122
|
+
*
|
|
123
|
+
* That is why "no spec-compliant producer can emit this key" and "this consumer
|
|
124
|
+
* read can never fire" are DIFFERENT claims. Several retirement cards in this
|
|
125
|
+
* family (objectui#7155, #7166, #7435) narrowed consumer reads to the camelCase
|
|
126
|
+
* spelling on the strength of the first claim alone. This arm supplies the
|
|
127
|
+
* second half for them, exactly as objectui#6837 half 2 supplied it for
|
|
128
|
+
* `reference_to`: the legacy spelling is canonicalised ONCE, here, at ingestion,
|
|
129
|
+
* and never at a consumer.
|
|
130
|
+
*
|
|
131
|
+
* ## How the fold is DERIVED, and why it is not a table
|
|
132
|
+
*
|
|
133
|
+
* Maintainer route ruling (2026-09-09, objectui#7650 comment 5605081157): fold
|
|
134
|
+
* by the spec's OWN alias-probe rule — lowercase, strip `_`, `-` and space —
|
|
135
|
+
* matched EXACTLY against `FieldSchema`'s declared key set, which this repo can
|
|
136
|
+
* read off `FieldSchema.shape`.
|
|
137
|
+
*
|
|
138
|
+
* Two properties of that rule are the reason it was chosen over a hand-written
|
|
139
|
+
* three-row table:
|
|
140
|
+
*
|
|
141
|
+
* - `id_field` needs NO special case. It probes to `idfield`, which matches no
|
|
142
|
+
* declared key — `FieldSchema` has no `idField`, the spec's only `idField`
|
|
143
|
+
* sits on `InlineGridColumnSchema` — so it lands in the leave arm on its
|
|
144
|
+
* own. `title_format` likewise (no declared `titleFormat`).
|
|
145
|
+
* - A TYPO is not folded. `sortible` probes to `sortible`, which matches no
|
|
146
|
+
* declared key, so it is left alone. ⛔ This is why the spec's published
|
|
147
|
+
* `lintAuthoredRecordKeys` is NOT called here: it falls through to a
|
|
148
|
+
* Levenshtein matcher when no `to` row exists, and would answer "did you
|
|
149
|
+
* mean `sortable`?" for that typo. A serve path that silently CORRECTS a
|
|
150
|
+
* typo is worse than the defect it is fixing.
|
|
151
|
+
*
|
|
152
|
+
* ⚠️ "Leave arm", not "drop arm" in the destructive sense: a key this arm does
|
|
153
|
+
* not fold is left on the document EXACTLY as served. Nothing here removes a
|
|
154
|
+
* key or a value. Dropping the legacy key was weighed on objectui#7650 and
|
|
155
|
+
* REFUSED — a stored legacy document would lose the value instead of arriving
|
|
156
|
+
* canonical, and silent data loss on a serve path is the worst of the shapes.
|
|
157
|
+
*
|
|
158
|
+
* ⭐ The leave arm is no longer SILENT, which is the other half of that
|
|
159
|
+
* sentence: the document keeps the value, and the consumers narrowed to the
|
|
160
|
+
* canonical spelling by objectui#7155 / #7166 / #7435 read none of it (not
|
|
161
|
+
* every consumer was narrowed — the kept retired-spelling readers are named in
|
|
162
|
+
* {@link warnUnfoldableRetiredKey}'s docblock). See
|
|
163
|
+
* {@link warnUnfoldableRetiredKey} (objectui#8938) for the diagnostic maintainer
|
|
164
|
+
* ruling item 3 asked for, and for the two cases it deliberately stays out of.
|
|
165
|
+
*
|
|
166
|
+
* ## ⛔ The WIDTH of this arm is measured, never declared
|
|
167
|
+
*
|
|
168
|
+
* This arm accepts one spelling rule applied to `FieldSchema`'s WHOLE declared
|
|
169
|
+
* key set, so its width is a property of the linked `@objectstack/spec` and
|
|
170
|
+
* grows with it — ⛔ it is not the handful of keys the cards that asked for it
|
|
171
|
+
* happened to name, and ⛔ no count of it belongs in prose here or in a
|
|
172
|
+
* changeset (objectui#8938; AGENTS.md #9). The instrument that re-derives it is
|
|
173
|
+
* the pin named `the width IS the spec's declared key set, not a list anyone
|
|
174
|
+
* typed`, which drives every snake twin the live spec implies through this
|
|
175
|
+
* choke point.
|
|
176
|
+
*
|
|
177
|
+
* ## `id_field`: never folded, and the diagnostic carries the spec's own reason
|
|
178
|
+
*
|
|
179
|
+
* `@objectstack/spec`'s `FIELD_KEY_GUIDANCE` publishes an `id_field` row with a
|
|
180
|
+
* `why` and no `to`: a retirement, not a rename. So `id_field` is still never
|
|
181
|
+
* folded and nothing here reads an `idField`. What the row changes is the
|
|
182
|
+
* `no-declared-twin` diagnostic, which appends that `why`, read off the
|
|
183
|
+
* installed spec when the warning is emitted ({@link warnUnfoldableRetiredKey};
|
|
184
|
+
* objectui#7650). Two other ways of getting the sentence there were refused on
|
|
185
|
+
* the card, and neither refusal moves: quoting it from a local copy (a second
|
|
186
|
+
* copy of contract prose is the drift AGENTS.md #0.1 exists to stop), and
|
|
187
|
+
* reading it optionally with a local fallback (an invisible fallback that
|
|
188
|
+
* silently degrades on an older spec is this card's own defect class). The read
|
|
189
|
+
* is therefore unconditional: on a spec without the row it throws in dev rather
|
|
190
|
+
* than printing less.
|
|
191
|
+
*
|
|
192
|
+
* ## What this arm deliberately does NOT do
|
|
193
|
+
*
|
|
194
|
+
* ⛔ It never overwrites. A canonical key the producer already set keeps the
|
|
195
|
+
* producer's value, whatever the legacy twin says — the same rule the
|
|
196
|
+
* `reference` arm follows, for the same reason.
|
|
197
|
+
* ⛔ It never folds a key `FieldSchema` already declares: those are canonical by
|
|
198
|
+
* definition and skipping them is what keeps the pass idempotent.
|
|
199
|
+
* ⛔ It folds nothing onto an AMBIGUOUS probe. If two declared keys ever collide
|
|
200
|
+
* under the probe rule, that probe folds nothing rather than picking one.
|
|
201
|
+
* Measured on 17.3.0: 74 declared keys, zero collisions. The guard is here so a
|
|
202
|
+
* later spec addition cannot silently start choosing.
|
|
203
|
+
*/
|
|
204
|
+
const aliasProbe = (key) => key.toLowerCase().replace(/[_\-\s]/g, '');
|
|
205
|
+
/** Memoized `FieldSchema` readings — see {@link fieldKeyFolds}. */
|
|
206
|
+
let declaredFieldKeys = null;
|
|
207
|
+
let probeFolds = null;
|
|
208
|
+
let collidingProbes = null;
|
|
209
|
+
/**
|
|
210
|
+
* `probe -> canonical declared key`, derived once from `FieldSchema.shape`.
|
|
211
|
+
*
|
|
212
|
+
* Derived rather than listed on purpose: a table would have to be edited every
|
|
213
|
+
* time the spec grows a camel key whose snake twin is still in stored
|
|
214
|
+
* documents, and the edit that does not happen is the bug.
|
|
215
|
+
*
|
|
216
|
+
* ⛔ Deliberately NOT reset-able and deliberately not exported: the key set is a
|
|
217
|
+
* property of the linked `@objectstack/spec`, so it cannot change while the
|
|
218
|
+
* process runs, and a reset hook would be public API bought for nothing. The
|
|
219
|
+
* collision guard above is therefore unexercised BY CONSTRUCTION today — the
|
|
220
|
+
* pin that keeps it honest asserts the zero-collision precondition against the
|
|
221
|
+
* real spec, so the day a collision appears the pin says so before the guard
|
|
222
|
+
* has to.
|
|
223
|
+
*/
|
|
224
|
+
function fieldKeyFolds() {
|
|
225
|
+
if (probeFolds)
|
|
226
|
+
return probeFolds;
|
|
227
|
+
const declared = Object.keys(FieldSchema.shape);
|
|
228
|
+
const seen = new Map();
|
|
229
|
+
for (const key of declared) {
|
|
230
|
+
const probe = aliasProbe(key);
|
|
231
|
+
seen.set(probe, [...(seen.get(probe) ?? []), key]);
|
|
232
|
+
}
|
|
233
|
+
const folds = new Map();
|
|
234
|
+
const collisions = new Map();
|
|
235
|
+
// Same partition as before — a probe two declared keys share folds NOTHING —
|
|
236
|
+
// but the colliding keys are now kept rather than flattened to a `null`, so
|
|
237
|
+
// the diagnostic below can say WHICH declared keys the probe could not choose
|
|
238
|
+
// between instead of reporting the refusal as an unrecognised spelling
|
|
239
|
+
// (objectui#8938).
|
|
240
|
+
for (const [probe, keys] of seen) {
|
|
241
|
+
if (keys.length === 1)
|
|
242
|
+
folds.set(probe, keys[0]);
|
|
243
|
+
else
|
|
244
|
+
collisions.set(probe, keys);
|
|
245
|
+
}
|
|
246
|
+
declaredFieldKeys = new Set(declared);
|
|
247
|
+
collidingProbes = collisions;
|
|
248
|
+
probeFolds = folds;
|
|
249
|
+
return folds;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Warn once per (OBJECT, field, legacy spelling, canonical) — same four-segment
|
|
253
|
+
* reasoning as {@link warnedLegacyOnly}, which the docblock below sets out.
|
|
254
|
+
*/
|
|
255
|
+
const warnedRetiredSpelling = new Set();
|
|
256
|
+
/**
|
|
257
|
+
* Warn once per (OBJECT, field, spelling, REASON) — see
|
|
258
|
+
* {@link warnUnfoldableRetiredKey}. The reason is in the key because the three
|
|
259
|
+
* refusals are three different fixes, and a def can hit more than one of them.
|
|
260
|
+
*/
|
|
261
|
+
const warnedUnfoldableSpelling = new Set();
|
|
262
|
+
/**
|
|
263
|
+
* ## ⭐ THE DIAGNOSTIC FOR A SPELLING THIS CHOKE POINT CANNOT FOLD — objectui#8938
|
|
264
|
+
*
|
|
265
|
+
* Maintainer ruling item 3 (objectui#7650, comment 5572018999) asked the
|
|
266
|
+
* execution for "a loud diagnostic (not a silent drop) for a spelling the choke
|
|
267
|
+
* point cannot fold". The fold arm shipped with a warning for the spelling it
|
|
268
|
+
* CAN fold ({@link canonicalizeRetiredFieldKeys}) and nothing for the one it
|
|
269
|
+
* cannot, which is the half the ruling named: a key that folds still reaches
|
|
270
|
+
* every consumer, while a key that does not fold reaches none of the consumers
|
|
271
|
+
* of the declared spellings.
|
|
272
|
+
*
|
|
273
|
+
* ⛔ "Not a silent DROP" is about the reader, not about the document. Nothing
|
|
274
|
+
* here removes a key or a value — the leave arm is lossless and stays lossless.
|
|
275
|
+
* What is lost is the READ by those consumers: the retirement cards in this
|
|
276
|
+
* family (objectui#7155, #7166, #7435) narrowed them to the canonical spelling,
|
|
277
|
+
* so a value that never reaches a canonical key is a value none of them reads.
|
|
278
|
+
* That is the silence this makes audible.
|
|
279
|
+
*
|
|
280
|
+
* ⚠️ Not NO reader, and the line never says so (objectui#7650). Some reads of a
|
|
281
|
+
* retired spelling were KEPT on purpose and still see a value this arm leaves
|
|
282
|
+
* alone. For the two `no-declared-twin` keys: `resolveActionParam` in
|
|
283
|
+
* `@object-ui/app-shell` reads `id_field` and `title_format` (objectui#7435
|
|
284
|
+
* kept them; its pin `resolveActionParams.declaredLookupLegs-7435.test.ts`
|
|
285
|
+
* calls them the snake spellings that must keep working), and
|
|
286
|
+
* `resolveGroupByLabels` in `@object-ui/plugin-charts` reads
|
|
287
|
+
* `fieldDef.id_field || 'id'`. Those two are named, not counted: other kept
|
|
288
|
+
* snake reads exist (`deriveColumns` in `@object-ui/plugin-form` reads
|
|
289
|
+
* `reference_field`, for one), and nothing re-derives a complete list. So the
|
|
290
|
+
* line claims only what holds for every refusal: a consumer that reads only
|
|
291
|
+
* declared spellings will not see the value. Retiring any of those reads is a
|
|
292
|
+
* separate decision, not taken here.
|
|
293
|
+
*
|
|
294
|
+
* ## The three refusals, and why each is a different sentence
|
|
295
|
+
*
|
|
296
|
+
* - `no-declared-twin` — `FieldSchema` declares neither the key nor anything
|
|
297
|
+
* sharing its alias probe. `id_field` and `title_format` land here by the
|
|
298
|
+
* derived rule rather than by an exclusion, and so does a typo (`sortible`):
|
|
299
|
+
* ⛔ the diagnostic deliberately does NOT offer a near match, for the same
|
|
300
|
+
* reason the fold does not — the spec's `lintAuthoredRecordKeys` falls
|
|
301
|
+
* through to a Levenshtein matcher, and a serve path that suggests a
|
|
302
|
+
* correction is one revision away from applying it. For `id_field` alone
|
|
303
|
+
* the line also carries the reason the spec publishes for that exact
|
|
304
|
+
* spelling, `FIELD_KEY_GUIDANCE.id_field.why`, read unconditionally where
|
|
305
|
+
* the line is built: the contract's own sentence about the key, not a near
|
|
306
|
+
* match this path computed. The spec keys that row snake_case on purpose —
|
|
307
|
+
* a `why` row matches only the exact authored spelling.
|
|
308
|
+
* - `ambiguous-probe` — two or more declared keys share the probe, so the
|
|
309
|
+
* fold refuses to choose. Unreachable against a spec with no collision;
|
|
310
|
+
* the pin that exercises it substitutes a colliding `FieldSchema`.
|
|
311
|
+
* - `canonical-occupied` — the declared twin is already on the def carrying a
|
|
312
|
+
* DIFFERENT value, and the fold never overwrites a value the producer set.
|
|
313
|
+
* The producer's value is served under the declared key, the retired one
|
|
314
|
+
* reaches no consumer of the declared spelling (a kept snake read can still
|
|
315
|
+
* see it — see above), and NOTHING said so before this. ⛔ Same value under
|
|
316
|
+
* both spellings is NOT this case: that is the state this pass leaves
|
|
317
|
+
* behind on its own second run, and reporting it would make every
|
|
318
|
+
* re-normalization of a correctly folded def noisy.
|
|
319
|
+
*
|
|
320
|
+
* ## ⛔ What it does NOT fire on — the two exclusions that keep it honest
|
|
321
|
+
*
|
|
322
|
+
* - A key that DID fold. That case is {@link canonicalizeRetiredFieldKeys}'s
|
|
323
|
+
* own warning, and firing here as well would report a def that works
|
|
324
|
+
* exactly as loudly as one that does not.
|
|
325
|
+
* - `reference_to` / `referenceTo`, which probe onto no declared key and
|
|
326
|
+
* would otherwise report as `no-declared-twin` while the reference arm was
|
|
327
|
+
* in the middle of folding them onto `reference`. They have their own arm and their own
|
|
328
|
+
* diagnostic ({@link warnOnLegacyOnlyReference}, objectui#6837).
|
|
329
|
+
*
|
|
330
|
+
* Dev-only and memoised, the discipline both existing warnings already use: the
|
|
331
|
+
* adapter re-serves a cached schema and `MetadataProvider` re-normalizes on
|
|
332
|
+
* every metadata refresh, so a warning that floods is a warning that gets muted.
|
|
333
|
+
*/
|
|
334
|
+
function warnUnfoldableRetiredKey(reason, key, detail, fieldName, objectName, f) {
|
|
335
|
+
if (!isDev())
|
|
336
|
+
return;
|
|
337
|
+
const named = fieldName ?? (typeof f.name === 'string' ? f.name : '(unnamed field)');
|
|
338
|
+
const owner = objectName ?? '(unknown object)';
|
|
339
|
+
const memo = `${owner}:${named}:${key}:${reason}`;
|
|
340
|
+
if (warnedUnfoldableSpelling.has(memo))
|
|
341
|
+
return;
|
|
342
|
+
// ⛔ No `?.` and no `??` here: a spec without the row throws before the memo
|
|
343
|
+
// is taken, rather than warning with less (objectui#7650).
|
|
344
|
+
const published = reason === 'no-declared-twin' && key === 'id_field'
|
|
345
|
+
? ` \`@objectstack/spec\`'s \`FIELD_KEY_GUIDANCE\` gives the reason: ${FIELD_KEY_GUIDANCE.id_field.why}`
|
|
346
|
+
: '';
|
|
347
|
+
warnedUnfoldableSpelling.add(memo);
|
|
348
|
+
console.warn(`[ObjectUI] Object \`${owner}\`, field \`${named}\`: the ingestion choke point CANNOT ` +
|
|
349
|
+
`canonicalize the retired spelling \`${key}\`. ${detail}${published} The key and its value are LEFT ` +
|
|
350
|
+
`on the def exactly as served — nothing is dropped here — but a consumer that reads only ` +
|
|
351
|
+
`the spellings \`@objectstack/spec\`'s \`FieldSchema\` declares will not see this value. ` +
|
|
352
|
+
`Fix the PRODUCER, or migrate the stored document. (maintainer ruling item 3 on ` +
|
|
353
|
+
`objectui#7650; objectui#8938)`);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Stamp the canonical spelling for every retired-dialect key on one field def.
|
|
357
|
+
*
|
|
358
|
+
* Runs for EVERY field def, not only relational ones: `display_field` and
|
|
359
|
+
* friends live on fields that carry no relationship target at all, so gating
|
|
360
|
+
* this on the reference arm's early return would have covered almost nothing.
|
|
361
|
+
*
|
|
362
|
+
* `Object.keys` snapshots before the loop, so the canonical keys stamped inside
|
|
363
|
+
* it are never themselves re-examined.
|
|
364
|
+
*/
|
|
365
|
+
function canonicalizeRetiredFieldKeys(f, fieldName, objectName) {
|
|
366
|
+
const folds = fieldKeyFolds();
|
|
367
|
+
for (const key of Object.keys(f)) {
|
|
368
|
+
if (declaredFieldKeys.has(key))
|
|
369
|
+
continue;
|
|
370
|
+
const value = f[key];
|
|
371
|
+
// Nothing to carry across and nothing to lose: no fold, and no diagnostic
|
|
372
|
+
// either — the reader is not missing a value that was never there.
|
|
373
|
+
if (value === undefined)
|
|
374
|
+
continue;
|
|
375
|
+
// The reference arm's own keys are handled (and warned about) below; they
|
|
376
|
+
// probe onto no declared key, so without this they would report here as an
|
|
377
|
+
// unfoldable spelling while that arm was about to fold them.
|
|
378
|
+
if (REFERENCE_ARM_KEYS.has(key))
|
|
379
|
+
continue;
|
|
380
|
+
const probe = aliasProbe(key);
|
|
381
|
+
const canonical = folds.get(probe);
|
|
382
|
+
if (canonical === undefined) {
|
|
383
|
+
const colliding = collidingProbes.get(probe);
|
|
384
|
+
if (colliding) {
|
|
385
|
+
warnUnfoldableRetiredKey('ambiguous-probe', key, `Its alias spelling is shared by MORE THAN ONE key \`FieldSchema\` declares ` +
|
|
386
|
+
`(${colliding.map((c) => `\`${c}\``).join(', ')}), so the fold refuses to choose ` +
|
|
387
|
+
`between them rather than picking one.`, fieldName, objectName, f);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
warnUnfoldableRetiredKey('no-declared-twin', key, `\`FieldSchema\` declares neither \`${key}\` nor any key sharing its alias spelling ` +
|
|
391
|
+
`(lowercased, with \`_\`, \`-\` and spaces removed), so there is no canonical key ` +
|
|
392
|
+
`to fold it onto — and this path deliberately does not guess a near match.`, fieldName, objectName, f);
|
|
393
|
+
}
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (f[canonical] !== undefined) {
|
|
397
|
+
// ⛔ Only when a value is actually being lost. `f[canonical] === value` is
|
|
398
|
+
// the state this pass itself leaves behind — it stamps by reference and
|
|
399
|
+
// the adapter re-serves a cached schema, so reporting it would fire on
|
|
400
|
+
// every re-normalization of a def that folded perfectly the first time.
|
|
401
|
+
// The idempotence pin in the objectui#7650 suite is what measures this.
|
|
402
|
+
if (f[canonical] !== value) {
|
|
403
|
+
warnUnfoldableRetiredKey('canonical-occupied', key, `\`FieldSchema\` declares \`${canonical}\`, and this def already carries a ` +
|
|
404
|
+
`DIFFERENT value under it; the producer's value stands, because this choke point ` +
|
|
405
|
+
`never overwrites one.`, fieldName, objectName, f);
|
|
406
|
+
}
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
f[canonical] = value;
|
|
410
|
+
if (!isDev())
|
|
411
|
+
continue;
|
|
412
|
+
const named = fieldName ?? (typeof f.name === 'string' ? f.name : '(unnamed field)');
|
|
413
|
+
const owner = objectName ?? '(unknown object)';
|
|
414
|
+
const memo = `${owner}:${named}:${key}:${canonical}`;
|
|
415
|
+
if (warnedRetiredSpelling.has(memo))
|
|
416
|
+
continue;
|
|
417
|
+
warnedRetiredSpelling.add(memo);
|
|
418
|
+
console.warn(`[ObjectUI] Object \`${owner}\`, field \`${named}\` carries the retired spelling ` +
|
|
419
|
+
`\`${key}\`. \`@objectstack/spec\`'s \`FieldSchema\` declares \`${canonical}\` and ` +
|
|
420
|
+
`does not declare \`${key}\`, so a producer emitting it would be refused at the ` +
|
|
421
|
+
`metadata write door. ObjectUI stamps \`${canonical}\` here so this stored def still ` +
|
|
422
|
+
`renders, but the consumers no longer carry a \`${key}\` fallback of their own — fix ` +
|
|
423
|
+
`the PRODUCER, or migrate the stored document. (objectui#7650)`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Warn once per (OBJECT name, field name, legacy spelling, target VALUE) rather
|
|
428
|
+
* than once per call: the adapter re-serves a cached schema and
|
|
429
|
+
* `MetadataProvider` re-normalizes on every metadata refresh, and a warning
|
|
430
|
+
* that floods the console is a warning that gets muted.
|
|
431
|
+
*
|
|
432
|
+
* All four segments are load-bearing, and each is here because dropping it
|
|
433
|
+
* makes the warning name less than a whole producer site:
|
|
434
|
+
*
|
|
435
|
+
* - OBJECT — the same field name (`owner`, `account_id`) lives on many
|
|
436
|
+
* objects, and each is a SEPARATE place to fix. Keyed on the field alone
|
|
437
|
+
* the memo reports the first object and goes quiet for every other one,
|
|
438
|
+
* while the message names a field the reader then has to go hunting for.
|
|
439
|
+
* `normalizeSchemaReferenceKeys` has `schema.name` in hand, so the whole
|
|
440
|
+
* address is available for free.
|
|
441
|
+
* - FIELD and SPELLING — one line per mis-spelled key, so a producer with
|
|
442
|
+
* three broken fields gets three fixes, not one and a silence.
|
|
443
|
+
* - VALUE — a field carrying two different stale targets reports both. This
|
|
444
|
+
* mirrors `column-identity.ts`'s `warnedConflicts` memo, which keys on the
|
|
445
|
+
* value for exactly this reason.
|
|
446
|
+
*
|
|
447
|
+
* ⚠️ A def normalized OUTSIDE a schema (a bare `normalizeFieldReferenceKeys`
|
|
448
|
+
* call) has no object to name; it warns under a placeholder rather than being
|
|
449
|
+
* silently merged with a real object's entry.
|
|
450
|
+
*/
|
|
451
|
+
const warnedLegacyOnly = new Set();
|
|
452
|
+
/** Reset the warn-once memos. Exported for tests. */
|
|
453
|
+
export function resetReferenceKeyWarnings() {
|
|
454
|
+
warnedLegacyOnly.clear();
|
|
455
|
+
warnedRetiredSpelling.clear();
|
|
456
|
+
warnedUnfoldableSpelling.clear();
|
|
457
|
+
}
|
|
458
|
+
const isDev = () => globalThis.process?.env?.NODE_ENV !==
|
|
459
|
+
'production';
|
|
460
|
+
/** The two spellings no contract declares, in the order the fold prefers them. */
|
|
461
|
+
const LEGACY_REFERENCE_KEYS = ['reference_to', 'referenceTo'];
|
|
462
|
+
/**
|
|
463
|
+
* The same two, as a set the retired-dialect arm tests before it reports a key
|
|
464
|
+
* as unfoldable — they are handled by the reference arm below, not left
|
|
465
|
+
* (objectui#8938).
|
|
466
|
+
*/
|
|
467
|
+
const REFERENCE_ARM_KEYS = new Set(LEGACY_REFERENCE_KEYS);
|
|
468
|
+
/**
|
|
469
|
+
* Dev-mode only: say out loud that a def arrived spelling ONLY a legacy key.
|
|
470
|
+
*
|
|
471
|
+
* Non-breaking by construction: changes no types, rejects nothing, drops no
|
|
472
|
+
* key, and is a no-op under `NODE_ENV=production`. The fold still runs, so the
|
|
473
|
+
* def still resolves its target — this only makes the producer's bug visible
|
|
474
|
+
* instead of absorbing it silently.
|
|
475
|
+
*/
|
|
476
|
+
function warnOnLegacyOnlyReference(f, fieldName, objectName) {
|
|
477
|
+
if (!isDev())
|
|
478
|
+
return;
|
|
479
|
+
if (f.reference !== undefined)
|
|
480
|
+
return;
|
|
481
|
+
const spelled = LEGACY_REFERENCE_KEYS.filter((k) => f[k] !== undefined && f[k] !== '');
|
|
482
|
+
if (spelled.length === 0)
|
|
483
|
+
return;
|
|
484
|
+
const named = fieldName ?? (typeof f.name === 'string' ? f.name : '(unnamed field)');
|
|
485
|
+
const owner = objectName ?? '(unknown object)';
|
|
486
|
+
for (const key of spelled) {
|
|
487
|
+
const memo = `${owner}:${named}:${key}:${String(f[key])}`;
|
|
488
|
+
if (warnedLegacyOnly.has(memo))
|
|
489
|
+
continue;
|
|
490
|
+
warnedLegacyOnly.add(memo);
|
|
491
|
+
console.warn(`[ObjectUI] Object \`${owner}\`, field \`${named}\` declares its relationship target as ` +
|
|
492
|
+
`\`${key}: '${String(f[key])}'\` and does NOT carry \`reference\`. ` +
|
|
493
|
+
`\`reference\` is the only spelling the protocol declares: ` +
|
|
494
|
+
`\`@objectstack/spec\`'s \`FieldSchema\` refuses \`${key}\` by name with ` +
|
|
495
|
+
`\`unrecognized_keys\` ("Did you mean \`${key}\` -> \`reference\`?"). ` +
|
|
496
|
+
`ObjectUI stamps \`reference\` here so this def still renders, but the ` +
|
|
497
|
+
`readers no longer carry a \`${key}\` fallback of their own — fix the ` +
|
|
498
|
+
`PRODUCER to emit \`reference\`, or serve the def through a backend that ` +
|
|
499
|
+
`normalizes it (objectstack#13847 does this on the serve path and in ` +
|
|
500
|
+
`\`os migrate meta\`). Maintainer ruling 2026-08-31: protocol ` +
|
|
501
|
+
`normalization belongs on the server, the front end just executes the ` +
|
|
502
|
+
`protocol. (objectui#6837)`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
export function normalizeFieldReferenceKeys(fieldDef, fieldName, objectName) {
|
|
26
506
|
if (!fieldDef || typeof fieldDef !== 'object')
|
|
27
507
|
return fieldDef;
|
|
28
508
|
const f = fieldDef;
|
|
29
|
-
|
|
30
|
-
|
|
509
|
+
// Runs FIRST and unconditionally: the retired dialect is not relational, so
|
|
510
|
+
// the reference arm's early return below would skip almost every def that
|
|
511
|
+
// needs it (objectui#7650).
|
|
512
|
+
canonicalizeRetiredFieldKeys(f, fieldName, objectName);
|
|
513
|
+
// The reference arm is a FOLD, never a stamp (objectui#11070 round 4): a
|
|
514
|
+
// producer's `reference` stands, and only a def that lacks it gains one from
|
|
515
|
+
// a foreign spelling. `reference_to` is never written, and the foreign key is
|
|
516
|
+
// left where it was.
|
|
517
|
+
if (f.reference !== undefined)
|
|
31
518
|
return fieldDef;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
519
|
+
const legacy = f.reference_to ?? f.referenceTo;
|
|
520
|
+
if (legacy == null || legacy === '')
|
|
521
|
+
return fieldDef;
|
|
522
|
+
warnOnLegacyOnlyReference(f, fieldName, objectName);
|
|
523
|
+
f.reference = legacy;
|
|
36
524
|
return fieldDef;
|
|
37
525
|
}
|
|
38
526
|
/**
|
|
39
527
|
* Apply {@link normalizeFieldReferenceKeys} to every field of an object
|
|
40
|
-
* schema
|
|
41
|
-
* a `name → def` map or
|
|
42
|
-
*
|
|
528
|
+
* schema — BOTH arms: the `reference` fold and the retired dialect. Accepts
|
|
529
|
+
* both field-container shapes the metadata API serves — a `name → def` map or
|
|
530
|
+
* an array of defs — and tolerates anything else by returning the input
|
|
531
|
+
* untouched. Mutates in place; idempotent.
|
|
43
532
|
*
|
|
44
533
|
* This is meant to run at the choke point where object schemas enter the
|
|
45
534
|
* client (`ObjectStackAdapter.getObjectSchema`, the app-shell metadata
|
|
46
|
-
* provider) so
|
|
535
|
+
* provider), so no consumer needs a dual-key fallback of its own.
|
|
536
|
+
*
|
|
537
|
+
* Field NAMES and the OBJECT name are forwarded so the dev-mode warning above
|
|
538
|
+
* can name the whole producer site rather than half of it: the map form keys
|
|
539
|
+
* the def by name, the array form carries it as `def.name`, and the object name
|
|
540
|
+
* comes off `schema.name`.
|
|
47
541
|
*/
|
|
48
542
|
export function normalizeSchemaReferenceKeys(schema) {
|
|
49
543
|
const fields = schema && typeof schema === 'object' ? schema.fields : null;
|
|
50
544
|
if (!fields || typeof fields !== 'object')
|
|
51
545
|
return schema;
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
546
|
+
const objectName = typeof schema.name === 'string'
|
|
547
|
+
? schema.name
|
|
548
|
+
: undefined;
|
|
549
|
+
if (Array.isArray(fields)) {
|
|
550
|
+
for (const def of fields)
|
|
551
|
+
normalizeFieldReferenceKeys(def, undefined, objectName);
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
for (const [name, def] of Object.entries(fields)) {
|
|
555
|
+
normalizeFieldReferenceKeys(def, name, objectName);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
55
558
|
return schema;
|
|
56
559
|
}
|