@object-ui/core 17.6.0 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7260 -0
- package/README.md +228 -14
- package/dist/actions/ActionEngine.d.ts +4 -2
- package/dist/actions/ActionEngine.js +13 -8
- package/dist/actions/ActionRunner.d.ts +473 -69
- package/dist/actions/ActionRunner.js +792 -125
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/UndoManager.d.ts +8 -13
- package/dist/actions/actionKeys.d.ts +9 -7
- package/dist/actions/actionKeys.js +67 -14
- package/dist/actions/actionResponse.js +2 -2
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/navigationTarget.d.ts +41 -0
- package/dist/actions/navigationTarget.js +20 -0
- package/dist/actions/recordDelete.d.ts +154 -0
- package/dist/actions/recordDelete.js +110 -0
- package/dist/adapters/ApiDataSource.d.ts +8 -4
- package/dist/adapters/ApiDataSource.js +19 -7
- 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/adapters/resolveDataSource.d.ts +9 -6
- package/dist/adapters/resolveDataSource.js +9 -12
- package/dist/builder/schema-builder.d.ts +38 -81
- package/dist/builder/schema-builder.js +33 -152
- package/dist/data-scope/ViewDataProvider.d.ts +8 -25
- package/dist/data-scope/ViewDataProvider.js +27 -54
- 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/deadlineSettled.d.ts +40 -0
- package/dist/evaluator/deadlineSettled.js +45 -0
- 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 +208 -31
- package/dist/evaluator/index.d.ts +2 -0
- package/dist/evaluator/index.js +2 -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 +105 -0
- package/dist/evaluator/subjectPermissions.js +86 -0
- package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
- package/dist/evaluator/unevaluablePredicate.js +70 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +83 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +382 -34
- package/dist/registry/Registry.js +247 -12
- 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/affordanceGrants.d.ts +371 -0
- package/dist/utils/affordanceGrants.js +252 -0
- 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 +165 -5
- package/dist/utils/chart-series.js +123 -18
- 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 +346 -0
- package/dist/utils/date-display.js +644 -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 +81 -9
- package/dist/utils/expand-fields.js +81 -9
- package/dist/utils/extract-records.d.ts +65 -5
- package/dist/utils/extract-records.js +65 -8
- package/dist/utils/filter-converter.d.ts +158 -6
- package/dist/utils/filter-converter.js +1098 -24
- 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 +135 -11
- package/dist/utils/normalize-list-view.js +243 -64
- 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 +141 -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 +225 -83
- package/dist/validation/server-owned-value.d.ts +5 -1
- package/dist/validation/server-owned-value.js +5 -1
- 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
|
@@ -16,7 +16,7 @@ import { CONTEXT_TOKENS } from '@objectstack/spec/data';
|
|
|
16
16
|
*
|
|
17
17
|
* { owner_id: '{current_user_id}' }
|
|
18
18
|
*
|
|
19
|
-
* ## Why this module exists (
|
|
19
|
+
* ## Why this module exists (objectstack-ai/objectstack#3574)
|
|
20
20
|
*
|
|
21
21
|
* There used to be no shared resolver. Three ad-hoc implementations had grown
|
|
22
22
|
* up independently — one in `ObjectView` for list views, one in
|
|
@@ -40,6 +40,25 @@ import { CONTEXT_TOKENS } from '@objectstack/spec/data';
|
|
|
40
40
|
* Dropping a `{current_user_id}` filter widens a view; it must never widen
|
|
41
41
|
* access. Never use a context token as an access control.
|
|
42
42
|
*
|
|
43
|
+
* ## `{record_id}` — the record in view, not the session (objectui#7297)
|
|
44
|
+
*
|
|
45
|
+
* `@objectstack/spec` declares one sibling of the two session tokens:
|
|
46
|
+
* `RECORD_CONTEXT_TOKENS` = `{record_id}`, the id of the record a
|
|
47
|
+
* `type: 'record'` page is showing (objectstack-ai/objectstack#20003). It is a
|
|
48
|
+
* separate list, not a member of `CONTEXT_TOKENS`, because it resolves against
|
|
49
|
+
* the SURFACE rather than the session, and only here: the server never knows
|
|
50
|
+
* which record a page shows, so it refuses a filter still carrying the token
|
|
51
|
+
* by name (`FILTER_TOKEN_UNRESOLVED`).
|
|
52
|
+
*
|
|
53
|
+
* Its value is `FilterTokenScope.recordId`, which `useFilterScope()` in
|
|
54
|
+
* `@object-ui/react` fills from the mounted `RecordContextProvider` and from
|
|
55
|
+
* nothing else — never a URL parameter, never a page variable. With no record
|
|
56
|
+
* in scope the token is refused by name through `onUnresolved`, in the voice
|
|
57
|
+
* an unresolved session token gets, and left as written: it never becomes
|
|
58
|
+
* `null` (a count about nobody) and its condition is never dropped (a count
|
|
59
|
+
* about everybody). Like the session tokens it scopes what a record page
|
|
60
|
+
* SHOWS; which of those rows the caller may read is still RLS's decision.
|
|
61
|
+
*
|
|
43
62
|
* ## Contract source
|
|
44
63
|
*
|
|
45
64
|
* The canonical vocabulary is published as part of the platform contract at
|
|
@@ -53,6 +72,20 @@ import { CONTEXT_TOKENS } from '@objectstack/spec/data';
|
|
|
53
72
|
* copy was byte-identical, so every value comparison and every behavioural
|
|
54
73
|
* test passed while it sat here. Reference identity is the one check that
|
|
55
74
|
* distinguishes a re-export from a fork (objectui#3003).
|
|
75
|
+
*
|
|
76
|
+
* The same argument retired the two module-local copies that sat beside it
|
|
77
|
+
* until objectui#7265 — the near-miss suggestion map and the membership
|
|
78
|
+
* predicate are imported from `@objectstack/spec/data` now, so neither can
|
|
79
|
+
* drift either. Both were re-measured against the RESOLVED pin (17.4.0; the
|
|
80
|
+
* card had measured 17.2.0) before the swap, because "byte-identical" is a
|
|
81
|
+
* statement about a version, not a property: the map matched on nine keys, in
|
|
82
|
+
* the same order, with the same values, and the two predicates agreed on every
|
|
83
|
+
* one of 61 real vocabulary members — the token tuple, both suggestion key
|
|
84
|
+
* sets, the date-macro tokens and their aliases, plus casing, whitespace and
|
|
85
|
+
* prototype-key spellings. Each comparison ran a lit control of the same kind
|
|
86
|
+
* through the same comparator (the spec's own token-description map, and its
|
|
87
|
+
* date-macro predicate), so "no difference" came from an instrument shown able
|
|
88
|
+
* to report one.
|
|
56
89
|
*/
|
|
57
90
|
/** The complete set of session-scoped filter tokens (spec-owned, re-exported). */
|
|
58
91
|
export { CONTEXT_TOKENS };
|
|
@@ -63,15 +96,26 @@ export interface FilterTokenScope {
|
|
|
63
96
|
currentUserId?: string | null;
|
|
64
97
|
/** The active organization id. */
|
|
65
98
|
currentOrgId?: string | null;
|
|
99
|
+
/**
|
|
100
|
+
* The id of the record the surface is bound to, which resolves
|
|
101
|
+
* `{record_id}` (objectui#7297): the mounted record context of a
|
|
102
|
+
* `type: 'record'` page. `useFilterScope()` in `@object-ui/react` fills it
|
|
103
|
+
* from the nearest `RecordContextProvider`, and a host that renders no
|
|
104
|
+
* record leaves it unset, so the token is refused there. ⛔ Never fill it
|
|
105
|
+
* from a URL parameter or a page variable.
|
|
106
|
+
*/
|
|
107
|
+
recordId?: string | null;
|
|
66
108
|
/**
|
|
67
109
|
* Called when a placeholder cannot be resolved — either a recognised token
|
|
68
|
-
* with no value in scope (signed out
|
|
69
|
-
* in no vocabulary. Defaults to a
|
|
110
|
+
* with no value in scope (signed out, or no record in context), or a
|
|
111
|
+
* near-miss spelling that resolves in no vocabulary. Defaults to a
|
|
112
|
+
* `console.warn`; pass `null` to silence.
|
|
70
113
|
*/
|
|
71
114
|
onUnresolved?: ((message: string) => void) | null;
|
|
72
115
|
}
|
|
73
116
|
/**
|
|
74
|
-
* Expand `{current_user_id}` / `{current_org_id}`
|
|
117
|
+
* Expand `{current_user_id}` / `{current_org_id}`, and `{record_id}` where a
|
|
118
|
+
* record is in scope, inside a filter.
|
|
75
119
|
*
|
|
76
120
|
* Walks arrays and plain objects recursively, which is what makes one
|
|
77
121
|
* resolver cover both platform filter shapes: the MongoDB-style object a
|
|
@@ -98,7 +142,7 @@ export declare function resolveContextTokens<T = any>(filter: T, scope?: FilterT
|
|
|
98
142
|
* macros and context tokens — in one call.
|
|
99
143
|
*
|
|
100
144
|
* This is the function surfaces should call. Calling only one of the two
|
|
101
|
-
* resolvers is the defect behind
|
|
145
|
+
* resolvers is the defect behind objectstack-ai/objectstack#3574: dashboard widgets called
|
|
102
146
|
* `resolveDateMacros` alone, so `{today}` worked and `{current_user_id}`
|
|
103
147
|
* silently did not.
|
|
104
148
|
*
|
|
@@ -5,7 +5,7 @@
|
|
|
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 { CONTEXT_TOKENS } from '@objectstack/spec/data';
|
|
8
|
+
import { CONTEXT_TOKENS, CONTEXT_TOKEN_SUGGESTIONS, isContextToken, isRecordContextToken, } from '@objectstack/spec/data';
|
|
9
9
|
import { resolveDateMacros } from './date-macros.js';
|
|
10
10
|
/**
|
|
11
11
|
* Session-scoped filter placeholders — the sibling vocabulary to
|
|
@@ -17,7 +17,7 @@ import { resolveDateMacros } from './date-macros.js';
|
|
|
17
17
|
*
|
|
18
18
|
* { owner_id: '{current_user_id}' }
|
|
19
19
|
*
|
|
20
|
-
* ## Why this module exists (
|
|
20
|
+
* ## Why this module exists (objectstack-ai/objectstack#3574)
|
|
21
21
|
*
|
|
22
22
|
* There used to be no shared resolver. Three ad-hoc implementations had grown
|
|
23
23
|
* up independently — one in `ObjectView` for list views, one in
|
|
@@ -41,6 +41,25 @@ import { resolveDateMacros } from './date-macros.js';
|
|
|
41
41
|
* Dropping a `{current_user_id}` filter widens a view; it must never widen
|
|
42
42
|
* access. Never use a context token as an access control.
|
|
43
43
|
*
|
|
44
|
+
* ## `{record_id}` — the record in view, not the session (objectui#7297)
|
|
45
|
+
*
|
|
46
|
+
* `@objectstack/spec` declares one sibling of the two session tokens:
|
|
47
|
+
* `RECORD_CONTEXT_TOKENS` = `{record_id}`, the id of the record a
|
|
48
|
+
* `type: 'record'` page is showing (objectstack-ai/objectstack#20003). It is a
|
|
49
|
+
* separate list, not a member of `CONTEXT_TOKENS`, because it resolves against
|
|
50
|
+
* the SURFACE rather than the session, and only here: the server never knows
|
|
51
|
+
* which record a page shows, so it refuses a filter still carrying the token
|
|
52
|
+
* by name (`FILTER_TOKEN_UNRESOLVED`).
|
|
53
|
+
*
|
|
54
|
+
* Its value is `FilterTokenScope.recordId`, which `useFilterScope()` in
|
|
55
|
+
* `@object-ui/react` fills from the mounted `RecordContextProvider` and from
|
|
56
|
+
* nothing else — never a URL parameter, never a page variable. With no record
|
|
57
|
+
* in scope the token is refused by name through `onUnresolved`, in the voice
|
|
58
|
+
* an unresolved session token gets, and left as written: it never becomes
|
|
59
|
+
* `null` (a count about nobody) and its condition is never dropped (a count
|
|
60
|
+
* about everybody). Like the session tokens it scopes what a record page
|
|
61
|
+
* SHOWS; which of those rows the caller may read is still RLS's decision.
|
|
62
|
+
*
|
|
44
63
|
* ## Contract source
|
|
45
64
|
*
|
|
46
65
|
* The canonical vocabulary is published as part of the platform contract at
|
|
@@ -54,40 +73,47 @@ import { resolveDateMacros } from './date-macros.js';
|
|
|
54
73
|
* copy was byte-identical, so every value comparison and every behavioural
|
|
55
74
|
* test passed while it sat here. Reference identity is the one check that
|
|
56
75
|
* distinguishes a re-export from a fork (objectui#3003).
|
|
76
|
+
*
|
|
77
|
+
* The same argument retired the two module-local copies that sat beside it
|
|
78
|
+
* until objectui#7265 — the near-miss suggestion map and the membership
|
|
79
|
+
* predicate are imported from `@objectstack/spec/data` now, so neither can
|
|
80
|
+
* drift either. Both were re-measured against the RESOLVED pin (17.4.0; the
|
|
81
|
+
* card had measured 17.2.0) before the swap, because "byte-identical" is a
|
|
82
|
+
* statement about a version, not a property: the map matched on nine keys, in
|
|
83
|
+
* the same order, with the same values, and the two predicates agreed on every
|
|
84
|
+
* one of 61 real vocabulary members — the token tuple, both suggestion key
|
|
85
|
+
* sets, the date-macro tokens and their aliases, plus casing, whitespace and
|
|
86
|
+
* prototype-key spellings. Each comparison ran a lit control of the same kind
|
|
87
|
+
* through the same comparator (the spec's own token-description map, and its
|
|
88
|
+
* date-macro predicate), so "no difference" came from an instrument shown able
|
|
89
|
+
* to report one.
|
|
57
90
|
*/
|
|
58
91
|
/** The complete set of session-scoped filter tokens (spec-owned, re-exported). */
|
|
59
92
|
export { CONTEXT_TOKENS };
|
|
60
|
-
/**
|
|
61
|
-
* Near-miss spellings → the token the author meant.
|
|
62
|
-
*
|
|
63
|
-
* Every entry is a real authoring mistake, and each is a correct spelling
|
|
64
|
-
* *somewhere else* in the platform, which is exactly why authors reach for it:
|
|
65
|
-
* `current_user` is the RLS expression root, `{user_id}` is valid
|
|
66
|
-
* `titleFormat` field interpolation, `organization_id` is a real column name.
|
|
67
|
-
*
|
|
68
|
-
* Mirrors `CONTEXT_TOKEN_SUGGESTIONS` in `@objectstack/spec`. Used only to
|
|
69
|
-
* make the runtime warning actionable; the authoring-time gate
|
|
70
|
-
* (`validateFilterTokens` in `@objectstack/lint`) is what actually prevents
|
|
71
|
-
* these from shipping.
|
|
72
|
-
*/
|
|
73
|
-
const CONTEXT_TOKEN_SUGGESTIONS = {
|
|
74
|
-
current_user: 'current_user_id',
|
|
75
|
-
current_user_email: 'current_user_id',
|
|
76
|
-
user_id: 'current_user_id',
|
|
77
|
-
userid: 'current_user_id',
|
|
78
|
-
me: 'current_user_id',
|
|
79
|
-
current_organization_id: 'current_org_id',
|
|
80
|
-
org_id: 'current_org_id',
|
|
81
|
-
organization_id: 'current_org_id',
|
|
82
|
-
current_tenant_id: 'current_org_id',
|
|
83
|
-
};
|
|
84
93
|
/** Whole-string placeholder: `{token}` or `${token}`, anchored. */
|
|
85
94
|
const WHOLE_TOKEN_RE = /^\$?\{([a-zA-Z0-9_]+)\}$/;
|
|
86
|
-
function isContextToken(token) {
|
|
87
|
-
return CONTEXT_TOKENS.includes(token);
|
|
88
|
-
}
|
|
89
95
|
/**
|
|
90
|
-
*
|
|
96
|
+
* A null-prototype copy of the spec's near-miss suggestion map (objectui#9129).
|
|
97
|
+
*
|
|
98
|
+
* `CONTEXT_TOKEN_SUGGESTIONS` is a plain object, so indexing it with an
|
|
99
|
+
* author-controlled, lower-cased string reaches `Object.prototype` for any
|
|
100
|
+
* spelling that happens to be an inherited member name — `constructor` and
|
|
101
|
+
* `__proto__` today, and, silently, whichever future member is added in
|
|
102
|
+
* lower case (see the lookup below). This is a read-only console hint, never
|
|
103
|
+
* an assignment, so no filter value is ever widened, narrowed or mismatched
|
|
104
|
+
* by it; the only externally visible effect of the underlying bug is a
|
|
105
|
+
* confusing suggestion string in a `warn()` call.
|
|
106
|
+
*
|
|
107
|
+
* Copying into an `Object.create(null)` base closes the whole class rather
|
|
108
|
+
* than special-casing the two spellings measured today: this object has no
|
|
109
|
+
* prototype at all, so *no* key — known or future — can resolve through it.
|
|
110
|
+
* `@objectstack/spec`'s own map is left untouched (out of scope here; the
|
|
111
|
+
* identical shape in its `classifyFilterToken` is the one objectstack `4342c9923` closed).
|
|
112
|
+
*/
|
|
113
|
+
const NEAR_MISS_SUGGESTIONS = Object.assign(Object.create(null), CONTEXT_TOKEN_SUGGESTIONS);
|
|
114
|
+
/**
|
|
115
|
+
* Expand `{current_user_id}` / `{current_org_id}`, and `{record_id}` where a
|
|
116
|
+
* record is in scope, inside a filter.
|
|
91
117
|
*
|
|
92
118
|
* Walks arrays and plain objects recursively, which is what makes one
|
|
93
119
|
* resolver cover both platform filter shapes: the MongoDB-style object a
|
|
@@ -111,17 +137,30 @@ function isContextToken(token) {
|
|
|
111
137
|
export function resolveContextTokens(filter, scope = {}) {
|
|
112
138
|
if (filter == null)
|
|
113
139
|
return filter;
|
|
114
|
-
const { currentUserId, currentOrgId } = scope;
|
|
140
|
+
const { currentUserId, currentOrgId, recordId } = scope;
|
|
115
141
|
const warn = scope.onUnresolved === null
|
|
116
142
|
? () => { }
|
|
117
143
|
: scope.onUnresolved ??
|
|
118
144
|
((message) => {
|
|
119
145
|
console.warn(`[object-ui] ${message}`);
|
|
120
146
|
});
|
|
147
|
+
// Annotated `Record<string, …>` because the spec's predicate returns a plain
|
|
148
|
+
// `boolean`, not the narrowing `token is ContextTokenName` the deleted local
|
|
149
|
+
// copy declared, so the narrowed branch below hands this lookup a `string`.
|
|
150
|
+
// The `satisfies` clause keeps the one thing that narrowing bought: a third
|
|
151
|
+
// context token in the spec reds this literal at compile time instead of
|
|
152
|
+
// resolving to `undefined` at runtime.
|
|
121
153
|
const values = {
|
|
122
154
|
current_user_id: currentUserId,
|
|
123
155
|
current_org_id: currentOrgId,
|
|
124
156
|
};
|
|
157
|
+
// The record-context sibling, kept apart for the reason the spec keeps the
|
|
158
|
+
// two lists apart: it is filled from the record in view, never from the
|
|
159
|
+
// session. The same `satisfies` ratchet: a second record-context token in
|
|
160
|
+
// the spec reds this literal at compile time.
|
|
161
|
+
const recordValues = {
|
|
162
|
+
record_id: recordId,
|
|
163
|
+
};
|
|
125
164
|
const walk = (value) => {
|
|
126
165
|
if (value == null)
|
|
127
166
|
return value;
|
|
@@ -137,10 +176,36 @@ export function resolveContextTokens(filter, scope = {}) {
|
|
|
137
176
|
warn(`Filter placeholder "{${token}}" could not be resolved — no ${token === 'current_user_id' ? 'signed-in user' : 'active organization'} in scope. The filter will match no records.`);
|
|
138
177
|
return value;
|
|
139
178
|
}
|
|
179
|
+
// `{record_id}` (objectui#7297). Resolved only from the record in scope.
|
|
180
|
+
// With none, it is refused BY NAME, in the voice of the unresolved
|
|
181
|
+
// session token above, and left as written: never `null` (a count about
|
|
182
|
+
// nobody), never dropped (a count about everybody), and never reported as
|
|
183
|
+
// an unknown spelling, because the spelling is right and the surface is
|
|
184
|
+
// not. The ObjectStack server then refuses the same filter by name
|
|
185
|
+
// (`FILTER_TOKEN_UNRESOLVED`), since no server path has a record in view.
|
|
186
|
+
if (isRecordContextToken(token)) {
|
|
187
|
+
const resolved = recordValues[token];
|
|
188
|
+
if (resolved != null && resolved !== '')
|
|
189
|
+
return resolved;
|
|
190
|
+
warn(`Filter placeholder "{${token}}" could not be resolved — no record in context ` +
|
|
191
|
+
`on this surface. It resolves only on a component of a \`type: 'record'\` page, ` +
|
|
192
|
+
`to the id of the record that page shows. It is left as written, so the filter ` +
|
|
193
|
+
`never widens: the server refuses it by name, and a backend with no resolver ` +
|
|
194
|
+
`matches no records.`);
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
140
197
|
// Not ours. Warn only for near-misses, which resolve in no vocabulary
|
|
141
198
|
// at all and would otherwise fail silently; genuine date macros and
|
|
142
199
|
// nav context-selector ids must pass through quietly.
|
|
143
|
-
|
|
200
|
+
//
|
|
201
|
+
// Every spelling in the imported map is a real authoring mistake, and
|
|
202
|
+
// each is a correct spelling *somewhere else* in the platform, which is
|
|
203
|
+
// exactly why authors reach for it: `current_user` is the RLS expression
|
|
204
|
+
// root, `{user_id}` is valid `titleFormat` field interpolation,
|
|
205
|
+
// `organization_id` is a real column name. The map only makes the runtime
|
|
206
|
+
// warning actionable; the authoring-time gate (`validateFilterTokens` in
|
|
207
|
+
// `@objectstack/lint`) is what actually prevents these from shipping.
|
|
208
|
+
const suggestion = NEAR_MISS_SUGGESTIONS[token.toLowerCase()];
|
|
144
209
|
if (suggestion) {
|
|
145
210
|
warn(`Filter placeholder "{${token}}" is not a recognised token — did you mean ` +
|
|
146
211
|
`"{${suggestion}}"? It is sent to the server as a literal string and will ` +
|
|
@@ -150,7 +215,14 @@ export function resolveContextTokens(filter, scope = {}) {
|
|
|
150
215
|
}
|
|
151
216
|
if (Array.isArray(value))
|
|
152
217
|
return value.map(walk);
|
|
218
|
+
// Only a PLAIN object (prototype `Object.prototype` or `null`) is a bag to
|
|
219
|
+
// walk. Anything else is a leaf, returned as the same instance: rebuilding
|
|
220
|
+
// a `Date` from its own keys — it has none — turned a comparand the spec
|
|
221
|
+
// admits (`ACCEPTED_FILTER_COMPARAND_TYPES`) into `{}` (objectui#10506).
|
|
153
222
|
if (typeof value === 'object') {
|
|
223
|
+
const proto = Object.getPrototypeOf(value);
|
|
224
|
+
if (proto !== Object.prototype && proto !== null)
|
|
225
|
+
return value;
|
|
154
226
|
const out = {};
|
|
155
227
|
for (const k of Object.keys(value))
|
|
156
228
|
out[k] = walk(value[k]);
|
|
@@ -165,7 +237,7 @@ export function resolveContextTokens(filter, scope = {}) {
|
|
|
165
237
|
* macros and context tokens — in one call.
|
|
166
238
|
*
|
|
167
239
|
* This is the function surfaces should call. Calling only one of the two
|
|
168
|
-
* resolvers is the defect behind
|
|
240
|
+
* resolvers is the defect behind objectstack-ai/objectstack#3574: dashboard widgets called
|
|
169
241
|
* `resolveDateMacros` alone, so `{today}` worked and `{current_user_id}`
|
|
170
242
|
* silently did not.
|
|
171
243
|
*
|
|
@@ -0,0 +1,61 @@
|
|
|
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 THIRD half of a view's field appetite (objectui#7179).
|
|
10
|
+
*
|
|
11
|
+
* A list view projects what it DISPLAYS — `$select` is built from `columns`.
|
|
12
|
+
* `predicate-fields.ts` covers the fields a view's PREDICATES read. This covers
|
|
13
|
+
* the fields it GROUPS BY, which is a third, independent source of demand: the
|
|
14
|
+
* spec's `grouping` block is a sibling of `columns`, not a subset of it, so a
|
|
15
|
+
* view may legitimately group by a field it never shows.
|
|
16
|
+
*
|
|
17
|
+
* ## Why it needed a fix rather than a gate
|
|
18
|
+
*
|
|
19
|
+
* `GroupingConfigSchema` accepts `grouping` with no matching column, and the
|
|
20
|
+
* neighbouring view kinds (kanban / gantt / timeline) already union their
|
|
21
|
+
* `groupByField` into the projection with no column required. Grouping by a
|
|
22
|
+
* field you do not want on screen is an ordinary thing to want. Refusing it at
|
|
23
|
+
* author time would make the grid the odd one out and reject working intent.
|
|
24
|
+
*
|
|
25
|
+
* ## The failure this closes
|
|
26
|
+
*
|
|
27
|
+
* With the grouping field absent from `$select` the server never returns it,
|
|
28
|
+
* so `useGroupedData` reads `undefined` on every row and `buildSegmentLabel`
|
|
29
|
+
* answers `(empty)` for all of them: ONE group holding every record, with no
|
|
30
|
+
* error, no warning and no empty state. It reads as "these records have no
|
|
31
|
+
* value for this field" — a plausible, wrong, actionable conclusion about the
|
|
32
|
+
* data rather than a visible bug in the view.
|
|
33
|
+
*
|
|
34
|
+
* ## ⛔ THE RESULT IS CANDIDATES, NOT VERIFIED FIELDS — the caller MUST gate it
|
|
35
|
+
*
|
|
36
|
+
* `GroupingFieldSchema.field` is a string that refuses a padded name
|
|
37
|
+
* (objectui#7347 — `grouping-field-padded-refused-7347.test.ts` in
|
|
38
|
+
* `@object-ui/types` pins it on every face declaring `grouping`; the trim below
|
|
39
|
+
* stays as defence for blocks that skipped that parse). Nothing in the schema
|
|
40
|
+
* requires it to name a field the object declares, and the whole premise of
|
|
41
|
+
* this harvest is that it has NOT been through column validation. Some backends
|
|
42
|
+
* answer an unknown `$select` key with an EMPTY RESULT SET rather than ignoring
|
|
43
|
+
* it — the cloud multi-tenant runtime does exactly that — so a single unknown
|
|
44
|
+
* grouping field put in the projection unguarded silently zeroes the whole
|
|
45
|
+
* list. That would convert this card's bug (one `(empty)` group holding every
|
|
46
|
+
* row) into a strictly worse one (no rows at all, still silent).
|
|
47
|
+
*
|
|
48
|
+
* So every caller intersects this result with the object's declared fields —
|
|
49
|
+
* {@link isProjectableField}, or the caller's equivalent known-field set —
|
|
50
|
+
* exactly as {@link collectPredicateFieldRefs}'s callers do, and for the same
|
|
51
|
+
* measured reason. Callers additionally FLS-gate it: a grouping field names a
|
|
52
|
+
* field just as capable of being denied as a column is, and the projection is
|
|
53
|
+
* what goes on the wire (objectui#6898).
|
|
54
|
+
*
|
|
55
|
+
* @param grouping - The view's `grouping` block in any authored state
|
|
56
|
+
* (`undefined`, malformed, or the spec shape). Anything that is not an entry
|
|
57
|
+
* carrying a non-empty string `field` contributes nothing, so a malformed
|
|
58
|
+
* block yields an empty harvest instead of a plausible wrong name.
|
|
59
|
+
* @returns Grouping field names in first-seen order, deduplicated.
|
|
60
|
+
*/
|
|
61
|
+
export declare function collectGroupingFieldRefs(grouping: unknown): string[];
|
|
@@ -0,0 +1,84 @@
|
|
|
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 THIRD half of a view's field appetite (objectui#7179).
|
|
10
|
+
*
|
|
11
|
+
* A list view projects what it DISPLAYS — `$select` is built from `columns`.
|
|
12
|
+
* `predicate-fields.ts` covers the fields a view's PREDICATES read. This covers
|
|
13
|
+
* the fields it GROUPS BY, which is a third, independent source of demand: the
|
|
14
|
+
* spec's `grouping` block is a sibling of `columns`, not a subset of it, so a
|
|
15
|
+
* view may legitimately group by a field it never shows.
|
|
16
|
+
*
|
|
17
|
+
* ## Why it needed a fix rather than a gate
|
|
18
|
+
*
|
|
19
|
+
* `GroupingConfigSchema` accepts `grouping` with no matching column, and the
|
|
20
|
+
* neighbouring view kinds (kanban / gantt / timeline) already union their
|
|
21
|
+
* `groupByField` into the projection with no column required. Grouping by a
|
|
22
|
+
* field you do not want on screen is an ordinary thing to want. Refusing it at
|
|
23
|
+
* author time would make the grid the odd one out and reject working intent.
|
|
24
|
+
*
|
|
25
|
+
* ## The failure this closes
|
|
26
|
+
*
|
|
27
|
+
* With the grouping field absent from `$select` the server never returns it,
|
|
28
|
+
* so `useGroupedData` reads `undefined` on every row and `buildSegmentLabel`
|
|
29
|
+
* answers `(empty)` for all of them: ONE group holding every record, with no
|
|
30
|
+
* error, no warning and no empty state. It reads as "these records have no
|
|
31
|
+
* value for this field" — a plausible, wrong, actionable conclusion about the
|
|
32
|
+
* data rather than a visible bug in the view.
|
|
33
|
+
*
|
|
34
|
+
* ## ⛔ THE RESULT IS CANDIDATES, NOT VERIFIED FIELDS — the caller MUST gate it
|
|
35
|
+
*
|
|
36
|
+
* `GroupingFieldSchema.field` is a string that refuses a padded name
|
|
37
|
+
* (objectui#7347 — `grouping-field-padded-refused-7347.test.ts` in
|
|
38
|
+
* `@object-ui/types` pins it on every face declaring `grouping`; the trim below
|
|
39
|
+
* stays as defence for blocks that skipped that parse). Nothing in the schema
|
|
40
|
+
* requires it to name a field the object declares, and the whole premise of
|
|
41
|
+
* this harvest is that it has NOT been through column validation. Some backends
|
|
42
|
+
* answer an unknown `$select` key with an EMPTY RESULT SET rather than ignoring
|
|
43
|
+
* it — the cloud multi-tenant runtime does exactly that — so a single unknown
|
|
44
|
+
* grouping field put in the projection unguarded silently zeroes the whole
|
|
45
|
+
* list. That would convert this card's bug (one `(empty)` group holding every
|
|
46
|
+
* row) into a strictly worse one (no rows at all, still silent).
|
|
47
|
+
*
|
|
48
|
+
* So every caller intersects this result with the object's declared fields —
|
|
49
|
+
* {@link isProjectableField}, or the caller's equivalent known-field set —
|
|
50
|
+
* exactly as {@link collectPredicateFieldRefs}'s callers do, and for the same
|
|
51
|
+
* measured reason. Callers additionally FLS-gate it: a grouping field names a
|
|
52
|
+
* field just as capable of being denied as a column is, and the projection is
|
|
53
|
+
* what goes on the wire (objectui#6898).
|
|
54
|
+
*
|
|
55
|
+
* @param grouping - The view's `grouping` block in any authored state
|
|
56
|
+
* (`undefined`, malformed, or the spec shape). Anything that is not an entry
|
|
57
|
+
* carrying a non-empty string `field` contributes nothing, so a malformed
|
|
58
|
+
* block yields an empty harvest instead of a plausible wrong name.
|
|
59
|
+
* @returns Grouping field names in first-seen order, deduplicated.
|
|
60
|
+
*/
|
|
61
|
+
export function collectGroupingFieldRefs(grouping) {
|
|
62
|
+
const fields = grouping?.fields;
|
|
63
|
+
if (!Array.isArray(fields))
|
|
64
|
+
return [];
|
|
65
|
+
const out = [];
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
for (const entry of fields) {
|
|
68
|
+
// The spec shape is `{ field, order, collapsed }`. A bare string is NOT
|
|
69
|
+
// accepted here even though it would be a natural shorthand: `grouping` is
|
|
70
|
+
// a `$strict` object schema in `@objectstack/spec`, so a bare string is
|
|
71
|
+
// off-spec metadata, and reading it anyway would be exactly the lenient
|
|
72
|
+
// renderer-side alias AGENTS.md #0.1 forbids — it fossilizes a second
|
|
73
|
+
// de-facto contract instead of having the producer rejected at publish.
|
|
74
|
+
const name = entry?.field;
|
|
75
|
+
if (typeof name !== 'string')
|
|
76
|
+
continue;
|
|
77
|
+
const trimmed = name.trim();
|
|
78
|
+
if (!trimmed || seen.has(trimmed))
|
|
79
|
+
continue;
|
|
80
|
+
seen.add(trimmed);
|
|
81
|
+
out.push(trimmed);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
* What is genuinely objectui's and keeps a local name:
|
|
21
21
|
* {@link resolveEffectiveCrudAffordances} — the spec's object-level matrix
|
|
22
|
-
* INTERSECTED with the server-resolved effective API operation set (#3391), so
|
|
22
|
+
* INTERSECTED with the server-resolved effective API operation set (objectstack#3391), so
|
|
23
23
|
* the UI never offers a button the server would 405. The spec's function has
|
|
24
24
|
* no such notion; naming ours after it was the misdescription this burn-down
|
|
25
25
|
* removes.
|
|
@@ -125,7 +125,7 @@ export declare function userActionPredicates(v: UserActionOverride | undefined |
|
|
|
125
125
|
*
|
|
126
126
|
* @param obj The object schema (managedBy bucket + userActions overrides).
|
|
127
127
|
* @param effectiveApiOperations OPTIONAL server-resolved effective API operation
|
|
128
|
-
* set for this object (from `/me/permissions` `apiOperations`, #3391). When
|
|
128
|
+
* set for this object (from `/me/permissions` `apiOperations`, objectstack#3391). When
|
|
129
129
|
* provided, each affordance bit is ANDed with the corresponding API operation
|
|
130
130
|
* (create/import→create/import, edit→update, delete→delete, exportCsv→export),
|
|
131
131
|
* so the UI never shows a button the server would 405. Passing `undefined`
|
|
@@ -163,7 +163,7 @@ export declare function isSystemWritable(obj: SchemaLike | null | undefined): bo
|
|
|
163
163
|
* object-level editability, so only the resolved boolean matters here.)
|
|
164
164
|
*
|
|
165
165
|
* @param effectiveApiOperations OPTIONAL server-resolved effective API operation
|
|
166
|
-
* set for this object (`/me/permissions` `apiOperations`, #3391
|
|
166
|
+
* set for this object (`/me/permissions` `apiOperations`, objectstack#3391/objectstack#3546). When
|
|
167
167
|
* provided, inline-edit is additionally ANDed with the server allowing
|
|
168
168
|
* `update`, so a record page never offers double-click/pencil editing the
|
|
169
169
|
* server would 405. `undefined` (old backend / unrestricted) leaves the
|
package/dist/utils/managedBy.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
* What is genuinely objectui's and keeps a local name:
|
|
21
21
|
* {@link resolveEffectiveCrudAffordances} — the spec's object-level matrix
|
|
22
|
-
* INTERSECTED with the server-resolved effective API operation set (#3391), so
|
|
22
|
+
* INTERSECTED with the server-resolved effective API operation set (objectstack#3391), so
|
|
23
23
|
* the UI never offers a button the server would 405. The spec's function has
|
|
24
24
|
* no such notion; naming ours after it was the misdescription this burn-down
|
|
25
25
|
* removes.
|
|
@@ -73,7 +73,7 @@ export function userActionPredicates(v) {
|
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
75
|
* The affordance-bit → server API-operation mapping used to intersect the
|
|
76
|
-
* UI-intent affordances with the server's effective API operation set (#3391).
|
|
76
|
+
* UI-intent affordances with the server's effective API operation set (objectstack#3391).
|
|
77
77
|
* This is the objectui end of the framework's `API_METHOD_DERIVATION` contract:
|
|
78
78
|
* the button predicate is `affordance(op) ∧ effective.api(op)`.
|
|
79
79
|
*/
|
|
@@ -97,7 +97,7 @@ const AFFORDANCE_TO_API_OPERATION = {
|
|
|
97
97
|
*
|
|
98
98
|
* @param obj The object schema (managedBy bucket + userActions overrides).
|
|
99
99
|
* @param effectiveApiOperations OPTIONAL server-resolved effective API operation
|
|
100
|
-
* set for this object (from `/me/permissions` `apiOperations`, #3391). When
|
|
100
|
+
* set for this object (from `/me/permissions` `apiOperations`, objectstack#3391). When
|
|
101
101
|
* provided, each affordance bit is ANDed with the corresponding API operation
|
|
102
102
|
* (create/import→create/import, edit→update, delete→delete, exportCsv→export),
|
|
103
103
|
* so the UI never shows a button the server would 405. Passing `undefined`
|
|
@@ -113,7 +113,7 @@ export function resolveEffectiveCrudAffordances(obj, effectiveApiOperations) {
|
|
|
113
113
|
managedBy: obj?.managedBy ?? undefined,
|
|
114
114
|
userActions: (obj?.userActions ?? undefined),
|
|
115
115
|
});
|
|
116
|
-
// [#3391] Intersect with the server's effective API operations when present.
|
|
116
|
+
// [objectstack#3391] Intersect with the server's effective API operations when present.
|
|
117
117
|
// The frontend consumes the effective set the server hands down; it never
|
|
118
118
|
// reads the raw `apiMethods` nor re-derives.
|
|
119
119
|
if (Array.isArray(effectiveApiOperations)) {
|
|
@@ -159,7 +159,7 @@ export function isSystemWritable(obj) {
|
|
|
159
159
|
* object-level editability, so only the resolved boolean matters here.)
|
|
160
160
|
*
|
|
161
161
|
* @param effectiveApiOperations OPTIONAL server-resolved effective API operation
|
|
162
|
-
* set for this object (`/me/permissions` `apiOperations`, #3391
|
|
162
|
+
* set for this object (`/me/permissions` `apiOperations`, objectstack#3391/objectstack#3546). When
|
|
163
163
|
* provided, inline-edit is additionally ANDed with the server allowing
|
|
164
164
|
* `update`, so a record page never offers double-click/pencil editing the
|
|
165
165
|
* server would 405. `undefined` (old backend / unrestricted) leaves the
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
* Stored value → `<input type="date">` value (`YYYY-MM-DD`).
|
|
10
|
+
*
|
|
11
|
+
* A leading `YYYY-MM-DD` is passed through VERBATIM rather than re-parsed: a
|
|
12
|
+
* date-only string is parsed as UTC midnight by `new Date()`, so reading local
|
|
13
|
+
* calendar components back out of it shifts the day by one everywhere west of
|
|
14
|
+
* Greenwich. `2026-06-17` must stay June 17th in every zone.
|
|
15
|
+
*/
|
|
16
|
+
export declare function toDateInputValue(value: unknown): string;
|
|
17
|
+
/**
|
|
18
|
+
* Stored value → `<input type="datetime-local">` value (`YYYY-MM-DDTHH:mm`),
|
|
19
|
+
* expressed in the viewer's local wall clock — the same instant the detail
|
|
20
|
+
* page shows, so opening the editor never appears to move the time.
|
|
21
|
+
*
|
|
22
|
+
* A value that is ALREADY a zone-less local datetime is truncated to minutes
|
|
23
|
+
* in place instead of being round-tripped through `Date`, which would
|
|
24
|
+
* reinterpret it and then re-render it, twice risking an off-by-a-zone shift.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toDateTimeInputValue(value: unknown): string;
|
|
27
|
+
/**
|
|
28
|
+
* `<input type="datetime-local">` value → stored value (ISO-8601, UTC).
|
|
29
|
+
*
|
|
30
|
+
* The control hands us a zone-less local wall clock; `new Date()` reads that
|
|
31
|
+
* form as local time, which is the inverse of what `toDateTimeInputValue`
|
|
32
|
+
* wrote — the pair is what keeps read and write on one basis.
|
|
33
|
+
*
|
|
34
|
+
* An unparseable string is returned untouched rather than blanked: dropping a
|
|
35
|
+
* value the user can see in the box would be a data loss dressed up as a fix.
|
|
36
|
+
*/
|
|
37
|
+
export declare function fromDateTimeInputValue(value: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* True when a stored value is written on a calendar day that does not exist
|
|
40
|
+
* (`2026-02-30`, `2026-02-30T10:00:00Z`, `2026-02-30T10:00+08:00`) — the day
|
|
41
|
+
* read AS WRITTEN from the leading `YYYY-MM-DD`, by the one judgement the read
|
|
42
|
+
* faces use (`isRealCalendarDate`). A parsed `Date` always names a real day,
|
|
43
|
+
* so the check can only be made before any conversion.
|
|
44
|
+
*
|
|
45
|
+
* Anything without a leading `YYYY-MM-DD` (a `Date`, a number, free text) is
|
|
46
|
+
* not this case and answers `false`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isImpossibleStoredDay(value: unknown): boolean;
|