@nmnmcc/ability 2.0.7 → 2.1.1
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/README.md +50 -27
- package/dist/Authorizer.d.ts +21 -12
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +116 -16
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +27 -18
- package/dist/Condition.js.map +1 -1
- package/dist/Domain.d.ts +9 -0
- package/dist/Domain.d.ts.map +1 -1
- package/dist/Domain.js +5 -0
- package/dist/Domain.js.map +1 -1
- package/dist/Limits.d.ts +44 -13
- package/dist/Limits.d.ts.map +1 -1
- package/dist/Limits.js +52 -22
- package/dist/Limits.js.map +1 -1
- package/dist/Policy.d.ts +95 -12
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/internal/algebra.js +2 -2
- package/dist/internal/analysis.js +81 -75
- package/dist/internal/analysis.js.map +1 -1
- package/dist/internal/authorizer.d.ts.map +1 -1
- package/dist/internal/authorizer.js +11 -7
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +86 -64
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/catalog.d.ts +33 -10
- package/dist/internal/catalog.d.ts.map +1 -1
- package/dist/internal/catalog.js +147 -72
- package/dist/internal/catalog.js.map +1 -1
- package/dist/internal/decide.js +25 -29
- package/dist/internal/decide.js.map +1 -1
- package/dist/internal/domain.js +19 -5
- package/dist/internal/domain.js.map +1 -1
- package/dist/internal/json.js +120 -76
- package/dist/internal/json.js.map +1 -1
- package/dist/internal/limits.d.ts +28 -0
- package/dist/internal/limits.d.ts.map +1 -1
- package/dist/internal/limits.js +75 -16
- package/dist/internal/limits.js.map +1 -1
- package/dist/internal/nodes.d.ts.map +1 -1
- package/dist/internal/nodes.js +53 -44
- package/dist/internal/nodes.js.map +1 -1
- package/dist/internal/patterns.js +50 -15
- package/dist/internal/patterns.js.map +1 -1
- package/dist/internal/policy-json.js +169 -127
- package/dist/internal/policy-json.js.map +1 -1
- package/dist/internal/policy-nodes.d.ts.map +1 -1
- package/dist/internal/policy-nodes.js +16 -13
- package/dist/internal/policy-nodes.js.map +1 -1
- package/dist/internal/policy.d.ts.map +1 -1
- package/dist/internal/policy.js +38 -22
- package/dist/internal/policy.js.map +1 -1
- package/dist/internal/resource.js +1 -1
- package/dist/internal/rule.d.ts.map +1 -1
- package/dist/internal/rule.js +6 -5
- package/dist/internal/rule.js.map +1 -1
- package/dist/internal/search.d.ts.map +1 -1
- package/dist/internal/search.js +106 -92
- package/dist/internal/search.js.map +1 -1
- package/dist/internal/trampoline.js +19 -5
- package/dist/internal/trampoline.js.map +1 -1
- package/package.json +1 -1
- package/src/Authorizer.ts +30 -20
- package/src/Condition.ts +308 -83
- package/src/Domain.ts +9 -0
- package/src/Limits.ts +69 -29
- package/src/Policy.ts +131 -15
- package/src/internal/analysis.ts +154 -124
- package/src/internal/authorizer.ts +29 -14
- package/src/internal/bind.ts +193 -136
- package/src/internal/catalog.ts +295 -112
- package/src/internal/decide.ts +58 -49
- package/src/internal/domain.ts +39 -3
- package/src/internal/json.ts +212 -117
- package/src/internal/limits.ts +101 -31
- package/src/internal/nodes.ts +65 -47
- package/src/internal/patterns.ts +72 -26
- package/src/internal/policy-json.ts +266 -197
- package/src/internal/policy-nodes.ts +31 -23
- package/src/internal/policy.ts +63 -23
- package/src/internal/rule.ts +16 -3
- package/src/internal/search.ts +186 -136
- package/src/internal/trampoline.ts +51 -4
package/src/internal/catalog.ts
CHANGED
|
@@ -136,34 +136,57 @@ export interface Field {
|
|
|
136
136
|
* above the field may be an object that does not have it as a data field:
|
|
137
137
|
* a class without the field, which may have a getter of that name, a
|
|
138
138
|
* value such as a date or a list, or any object, as of `Schema.Unknown`.
|
|
139
|
-
* A
|
|
140
|
-
*
|
|
139
|
+
* A struct of a union without the field makes it not plain too, since a
|
|
140
|
+
* value made by hand may have it with any value.
|
|
141
141
|
*/
|
|
142
142
|
readonly plain: boolean
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* A place in the graph of a catalog. A path of a field leads to a place, and
|
|
147
|
+
* many paths may lead to one place, as in a recursive Schema: `next` and
|
|
148
|
+
* `next.next` of a list of links are one place. A place keeps the ids of the
|
|
149
|
+
* places below it, so the graph has no loop of objects.
|
|
150
|
+
*/
|
|
146
151
|
export interface Branch {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
readonly id: number
|
|
153
|
+
/** The kinds of the values of the field at this place. */
|
|
154
|
+
readonly kinds: Kinds
|
|
155
|
+
/** The field holds a scalar, a record or a list: see `Field.listed`. */
|
|
156
|
+
readonly values: boolean
|
|
157
|
+
/** The field may hold a struct. */
|
|
158
|
+
readonly structs: boolean
|
|
159
|
+
readonly open: boolean
|
|
160
|
+
readonly plain: boolean
|
|
161
|
+
/** The ids of the places below, by name, in the order of the Schema. */
|
|
162
|
+
readonly children: ReadonlyArray<readonly [string, number]>
|
|
151
163
|
/**
|
|
152
164
|
* The places below, by name. A lookup gets each name from `split`, as a
|
|
153
165
|
* new string, and a `HashMap` finds a new string about 20% faster than a
|
|
154
166
|
* native `Map`. The names come from the Schema, so a name from a policy
|
|
155
167
|
* can not make a long list of names with one hash here.
|
|
156
168
|
*/
|
|
157
|
-
readonly names: HashMap.HashMap<string,
|
|
169
|
+
readonly names: HashMap.HashMap<string, number>
|
|
158
170
|
/** The place for the keys of a record, when the value may be a record. */
|
|
159
171
|
readonly key: Key | undefined
|
|
160
|
-
readonly keyBranch:
|
|
172
|
+
readonly keyBranch: number | undefined
|
|
161
173
|
}
|
|
162
174
|
|
|
163
175
|
/** The catalog of the fields of a Schema. */
|
|
164
176
|
export interface Catalog {
|
|
165
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* The fields that a list shows, a field before the fields below it. For a
|
|
179
|
+
* recursive Schema, the list goes into each recursive place one time on
|
|
180
|
+
* each path, so it is finite.
|
|
181
|
+
*/
|
|
166
182
|
readonly fields: ReadonlyArray<Field>
|
|
183
|
+
/** The places, by id. The root is the place with the id 0. */
|
|
184
|
+
readonly branches: ReadonlyArray<Branch>
|
|
185
|
+
/**
|
|
186
|
+
* The number of steps from a place to a place below it. A search of the
|
|
187
|
+
* catalog, as for a field pattern, reads each one, so this is its work.
|
|
188
|
+
*/
|
|
189
|
+
readonly edges: number
|
|
167
190
|
readonly root: Branch
|
|
168
191
|
}
|
|
169
192
|
|
|
@@ -341,9 +364,8 @@ const declarationId = (ast: SchemaAST.Declaration): string | undefined => {
|
|
|
341
364
|
: undefined
|
|
342
365
|
}
|
|
343
366
|
|
|
344
|
-
/** The things that
|
|
367
|
+
/** The things that the walk of a catalog knows at one place. */
|
|
345
368
|
interface Place {
|
|
346
|
-
readonly path: ReadonlyArray<Segment>
|
|
347
369
|
readonly schemas: ReadonlyArray<SchemaAST.AST>
|
|
348
370
|
/** The value may be missing. */
|
|
349
371
|
readonly missing: boolean
|
|
@@ -411,12 +433,15 @@ const namesOf = (
|
|
|
411
433
|
),
|
|
412
434
|
)
|
|
413
435
|
|
|
414
|
-
/**
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
436
|
+
/** A place of the walk, and the places below it, before they have ids. */
|
|
437
|
+
interface Made {
|
|
438
|
+
readonly branch: Omit<Branch, "id" | "children" | "names" | "keyBranch">
|
|
439
|
+
readonly children: ReadonlyArray<readonly [string, Place]>
|
|
440
|
+
readonly keyPlace: Place | undefined
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Works out the facts of a place, and the places below it. */
|
|
444
|
+
const placeOf = (place: Place): Made => {
|
|
420
445
|
const structs = Arr.filter(Arr.map(place.schemas, structOf), isStruct)
|
|
421
446
|
const values = Arr.filter(
|
|
422
447
|
place.schemas,
|
|
@@ -435,49 +460,6 @@ const branchOf = (place: Place, count: { size: number }): Branch => {
|
|
|
435
460
|
structs,
|
|
436
461
|
(struct) => struct.indexSignatures.length > 0,
|
|
437
462
|
)
|
|
438
|
-
// The last part that a path may have: the walk does not go below it.
|
|
439
|
-
const last = place.path.length === Limits.fieldPathParts
|
|
440
|
-
const field: Field | undefined =
|
|
441
|
-
place.path.length === 0
|
|
442
|
-
? undefined
|
|
443
|
-
: {
|
|
444
|
-
path: place.path,
|
|
445
|
-
text: textOf(place.path),
|
|
446
|
-
kinds,
|
|
447
|
-
listed:
|
|
448
|
-
values.length > 0 ||
|
|
449
|
-
records.length > 0 ||
|
|
450
|
-
(last && structs.length > 0),
|
|
451
|
-
// A field that is a scalar in one branch of a union and a struct
|
|
452
|
-
// in another holds parts too.
|
|
453
|
-
open:
|
|
454
|
-
records.length > 0 ||
|
|
455
|
-
structs.length > 0 ||
|
|
456
|
-
valueKinds.object ||
|
|
457
|
-
place.top,
|
|
458
|
-
plain: place.plain,
|
|
459
|
-
}
|
|
460
|
-
if (field !== undefined) {
|
|
461
|
-
count.size += 1
|
|
462
|
-
if (count.size > Limits.catalogFields) {
|
|
463
|
-
throw Limits.exceeded(
|
|
464
|
-
"A Schema",
|
|
465
|
-
"catalogFields",
|
|
466
|
-
"fields",
|
|
467
|
-
count.size,
|
|
468
|
-
Limits.catalogFields,
|
|
469
|
-
)
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
if (last) {
|
|
473
|
-
return {
|
|
474
|
-
field,
|
|
475
|
-
children: [],
|
|
476
|
-
names: HashMap.empty(),
|
|
477
|
-
key: undefined,
|
|
478
|
-
keyBranch: undefined,
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
463
|
const structProperties = Arr.map(structs, propertiesOf)
|
|
482
464
|
const children = Arr.map(namesOf(structs), (name) => {
|
|
483
465
|
const properties = Arr.flatMap(structProperties, (named) => {
|
|
@@ -492,7 +474,6 @@ const branchOf = (place: Place, count: { size: number }): Branch => {
|
|
|
492
474
|
const top =
|
|
493
475
|
place.top || valueKinds.object || properties.length < structs.length
|
|
494
476
|
const below: Place = {
|
|
495
|
-
path: [...place.path, name],
|
|
496
477
|
schemas: Arr.flatMap(properties, (property) =>
|
|
497
478
|
alternatives(property.type, new Set()),
|
|
498
479
|
),
|
|
@@ -507,80 +488,282 @@ const branchOf = (place: Place, count: { size: number }): Branch => {
|
|
|
507
488
|
// a read below it may not find plain data.
|
|
508
489
|
plain: place.plain && !top,
|
|
509
490
|
}
|
|
510
|
-
return [name,
|
|
491
|
+
return [name, below] as const
|
|
511
492
|
})
|
|
512
|
-
const names = HashMap.fromIterable(children)
|
|
513
493
|
const signatures = Arr.flatMap(records, (struct) => struct.indexSignatures)
|
|
514
|
-
if (signatures.length === 0) {
|
|
515
|
-
return { field, children, names, key: undefined, keyBranch: undefined }
|
|
516
|
-
}
|
|
517
494
|
const keyTop =
|
|
518
495
|
place.top || valueKinds.object || records.length < structs.length
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
496
|
+
return {
|
|
497
|
+
branch: {
|
|
498
|
+
kinds,
|
|
499
|
+
values: values.length > 0 || records.length > 0,
|
|
500
|
+
structs: structs.length > 0,
|
|
501
|
+
// A field that is a scalar in one branch of a union and a struct in
|
|
502
|
+
// another holds parts too.
|
|
503
|
+
open:
|
|
504
|
+
records.length > 0 ||
|
|
505
|
+
structs.length > 0 ||
|
|
506
|
+
valueKinds.object ||
|
|
507
|
+
place.top,
|
|
508
|
+
plain: place.plain,
|
|
509
|
+
key:
|
|
510
|
+
signatures.length === 0
|
|
511
|
+
? undefined
|
|
512
|
+
: {
|
|
513
|
+
_tag: "Key",
|
|
514
|
+
parameters: Arr.map(
|
|
515
|
+
signatures,
|
|
516
|
+
(signature) => signature.parameter,
|
|
517
|
+
),
|
|
518
|
+
},
|
|
533
519
|
},
|
|
534
|
-
|
|
520
|
+
children,
|
|
521
|
+
keyPlace:
|
|
522
|
+
signatures.length === 0
|
|
523
|
+
? undefined
|
|
524
|
+
: {
|
|
525
|
+
schemas: Arr.flatMap(signatures, (signature) =>
|
|
526
|
+
alternatives(signature.type, new Set()),
|
|
527
|
+
),
|
|
528
|
+
// A record may not have a given key.
|
|
529
|
+
missing: true,
|
|
530
|
+
top: keyTop,
|
|
531
|
+
plain: place.plain && !keyTop,
|
|
532
|
+
},
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Makes the places of a Schema. Each place comes one time: two paths with the
|
|
538
|
+
* same Schemas and the same marks lead to one place. The walk keeps a queue
|
|
539
|
+
* of the places that it has found, and it makes them in order, so a deep
|
|
540
|
+
* Schema does not fill the stack.
|
|
541
|
+
*/
|
|
542
|
+
const branchesOf = (root: Place): ReadonlyArray<Branch> => {
|
|
543
|
+
// The id of each Schema node, and of each place, for this walk. The ids
|
|
544
|
+
// are in native maps, because the walk must find a Schema node by the node
|
|
545
|
+
// itself: `Equal` compares Schema nodes by structure, and a recursive
|
|
546
|
+
// Schema has no end. The maps and the queue belong to this walk.
|
|
547
|
+
const astIds = new Map<SchemaAST.AST, number>()
|
|
548
|
+
const placeIds = new Map<string, number>()
|
|
549
|
+
const queue: Array<Place> = []
|
|
550
|
+
const idOf = (place: Place): number => {
|
|
551
|
+
const ids = Arr.map(place.schemas, (schema) => {
|
|
552
|
+
const known = astIds.get(schema)
|
|
553
|
+
if (known !== undefined) {
|
|
554
|
+
return known
|
|
555
|
+
}
|
|
556
|
+
astIds.set(schema, astIds.size)
|
|
557
|
+
return astIds.size - 1
|
|
558
|
+
})
|
|
559
|
+
const key = `${Array.from(new Set(ids))
|
|
560
|
+
.sort((a, b) => a - b)
|
|
561
|
+
.join(
|
|
562
|
+
",",
|
|
563
|
+
)}|${String(place.missing)}|${String(place.top)}|${String(place.plain)}`
|
|
564
|
+
const known = placeIds.get(key)
|
|
565
|
+
if (known !== undefined) {
|
|
566
|
+
return known
|
|
567
|
+
}
|
|
568
|
+
if (queue.length >= Limits.ceilings.catalogFields) {
|
|
569
|
+
throw Limits.exceeded(
|
|
570
|
+
"A Schema",
|
|
571
|
+
"catalogFields",
|
|
572
|
+
"places",
|
|
573
|
+
queue.length + 1,
|
|
574
|
+
Limits.ceilings.catalogFields,
|
|
575
|
+
)
|
|
576
|
+
}
|
|
577
|
+
placeIds.set(key, queue.length)
|
|
578
|
+
queue.push(place)
|
|
579
|
+
return queue.length - 1
|
|
580
|
+
}
|
|
581
|
+
idOf(root)
|
|
582
|
+
return Arr.unfold(0, (id) => {
|
|
583
|
+
const place = queue[id]
|
|
584
|
+
if (place === undefined) {
|
|
585
|
+
return Option.none()
|
|
586
|
+
}
|
|
587
|
+
const made = placeOf(place)
|
|
588
|
+
const children = Arr.map(
|
|
589
|
+
made.children,
|
|
590
|
+
([name, below]) => [name, idOf(below)] as const,
|
|
591
|
+
)
|
|
592
|
+
const branch: Branch = {
|
|
593
|
+
...made.branch,
|
|
594
|
+
id,
|
|
595
|
+
children,
|
|
596
|
+
names: HashMap.fromIterable(children),
|
|
597
|
+
keyBranch: made.keyPlace === undefined ? undefined : idOf(made.keyPlace),
|
|
598
|
+
}
|
|
599
|
+
return Option.some([branch, id + 1] as const)
|
|
600
|
+
})
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** The field at a place, for a path. */
|
|
604
|
+
const fieldAt = (
|
|
605
|
+
branch: Branch,
|
|
606
|
+
path: ReadonlyArray<Segment>,
|
|
607
|
+
parts: number,
|
|
608
|
+
): Field => ({
|
|
609
|
+
path,
|
|
610
|
+
text: textOf(path),
|
|
611
|
+
kinds: branch.kinds,
|
|
612
|
+
// A struct at the last part that a path may have is one field, since a
|
|
613
|
+
// path does not go below it.
|
|
614
|
+
listed: branch.values || (path.length === parts && branch.structs),
|
|
615
|
+
open: branch.open,
|
|
616
|
+
plain: branch.plain,
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* The fields that a list shows, a field before the fields below it, down to
|
|
621
|
+
* `parts` parts. A path goes into a place that is on it already one time
|
|
622
|
+
* only, so the list of a recursive Schema is finite. The walk keeps a stack
|
|
623
|
+
* of the places to visit, so a deep Schema does not fill the stack. The
|
|
624
|
+
* stack belongs to this walk.
|
|
625
|
+
*/
|
|
626
|
+
const listOf = (
|
|
627
|
+
branches: ReadonlyArray<Branch>,
|
|
628
|
+
parts: number,
|
|
629
|
+
): ReadonlyArray<Field> => {
|
|
630
|
+
interface Visit {
|
|
631
|
+
readonly branch: Branch
|
|
632
|
+
readonly path: ReadonlyArray<Segment>
|
|
633
|
+
/** The ids of the places on the path, as a set of Effect that shares its parts. */
|
|
634
|
+
readonly above: HashSet.HashSet<number>
|
|
635
|
+
}
|
|
636
|
+
const below = (visit: Visit): ReadonlyArray<Visit> => {
|
|
637
|
+
const { branch, path } = visit
|
|
638
|
+
if (
|
|
639
|
+
path.length === parts ||
|
|
640
|
+
(path.length > 0 && HashSet.has(visit.above, branch.id))
|
|
641
|
+
) {
|
|
642
|
+
return []
|
|
643
|
+
}
|
|
644
|
+
const above = HashSet.add(visit.above, branch.id)
|
|
645
|
+
const named = Arr.map(branch.children, ([name, id]) => ({
|
|
646
|
+
branch: branches[id]!,
|
|
647
|
+
path: [...path, name],
|
|
648
|
+
above,
|
|
649
|
+
}))
|
|
650
|
+
return branch.key === undefined || branch.keyBranch === undefined
|
|
651
|
+
? named
|
|
652
|
+
: [
|
|
653
|
+
...named,
|
|
654
|
+
{
|
|
655
|
+
branch: branches[branch.keyBranch]!,
|
|
656
|
+
path: [...path, branch.key],
|
|
657
|
+
above,
|
|
658
|
+
},
|
|
659
|
+
]
|
|
660
|
+
}
|
|
661
|
+
const stack: Array<Visit> = [
|
|
662
|
+
{ branch: branches[0]!, path: [], above: HashSet.empty() },
|
|
663
|
+
]
|
|
664
|
+
return Arr.flatten(
|
|
665
|
+
Arr.unfold(stack, (rest) => {
|
|
666
|
+
const visit = rest.pop()
|
|
667
|
+
if (visit === undefined) {
|
|
668
|
+
return Option.none()
|
|
669
|
+
}
|
|
670
|
+
rest.push(...Arr.reverse(below(visit)))
|
|
671
|
+
const field =
|
|
672
|
+
visit.path.length === 0
|
|
673
|
+
? []
|
|
674
|
+
: [fieldAt(visit.branch, visit.path, parts)]
|
|
675
|
+
if (field.length > 0 && rest.length + 1 > Limits.ceilings.catalogFields) {
|
|
676
|
+
throw Limits.exceeded(
|
|
677
|
+
"A Schema",
|
|
678
|
+
"catalogFields",
|
|
679
|
+
"fields",
|
|
680
|
+
rest.length + 1,
|
|
681
|
+
Limits.ceilings.catalogFields,
|
|
682
|
+
)
|
|
683
|
+
}
|
|
684
|
+
return Option.some([field, rest] as const)
|
|
685
|
+
}),
|
|
535
686
|
)
|
|
536
|
-
return { field, children, names, key, keyBranch }
|
|
537
687
|
}
|
|
538
688
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
689
|
+
/**
|
|
690
|
+
* The fields that a list shows for paths of at most `parts` parts, as for a
|
|
691
|
+
* domain with that limit. See `Catalog.fields`.
|
|
692
|
+
*/
|
|
693
|
+
export const fieldsOf = (
|
|
694
|
+
catalog: Catalog,
|
|
695
|
+
parts: number,
|
|
696
|
+
): ReadonlyArray<Field> =>
|
|
697
|
+
parts === Limits.fieldPathParts
|
|
698
|
+
? catalog.fields
|
|
699
|
+
: listOf(catalog.branches, parts)
|
|
544
700
|
|
|
545
701
|
/**
|
|
546
|
-
* Makes the catalog of a Schema
|
|
547
|
-
*
|
|
702
|
+
* Makes the catalog of a Schema, with the list of its fields for the default
|
|
703
|
+
* limit of the parts of a path. It throws when a field name has a dot or is
|
|
704
|
+
* empty, and when the Schema has more places or fields than the ceiling of
|
|
705
|
+
* `catalogFields`. A domain checks the size of the list with its own limit.
|
|
548
706
|
*/
|
|
549
707
|
export const catalogOf = (ast: SchemaAST.AST): Catalog => {
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
708
|
+
const branches = branchesOf({
|
|
709
|
+
schemas: alternatives(ast, new Set()),
|
|
710
|
+
missing: false,
|
|
711
|
+
top: false,
|
|
712
|
+
plain: true,
|
|
713
|
+
})
|
|
714
|
+
return {
|
|
715
|
+
fields: listOf(branches, Limits.fieldPathParts),
|
|
716
|
+
branches,
|
|
717
|
+
edges: Arr.reduce(
|
|
718
|
+
branches,
|
|
719
|
+
0,
|
|
720
|
+
(total, branch) =>
|
|
721
|
+
total +
|
|
722
|
+
branch.children.length +
|
|
723
|
+
(branch.keyBranch === undefined ? 0 : 1),
|
|
724
|
+
),
|
|
725
|
+
root: branches[0]!,
|
|
726
|
+
}
|
|
561
727
|
}
|
|
562
728
|
|
|
563
|
-
/**
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
729
|
+
/**
|
|
730
|
+
* Gives the field of a catalog at a path with dots, if the catalog has it.
|
|
731
|
+
* A path has at most `parts` parts. The lookup follows one place for each
|
|
732
|
+
* part, in a loop.
|
|
733
|
+
*/
|
|
734
|
+
export const lookup = (
|
|
735
|
+
catalog: Catalog,
|
|
736
|
+
path: string,
|
|
737
|
+
parts: number = Limits.fieldPathParts,
|
|
738
|
+
): Field | undefined => {
|
|
739
|
+
const names = path.split(".", parts + 1)
|
|
740
|
+
if (names.length > parts) {
|
|
567
741
|
return undefined
|
|
568
742
|
}
|
|
569
|
-
const
|
|
743
|
+
const found = Arr.reduce(
|
|
570
744
|
names,
|
|
571
|
-
catalog.root
|
|
745
|
+
{ branch: catalog.root, path: [] } as
|
|
746
|
+
| { readonly branch: Branch; readonly path: ReadonlyArray<Segment> }
|
|
747
|
+
| undefined,
|
|
572
748
|
(current, name) => {
|
|
573
749
|
if (current === undefined) {
|
|
574
750
|
return undefined
|
|
575
751
|
}
|
|
576
|
-
const named = HashMap.get(current.names, name)
|
|
752
|
+
const named = HashMap.get(current.branch.names, name)
|
|
577
753
|
if (Option.isSome(named)) {
|
|
578
|
-
return
|
|
754
|
+
return {
|
|
755
|
+
branch: catalog.branches[named.value]!,
|
|
756
|
+
path: [...current.path, name],
|
|
757
|
+
}
|
|
579
758
|
}
|
|
580
|
-
|
|
581
|
-
|
|
759
|
+
const key = current.branch.key
|
|
760
|
+
const keyBranch = current.branch.keyBranch
|
|
761
|
+
return key !== undefined && keyBranch !== undefined && keyFits(key, name)
|
|
762
|
+
? { branch: catalog.branches[keyBranch]!, path: [...current.path, key] }
|
|
582
763
|
: undefined
|
|
583
764
|
},
|
|
584
765
|
)
|
|
585
|
-
return
|
|
766
|
+
return found === undefined
|
|
767
|
+
? undefined
|
|
768
|
+
: fieldAt(found.branch, found.path, parts)
|
|
586
769
|
}
|