@object-ui/core 17.5.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 +7404 -0
- package/README.md +127 -26
- package/dist/actions/ActionEngine.d.ts +5 -3
- package/dist/actions/ActionEngine.js +14 -10
- package/dist/actions/ActionRunner.d.ts +363 -30
- package/dist/actions/ActionRunner.js +544 -58
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/actionKeys.d.ts +75 -13
- package/dist/actions/actionKeys.js +131 -16
- package/dist/actions/actionResponse.js +3 -3
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/recordDelete.d.ts +118 -0
- package/dist/actions/recordDelete.js +112 -0
- package/dist/actions/recordIdParam.d.ts +77 -0
- package/dist/actions/recordIdParam.js +37 -0
- package/dist/actions/serverActionHandler.d.ts +1 -1
- package/dist/actions/serverActionHandler.js +1 -1
- 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 +128 -1
- package/dist/evaluator/ExpressionEvaluator.js +226 -13
- package/dist/evaluator/FormulaFunctions.d.ts +0 -10
- package/dist/evaluator/FormulaFunctions.js +166 -27
- package/dist/evaluator/SafeExpressionParser.js +0 -4
- package/dist/evaluator/declaredPredicate.d.ts +93 -9
- package/dist/evaluator/declaredPredicate.js +118 -17
- package/dist/evaluator/fieldRules.d.ts +107 -4
- package/dist/evaluator/fieldRules.js +231 -35
- package/dist/evaluator/index.d.ts +2 -0
- package/dist/evaluator/index.js +2 -0
- package/dist/evaluator/listConditional.d.ts +125 -9
- package/dist/evaluator/listConditional.js +167 -26
- package/dist/evaluator/optionRules.d.ts +126 -0
- package/dist/evaluator/optionRules.js +141 -4
- package/dist/evaluator/predicateInput.d.ts +15 -4
- package/dist/evaluator/predicateInput.js +35 -2
- package/dist/evaluator/rowPredicateCanon.d.ts +56 -0
- package/dist/evaluator/rowPredicateCanon.js +158 -0
- 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 +18 -0
- package/dist/index.js +97 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +397 -48
- 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/theme/index.d.ts +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/types/index.d.ts +92 -13
- 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 +188 -0
- package/dist/utils/chart-presentation.js +345 -0
- package/dist/utils/chart-series.d.ts +271 -31
- package/dist/utils/chart-series.js +478 -55
- package/dist/utils/column-identity.d.ts +43 -0
- package/dist/utils/column-identity.js +47 -1
- 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 +407 -52
- 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/debug.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 +171 -11
- package/dist/utils/expand-fields.js +171 -11
- 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 -34
- package/dist/utils/freeze-schema.d.ts +38 -1
- package/dist/utils/freeze-schema.js +10 -0
- package/dist/utils/grouping-fields.d.ts +61 -0
- package/dist/utils/grouping-fields.js +84 -0
- package/dist/utils/humanize-label.d.ts +60 -0
- package/dist/utils/humanize-label.js +62 -0
- package/dist/utils/managedBy.d.ts +30 -5
- 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 +131 -1
- package/dist/utils/normalize-list-view.js +248 -6
- package/dist/utils/number-display.d.ts +70 -17
- 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 +62 -4
- package/dist/utils/predicate-fields.js +169 -4
- 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/retired-field-types.d.ts +90 -0
- package/dist/utils/retired-field-types.js +124 -0
- package/dist/utils/sort-query.d.ts +128 -26
- 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/unmaterialized-fields.d.ts +52 -0
- package/dist/utils/unmaterialized-fields.js +57 -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 +9 -4
- package/dist/validation/index.js +9 -4
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.js +305 -82
- package/dist/validation/server-owned-value.d.ts +62 -0
- package/dist/validation/server-owned-value.js +99 -0
- package/dist/validation/validators/object-validation-engine.d.ts +1 -1
- package/dist/validation/validators/object-validation-engine.js +14 -5
- package/package.json +8 -9
- package/dist/__benchmarks__/core.bench.d.ts +0 -8
- package/dist/__benchmarks__/core.bench.js +0 -53
- 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
|
@@ -5,6 +5,28 @@
|
|
|
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
7
|
*/
|
|
8
|
+
import { isUnevaluablePredicate } from './unevaluablePredicate.js';
|
|
9
|
+
/**
|
|
10
|
+
* The normalized answer for a gate that is DECLARED but has nothing the client
|
|
11
|
+
* can evaluate (objectui#11358) — the fourth member of
|
|
12
|
+
* {@link EvaluatorPredicateInput}, a `cel` envelope with no `source`.
|
|
13
|
+
*
|
|
14
|
+
* It exists so the normalizer can tell three states apart instead of two.
|
|
15
|
+
* `undefined` is "absent" and evaluates to `true` (visible / enabled) by the
|
|
16
|
+
* evaluator's one documented default; this value is "declared and faulting"
|
|
17
|
+
* and `ExpressionEvaluator.evaluateCondition` treats it as a FAULT — reported,
|
|
18
|
+
* thrown to a `throwOnError` caller, and answered with the fail-soft `true`
|
|
19
|
+
* otherwise — so every reader gets the fail direction its key already has for
|
|
20
|
+
* a predicate that cannot be evaluated. Folding it into `undefined`, as this
|
|
21
|
+
* function did, made an `ast`-only envelope, `0`, `{}` or an array read as
|
|
22
|
+
* "no gate" everywhere, in silence (ADR-0137 D4).
|
|
23
|
+
*
|
|
24
|
+
* Shape, not a marker: it says what the evaluator receives — a CEL predicate
|
|
25
|
+
* (a bare predicate is CEL by the spec's contract) whose `source` is absent —
|
|
26
|
+
* and carries nothing of the original value, so no reader can guess what an
|
|
27
|
+
* `ast` meant. Frozen and shared, so a reader's memo sees one value per state.
|
|
28
|
+
*/
|
|
29
|
+
const DECLARED_UNEVALUABLE = Object.freeze({ dialect: 'cel' });
|
|
8
30
|
/**
|
|
9
31
|
* Wrap a BARE expression as a `${…}` template, and leave a string that already
|
|
10
32
|
* carries template syntax alone (objectui#3871 — rationale in
|
|
@@ -29,8 +51,13 @@ function wrapIfBare(expression) {
|
|
|
29
51
|
* authored predicate, including bare strings) → a `cel` dialect keeps
|
|
30
52
|
* its envelope; every other dialect is unwrapped and wrapped as
|
|
31
53
|
* `${source}`.
|
|
32
|
-
* - `null` / `undefined` /
|
|
33
|
-
* (default visible/enabled).
|
|
54
|
+
* - `null` / `undefined` / `''` / an envelope whose `source` is `''` →
|
|
55
|
+
* `undefined` (absent: default visible/enabled).
|
|
56
|
+
* - anything else that is present — an envelope with no string `source`
|
|
57
|
+
* (`{ dialect: 'cel', ast }`), a number, `{}`, an array → the frozen
|
|
58
|
+
* `{ dialect: 'cel' }` with no `source`: declared, and a FAULT to the
|
|
59
|
+
* evaluator (objectui#11358 — see `DECLARED_UNEVALUABLE` above). This used
|
|
60
|
+
* to fold into `undefined` with the absent shapes.
|
|
34
61
|
*
|
|
35
62
|
* ## Why the `cel` envelope must survive normalization (#2661 / #3314)
|
|
36
63
|
*
|
|
@@ -113,6 +140,10 @@ export function toPredicateInput(value) {
|
|
|
113
140
|
return value;
|
|
114
141
|
if (typeof value === 'string')
|
|
115
142
|
return wrapIfBare(value);
|
|
143
|
+
// Declared, but nothing here can be evaluated (objectui#11358): kept as its
|
|
144
|
+
// own state rather than folded into "absent". See `DECLARED_UNEVALUABLE`.
|
|
145
|
+
if (isUnevaluablePredicate(value))
|
|
146
|
+
return DECLARED_UNEVALUABLE;
|
|
116
147
|
if (typeof value === 'object' && typeof value.source === 'string') {
|
|
117
148
|
const src = value.source;
|
|
118
149
|
if (!src)
|
|
@@ -125,5 +156,7 @@ export function toPredicateInput(value) {
|
|
|
125
156
|
// Every other dialect (template / unset) keeps the legacy `${…}` behavior.
|
|
126
157
|
return wrapIfBare(src);
|
|
127
158
|
}
|
|
159
|
+
// Unreachable: every present value is a boolean, a string, an envelope with a
|
|
160
|
+
// string `source`, or unevaluable — all answered above. Kept for the compiler.
|
|
128
161
|
return undefined;
|
|
129
162
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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 one canonical row-predicate root. */
|
|
9
|
+
export declare const ROW_PREDICATE_CANONICAL_ROOT = "record";
|
|
10
|
+
/** A non-canonical row-predicate spelling, and the edit that fixes it. */
|
|
11
|
+
export type NonCanonicalRowSpelling = {
|
|
12
|
+
/** Bare shorthand: `status == 'active'` — a field of THIS row, unrooted. */
|
|
13
|
+
kind: 'bare-shorthand';
|
|
14
|
+
/** The offending identifier (`status`). */
|
|
15
|
+
identifier: string;
|
|
16
|
+
/** What the author should write instead (`record.status`). */
|
|
17
|
+
canonical: string;
|
|
18
|
+
} | {
|
|
19
|
+
/** Wrong-layer root: `data.status == 'active'` on a runtime record surface. */
|
|
20
|
+
kind: 'metadata-layer-root';
|
|
21
|
+
identifier: string;
|
|
22
|
+
canonical: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Detect a non-canonical spelling in ONE row predicate, or `null` when the
|
|
26
|
+
* predicate is already canonical (or is not this module's verdict to give).
|
|
27
|
+
*
|
|
28
|
+
* Deliberately conservative in both arms — every condition below can only
|
|
29
|
+
* REMOVE a finding, never invent one, because a false finding sends an author
|
|
30
|
+
* to rewrite a predicate that was correct:
|
|
31
|
+
*
|
|
32
|
+
* - **Unparseable source** → `null`. Syntax is another gate's verdict.
|
|
33
|
+
* - **Bare shorthand** is reported only when the undeclared identifier is an own
|
|
34
|
+
* key of THIS row. That is what makes it unambiguously the #4075 shorthand
|
|
35
|
+
* rather than a host-scope global this module cannot see (a deployment key
|
|
36
|
+
* that is simply not in `SCOPE_ROOTS` would otherwise read as a bare field).
|
|
37
|
+
* - **`data.*`** is reported only when the caller confirms `data` names the row
|
|
38
|
+
* (`dataNamesRow`). A surface whose `data` is the host scope's own — a
|
|
39
|
+
* `rowless` dialog, a metadata-editing form — is a legitimate `data.*` site
|
|
40
|
+
* and is left alone.
|
|
41
|
+
*
|
|
42
|
+
* Only the FIRST finding is returned, and the bare-shorthand arm is checked
|
|
43
|
+
* first: it is the arm the server refuses outright (`visibility-bare-identifier`
|
|
44
|
+
* is an `error` there, while a mis-layered root is a `warning`), so when a
|
|
45
|
+
* predicate manages both it is the one the author must fix to have anything
|
|
46
|
+
* evaluate at all.
|
|
47
|
+
*
|
|
48
|
+
* @param source The predicate's CEL text. A legacy `${…}`-dialect string
|
|
49
|
+
* is not CEL and returns `null` here (unparseable); classify
|
|
50
|
+
* it by its own dialect's rules — on a runtime record surface
|
|
51
|
+
* it retired with the CEL spellings (`83fe6e741`), while on
|
|
52
|
+
* the schema/widget tier `data` is a different scope entirely.
|
|
53
|
+
* @param row The row the predicate is bound against.
|
|
54
|
+
* @param dataNamesRow Whether `data` is bound to that same row on this surface.
|
|
55
|
+
*/
|
|
56
|
+
export declare function detectNonCanonicalRowSpelling(source: string, row: Record<string, unknown> | null | undefined, dataNamesRow: boolean): NonCanonicalRowSpelling | null;
|
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* The row-predicate spelling CANON: on a runtime record surface the row is bound
|
|
10
|
+
* as `record.*` and nothing else (objectui#5330, maintainer ruling 2026-08-20,
|
|
11
|
+
* option B; Phase 2 executed by `83fe6e741`).
|
|
12
|
+
*
|
|
13
|
+
* ## The canon
|
|
14
|
+
*
|
|
15
|
+
* A row predicate — `visible` / `disabled` / `enabled` on an action renderer, a
|
|
16
|
+
* row scope, a `record:alert`, a conditional-formatting `condition` — used to
|
|
17
|
+
* bind the row THREE ways: canonical `record.status`, bare shorthand `status`,
|
|
18
|
+
* and legacy `data.status` (objectui#4075 / PR #4079 bound all four action
|
|
19
|
+
* renderers all three ways to restore consistency, deliberately without
|
|
20
|
+
* deciding which of them was CONTRACT). The ruling decided it, in two phases:
|
|
21
|
+
*
|
|
22
|
+
* > **The canon is `record.*`.** Phase 1 (`d1ab06f0f`) declared it and warned
|
|
23
|
+
* > once, in dev, on the two other spellings. Phase 2 (`83fe6e741`, ruled
|
|
24
|
+
* > 2026-09-02 and amended 2026-09-05) retired them: the bare shorthand and
|
|
25
|
+
* > `data.*` are no longer bound on runtime record surfaces, and the Phase-1
|
|
26
|
+
* > warning went with them. No stored-metadata survey was run (「不考虑存量」);
|
|
27
|
+
* > the Phase-1 warning period was the notice.
|
|
28
|
+
*
|
|
29
|
+
* ## What a retired spelling does now: it FAULTS, as it always did on the server
|
|
30
|
+
*
|
|
31
|
+
* The canon states the SERVER's accept set. Measured against
|
|
32
|
+
* `@objectstack/formula@17.1.0` — the engine the server evaluates with, and the
|
|
33
|
+
* one `fieldRules.ts` delegates to:
|
|
34
|
+
*
|
|
35
|
+
* | spelling | server runtime (`buildScope` + `celEngine`) | server authoring oracle |
|
|
36
|
+
* |---|---|---|
|
|
37
|
+
* | `record.status` | ✅ `{ ok: true, value: true }` | ✅ accepted |
|
|
38
|
+
* | bare `status` | ❌ `Unknown variable: status` | ❌ refused (`'status'`) |
|
|
39
|
+
* | `data.status` | ❌ `Unknown variable: data` | ⚠️ silently accepted |
|
|
40
|
+
*
|
|
41
|
+
* `buildScope({ record })` mounts exactly `['record']`. Since Phase 2 the client
|
|
42
|
+
* binds the same set (`listConditional.ts`' scope bag and `@object-ui/react`'s
|
|
43
|
+
* `usePredicateRecordContext`), so both retired spellings fault on the client
|
|
44
|
+
* with the server's verdict, and each surface applies its EXISTING fault policy:
|
|
45
|
+
* fail-closed `visible` on the throwing `useCondition` legs, the caller's
|
|
46
|
+
* `fallback` on `evalRowPredicate` / `partitionRowsByPredicate`, fail-soft on
|
|
47
|
+
* the non-throwing `useCondition` legs. There is no runtime detector, no
|
|
48
|
+
* "treat as absent" special case and no uniform override — a retired spelling
|
|
49
|
+
* is not a recognised-and-rejected thing, it is an unknown variable like any
|
|
50
|
+
* other, and the existing fault warnings are what name it.
|
|
51
|
+
*
|
|
52
|
+
* One consequence, stated so it is not read back as a bug: a host scope may
|
|
53
|
+
* legitimately carry its OWN `data` (a rowless dialog's, or app-shell's ambient
|
|
54
|
+
* `data: {}`), and it is left standing. `data.*` on a record surface then reads
|
|
55
|
+
* the host's object rather than the row — which is exactly what "no longer bound
|
|
56
|
+
* to the row" means, and, against an ambient `data: {}`, the constant-false
|
|
57
|
+
* signature the Phase-1 warning text already described for the server.
|
|
58
|
+
*
|
|
59
|
+
* ## `data.*` is retired HERE, not everywhere — the canon is layer-scoped
|
|
60
|
+
*
|
|
61
|
+
* `data` is the CANONICAL root one layer over, in a metadata-editing form (the
|
|
62
|
+
* row under edit): objectstack's `CANONICAL_ROOT_BY_LAYER` reads
|
|
63
|
+
* `{ runtime: 'record', metadata: 'data' }` (ADR-0089 D3), and objectui's own
|
|
64
|
+
* `app-shell` metadata-admin `SchemaForm` binds `{ data: row }` on purpose,
|
|
65
|
+
* through its own evaluator (`views/metadata-admin/predicate.ts`) — never
|
|
66
|
+
* through `evalRowPredicate` or `usePredicateRecordContext`. So `data.*` is not
|
|
67
|
+
* retired platform-wide: it is a WRONG-LAYER spelling on a runtime record
|
|
68
|
+
* surface, and that is the only thing the detector below reports.
|
|
69
|
+
*
|
|
70
|
+
* ## What is left in this module, and why
|
|
71
|
+
*
|
|
72
|
+
* {@link detectNonCanonicalRowSpelling} is the OFFLINE instrument: it classifies
|
|
73
|
+
* an authored predicate's spelling against a row without evaluating it, so a
|
|
74
|
+
* sweep over stored or in-repo metadata (the `702c48a62` corpus sweep's
|
|
75
|
+
* recipe) can find the documents that still need rewriting. It is
|
|
76
|
+
* exported for that purpose and nothing on the hot path calls it — the runtime
|
|
77
|
+
* warning half Phase 1 built on it (`warnNonCanonicalRowSpelling`,
|
|
78
|
+
* `resetRowPredicateCanonWarnings`) was removed with the bindings.
|
|
79
|
+
*
|
|
80
|
+
* ## Why the detection reuses the server's oracle instead of a regex
|
|
81
|
+
*
|
|
82
|
+
* `collectCelRootIdentifiers` and `firstUndeclaredReference` are
|
|
83
|
+
* `@objectstack/formula` exports — the same two oracles objectstack's
|
|
84
|
+
* `visibility-bare-identifier` gate uses. Reading roots off the canonical AST
|
|
85
|
+
* rather than pattern-matching the source is what keeps this from inventing a
|
|
86
|
+
* second dialect judgement client-side: a string this module cannot parse is
|
|
87
|
+
* NOT this module's verdict to give (syntax belongs to the gates that own it),
|
|
88
|
+
* and it stands down rather than guessing.
|
|
89
|
+
*/
|
|
90
|
+
import { collectCelRootIdentifiers, firstUndeclaredReference } from '@objectstack/formula';
|
|
91
|
+
/** The one canonical row-predicate root. */
|
|
92
|
+
export const ROW_PREDICATE_CANONICAL_ROOT = 'record';
|
|
93
|
+
/**
|
|
94
|
+
* The metadata-editing-form root (ADR-0089 D3). Canonical on THAT layer, and a
|
|
95
|
+
* wrong-layer paste on a runtime record surface — see the module note.
|
|
96
|
+
*/
|
|
97
|
+
const METADATA_LAYER_ROOT = 'data';
|
|
98
|
+
/**
|
|
99
|
+
* Detect a non-canonical spelling in ONE row predicate, or `null` when the
|
|
100
|
+
* predicate is already canonical (or is not this module's verdict to give).
|
|
101
|
+
*
|
|
102
|
+
* Deliberately conservative in both arms — every condition below can only
|
|
103
|
+
* REMOVE a finding, never invent one, because a false finding sends an author
|
|
104
|
+
* to rewrite a predicate that was correct:
|
|
105
|
+
*
|
|
106
|
+
* - **Unparseable source** → `null`. Syntax is another gate's verdict.
|
|
107
|
+
* - **Bare shorthand** is reported only when the undeclared identifier is an own
|
|
108
|
+
* key of THIS row. That is what makes it unambiguously the #4075 shorthand
|
|
109
|
+
* rather than a host-scope global this module cannot see (a deployment key
|
|
110
|
+
* that is simply not in `SCOPE_ROOTS` would otherwise read as a bare field).
|
|
111
|
+
* - **`data.*`** is reported only when the caller confirms `data` names the row
|
|
112
|
+
* (`dataNamesRow`). A surface whose `data` is the host scope's own — a
|
|
113
|
+
* `rowless` dialog, a metadata-editing form — is a legitimate `data.*` site
|
|
114
|
+
* and is left alone.
|
|
115
|
+
*
|
|
116
|
+
* Only the FIRST finding is returned, and the bare-shorthand arm is checked
|
|
117
|
+
* first: it is the arm the server refuses outright (`visibility-bare-identifier`
|
|
118
|
+
* is an `error` there, while a mis-layered root is a `warning`), so when a
|
|
119
|
+
* predicate manages both it is the one the author must fix to have anything
|
|
120
|
+
* evaluate at all.
|
|
121
|
+
*
|
|
122
|
+
* @param source The predicate's CEL text. A legacy `${…}`-dialect string
|
|
123
|
+
* is not CEL and returns `null` here (unparseable); classify
|
|
124
|
+
* it by its own dialect's rules — on a runtime record surface
|
|
125
|
+
* it retired with the CEL spellings (`83fe6e741`), while on
|
|
126
|
+
* the schema/widget tier `data` is a different scope entirely.
|
|
127
|
+
* @param row The row the predicate is bound against.
|
|
128
|
+
* @param dataNamesRow Whether `data` is bound to that same row on this surface.
|
|
129
|
+
*/
|
|
130
|
+
export function detectNonCanonicalRowSpelling(source, row, dataNamesRow) {
|
|
131
|
+
if (typeof source !== 'string' || !source.trim())
|
|
132
|
+
return null;
|
|
133
|
+
const roots = collectCelRootIdentifiers(source);
|
|
134
|
+
// Not parseable through the canonical front end — not our verdict.
|
|
135
|
+
if (!roots || roots.ok !== true)
|
|
136
|
+
return null;
|
|
137
|
+
// (1) Bare shorthand — the arm the server refuses outright.
|
|
138
|
+
const bare = firstUndeclaredReference(source);
|
|
139
|
+
if (typeof bare === 'string' &&
|
|
140
|
+
row != null &&
|
|
141
|
+
typeof row === 'object' &&
|
|
142
|
+
Object.prototype.hasOwnProperty.call(row, bare)) {
|
|
143
|
+
return {
|
|
144
|
+
kind: 'bare-shorthand',
|
|
145
|
+
identifier: bare,
|
|
146
|
+
canonical: `${ROW_PREDICATE_CANONICAL_ROOT}.${bare}`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// (2) Wrong-layer `data.*` root — silent at runtime on the server.
|
|
150
|
+
if (dataNamesRow && Array.isArray(roots.roots) && roots.roots.includes(METADATA_LAYER_ROOT)) {
|
|
151
|
+
return {
|
|
152
|
+
kind: 'metadata-layer-root',
|
|
153
|
+
identifier: METADATA_LAYER_ROOT,
|
|
154
|
+
canonical: ROW_PREDICATE_CANONICAL_ROOT,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* The acting subject's effective object permissions, carried to the one place
|
|
10
|
+
* this repo hands a predicate to the CEL engine (objectui#4421).
|
|
11
|
+
*
|
|
12
|
+
* ## What it is for
|
|
13
|
+
*
|
|
14
|
+
* `@objectstack/formula` registers `current_user.can(object, verb)` as a
|
|
15
|
+
* receiver method on the acting subject, and answers it from
|
|
16
|
+
* `EvalContext.permissions` — the verbatim `objects` map of the
|
|
17
|
+
* `/auth/me/permissions` response. That map is a separate field of the engine
|
|
18
|
+
* context, and the engine is explicit that it is ⛔ NOT a CEL variable: an
|
|
19
|
+
* authored predicate must not be able to read `permissions.crm_lead.allowEdit`
|
|
20
|
+
* and step around the closed verb table.
|
|
21
|
+
*
|
|
22
|
+
* This repo's evaluation seam (`evalFieldPredicate`) is not handed an engine
|
|
23
|
+
* context by its callers, though. Every action surface — the row menu, the
|
|
24
|
+
* record header, the `action:*` renderers, `DeclaredActionsBar` — hands it a
|
|
25
|
+
* variable BAG (the host's predicate scope, spread, merged, pushed through an
|
|
26
|
+
* `ExpressionContext`), and the bag's string keys all become CEL roots. So the
|
|
27
|
+
* permissions cannot ride on the bag under a name: any name would be a root.
|
|
28
|
+
*
|
|
29
|
+
* They ride on the SUBJECT instead, under a symbol:
|
|
30
|
+
*
|
|
31
|
+
* - The engine models the map as the subject's own answer sheet — it binds
|
|
32
|
+
* `permissions` only alongside a user, and `can` refuses any receiver that
|
|
33
|
+
* is not that user (`receiver !== subject`). The subject is the one object
|
|
34
|
+
* every bag already carries by reference under `current_user` (and the
|
|
35
|
+
* `user` / `ctx.user` / `os.user` aliases of it), through every spread and
|
|
36
|
+
* every `ExpressionContext` scope, so no surface needs its own copy of the
|
|
37
|
+
* hand-off.
|
|
38
|
+
* - A symbol is the one kind of key no predicate dialect can name: CEL
|
|
39
|
+
* identifiers and member names are strings, and so are the legacy
|
|
40
|
+
* evaluator's. `current_user.permissions` still faults as an unknown key.
|
|
41
|
+
*
|
|
42
|
+
* ## Absent is not empty
|
|
43
|
+
*
|
|
44
|
+
* A subject that carries NO map — the payload has not loaded, no permission
|
|
45
|
+
* provider is mounted, or the provider has no `/auth/me/permissions` answer at
|
|
46
|
+
* all — hands the engine no `permissions`, and `can` then refuses loudly
|
|
47
|
+
* (`ok: false`, `kind: 'runtime'`). That refusal is a FAULT, so each surface
|
|
48
|
+
* applies its own fault policy to it; it is never turned into a quiet `false`
|
|
49
|
+
* here, because the engine's contract is that an empty map means "holds
|
|
50
|
+
* nothing" and a caller that has no data must not claim it has that answer.
|
|
51
|
+
*/
|
|
52
|
+
import type { EvalPermissions } from '@objectstack/formula';
|
|
53
|
+
/**
|
|
54
|
+
* The key the subject carries its effective object permissions under.
|
|
55
|
+
*
|
|
56
|
+
* `Symbol.for` rather than `Symbol()` so two copies of this module in one page
|
|
57
|
+
* (a host that bundles `@object-ui/core` twice) still agree on the key; the
|
|
58
|
+
* namespace keeps it from colliding with anyone else's registry entry.
|
|
59
|
+
*/
|
|
60
|
+
export declare const SUBJECT_PERMISSIONS: unique symbol;
|
|
61
|
+
/**
|
|
62
|
+
* The subject to publish as `current_user`, carrying `permissions` when there
|
|
63
|
+
* are any.
|
|
64
|
+
*
|
|
65
|
+
* Returns a shallow copy so the caller's own user object is never mutated; the
|
|
66
|
+
* copy is what the whole predicate scope must use for every alias of the
|
|
67
|
+
* subject, because the engine compares the `can` receiver by IDENTITY. With no
|
|
68
|
+
* `permissions` the result carries none — a subject built from one that did is
|
|
69
|
+
* stripped rather than left answering from a stale map.
|
|
70
|
+
*/
|
|
71
|
+
export declare function bindSubjectPermissions<T extends object>(subject: T, permissions: EvalPermissions | undefined): T;
|
|
72
|
+
/**
|
|
73
|
+
* The effective object permissions a subject carries, or `undefined` when it
|
|
74
|
+
* carries none (see "Absent is not empty" above).
|
|
75
|
+
*/
|
|
76
|
+
export declare function subjectPermissionsOf(subject: unknown): EvalPermissions | undefined;
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* The key the subject carries its effective object permissions under.
|
|
10
|
+
*
|
|
11
|
+
* `Symbol.for` rather than `Symbol()` so two copies of this module in one page
|
|
12
|
+
* (a host that bundles `@object-ui/core` twice) still agree on the key; the
|
|
13
|
+
* namespace keeps it from colliding with anyone else's registry entry.
|
|
14
|
+
*/
|
|
15
|
+
export const SUBJECT_PERMISSIONS = Symbol.for('@object-ui/core:subject-permissions');
|
|
16
|
+
/**
|
|
17
|
+
* The subject to publish as `current_user`, carrying `permissions` when there
|
|
18
|
+
* are any.
|
|
19
|
+
*
|
|
20
|
+
* Returns a shallow copy so the caller's own user object is never mutated; the
|
|
21
|
+
* copy is what the whole predicate scope must use for every alias of the
|
|
22
|
+
* subject, because the engine compares the `can` receiver by IDENTITY. With no
|
|
23
|
+
* `permissions` the result carries none — a subject built from one that did is
|
|
24
|
+
* stripped rather than left answering from a stale map.
|
|
25
|
+
*/
|
|
26
|
+
export function bindSubjectPermissions(subject, permissions) {
|
|
27
|
+
if (permissions === undefined) {
|
|
28
|
+
if (!Object.prototype.hasOwnProperty.call(subject, SUBJECT_PERMISSIONS))
|
|
29
|
+
return subject;
|
|
30
|
+
const stripped = { ...subject };
|
|
31
|
+
delete stripped[SUBJECT_PERMISSIONS];
|
|
32
|
+
return stripped;
|
|
33
|
+
}
|
|
34
|
+
return { ...subject, [SUBJECT_PERMISSIONS]: permissions };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The effective object permissions a subject carries, or `undefined` when it
|
|
38
|
+
* carries none (see "Absent is not empty" above).
|
|
39
|
+
*/
|
|
40
|
+
export function subjectPermissionsOf(subject) {
|
|
41
|
+
if (subject === null || typeof subject !== 'object')
|
|
42
|
+
return undefined;
|
|
43
|
+
return subject[SUBJECT_PERMISSIONS];
|
|
44
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* Is this value a gate the author DECLARED but the client cannot EVALUATE?
|
|
10
|
+
* (objectui#11358)
|
|
11
|
+
*
|
|
12
|
+
* A predicate gate (`visible` / `hidden` / `enabled` / `disabled` /
|
|
13
|
+
* `condition`) is in one of three states, and this is the one place the third
|
|
14
|
+
* is told apart from the other two:
|
|
15
|
+
*
|
|
16
|
+
* - **absent** — `null` / `undefined` / `''` / blank predicate text. Nothing
|
|
17
|
+
* was declared; {@link hasDeclaredPredicate} answers "not declared" (and
|
|
18
|
+
* reports a blank one, objectui#8069).
|
|
19
|
+
* - **evaluable** — a boolean, a predicate string, or an envelope whose
|
|
20
|
+
* `source` is a non-blank string.
|
|
21
|
+
* - **declared but not evaluable** — anything else that is present: an
|
|
22
|
+
* envelope with no string `source` (the `ast`-only `{ dialect: 'cel', ast }`
|
|
23
|
+
* an authoring door can admit), a number (`0`, `42`), an object with no
|
|
24
|
+
* `source` (`{}`), an array. This function answers `true` for exactly
|
|
25
|
+
* these.
|
|
26
|
+
*
|
|
27
|
+
* Triage's ruling on objectui#11358 makes the third state "declared and
|
|
28
|
+
* faulting": it is a gate, so it is never "no gate", and it cannot be
|
|
29
|
+
* evaluated, so it takes the path every other faulting predicate takes —
|
|
30
|
+
* reported, and answered with the fail direction its key already has. Before
|
|
31
|
+
* that ruling the normalizer folded it to `undefined`, the same answer as
|
|
32
|
+
* "absent", so every reader showed / enabled / ran it in silence: ADR-0137 D4's
|
|
33
|
+
* "a gate predicate that is blank or faulting is diagnosed, never a silent
|
|
34
|
+
* `true`", broken by a fold.
|
|
35
|
+
*
|
|
36
|
+
* Read by the normalizer ({@link toPredicateInput}, which now keeps the state
|
|
37
|
+
* instead of folding it), the evaluation step
|
|
38
|
+
* (`ExpressionEvaluator.evaluateCondition`, which reports it and answers it as a
|
|
39
|
+
* fault), the canonical predicate helper (`evalFieldPredicate`, which tags its
|
|
40
|
+
* reason) and the dev-mode schema validator (which still refuses it). ⛔ Not
|
|
41
|
+
* re-exported from the package entry: it is the internal definition those four
|
|
42
|
+
* share, not public API.
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ No client-side reading of what an `ast` MEANT: the client evaluates
|
|
45
|
+
* `source` text only, so an `ast` with no `source` is not evaluable here,
|
|
46
|
+
* whatever it says.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isUnevaluablePredicate(value: unknown): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The `reason` reported for a gate that is declared but cannot be evaluated,
|
|
51
|
+
* tagged like an engine reason (`[parse]`, `[type]`, `[blank]`) because it
|
|
52
|
+
* travels the same two channels — `evalFieldPredicate`'s one-time warning and
|
|
53
|
+
* its `onFault` passback — and a caller that routes on the tag must tell it
|
|
54
|
+
* apart from a predicate that faulted with text in it: the fix is to give the
|
|
55
|
+
* gate a `source` (or delete it), never to debug CEL syntax.
|
|
56
|
+
*/
|
|
57
|
+
export declare const UNEVALUABLE_PREDICATE_REASON: string;
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* Is this value a gate the author DECLARED but the client cannot EVALUATE?
|
|
10
|
+
* (objectui#11358)
|
|
11
|
+
*
|
|
12
|
+
* A predicate gate (`visible` / `hidden` / `enabled` / `disabled` /
|
|
13
|
+
* `condition`) is in one of three states, and this is the one place the third
|
|
14
|
+
* is told apart from the other two:
|
|
15
|
+
*
|
|
16
|
+
* - **absent** — `null` / `undefined` / `''` / blank predicate text. Nothing
|
|
17
|
+
* was declared; {@link hasDeclaredPredicate} answers "not declared" (and
|
|
18
|
+
* reports a blank one, objectui#8069).
|
|
19
|
+
* - **evaluable** — a boolean, a predicate string, or an envelope whose
|
|
20
|
+
* `source` is a non-blank string.
|
|
21
|
+
* - **declared but not evaluable** — anything else that is present: an
|
|
22
|
+
* envelope with no string `source` (the `ast`-only `{ dialect: 'cel', ast }`
|
|
23
|
+
* an authoring door can admit), a number (`0`, `42`), an object with no
|
|
24
|
+
* `source` (`{}`), an array. This function answers `true` for exactly
|
|
25
|
+
* these.
|
|
26
|
+
*
|
|
27
|
+
* Triage's ruling on objectui#11358 makes the third state "declared and
|
|
28
|
+
* faulting": it is a gate, so it is never "no gate", and it cannot be
|
|
29
|
+
* evaluated, so it takes the path every other faulting predicate takes —
|
|
30
|
+
* reported, and answered with the fail direction its key already has. Before
|
|
31
|
+
* that ruling the normalizer folded it to `undefined`, the same answer as
|
|
32
|
+
* "absent", so every reader showed / enabled / ran it in silence: ADR-0137 D4's
|
|
33
|
+
* "a gate predicate that is blank or faulting is diagnosed, never a silent
|
|
34
|
+
* `true`", broken by a fold.
|
|
35
|
+
*
|
|
36
|
+
* Read by the normalizer ({@link toPredicateInput}, which now keeps the state
|
|
37
|
+
* instead of folding it), the evaluation step
|
|
38
|
+
* (`ExpressionEvaluator.evaluateCondition`, which reports it and answers it as a
|
|
39
|
+
* fault), the canonical predicate helper (`evalFieldPredicate`, which tags its
|
|
40
|
+
* reason) and the dev-mode schema validator (which still refuses it). ⛔ Not
|
|
41
|
+
* re-exported from the package entry: it is the internal definition those four
|
|
42
|
+
* share, not public API.
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ No client-side reading of what an `ast` MEANT: the client evaluates
|
|
45
|
+
* `source` text only, so an `ast` with no `source` is not evaluable here,
|
|
46
|
+
* whatever it says.
|
|
47
|
+
*/
|
|
48
|
+
export function isUnevaluablePredicate(value) {
|
|
49
|
+
if (value === null || value === undefined)
|
|
50
|
+
return false;
|
|
51
|
+
if (typeof value === 'boolean' || typeof value === 'string')
|
|
52
|
+
return false;
|
|
53
|
+
if (typeof value === 'object') {
|
|
54
|
+
return typeof value.source !== 'string';
|
|
55
|
+
}
|
|
56
|
+
// A number (`0` included — it is present, not absent), a function, a symbol
|
|
57
|
+
// or a bigint: nothing a predicate is spelled as.
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The `reason` reported for a gate that is declared but cannot be evaluated,
|
|
62
|
+
* tagged like an engine reason (`[parse]`, `[type]`, `[blank]`) because it
|
|
63
|
+
* travels the same two channels — `evalFieldPredicate`'s one-time warning and
|
|
64
|
+
* its `onFault` passback — and a caller that routes on the tag must tell it
|
|
65
|
+
* apart from a predicate that faulted with text in it: the fix is to give the
|
|
66
|
+
* gate a `source` (or delete it), never to debug CEL syntax.
|
|
67
|
+
*/
|
|
68
|
+
export const UNEVALUABLE_PREDICATE_REASON = '[unevaluable] the gate is declared but has no `source` to evaluate (an ' +
|
|
69
|
+
'`ast`-only envelope, a number, an object or an array) — it fails in the ' +
|
|
70
|
+
'direction its key fails on any fault';
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
export type { SchemaNode, ComponentRendererProps } from './types/index.js';
|
|
9
9
|
export * from './registry/Registry.js';
|
|
10
10
|
export * from './registry/public-blocks.js';
|
|
11
|
+
export * from './registry/html-tier-intrinsics.js';
|
|
11
12
|
export * from './registry/PluginSystem.js';
|
|
12
13
|
export * from './registry/PluginScopeImpl.js';
|
|
13
14
|
export * from './registry/WidgetRegistry.js';
|
|
@@ -17,7 +18,12 @@ export * from './utils/dom-props.js';
|
|
|
17
18
|
export * from './utils/filter-converter.js';
|
|
18
19
|
export * from './utils/managedBy.js';
|
|
19
20
|
export * from './utils/extract-records.js';
|
|
21
|
+
export * from './utils/non-grid-row-ceiling.js';
|
|
22
|
+
export * from './utils/emptiness.js';
|
|
20
23
|
export * from './utils/expand-fields.js';
|
|
24
|
+
export * from './utils/retired-field-types.js';
|
|
25
|
+
export * from './utils/unmaterialized-fields.js';
|
|
26
|
+
export * from './utils/column-sortability.js';
|
|
21
27
|
export * from './utils/column-identity.js';
|
|
22
28
|
export * from './utils/sort-values.js';
|
|
23
29
|
export * from './utils/sort-query.js';
|
|
@@ -49,12 +55,24 @@ export * from './utils/dashboard-filters.js';
|
|
|
49
55
|
export * from './utils/merge-filters.js';
|
|
50
56
|
export * from './utils/compare-to.js';
|
|
51
57
|
export * from './utils/chart-series.js';
|
|
58
|
+
export * from './utils/chart-measure-key.js';
|
|
59
|
+
export * from './utils/chart-category-key.js';
|
|
60
|
+
export * from './utils/object-aggregate-query.js';
|
|
61
|
+
export * from './utils/chart-presentation.js';
|
|
52
62
|
export * from './utils/number-display.js';
|
|
63
|
+
export * from './utils/date-display.js';
|
|
64
|
+
export * from './utils/native-date-value.js';
|
|
53
65
|
export * from './utils/dataset-format.js';
|
|
54
66
|
export * from './utils/dataset-pivot.js';
|
|
55
67
|
export * from './utils/record-title.js';
|
|
68
|
+
export * from './utils/record-color.js';
|
|
56
69
|
export * from './utils/export-filename.js';
|
|
57
70
|
export * from './utils/reference-keys.js';
|
|
58
71
|
export * from './utils/predicate-record.js';
|
|
72
|
+
export * from './utils/without-denied-fields.js';
|
|
73
|
+
export * from './utils/parent-scope.js';
|
|
59
74
|
export * from './utils/predicate-fields.js';
|
|
75
|
+
export * from './utils/grouping-fields.js';
|
|
60
76
|
export * from './utils/normalize-list-view.js';
|
|
77
|
+
export * from './utils/record-source.js';
|
|
78
|
+
export * from './utils/humanize-label.js';
|