@nmnmcc/ability 2.1.2 → 2.1.3
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/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.js +23 -9
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +16 -10
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/catalog.js +13 -1
- package/dist/internal/catalog.js.map +1 -1
- package/dist/internal/policy-nodes.js +21 -12
- package/dist/internal/policy-nodes.js.map +1 -1
- package/dist/internal/search.js +38 -26
- 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 +49 -34
- package/src/internal/bind.ts +28 -30
- package/src/internal/catalog.ts +24 -7
- package/src/internal/policy-nodes.ts +19 -13
- package/src/internal/search.ts +31 -21
package/src/Policy.ts
CHANGED
|
@@ -280,6 +280,17 @@ export type CheckField<D, T> = T extends { readonly field?: infer F }
|
|
|
280
280
|
}
|
|
281
281
|
: unknown
|
|
282
282
|
|
|
283
|
+
/**
|
|
284
|
+
* A target or a request with its field checked, as {@link CheckField} does.
|
|
285
|
+
* Each member of a union is checked by itself, so a member with no field
|
|
286
|
+
* does not take the place of the check of another member.
|
|
287
|
+
*
|
|
288
|
+
* @stability stable
|
|
289
|
+
* @category utility types
|
|
290
|
+
* @since 2.1.3
|
|
291
|
+
*/
|
|
292
|
+
export type Checked<D, T> = T extends unknown ? T & CheckField<D, T> : never
|
|
293
|
+
|
|
283
294
|
/**
|
|
284
295
|
* A request: a principal, an action, and a resource value.
|
|
285
296
|
*
|
|
@@ -804,7 +815,7 @@ export const decide: <
|
|
|
804
815
|
const Req extends DeepRequest<NoInfer<D>>,
|
|
805
816
|
>(
|
|
806
817
|
policy: Policy<D, X>,
|
|
807
|
-
request:
|
|
818
|
+
request: Checked<NoInfer<D>, Req>,
|
|
808
819
|
) => Effect.Effect<
|
|
809
820
|
Decision.Decision,
|
|
810
821
|
Condition.ErrorOf<X>,
|
|
@@ -823,7 +834,7 @@ export const decideSync: <
|
|
|
823
834
|
const Req extends DeepRequest<NoInfer<D>>,
|
|
824
835
|
>(
|
|
825
836
|
policy: Policy<D>,
|
|
826
|
-
request:
|
|
837
|
+
request: Checked<NoInfer<D>, Req>,
|
|
827
838
|
) => Decision.Decision = Decide.decideSync
|
|
828
839
|
|
|
829
840
|
/**
|
|
@@ -842,7 +853,7 @@ export const explain: <
|
|
|
842
853
|
const Req extends DeepRequest<NoInfer<D>>,
|
|
843
854
|
>(
|
|
844
855
|
policy: Policy<D, X>,
|
|
845
|
-
request:
|
|
856
|
+
request: Checked<NoInfer<D>, Req>,
|
|
846
857
|
) => Effect.Effect<Explanation, Condition.ErrorOf<X>, Condition.ServicesOf<X>> =
|
|
847
858
|
Decide.explain as never
|
|
848
859
|
|
|
@@ -923,7 +934,7 @@ export const residual: <
|
|
|
923
934
|
const Req extends DeepKindRequest<NoInfer<D>>,
|
|
924
935
|
>(
|
|
925
936
|
policy: Policy<D, X>,
|
|
926
|
-
request:
|
|
937
|
+
request: Checked<NoInfer<D>, Req>,
|
|
927
938
|
) => Condition.Condition<ResidualReads<D, Req["kind"]>, X> =
|
|
928
939
|
Analysis.residual as never
|
|
929
940
|
|
|
@@ -985,7 +996,7 @@ export const coverage: <
|
|
|
985
996
|
const Req extends DeepKindRequest<NoInfer<D>>,
|
|
986
997
|
>(
|
|
987
998
|
policy: Policy<D, X>,
|
|
988
|
-
request:
|
|
999
|
+
request: Checked<NoInfer<D>, Req>,
|
|
989
1000
|
) => Coverage<ResidualReads<D, Req["kind"]>, X> = Analysis.coverageAny as never
|
|
990
1001
|
|
|
991
1002
|
// Schema
|
package/src/internal/algebra.ts
CHANGED
|
@@ -655,25 +655,29 @@ export const provide = (node: Node, context: Context.Context<never>): Node =>
|
|
|
655
655
|
|
|
656
656
|
/** The fields that a condition tests, in the order that they first come. */
|
|
657
657
|
export const fields = (node: Node): ReadonlyArray<string> => {
|
|
658
|
-
|
|
659
|
-
|
|
658
|
+
// The fields go into one native `Set`, in order, as in `dedupe`: a field
|
|
659
|
+
// of a condition that is not bound to a domain may be any string. A walk
|
|
660
|
+
// that joined the lists of the children would copy the list of a deep
|
|
661
|
+
// condition at each level. The set belongs to this walk.
|
|
662
|
+
const seen = new Set<string>()
|
|
663
|
+
Trampoline.run(node.depth, (R) => {
|
|
664
|
+
const go = (current: Node): Step<undefined> => {
|
|
660
665
|
switch (current._tag) {
|
|
661
666
|
case "Compare":
|
|
662
667
|
case "In":
|
|
663
668
|
case "Exists":
|
|
664
|
-
|
|
669
|
+
seen.add(current.field)
|
|
670
|
+
return R.succeed(undefined)
|
|
665
671
|
case "And":
|
|
666
672
|
case "Or":
|
|
667
|
-
return R.map(eachChild(R, current.conditions, go),
|
|
673
|
+
return R.map(eachChild(R, current.conditions, go), () => undefined)
|
|
668
674
|
case "Not":
|
|
669
675
|
return R.suspend(() => go(current.condition))
|
|
670
676
|
default:
|
|
671
|
-
return R.succeed(
|
|
677
|
+
return R.succeed(undefined)
|
|
672
678
|
}
|
|
673
679
|
}
|
|
674
680
|
return go(node)
|
|
675
681
|
})
|
|
676
|
-
|
|
677
|
-
// to a domain may be any string.
|
|
678
|
-
return Array.from(new Set(all))
|
|
682
|
+
return Array.from(seen)
|
|
679
683
|
}
|
|
@@ -139,43 +139,58 @@ class PrincipalKey implements Equal.Equal {
|
|
|
139
139
|
* the order that a loop sees them. `Equal` does not see the order of the
|
|
140
140
|
* fields of an object or of the items of a map, but a loader may read it, as
|
|
141
141
|
* `Object.values` does. So two keys need the same order too. A scalar is in
|
|
142
|
-
* the text with its type, so the items of a set or a map show their order
|
|
142
|
+
* the text with its type, so the items of a set or a map show their order,
|
|
143
|
+
* and `-0` is not `0`: `Equal` takes them as one, but a loader may not.
|
|
144
|
+
*
|
|
145
|
+
* Each object gets a number at its first visit, and a later visit of it, as
|
|
146
|
+
* of a part that the value shares or of a loop back to a part above, writes
|
|
147
|
+
* `#` and the number. So the walk ends for a value with a loop, and a shared
|
|
148
|
+
* part does not make the text very long. The numbers are in a native `Map`,
|
|
149
|
+
* because the walk must find an object by the object itself. The map belongs
|
|
150
|
+
* to one walk.
|
|
143
151
|
*/
|
|
144
|
-
const orderOf
|
|
145
|
-
|
|
146
|
-
string
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
152
|
+
const orderOf = (root: unknown): string => {
|
|
153
|
+
const ids = new Map<object, number>()
|
|
154
|
+
return Trampoline.adaptive<unknown, string>((R, child) => (value, depth) => {
|
|
155
|
+
if (typeof value !== "object" || value === null) {
|
|
156
|
+
return R.succeed(
|
|
157
|
+
typeof value === "string"
|
|
158
|
+
? JSON.stringify(value)
|
|
159
|
+
: Object.is(value, -0)
|
|
160
|
+
? "number:-0"
|
|
161
|
+
: `${typeof value}:${String(value)}`,
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
const known = ids.get(value)
|
|
165
|
+
if (known !== undefined) {
|
|
166
|
+
return R.succeed(`#${String(known)}`)
|
|
167
|
+
}
|
|
168
|
+
ids.set(value, ids.size)
|
|
169
|
+
const items = (list: ReadonlyArray<unknown>) =>
|
|
170
|
+
R.forEach(list, (item) => child(item, depth + 1))
|
|
171
|
+
if (Array.isArray(value)) {
|
|
172
|
+
return R.map(items(value), (texts) => `[${texts.join(",")}]`)
|
|
173
|
+
}
|
|
174
|
+
if (
|
|
175
|
+
value instanceof Map ||
|
|
176
|
+
value instanceof Set ||
|
|
177
|
+
HashMap.isHashMap(value) ||
|
|
178
|
+
HashSet.isHashSet(value) ||
|
|
179
|
+
Chunk.isChunk(value)
|
|
180
|
+
) {
|
|
181
|
+
return R.map(
|
|
182
|
+
items(Array.from(value as Iterable<unknown>)),
|
|
183
|
+
(texts) => `<${texts.join(",")}>`,
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
const names = Object.keys(value)
|
|
167
187
|
return R.map(
|
|
168
|
-
items(
|
|
169
|
-
(texts) =>
|
|
188
|
+
items(Arr.map(names, (name) => (value as Record<string, unknown>)[name])),
|
|
189
|
+
(texts) =>
|
|
190
|
+
`{${Arr.map(names, (name, index) => `${JSON.stringify(name)}:${texts[index]!}`).join(",")}}`,
|
|
170
191
|
)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return R.map(
|
|
174
|
-
items(Arr.map(names, (name) => (value as Record<string, unknown>)[name])),
|
|
175
|
-
(texts) =>
|
|
176
|
-
`{${Arr.map(names, (name, index) => `${JSON.stringify(name)}:${texts[index]!}`).join(",")}}`,
|
|
177
|
-
)
|
|
178
|
-
})
|
|
192
|
+
})(root)
|
|
193
|
+
}
|
|
179
194
|
|
|
180
195
|
/**
|
|
181
196
|
* Gives the JSON codec of a Schema, or `undefined` when the Schema has no JSON
|
package/src/internal/bind.ts
CHANGED
|
@@ -815,48 +815,46 @@ export const treeIssues = (
|
|
|
815
815
|
// shares the path of its parent. The tree may be the JSON form, with no
|
|
816
816
|
// depth that a node knows, so the walk is a walk of the trampoline for an
|
|
817
817
|
// input of an unknown depth.
|
|
818
|
-
|
|
818
|
+
// The rules go into one list, in order: a walk that joined the lists of
|
|
819
|
+
// the children at each node would copy the list of a deep tree at each
|
|
820
|
+
// level. The list belongs to this walk.
|
|
821
|
+
const found: Array<readonly [RuleData, Place.Place]> = []
|
|
819
822
|
const rulesOf = Trampoline.adaptive<
|
|
820
823
|
{ readonly node: TreeNode; readonly at: At },
|
|
821
|
-
|
|
824
|
+
undefined
|
|
822
825
|
>((R, child) => ({ node, at }, level) => {
|
|
826
|
+
const below = (
|
|
827
|
+
parts: ReadonlyArray<{ readonly node: TreeNode; readonly at: At }>,
|
|
828
|
+
) =>
|
|
829
|
+
R.map(
|
|
830
|
+
R.forEach(parts, (part) => child(part, level + 1)),
|
|
831
|
+
() => undefined,
|
|
832
|
+
)
|
|
823
833
|
switch (node._tag) {
|
|
824
834
|
case "Rules":
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
),
|
|
830
|
-
)
|
|
835
|
+
Arr.forEach(node.rules, (rule, index) => {
|
|
836
|
+
found.push([rule, Place.at(at, "rules", index)])
|
|
837
|
+
})
|
|
838
|
+
return R.succeed(undefined)
|
|
831
839
|
case "All":
|
|
832
840
|
case "Any":
|
|
833
841
|
case "FirstOf":
|
|
834
|
-
return
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
),
|
|
840
|
-
),
|
|
841
|
-
Arr.flatten,
|
|
842
|
+
return below(
|
|
843
|
+
Arr.map(node.policies, (inner, index) => ({
|
|
844
|
+
node: inner,
|
|
845
|
+
at: Place.at(at, "policies", index),
|
|
846
|
+
})),
|
|
842
847
|
)
|
|
843
848
|
case "Ceiling":
|
|
844
|
-
return
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
{ node: node.policy, at: Place.at(at, "policy") },
|
|
849
|
-
],
|
|
850
|
-
(part) => child(part, level + 1),
|
|
851
|
-
),
|
|
852
|
-
Arr.flatten,
|
|
853
|
-
)
|
|
849
|
+
return below([
|
|
850
|
+
{ node: node.cap, at: Place.at(at, "cap") },
|
|
851
|
+
{ node: node.policy, at: Place.at(at, "policy") },
|
|
852
|
+
])
|
|
854
853
|
}
|
|
855
854
|
})
|
|
855
|
+
rulesOf({ node: root, at: undefined })
|
|
856
856
|
const memo = makeMemo(domain.limits)
|
|
857
|
-
return Arr.flatMap(
|
|
858
|
-
|
|
859
|
-
? ruleIssues(domain, found[0], found[1], memo)
|
|
860
|
-
: [found as Issue],
|
|
857
|
+
return Arr.flatMap(found, ([rule, place]) =>
|
|
858
|
+
ruleIssues(domain, rule, place, memo),
|
|
861
859
|
)
|
|
862
860
|
}
|
package/src/internal/catalog.ts
CHANGED
|
@@ -272,13 +272,30 @@ const structOf = (ast: SchemaAST.AST): SchemaAST.Objects | undefined =>
|
|
|
272
272
|
const hasCheck = (
|
|
273
273
|
checks: ReadonlyArray<SchemaAST.Check<never>>,
|
|
274
274
|
id: string,
|
|
275
|
-
): boolean =>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
275
|
+
): boolean => hasCheckIn({ checks, id })
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The walk of {@link hasCheck}. A group of checks may hold a group, very many
|
|
279
|
+
* levels down, as `.and(...)` many times makes, so it is a walk of the
|
|
280
|
+
* trampoline for an input of an unknown depth.
|
|
281
|
+
*/
|
|
282
|
+
const hasCheckIn = Trampoline.adaptive<
|
|
283
|
+
{
|
|
284
|
+
readonly checks: ReadonlyArray<SchemaAST.Check<never>>
|
|
285
|
+
readonly id: string
|
|
286
|
+
},
|
|
287
|
+
boolean
|
|
288
|
+
>(
|
|
289
|
+
(R, child) =>
|
|
290
|
+
({ checks, id }, depth) =>
|
|
291
|
+
R.some(checks, (check) =>
|
|
292
|
+
check.annotations?.representation?.id === id
|
|
293
|
+
? R.succeed(true)
|
|
294
|
+
: check._tag === "FilterGroup"
|
|
295
|
+
? child({ checks: check.checks, id }, depth + 1)
|
|
296
|
+
: R.succeed(false),
|
|
297
|
+
),
|
|
298
|
+
)
|
|
282
299
|
|
|
283
300
|
/** A struct with named fields, or with the keys of a record. */
|
|
284
301
|
const isStruct = (
|
|
@@ -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
|
|