@nmnmcc/ability 2.1.2 → 2.1.4
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/dist/Authorizer.d.ts +11 -11
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +1 -1
- package/dist/HttpApi.js +1 -1
- package/dist/Policy.d.ts +16 -6
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js.map +1 -1
- package/dist/internal/algebra.js +8 -5
- package/dist/internal/algebra.js.map +1 -1
- package/dist/internal/authorizer.d.ts.map +1 -1
- package/dist/internal/authorizer.js +35 -9
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +37 -27
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/catalog.d.ts +0 -31
- package/dist/internal/catalog.d.ts.map +1 -1
- package/dist/internal/catalog.js +39 -17
- package/dist/internal/catalog.js.map +1 -1
- package/dist/internal/decide.js +1 -1
- package/dist/internal/decide.js.map +1 -1
- package/dist/internal/domain.js +8 -5
- package/dist/internal/domain.js.map +1 -1
- package/dist/internal/patterns.js +1 -1
- package/dist/internal/policy-json.js +2 -2
- package/dist/internal/policy-json.js.map +1 -1
- package/dist/internal/policy-nodes.js +21 -12
- package/dist/internal/policy-nodes.js.map +1 -1
- package/dist/internal/policy.js +1 -1
- package/dist/internal/search.js +39 -27
- package/dist/internal/search.js.map +1 -1
- package/package.json +1 -1
- package/src/Authorizer.ts +10 -10
- package/src/Policy.ts +16 -5
- package/src/internal/algebra.ts +12 -8
- package/src/internal/authorizer.ts +77 -34
- package/src/internal/bind.ts +43 -36
- package/src/internal/catalog.ts +69 -40
- package/src/internal/decide.ts +5 -1
- package/src/internal/domain.ts +6 -11
- package/src/internal/policy-json.ts +24 -8
- package/src/internal/policy-nodes.ts +19 -13
- package/src/internal/search.ts +31 -21
package/src/internal/catalog.ts
CHANGED
|
@@ -175,12 +175,6 @@ export interface Branch {
|
|
|
175
175
|
|
|
176
176
|
/** The catalog of the fields of a Schema. */
|
|
177
177
|
export interface Catalog {
|
|
178
|
-
/**
|
|
179
|
-
* The fields that a list shows, a field before the fields below it. For a
|
|
180
|
-
* recursive Schema, the list goes into each recursive place one time on
|
|
181
|
-
* each path, so it is finite.
|
|
182
|
-
*/
|
|
183
|
-
readonly fields: ReadonlyArray<Field>
|
|
184
178
|
/** The places, by id. The root is the place with the id 0. */
|
|
185
179
|
readonly branches: ReadonlyArray<Branch>
|
|
186
180
|
/**
|
|
@@ -272,13 +266,30 @@ const structOf = (ast: SchemaAST.AST): SchemaAST.Objects | undefined =>
|
|
|
272
266
|
const hasCheck = (
|
|
273
267
|
checks: ReadonlyArray<SchemaAST.Check<never>>,
|
|
274
268
|
id: string,
|
|
275
|
-
): boolean =>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
269
|
+
): boolean => hasCheckIn({ checks, id })
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* The walk of {@link hasCheck}. A group of checks may hold a group, very many
|
|
273
|
+
* levels down, as `.and(...)` many times makes, so it is a walk of the
|
|
274
|
+
* trampoline for an input of an unknown depth.
|
|
275
|
+
*/
|
|
276
|
+
const hasCheckIn = Trampoline.adaptive<
|
|
277
|
+
{
|
|
278
|
+
readonly checks: ReadonlyArray<SchemaAST.Check<never>>
|
|
279
|
+
readonly id: string
|
|
280
|
+
},
|
|
281
|
+
boolean
|
|
282
|
+
>(
|
|
283
|
+
(R, child) =>
|
|
284
|
+
({ checks, id }, depth) =>
|
|
285
|
+
R.some(checks, (check) =>
|
|
286
|
+
check.annotations?.representation?.id === id
|
|
287
|
+
? R.succeed(true)
|
|
288
|
+
: check._tag === "FilterGroup"
|
|
289
|
+
? child({ checks: check.checks, id }, depth + 1)
|
|
290
|
+
: R.succeed(false),
|
|
291
|
+
),
|
|
292
|
+
)
|
|
282
293
|
|
|
283
294
|
/** A struct with named fields, or with the keys of a record. */
|
|
284
295
|
const isStruct = (
|
|
@@ -287,6 +298,13 @@ const isStruct = (
|
|
|
287
298
|
ast !== undefined &&
|
|
288
299
|
(ast.propertySignatures.length > 0 || ast.indexSignatures.length > 0)
|
|
289
300
|
|
|
301
|
+
/** Tests if a Schema holds only values that are not objects, as `bigint`. */
|
|
302
|
+
const isPrimitive = (ast: SchemaAST.AST): boolean =>
|
|
303
|
+
ast._tag === "BigInt" ||
|
|
304
|
+
ast._tag === "Symbol" ||
|
|
305
|
+
ast._tag === "UniqueSymbol" ||
|
|
306
|
+
(ast._tag === "Literal" && typeof ast.literal === "bigint")
|
|
307
|
+
|
|
290
308
|
/** The kinds of the values of a Schema that is not a struct. */
|
|
291
309
|
const kindsOf = (ast: SchemaAST.AST): Kinds => {
|
|
292
310
|
switch (ast._tag) {
|
|
@@ -464,6 +482,15 @@ const placeOf = (place: Place): Made => {
|
|
|
464
482
|
const valueKinds = Arr.reduce(values, noKinds, (kinds, schema) =>
|
|
465
483
|
joinKinds(kinds, kindsOf(structOf(schema) ?? schema)),
|
|
466
484
|
)
|
|
485
|
+
// A value of a branch may be an object, with parts that the Schema does
|
|
486
|
+
// not name. A `bigint` or a `symbol` is not a scalar that a test takes,
|
|
487
|
+
// so its kinds say `object`, but it has no parts: a read below it gives
|
|
488
|
+
// `null`, as below a string.
|
|
489
|
+
const objects = Arr.some(
|
|
490
|
+
values,
|
|
491
|
+
(schema) =>
|
|
492
|
+
!isPrimitive(schema) && kindsOf(structOf(schema) ?? schema).object,
|
|
493
|
+
)
|
|
467
494
|
const kinds = place.top
|
|
468
495
|
? anyKinds
|
|
469
496
|
: joinKinds(
|
|
@@ -485,8 +512,7 @@ const placeOf = (place: Place): Made => {
|
|
|
485
512
|
// decision may get a value made by hand. A value that may be any
|
|
486
513
|
// object, as one of `Schema.Unknown` may, may have the name with any
|
|
487
514
|
// value too.
|
|
488
|
-
const top =
|
|
489
|
-
place.top || valueKinds.object || properties.length < structs.length
|
|
515
|
+
const top = place.top || objects || properties.length < structs.length
|
|
490
516
|
const below: Place = {
|
|
491
517
|
schemas: Arr.flatMap(properties, (property) =>
|
|
492
518
|
alternatives(property.type, new Set()),
|
|
@@ -505,8 +531,7 @@ const placeOf = (place: Place): Made => {
|
|
|
505
531
|
return [name, below] as const
|
|
506
532
|
})
|
|
507
533
|
const signatures = Arr.flatMap(records, (struct) => struct.indexSignatures)
|
|
508
|
-
const keyTop =
|
|
509
|
-
place.top || valueKinds.object || records.length < structs.length
|
|
534
|
+
const keyTop = place.top || objects || records.length < structs.length
|
|
510
535
|
return {
|
|
511
536
|
branch: {
|
|
512
537
|
kinds,
|
|
@@ -514,11 +539,7 @@ const placeOf = (place: Place): Made => {
|
|
|
514
539
|
structs: structs.length > 0,
|
|
515
540
|
// A field that is a scalar in one branch of a union and a struct in
|
|
516
541
|
// another holds parts too.
|
|
517
|
-
open:
|
|
518
|
-
records.length > 0 ||
|
|
519
|
-
structs.length > 0 ||
|
|
520
|
-
valueKinds.object ||
|
|
521
|
-
place.top,
|
|
542
|
+
open: records.length > 0 || structs.length > 0 || objects || place.top,
|
|
522
543
|
plain: place.plain,
|
|
523
544
|
key:
|
|
524
545
|
signatures.length === 0
|
|
@@ -634,13 +655,18 @@ const fieldAt = (
|
|
|
634
655
|
* The fields that a list shows, a field before the fields below it, down to
|
|
635
656
|
* `parts` parts. A path goes into a place that is on it already one time
|
|
636
657
|
* only, so the list of a recursive Schema is finite. The walk keeps a stack
|
|
637
|
-
* of the places to visit, so a deep Schema does not fill the stack.
|
|
638
|
-
*
|
|
658
|
+
* of the places to visit, so a deep Schema does not fill the stack. A Schema
|
|
659
|
+
* that shares one part in many places may have very many fields, so the
|
|
660
|
+
* walk counts the fields that it makes, and it stops at the first one past
|
|
661
|
+
* `most`. The stack and the count belong to this walk.
|
|
639
662
|
*/
|
|
640
663
|
const listOf = (
|
|
641
664
|
branches: ReadonlyArray<Branch>,
|
|
642
665
|
parts: number,
|
|
666
|
+
most: number,
|
|
667
|
+
thing: string,
|
|
643
668
|
): ReadonlyArray<Field> => {
|
|
669
|
+
const count = { fields: 0 }
|
|
644
670
|
interface Visit {
|
|
645
671
|
readonly branch: Branch
|
|
646
672
|
readonly path: ReadonlyArray<Segment>
|
|
@@ -691,13 +717,14 @@ const listOf = (
|
|
|
691
717
|
visit.path.length === 0
|
|
692
718
|
? []
|
|
693
719
|
: [fieldAt(visit.branch, visit.path, parts)]
|
|
694
|
-
|
|
720
|
+
count.fields += field.length
|
|
721
|
+
if (count.fields > most) {
|
|
695
722
|
throw Limits.exceeded(
|
|
696
|
-
|
|
723
|
+
thing,
|
|
697
724
|
"catalogFields",
|
|
698
725
|
"fields",
|
|
699
|
-
|
|
700
|
-
|
|
726
|
+
count.fields,
|
|
727
|
+
most,
|
|
701
728
|
)
|
|
702
729
|
}
|
|
703
730
|
return Option.some([field, rest] as const)
|
|
@@ -706,22 +733,25 @@ const listOf = (
|
|
|
706
733
|
}
|
|
707
734
|
|
|
708
735
|
/**
|
|
709
|
-
* The fields that a list shows
|
|
710
|
-
*
|
|
736
|
+
* The fields that a list shows, a field before the fields below it, for
|
|
737
|
+
* paths of at most `parts` parts. For a recursive Schema, the list goes into
|
|
738
|
+
* each recursive place one time on each path, so it is finite. A list with
|
|
739
|
+
* more than `most` fields is an error, as for a domain with these limits.
|
|
740
|
+
* Each call makes the list again: a domain checks its size one time, and a
|
|
741
|
+
* list of permitted fields reads it.
|
|
711
742
|
*/
|
|
712
743
|
export const fieldsOf = (
|
|
713
744
|
catalog: Catalog,
|
|
714
|
-
parts: number,
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
: listOf(catalog.branches, parts)
|
|
745
|
+
parts: number = Limits.fieldPathParts,
|
|
746
|
+
most: number = Limits.ceilings.catalogFields,
|
|
747
|
+
thing: string = "A Schema",
|
|
748
|
+
): ReadonlyArray<Field> => listOf(catalog.branches, parts, most, thing)
|
|
719
749
|
|
|
720
750
|
/**
|
|
721
|
-
* Makes the catalog of a Schema
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
751
|
+
* Makes the catalog of a Schema. It throws when a field name has a dot or is
|
|
752
|
+
* empty, and when the Schema has more places than the ceiling of
|
|
753
|
+
* `catalogFields`. A domain checks the size of the list of the fields with
|
|
754
|
+
* its own limit: see {@link fieldsOf}.
|
|
725
755
|
*/
|
|
726
756
|
export const catalogOf = (ast: SchemaAST.AST): Catalog => {
|
|
727
757
|
const branches = branchesOf({
|
|
@@ -731,7 +761,6 @@ export const catalogOf = (ast: SchemaAST.AST): Catalog => {
|
|
|
731
761
|
plain: true,
|
|
732
762
|
})
|
|
733
763
|
return {
|
|
734
|
-
fields: listOf(branches, Limits.fieldPathParts),
|
|
735
764
|
branches,
|
|
736
765
|
edges: Arr.reduce(
|
|
737
766
|
branches,
|
package/src/internal/decide.ts
CHANGED
|
@@ -390,7 +390,11 @@ export const permittedFields = (
|
|
|
390
390
|
const listed = Arr.filter(
|
|
391
391
|
// The list for the limit of the parts of a path of the domain, as the
|
|
392
392
|
// decision of each field reads it.
|
|
393
|
-
Catalog.fieldsOf(
|
|
393
|
+
Catalog.fieldsOf(
|
|
394
|
+
descriptor.catalog,
|
|
395
|
+
policy.domain.limits.fieldPathParts,
|
|
396
|
+
policy.domain.limits.catalogFields,
|
|
397
|
+
),
|
|
394
398
|
(field) =>
|
|
395
399
|
field.listed &&
|
|
396
400
|
Arr.every(field.path, (segment) => typeof segment === "string"),
|
package/src/internal/domain.ts
CHANGED
|
@@ -21,22 +21,17 @@ class DomainData extends Data.Class<{
|
|
|
21
21
|
readonly limits: Limits.Limits
|
|
22
22
|
}> {}
|
|
23
23
|
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Stops a catalog with more fields in its list than the domain takes. The
|
|
26
|
+
* list stops at the first field past the limit, so a Schema with very many
|
|
27
|
+
* fields does not take very much memory first.
|
|
28
|
+
*/
|
|
25
29
|
const checkCatalog = (
|
|
26
30
|
catalog: Catalog.Catalog,
|
|
27
31
|
limits: Limits.Limits,
|
|
28
32
|
name: string,
|
|
29
33
|
): void => {
|
|
30
|
-
|
|
31
|
-
if (fields > limits.catalogFields) {
|
|
32
|
-
throw Limits.exceeded(
|
|
33
|
-
name,
|
|
34
|
-
"catalogFields",
|
|
35
|
-
"fields",
|
|
36
|
-
fields,
|
|
37
|
-
limits.catalogFields,
|
|
38
|
-
)
|
|
39
|
-
}
|
|
34
|
+
Catalog.fieldsOf(catalog, limits.fieldPathParts, limits.catalogFields, name)
|
|
40
35
|
}
|
|
41
36
|
|
|
42
37
|
/**
|
|
@@ -287,14 +287,30 @@ const decodeMeta = (
|
|
|
287
287
|
(found) =>
|
|
288
288
|
found === undefined
|
|
289
289
|
? Effect.void
|
|
290
|
-
:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
290
|
+
: Schema.decodeUnknownEffect(metaCodec(found.schema))(found.value).pipe(
|
|
291
|
+
Effect.mapError(
|
|
292
|
+
(error) =>
|
|
293
|
+
new Json.Problem({
|
|
294
|
+
path: Place.pathOf(at),
|
|
295
|
+
message: error.message,
|
|
296
|
+
issue: error.issue,
|
|
297
|
+
}),
|
|
298
|
+
),
|
|
299
|
+
// The decoder of a Schema in Effect calls itself for each level of
|
|
300
|
+
// the value, so a deep value that the budget takes, as 5,000 levels
|
|
301
|
+
// of a recursive `meta` Schema, fills its stack. That is a defect
|
|
302
|
+
// of the decoder, not of the policy, so it is a problem of the
|
|
303
|
+
// input here, and the policy does not load.
|
|
304
|
+
Effect.catchDefect((defect) =>
|
|
305
|
+
defect instanceof RangeError
|
|
306
|
+
? Effect.fail(
|
|
307
|
+
Json.problemAt(
|
|
308
|
+
at,
|
|
309
|
+
"The meta of the rule has too many levels for the Schema of meta",
|
|
310
|
+
),
|
|
311
|
+
)
|
|
312
|
+
: Effect.die(defect),
|
|
313
|
+
),
|
|
298
314
|
),
|
|
299
315
|
)
|
|
300
316
|
}
|
|
@@ -196,29 +196,35 @@ export const isNode = (u: unknown): u is Node => u instanceof Base
|
|
|
196
196
|
* into the condition of each rule takes the rules from this list first, so it
|
|
197
197
|
* does not go down the levels of the tree and of a condition at once.
|
|
198
198
|
*/
|
|
199
|
-
export const rulesOf = (root: Node): ReadonlyArray<Rule.Rule> =>
|
|
199
|
+
export const rulesOf = (root: Node): ReadonlyArray<Rule.Rule> => {
|
|
200
|
+
// The rules go into one list, in order. A walk that joined the lists of
|
|
201
|
+
// the children at each node would copy the list of a deep tree at each
|
|
202
|
+
// level. The list belongs to this walk.
|
|
203
|
+
const rules: Array<Rule.Rule> = []
|
|
200
204
|
// The walk takes the steps of a trampoline, so a deep tree does not fill
|
|
201
205
|
// the stack.
|
|
202
206
|
Trampoline.run(root.depth, (R) => {
|
|
203
|
-
const
|
|
207
|
+
const children = (nodes: ReadonlyArray<Node>) =>
|
|
208
|
+
R.map(
|
|
209
|
+
R.forEach(nodes, (child) => R.suspend(() => go(child))),
|
|
210
|
+
() => undefined,
|
|
211
|
+
)
|
|
212
|
+
const go = (node: Node): Trampoline.Step<undefined> => {
|
|
204
213
|
switch (node._tag) {
|
|
205
214
|
case "Rules":
|
|
206
|
-
|
|
215
|
+
Arr.forEach(node.rules, (rule) => {
|
|
216
|
+
rules.push(rule)
|
|
217
|
+
})
|
|
218
|
+
return R.succeed(undefined)
|
|
207
219
|
case "All":
|
|
208
220
|
case "Any":
|
|
209
221
|
case "FirstOf":
|
|
210
|
-
return
|
|
211
|
-
R.forEach(node.policies, (child) => R.suspend(() => go(child))),
|
|
212
|
-
Arr.flatten,
|
|
213
|
-
)
|
|
222
|
+
return children(node.policies)
|
|
214
223
|
case "Ceiling":
|
|
215
|
-
return
|
|
216
|
-
R.forEach([node.cap, node.policy], (child) =>
|
|
217
|
-
R.suspend(() => go(child)),
|
|
218
|
-
),
|
|
219
|
-
Arr.flatten,
|
|
220
|
-
)
|
|
224
|
+
return children([node.cap, node.policy])
|
|
221
225
|
}
|
|
222
226
|
}
|
|
223
227
|
return go(root)
|
|
224
228
|
})
|
|
229
|
+
return rules
|
|
230
|
+
}
|
package/src/internal/search.ts
CHANGED
|
@@ -317,28 +317,35 @@ const fix = (root: Node, field: string, value: unknown, search: Search): Node =>
|
|
|
317
317
|
})
|
|
318
318
|
|
|
319
319
|
/** The fields of the tests of a condition, once for each test. */
|
|
320
|
-
const testedFields = (root: Node): ReadonlyArray<string> =>
|
|
320
|
+
const testedFields = (root: Node): ReadonlyArray<string> => {
|
|
321
|
+
// The fields go into one list, in order. A walk that joined the lists of
|
|
322
|
+
// the children would copy the list of a deep condition at each level. The
|
|
323
|
+
// list belongs to this walk.
|
|
324
|
+
const fields: Array<string> = []
|
|
321
325
|
Trampoline.run(root.depth, (R) => {
|
|
322
|
-
const go = (node: Node): Trampoline.Step<
|
|
326
|
+
const go = (node: Node): Trampoline.Step<undefined> => {
|
|
323
327
|
switch (node._tag) {
|
|
324
328
|
case "Compare":
|
|
325
329
|
case "In":
|
|
326
330
|
case "Exists":
|
|
327
|
-
|
|
331
|
+
fields.push(node.field)
|
|
332
|
+
return R.succeed(undefined)
|
|
328
333
|
case "And":
|
|
329
334
|
case "Or":
|
|
330
335
|
return R.map(
|
|
331
336
|
R.forEach(node.conditions, (child) => R.suspend(() => go(child))),
|
|
332
|
-
|
|
337
|
+
() => undefined,
|
|
333
338
|
)
|
|
334
339
|
case "Not":
|
|
335
340
|
return R.suspend(() => go(node.condition))
|
|
336
341
|
default:
|
|
337
|
-
return R.succeed(
|
|
342
|
+
return R.succeed(undefined)
|
|
338
343
|
}
|
|
339
344
|
}
|
|
340
345
|
return go(root)
|
|
341
346
|
})
|
|
347
|
+
return fields
|
|
348
|
+
}
|
|
342
349
|
|
|
343
350
|
/**
|
|
344
351
|
* Picks the next field to split on. A field comes before the fields below it,
|
|
@@ -486,20 +493,29 @@ const exampleOf = (
|
|
|
486
493
|
* belongs to one search.
|
|
487
494
|
*/
|
|
488
495
|
const constantsOf = (node: Node): Map<string, Set<Nodes.Scalar>> => {
|
|
489
|
-
|
|
496
|
+
// The constants go into one map as the walk finds them. A walk that
|
|
497
|
+
// joined the lists of the children would copy the list of a deep
|
|
498
|
+
// condition at each level. The map belongs to one search.
|
|
499
|
+
const all = new Map<string, Set<Nodes.Scalar>>()
|
|
500
|
+
const add = (field: string, values: ReadonlyArray<Nodes.Scalar>): void => {
|
|
501
|
+
const known = all.get(field) ?? new Set<Nodes.Scalar>()
|
|
502
|
+
Arr.forEach(values, (value) => known.add(value))
|
|
503
|
+
all.set(field, known)
|
|
504
|
+
}
|
|
490
505
|
// The walk takes the steps of a trampoline, so a deep condition does not
|
|
491
506
|
// fill the stack.
|
|
492
|
-
|
|
493
|
-
const go = (current: Node): Trampoline.Step<
|
|
507
|
+
Trampoline.run(node.depth, (R) => {
|
|
508
|
+
const go = (current: Node): Trampoline.Step<undefined> => {
|
|
494
509
|
switch (current._tag) {
|
|
495
510
|
case "Compare":
|
|
496
|
-
|
|
511
|
+
add(current.field, [literalOf(current.value)])
|
|
512
|
+
return R.succeed(undefined)
|
|
497
513
|
case "In":
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
])
|
|
514
|
+
add(current.field, Arr.map(current.values, literalOf))
|
|
515
|
+
return R.succeed(undefined)
|
|
501
516
|
case "Exists":
|
|
502
|
-
|
|
517
|
+
add(current.field, [])
|
|
518
|
+
return R.succeed(undefined)
|
|
503
519
|
case "Under":
|
|
504
520
|
throw new Error(
|
|
505
521
|
"Condition.satisfiable needs a condition with no Under nodes. Call Condition.partial first.",
|
|
@@ -510,22 +526,16 @@ const constantsOf = (node: Node): Map<string, Set<Nodes.Scalar>> => {
|
|
|
510
526
|
R.forEach(current.conditions, (child) =>
|
|
511
527
|
R.suspend(() => go(child)),
|
|
512
528
|
),
|
|
513
|
-
|
|
529
|
+
() => undefined,
|
|
514
530
|
)
|
|
515
531
|
case "Not":
|
|
516
532
|
return R.suspend(() => go(current.condition))
|
|
517
533
|
default:
|
|
518
|
-
return R.succeed(
|
|
534
|
+
return R.succeed(undefined)
|
|
519
535
|
}
|
|
520
536
|
}
|
|
521
537
|
return go(node)
|
|
522
538
|
})
|
|
523
|
-
const all = new Map<string, Set<Nodes.Scalar>>()
|
|
524
|
-
Arr.forEach(pairs, ([field, values]) => {
|
|
525
|
-
const known = all.get(field) ?? new Set<Nodes.Scalar>()
|
|
526
|
-
Arr.forEach(values, (value) => known.add(value))
|
|
527
|
-
all.set(field, known)
|
|
528
|
-
})
|
|
529
539
|
return all
|
|
530
540
|
}
|
|
531
541
|
|