@nmnmcc/ability 2.0.6 → 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 +56 -32
- 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 +118 -18
- 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 +101 -74
- package/dist/internal/algebra.js.map +1 -1
- 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 +16 -11
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +119 -80
- 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 +153 -78
- 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 +7 -0
- package/dist/internal/nodes.d.ts.map +1 -1
- package/dist/internal/nodes.js +86 -47
- 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 +17 -14
- 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 +89 -0
- package/dist/internal/trampoline.js.map +1 -0
- 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/algebra.ts +218 -135
- package/src/internal/analysis.ts +154 -122
- package/src/internal/authorizer.ts +34 -16
- package/src/internal/bind.ts +279 -148
- package/src/internal/catalog.ts +307 -133
- 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 +101 -59
- package/src/internal/patterns.ts +72 -26
- package/src/internal/policy-json.ts +266 -197
- package/src/internal/policy-nodes.ts +33 -24
- 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 +194 -0
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,22 +433,16 @@ 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
|
-
// The structs of classes. A value of a class is not copied into a plain
|
|
422
|
-
// object, so it keeps its getters and methods.
|
|
423
|
-
const classes = Arr.filter(
|
|
424
|
-
Arr.map(
|
|
425
|
-
Arr.filter(place.schemas, (schema) => schema._tag === "Declaration"),
|
|
426
|
-
structOf,
|
|
427
|
-
),
|
|
428
|
-
isStruct,
|
|
429
|
-
)
|
|
430
446
|
const values = Arr.filter(
|
|
431
447
|
place.schemas,
|
|
432
448
|
(schema) => !isStruct(structOf(schema)),
|
|
@@ -444,58 +460,20 @@ const branchOf = (place: Place, count: { size: number }): Branch => {
|
|
|
444
460
|
structs,
|
|
445
461
|
(struct) => struct.indexSignatures.length > 0,
|
|
446
462
|
)
|
|
447
|
-
// The last part that a path may have: the walk does not go below it.
|
|
448
|
-
const last = place.path.length === Limits.fieldPathParts
|
|
449
|
-
const field: Field | undefined =
|
|
450
|
-
place.path.length === 0
|
|
451
|
-
? undefined
|
|
452
|
-
: {
|
|
453
|
-
path: place.path,
|
|
454
|
-
text: textOf(place.path),
|
|
455
|
-
kinds,
|
|
456
|
-
listed:
|
|
457
|
-
values.length > 0 ||
|
|
458
|
-
records.length > 0 ||
|
|
459
|
-
(last && structs.length > 0),
|
|
460
|
-
// A field that is a scalar in one branch of a union and a struct
|
|
461
|
-
// in another holds parts too.
|
|
462
|
-
open:
|
|
463
|
-
records.length > 0 ||
|
|
464
|
-
structs.length > 0 ||
|
|
465
|
-
valueKinds.object ||
|
|
466
|
-
place.top,
|
|
467
|
-
plain: place.plain,
|
|
468
|
-
}
|
|
469
|
-
if (field !== undefined) {
|
|
470
|
-
count.size += 1
|
|
471
|
-
if (count.size > Limits.catalogFields) {
|
|
472
|
-
throw Limits.exceeded(
|
|
473
|
-
"A Schema",
|
|
474
|
-
"catalogFields",
|
|
475
|
-
"fields",
|
|
476
|
-
count.size,
|
|
477
|
-
Limits.catalogFields,
|
|
478
|
-
)
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
if (last) {
|
|
482
|
-
return {
|
|
483
|
-
field,
|
|
484
|
-
children: [],
|
|
485
|
-
names: HashMap.empty(),
|
|
486
|
-
key: undefined,
|
|
487
|
-
keyBranch: undefined,
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
463
|
const structProperties = Arr.map(structs, propertiesOf)
|
|
491
|
-
const classProperties = Arr.map(classes, propertiesOf)
|
|
492
464
|
const children = Arr.map(namesOf(structs), (name) => {
|
|
493
465
|
const properties = Arr.flatMap(structProperties, (named) => {
|
|
494
466
|
const property = named.get(name)
|
|
495
467
|
return property === undefined ? [] : [property]
|
|
496
468
|
})
|
|
469
|
+
// A struct without the name may still have it as an extra field, with
|
|
470
|
+
// any value: only a decode or a `make` takes extra fields away, and a
|
|
471
|
+
// decision may get a value made by hand. A value that may be any
|
|
472
|
+
// object, as one of `Schema.Unknown` may, may have the name with any
|
|
473
|
+
// value too.
|
|
474
|
+
const top =
|
|
475
|
+
place.top || valueKinds.object || properties.length < structs.length
|
|
497
476
|
const below: Place = {
|
|
498
|
-
path: [...place.path, name],
|
|
499
477
|
schemas: Arr.flatMap(properties, (property) =>
|
|
500
478
|
alternatives(property.type, new Set()),
|
|
501
479
|
),
|
|
@@ -505,91 +483,287 @@ const branchOf = (place: Place, count: { size: number }): Branch => {
|
|
|
505
483
|
place.missing ||
|
|
506
484
|
values.length > 0 ||
|
|
507
485
|
Arr.some(properties, (property) => SchemaAST.isOptional(property.type)),
|
|
508
|
-
|
|
509
|
-
//
|
|
510
|
-
//
|
|
511
|
-
|
|
512
|
-
top: place.top || valueKinds.object || properties.length < structs.length,
|
|
513
|
-
plain:
|
|
514
|
-
place.plain &&
|
|
515
|
-
!valueKinds.object &&
|
|
516
|
-
Arr.every(classProperties, (named) => named.has(name)),
|
|
486
|
+
top,
|
|
487
|
+
// A value that may be anything may be a list, or have a getter, so
|
|
488
|
+
// a read below it may not find plain data.
|
|
489
|
+
plain: place.plain && !top,
|
|
517
490
|
}
|
|
518
|
-
return [name,
|
|
491
|
+
return [name, below] as const
|
|
519
492
|
})
|
|
520
|
-
const names = HashMap.fromIterable(children)
|
|
521
493
|
const signatures = Arr.flatMap(records, (struct) => struct.indexSignatures)
|
|
522
|
-
|
|
523
|
-
|
|
494
|
+
const keyTop =
|
|
495
|
+
place.top || valueKinds.object || records.length < structs.length
|
|
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
|
+
},
|
|
519
|
+
},
|
|
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
|
|
524
580
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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>
|
|
528
635
|
}
|
|
529
|
-
const
|
|
530
|
-
{
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
|
|
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
|
+
}),
|
|
544
686
|
)
|
|
545
|
-
return { field, children, names, key, keyBranch }
|
|
546
687
|
}
|
|
547
688
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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)
|
|
553
700
|
|
|
554
701
|
/**
|
|
555
|
-
* Makes the catalog of a Schema
|
|
556
|
-
*
|
|
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.
|
|
557
706
|
*/
|
|
558
707
|
export const catalogOf = (ast: SchemaAST.AST): Catalog => {
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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
|
+
}
|
|
570
727
|
}
|
|
571
728
|
|
|
572
|
-
/**
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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) {
|
|
576
741
|
return undefined
|
|
577
742
|
}
|
|
578
|
-
const
|
|
743
|
+
const found = Arr.reduce(
|
|
579
744
|
names,
|
|
580
|
-
catalog.root
|
|
745
|
+
{ branch: catalog.root, path: [] } as
|
|
746
|
+
| { readonly branch: Branch; readonly path: ReadonlyArray<Segment> }
|
|
747
|
+
| undefined,
|
|
581
748
|
(current, name) => {
|
|
582
749
|
if (current === undefined) {
|
|
583
750
|
return undefined
|
|
584
751
|
}
|
|
585
|
-
const named = HashMap.get(current.names, name)
|
|
752
|
+
const named = HashMap.get(current.branch.names, name)
|
|
586
753
|
if (Option.isSome(named)) {
|
|
587
|
-
return
|
|
754
|
+
return {
|
|
755
|
+
branch: catalog.branches[named.value]!,
|
|
756
|
+
path: [...current.path, name],
|
|
757
|
+
}
|
|
588
758
|
}
|
|
589
|
-
|
|
590
|
-
|
|
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] }
|
|
591
763
|
: undefined
|
|
592
764
|
},
|
|
593
765
|
)
|
|
594
|
-
return
|
|
766
|
+
return found === undefined
|
|
767
|
+
? undefined
|
|
768
|
+
: fieldAt(found.branch, found.path, parts)
|
|
595
769
|
}
|