@reventlessdev/reventless-spec 3.0.0-alpha.114 → 3.0.0-alpha.115
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 +11 -0
- package/package.json +1 -1
- package/schema/platform-api.graphql +4 -2
- package/src/components/Plugin.res +52 -13
- package/src/components/Plugin.res.mjs +3 -1
- package/src/components/StateAnnotations.res +74 -7
- package/src/types/OwnerScope.res +105 -0
- package/src/types/OwnerScope.res.mjs +68 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.115 (2026-08-16)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **core:** let [@retired](https://github.com/retired) name a lifecycle state, not only a boolean ([6bb346b](https://github.com/ReventlessDev/reventless-core/commit/6bb346b4f6a5f33826fc24537953482a76067177))
|
|
11
|
+
* **core:** mark the state that retires a row, and allow more than one ([cb1461f](https://github.com/ReventlessDev/reventless-core/commit/cb1461f024d3ca3b53fd9c8b010a054e3fcc4555))
|
|
12
|
+
* **core:** publish queryableDef.retiredField from the [@retired](https://github.com/retired) annotation ([b44436a](https://github.com/ReventlessDev/reventless-core/commit/b44436a997b6c4ff0531f0b07d793cc858eef94a))
|
|
13
|
+
* **spec:** [@retired](https://github.com/retired) state-field annotation and its schema emission ([2d8234b](https://github.com/ReventlessDev/reventless-core/commit/2d8234b6b3dd8f479031a67eb5b4b47b5c0c2ff9))
|
|
14
|
+
* **spec:** classify a caller against a view's retirement flag ([5031ce5](https://github.com/ReventlessDev/reventless-core/commit/5031ce573ce56d2407888c7777274e5580c4fb51))
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
# 3.0.0-alpha.114 (2026-08-15)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @reventlessdev/reventless-spec
|
package/package.json
CHANGED
|
@@ -212,15 +212,17 @@ type Platform_ReadSideDef {
|
|
|
212
212
|
idFieldSource: String
|
|
213
213
|
labelField: String!
|
|
214
214
|
labelFieldSource: String
|
|
215
|
+
lifecycleField: String
|
|
215
216
|
linkedWriteSide: [String!]!
|
|
216
217
|
name: String!
|
|
217
218
|
ownerField: String
|
|
218
219
|
queryField: String!
|
|
219
220
|
requiredAccess: [String!]
|
|
221
|
+
retiredField: String
|
|
222
|
+
retiredValues: [String!]
|
|
220
223
|
schema: String!
|
|
221
224
|
searchableFields: [String!]!
|
|
222
225
|
singleQueryField: String
|
|
223
|
-
statusField: String
|
|
224
226
|
visibility: String
|
|
225
227
|
}
|
|
226
228
|
|
|
@@ -290,7 +292,7 @@ type Query {
|
|
|
290
292
|
Platform_ComponentDefinitions: [Platform_ComponentDefinitionEntry!]!
|
|
291
293
|
Platform_Plugin(id: ID!): Platform_Plugin
|
|
292
294
|
Platform_PluginStructures: [Platform_PluginStructureEntry!]!
|
|
293
|
-
Platform_Plugins(after: String, before: String, filter: Platform_PluginFilter, first: Int, last: Int): Platform_PluginConnection!
|
|
295
|
+
Platform_Plugins(after: String, before: String, filter: Platform_PluginFilter, first: Int, includeRetired: Boolean, last: Int): Platform_PluginConnection!
|
|
294
296
|
Platform_PluginsByIds(ids: [String!]!): [Platform_Plugin!]!
|
|
295
297
|
Platform_UIFragments: [Platform_UIFragmentEntry!]!
|
|
296
298
|
}
|
|
@@ -170,14 +170,15 @@ type commandDef = {
|
|
|
170
170
|
mutationField: string,
|
|
171
171
|
references: array<fieldReference>,
|
|
172
172
|
/**
|
|
173
|
-
|
|
173
|
+
Lifecycle states under which this command is meaningful. `None` means the command
|
|
174
174
|
is always available (back-compat default). `Some([…])` lets AutoUI hide the
|
|
175
|
-
command on rows whose
|
|
176
|
-
for how the row's
|
|
175
|
+
command on rows whose lifecycle field is not in the set — see
|
|
176
|
+
`queryableDef.lifecycleField` for how the row's state is located. `Some([])` is
|
|
177
|
+
the defensive "never show" form.
|
|
177
178
|
*/
|
|
178
179
|
allowedStates: @s.matches(stringArrayOptionSchema) option<array<string>>,
|
|
179
180
|
/**
|
|
180
|
-
The single
|
|
181
|
+
The single lifecycle state this command's handler writes — the command's *to*
|
|
181
182
|
state, sibling of `allowedStates`' *from* set. Source: the
|
|
182
183
|
`@targetState("Shipped")` command-variant annotation. `None` (absent
|
|
183
184
|
annotation) is the back-compat default: AutoUI's board resolver then falls
|
|
@@ -262,17 +263,17 @@ type queryableDef = {
|
|
|
262
263
|
`None` means not stated — defs persisted before this field existed, and
|
|
263
264
|
hand-rolled defs that decline to say. Distinct from `Some("fallback")`, which
|
|
264
265
|
is this side stating that it looked. js_nullable for the same JSON-safety
|
|
265
|
-
reason as `
|
|
266
|
+
reason as `lifecycleField`.
|
|
266
267
|
*/
|
|
267
268
|
labelFieldSource: @s.matches(stringOptionSchema) option<string>,
|
|
268
269
|
/**
|
|
269
|
-
Name of the state field whose value identifies the row's lifecycle
|
|
270
|
-
|
|
271
|
-
menu. Resolution order (codegen): (1) field annotated `@
|
|
272
|
-
literally named `"
|
|
273
|
-
`queryableDef` set this explicitly.
|
|
270
|
+
Name of the state field whose value identifies the row's lifecycle, used by
|
|
271
|
+
AutoUI together with `commandDef.allowedStates` to filter the per-row command
|
|
272
|
+
menu. Resolution order (codegen): (1) field annotated `@lifecycle`; (2) a field
|
|
273
|
+
literally named `"lifecycle"` whose shape is an enum; (3) `None`. Spec authors
|
|
274
|
+
that hand-roll a `queryableDef` set this explicitly.
|
|
274
275
|
*/
|
|
275
|
-
|
|
276
|
+
lifecycleField: @s.matches(stringOptionSchema) option<string>,
|
|
276
277
|
/**
|
|
277
278
|
Name of the state field that ties a row to the principal owning it (`@owner`),
|
|
278
279
|
when the view declares one. Two consequences for a client: reads of this view
|
|
@@ -285,6 +286,44 @@ type queryableDef = {
|
|
|
285
286
|
*/
|
|
286
287
|
ownerField: @s.matches(stringOptionSchema) option<string>,
|
|
287
288
|
/**
|
|
289
|
+
Name of the boolean state field that withdraws a row from ordinary reads
|
|
290
|
+
(`@retired`), when the view declares one. Reads of this view exclude rows whose
|
|
291
|
+
flag is true for callers outside `OwnerScope.elevatedGroups`, on the list door
|
|
292
|
+
and the single-entity door alike; an exempt caller reaches them by asking for
|
|
293
|
+
them.
|
|
294
|
+
|
|
295
|
+
Derived from the annotation and from nothing else — deliberately no fallback to
|
|
296
|
+
a conventionally-named boolean, unlike `lifecycleField`. A field named `archived`
|
|
297
|
+
that nobody annotated must not start hiding rows the day this ships, and the
|
|
298
|
+
cost of guessing wrong here is data disappearing rather than a menu filtering
|
|
299
|
+
oddly.
|
|
300
|
+
|
|
301
|
+
The label the flag reads as is not here. It travels on the state schema, which
|
|
302
|
+
every consumer of this def already holds, and a second copy is a second thing
|
|
303
|
+
to keep in step.
|
|
304
|
+
*/
|
|
305
|
+
retiredField: @s.matches(stringOptionSchema) option<string>,
|
|
306
|
+
/**
|
|
307
|
+
The states a row is retired *in*, when the view declares the state form of
|
|
308
|
+
`@retired` — `Some(["Archived", "Discontinued"])` beside
|
|
309
|
+
`retiredField: Some("shelfStatus")`. `None` is the boolean form, where the
|
|
310
|
+
excluded value is always `true` and naming it would be a field that can only
|
|
311
|
+
hold one thing.
|
|
312
|
+
|
|
313
|
+
A set: a lifecycle may be withdrawn by more than one state, which exclude
|
|
314
|
+
identically and differ only in the way back. Retired iff the field's value is in
|
|
315
|
+
it, and one member is the ordinary case rather than a special one.
|
|
316
|
+
|
|
317
|
+
Published beside the field rather than left for a consumer to re-derive from the
|
|
318
|
+
state schema: a client holding this def holds the whole predicate, and two places
|
|
319
|
+
deriving one comparison is how they come to disagree about it.
|
|
320
|
+
|
|
321
|
+
The state form is also what lets `@allowedStates` answer command applicability
|
|
322
|
+
when retired, with no annotation beyond the two — retirement expressed in the
|
|
323
|
+
vocabulary a command's stance is already written in.
|
|
324
|
+
*/
|
|
325
|
+
retiredValues: @s.matches(stringArrayOptionSchema) option<array<string>>,
|
|
326
|
+
/**
|
|
288
327
|
Component visibility hint (`@@reventless.visibility`). `Some("Internal")` marks a
|
|
289
328
|
ReadModel / StateViewSlice that the deployed AutoUI hides from its menu, drill-down
|
|
290
329
|
pages, web event graph and cross-plugin edges. `None` (absent) means Public. Internal
|
|
@@ -323,7 +362,7 @@ type queryableDef = {
|
|
|
323
362
|
|
|
324
363
|
`None` means not stated — defs persisted before this field existed, and
|
|
325
364
|
hand-rolled defs that decline to say; a consumer falls back to its own
|
|
326
|
-
derivation there. js_nullable for the same JSON-safety reason as `
|
|
365
|
+
derivation there. js_nullable for the same JSON-safety reason as `lifecycleField`.
|
|
327
366
|
*/
|
|
328
367
|
singleQueryField: @s.matches(stringOptionSchema) option<string>,
|
|
329
368
|
/**
|
|
@@ -334,7 +373,7 @@ type queryableDef = {
|
|
|
334
373
|
`None` means unresolved: a state with several `*Id` fields and no name match,
|
|
335
374
|
or with none at all. Such a component gets no key-derived filter or sort until
|
|
336
375
|
its spec declares `@id`. Also `None` on defs persisted before this field
|
|
337
|
-
existed. js_nullable for the same JSON-safety reason as `
|
|
376
|
+
existed. js_nullable for the same JSON-safety reason as `lifecycleField`.
|
|
338
377
|
*/
|
|
339
378
|
idField: @s.matches(stringOptionSchema) option<string>,
|
|
340
379
|
/**
|
|
@@ -118,8 +118,10 @@ let queryableDefSchema = S.schema(s => ({
|
|
|
118
118
|
labelField: s.m(S.string),
|
|
119
119
|
searchableFields: s.m(S.array(S.string)),
|
|
120
120
|
labelFieldSource: s.m(stringOptionSchema),
|
|
121
|
-
|
|
121
|
+
lifecycleField: s.m(stringOptionSchema),
|
|
122
122
|
ownerField: s.m(stringOptionSchema),
|
|
123
|
+
retiredField: s.m(stringOptionSchema),
|
|
124
|
+
retiredValues: s.m(stringArrayOptionSchema),
|
|
123
125
|
visibility: s.m(stringOptionSchema),
|
|
124
126
|
chapter: s.m(stringOptionSchema),
|
|
125
127
|
singleQueryField: s.m(stringOptionSchema),
|
|
@@ -5,7 +5,7 @@ Spec describing the structural annotations declared on the fields of an
|
|
|
5
5
|
`@compositeId`, `@subId`, `@compositeSubId`, `@index`), a visibility
|
|
6
6
|
annotation (`@hidden`, `@summary`), a hierarchical-rendering annotation
|
|
7
7
|
(`@drillTarget`, `@collapsed`), a server-query opt-in annotation
|
|
8
|
-
(`@scan`, `@scanSort`), a UI-list annotation (`@
|
|
8
|
+
(`@scan`, `@scanSort`), a UI-list annotation (`@lifecycle`, `@groupBy`), or a
|
|
9
9
|
type-level live-updates annotation (`@live` on the `state` declaration).
|
|
10
10
|
Downstream consumers (UI, MCP, codegen) read the
|
|
11
11
|
spec to surface field roles in JSON Schema as `x-reventless-*` extension
|
|
@@ -34,6 +34,54 @@ KPI label, or `""` to let the UI derive one from the field name.
|
|
|
34
34
|
*/
|
|
35
35
|
type metricSpec = {aggregate: string, label: string}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
The field declared `@retired`, and how a consumer should title it.
|
|
39
|
+
|
|
40
|
+
`field` names what withdraws the row from ordinary use — a deactivated customer,
|
|
41
|
+
an archived category. `label` is what that state is called ("Archived"), or `""`
|
|
42
|
+
to let a consumer derive one from the field name. `showWhenFalse` asks a consumer
|
|
43
|
+
to surface the flag in its negative state too; the default is false, because a
|
|
44
|
+
caller who is not exempt from the narrowing never receives a retired row and would
|
|
45
|
+
otherwise see the same negative marker on every record they can read.
|
|
46
|
+
|
|
47
|
+
`value` is what decides which of the two forms this is:
|
|
48
|
+
|
|
49
|
+
- `None` — the **boolean** form. The row is retired when the field is `true`. The
|
|
50
|
+
right shape for a record whose retirement genuinely is a flag: a `Products` view
|
|
51
|
+
with an `archived` boolean and no lifecycle should not have to invent a
|
|
52
|
+
two-valued enum.
|
|
53
|
+
- `Some(v)` — the **state** form. The row is retired when the field equals `v`,
|
|
54
|
+
and the field is the record's `@lifecycle` field. One field then carries the
|
|
55
|
+
fact once instead of twice, and `@allowedStates([Deactivated])` on the way-back
|
|
56
|
+
command is enough to make a generated menu offer it there and nowhere else —
|
|
57
|
+
because retirement is finally expressible in the vocabulary that stance is
|
|
58
|
+
already written in.
|
|
59
|
+
|
|
60
|
+
The narrowing itself is not described here. This spec carries what the field
|
|
61
|
+
*means*; who still sees a retired row is one deployment-wide rule
|
|
62
|
+
(`OwnerScope.elevatedGroups`), resolved where the query is answered.
|
|
63
|
+
*/
|
|
64
|
+
type retiredSpec = {
|
|
65
|
+
field: string,
|
|
66
|
+
label: string,
|
|
67
|
+
showWhenFalse: bool,
|
|
68
|
+
/**
|
|
69
|
+
The states that withdraw the row, or `None` for the boolean form.
|
|
70
|
+
|
|
71
|
+
A **set**, because a lifecycle may end in more than one way — a product is
|
|
72
|
+
withdrawn `Archived` or `Discontinued`, by different routes and with different
|
|
73
|
+
ways back — and one field carries all of them. Retired iff the field's value is
|
|
74
|
+
in the set; a single-element set is the ordinary case rather than a special one.
|
|
75
|
+
|
|
76
|
+
The `option` is the **form discriminator** and is load-bearing. `None` is the
|
|
77
|
+
boolean form, where the row is retired when the field is `true`. Flattened to a
|
|
78
|
+
bare array, `[]` would mean both "boolean form" and "state form naming no
|
|
79
|
+
states", and every consumer's boolean-form handling — the column suppression
|
|
80
|
+
most visibly — would stop firing on a value that looks merely empty.
|
|
81
|
+
*/
|
|
82
|
+
values: option<array<string>>,
|
|
83
|
+
}
|
|
84
|
+
|
|
37
85
|
type stateAnnotationSpec = {
|
|
38
86
|
ids: array<string>,
|
|
39
87
|
compositeIds: array<string>,
|
|
@@ -64,13 +112,16 @@ type stateAnnotationSpec = {
|
|
|
64
112
|
*/
|
|
65
113
|
metric: array<(string, metricSpec)>,
|
|
66
114
|
/**
|
|
67
|
-
Field annotated `@
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
115
|
+
Field annotated `@lifecycle` on the state record (PPX-emitted) — the enum a
|
|
116
|
+
command's `@allowedStates` is written in terms of, a board draws its columns
|
|
117
|
+
from and a state diagram renders. `Some(name)` when one such annotation
|
|
118
|
+
exists; the PPX errors on duplicate `@lifecycle` annotations within the same
|
|
119
|
+
record. Codegen consumes this to populate `queryableDef.lifecycleField`, and
|
|
120
|
+
falls back to a field literally named `"lifecycle"` whose shape is an enum
|
|
121
|
+
when this annotation is absent — so a record whose field can honestly be
|
|
122
|
+
called `lifecycle` declares one without ceremony.
|
|
72
123
|
*/
|
|
73
|
-
|
|
124
|
+
lifecycle: option<string>,
|
|
74
125
|
/**
|
|
75
126
|
Field annotated `@groupBy` on the state record (PPX-emitted). `Some(name)`
|
|
76
127
|
when one such annotation exists; the PPX errors on duplicate `@groupBy`
|
|
@@ -96,6 +147,22 @@ type stateAnnotationSpec = {
|
|
|
96
147
|
control is offered (`true`) or hidden (`false`) for the view.
|
|
97
148
|
*/
|
|
98
149
|
live: option<bool>,
|
|
150
|
+
/**
|
|
151
|
+
Field annotated `@retired` on the state record (PPX-emitted). `Some(spec)` when
|
|
152
|
+
one such annotation exists; the PPX errors on duplicates and on a non-boolean
|
|
153
|
+
field. `SuryToJsonSchema.deriveObjectSchema` emits `x-reventless-retired:
|
|
154
|
+
{label?, showWhenFalse}` on the named field.
|
|
155
|
+
|
|
156
|
+
`option<retiredSpec>` rather than an array: at most one per record. Two
|
|
157
|
+
retirement flags are not a stricter rule but an unanswered one — the read
|
|
158
|
+
predicate would have to guess whether they conjoin or disjoin, and the query
|
|
159
|
+
layer narrows on a single field.
|
|
160
|
+
|
|
161
|
+
Both the boolean and the state form ride this one key. `x-reventless-retired`
|
|
162
|
+
carries `value` only in the state form, on the same omit-rather-than-write-empty
|
|
163
|
+
rule `label` already follows.
|
|
164
|
+
*/
|
|
165
|
+
retired: option<retiredSpec>,
|
|
99
166
|
}
|
|
100
167
|
|
|
101
168
|
/** Sury metadata ID used to attach a `stateAnnotationSpec` to a state schema. */
|
package/src/types/OwnerScope.res
CHANGED
|
@@ -225,3 +225,108 @@ let scopeOf = (decision: decision): option<(string, string)> =>
|
|
|
225
225
|
| ScopeTo(field, required) => Some((field, required))
|
|
226
226
|
| Unscoped | RefuseOwned => None
|
|
227
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
What `@retired` does to one read of one view.
|
|
231
|
+
|
|
232
|
+
Deliberately in this module rather than beside the annotation. Owner scoping and
|
|
233
|
+
retirement narrowing are two rules over the same question — which rows does this
|
|
234
|
+
caller get — and they resolve the caller with the same `resolve`, against the
|
|
235
|
+
same deployment-wide `elevatedGroups`. Written apart, they could disagree about
|
|
236
|
+
who an operator is, and the disagreement would be invisible until a caller
|
|
237
|
+
elevated for one rule turned out to be scoped by the other.
|
|
238
|
+
|
|
239
|
+
`ExcludeRetired` carries the whole predicate — the field and, for the state form
|
|
240
|
+
of `@retired`, every state that retires the row. `values: None` is the boolean
|
|
241
|
+
form, where the excluded value is always `true`; naming it there would be a
|
|
242
|
+
parameter that can only hold one thing, and a parameter a call site can pass
|
|
243
|
+
wrongly.
|
|
244
|
+
|
|
245
|
+
Both halves travel together for the reason the pair exists at all: a field
|
|
246
|
+
without its values is half a comparison, and an adapter left to fetch the other
|
|
247
|
+
half from the schema is an adapter that can disagree with the one next to it
|
|
248
|
+
about which rows a caller may see.
|
|
249
|
+
*/
|
|
250
|
+
type retiredScope = {
|
|
251
|
+
field: string,
|
|
252
|
+
values: option<array<string>>,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
type retiredDecision =
|
|
256
|
+
| RetiredVisible
|
|
257
|
+
| ExcludeRetired(retiredScope)
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
Combine a view's declared retirement flag with the caller behind the request.
|
|
261
|
+
|
|
262
|
+
`~asked` is the caller's `includeRetired` request, honoured only where the
|
|
263
|
+
caller was going to see those rows anyway. Reading it before classifying would
|
|
264
|
+
make the argument the decision rather than a request.
|
|
265
|
+
|
|
266
|
+
`Unidentified` excludes rather than refuses, which is where this parts company
|
|
267
|
+
with `decide`. An owner-scoped read of an unidentified caller has no value to
|
|
268
|
+
match, so scoping it would be indistinguishable from an empty view and a refusal
|
|
269
|
+
says more. Retirement has no such value — the predicate is the same for every
|
|
270
|
+
non-exempt caller — so the fail-closed action is simply the narrow read.
|
|
271
|
+
*/
|
|
272
|
+
let decideRetired = (
|
|
273
|
+
identity: Identity.t,
|
|
274
|
+
~retiredField: option<string>,
|
|
275
|
+
~retiredValues: option<array<string>>=?,
|
|
276
|
+
~asked: bool=false,
|
|
277
|
+
~elevated: array<string>=elevatedGroups(),
|
|
278
|
+
): retiredDecision =>
|
|
279
|
+
switch retiredField {
|
|
280
|
+
| None => RetiredVisible
|
|
281
|
+
| Some(field) =>
|
|
282
|
+
let scope = {field, values: retiredValues}
|
|
283
|
+
switch resolve(identity, ~elevated) {
|
|
284
|
+
| System | Elevated(_) => asked ? RetiredVisible : ExcludeRetired(scope)
|
|
285
|
+
| Owned(_) | Unidentified(_) => ExcludeRetired(scope)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
The field a read excludes on, or `None` when it excludes nothing.
|
|
291
|
+
|
|
292
|
+
Note that an exempt caller who did not ask still gets `Some`: retired rows are
|
|
293
|
+
withheld from everyone by default, and elevation buys the ability to ask for
|
|
294
|
+
them rather than a standing exemption. An archive that is always underfoot is
|
|
295
|
+
not an archive.
|
|
296
|
+
*/
|
|
297
|
+
let retiredScopeOf = (decision: retiredDecision): option<retiredScope> =>
|
|
298
|
+
switch decision {
|
|
299
|
+
| ExcludeRetired(scope) => Some(scope)
|
|
300
|
+
| RetiredVisible => None
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
Whether a row's value in the retirement field is a retiring one.
|
|
305
|
+
|
|
306
|
+
The single place the two forms differ, so every reader asks one question rather
|
|
307
|
+
than branching on the form itself. The state form is a membership test: a
|
|
308
|
+
lifecycle may be withdrawn by several states, and they exclude identically —
|
|
309
|
+
what differs between them is the way back, which commands express and this does
|
|
310
|
+
not need to know.
|
|
311
|
+
|
|
312
|
+
`None` — the field absent from the row — answers false in both, which is the
|
|
313
|
+
mirror image of the owner rule and lands the opposite way for the same reason: an
|
|
314
|
+
owner-scoped read excludes a row that states no owner, because such a row belongs
|
|
315
|
+
to nobody in particular, while a retirement read KEEPS a row that states no flag,
|
|
316
|
+
because absent means not retired. That is what a row written before the
|
|
317
|
+
annotation existed is, and excluding those would empty the view the day the
|
|
318
|
+
annotation lands.
|
|
319
|
+
|
|
320
|
+
A state form naming no states excludes nothing, which is the same reading one
|
|
321
|
+
step further: no state has been declared to withdraw a row, so no row is.
|
|
322
|
+
*/
|
|
323
|
+
let isRetiredValue = (scope: retiredScope, cell: option<JSON.t>): bool =>
|
|
324
|
+
switch (scope.values, cell) {
|
|
325
|
+
| (_, None) => false
|
|
326
|
+
| (None, Some(v)) => v->JSON.Decode.bool->Option.getOr(false)
|
|
327
|
+
| (Some(states), Some(v)) =>
|
|
328
|
+
switch v->JSON.Decode.string {
|
|
329
|
+
| None => false
|
|
330
|
+
| Some(s) => states->Array.includes(s)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
3
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
5
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
5
6
|
import * as Identity$Reventless from "./Identity.res.mjs";
|
|
@@ -155,6 +156,70 @@ function scopeOf(decision) {
|
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
function decideRetired(identity, retiredField, retiredValues, askedOpt, elevatedOpt) {
|
|
160
|
+
let asked = askedOpt !== undefined ? askedOpt : false;
|
|
161
|
+
let elevated = elevatedOpt !== undefined ? elevatedOpt : elevatedGroups();
|
|
162
|
+
if (retiredField === undefined) {
|
|
163
|
+
return "RetiredVisible";
|
|
164
|
+
}
|
|
165
|
+
let scope = {
|
|
166
|
+
field: retiredField,
|
|
167
|
+
values: retiredValues
|
|
168
|
+
};
|
|
169
|
+
let match = resolve(identity, elevated);
|
|
170
|
+
if (typeof match !== "object") {
|
|
171
|
+
if (asked) {
|
|
172
|
+
return "RetiredVisible";
|
|
173
|
+
} else {
|
|
174
|
+
return {
|
|
175
|
+
TAG: "ExcludeRetired",
|
|
176
|
+
_0: scope
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
switch (match.TAG) {
|
|
181
|
+
case "Elevated" :
|
|
182
|
+
if (asked) {
|
|
183
|
+
return "RetiredVisible";
|
|
184
|
+
} else {
|
|
185
|
+
return {
|
|
186
|
+
TAG: "ExcludeRetired",
|
|
187
|
+
_0: scope
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
case "Owned" :
|
|
191
|
+
case "Unidentified" :
|
|
192
|
+
return {
|
|
193
|
+
TAG: "ExcludeRetired",
|
|
194
|
+
_0: scope
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function retiredScopeOf(decision) {
|
|
200
|
+
if (typeof decision !== "object") {
|
|
201
|
+
return;
|
|
202
|
+
} else {
|
|
203
|
+
return decision._0;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isRetiredValue(scope, cell) {
|
|
208
|
+
let match = scope.values;
|
|
209
|
+
if (cell === undefined) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
if (match === undefined) {
|
|
213
|
+
return Stdlib_Option.getOr(Stdlib_JSON.Decode.bool(cell), false);
|
|
214
|
+
}
|
|
215
|
+
let s = Stdlib_JSON.Decode.string(cell);
|
|
216
|
+
if (s !== undefined) {
|
|
217
|
+
return match.includes(s);
|
|
218
|
+
} else {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
158
223
|
export {
|
|
159
224
|
isJsString,
|
|
160
225
|
systemProviders,
|
|
@@ -169,5 +234,8 @@ export {
|
|
|
169
234
|
isExempt,
|
|
170
235
|
decide,
|
|
171
236
|
scopeOf,
|
|
237
|
+
decideRetired,
|
|
238
|
+
retiredScopeOf,
|
|
239
|
+
isRetiredValue,
|
|
172
240
|
}
|
|
173
241
|
/* Identity-Reventless Not a pure module */
|