@nmnmcc/ability 1.0.1 → 2.0.0

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.
Files changed (127) hide show
  1. package/README.md +112 -69
  2. package/dist/Authorizer.d.ts +43 -47
  3. package/dist/Authorizer.d.ts.map +1 -1
  4. package/dist/Authorizer.js +5 -290
  5. package/dist/Authorizer.js.map +1 -1
  6. package/dist/Condition.d.ts +164 -332
  7. package/dist/Condition.d.ts.map +1 -1
  8. package/dist/Condition.js +75 -890
  9. package/dist/Condition.js.map +1 -1
  10. package/dist/Decision.d.ts +13 -13
  11. package/dist/Decision.d.ts.map +1 -1
  12. package/dist/Decision.js.map +1 -1
  13. package/dist/Domain.d.ts +10 -21
  14. package/dist/Domain.d.ts.map +1 -1
  15. package/dist/Domain.js +4 -62
  16. package/dist/Domain.js.map +1 -1
  17. package/dist/HttpApi.d.ts +31 -7
  18. package/dist/HttpApi.d.ts.map +1 -1
  19. package/dist/HttpApi.js +25 -8
  20. package/dist/HttpApi.js.map +1 -1
  21. package/dist/Limits.d.ts +114 -0
  22. package/dist/Limits.d.ts.map +1 -0
  23. package/dist/Limits.js +132 -0
  24. package/dist/Limits.js.map +1 -0
  25. package/dist/Policy.d.ts +271 -269
  26. package/dist/Policy.d.ts.map +1 -1
  27. package/dist/Policy.js +68 -1177
  28. package/dist/Policy.js.map +1 -1
  29. package/dist/Resource.d.ts +4 -177
  30. package/dist/Resource.d.ts.map +1 -1
  31. package/dist/Resource.js +4 -803
  32. package/dist/Resource.js.map +1 -1
  33. package/dist/index.d.ts +3 -4
  34. package/dist/index.js +4 -5
  35. package/dist/internal/algebra.js +267 -0
  36. package/dist/internal/algebra.js.map +1 -0
  37. package/dist/internal/analysis.js +245 -0
  38. package/dist/internal/analysis.js.map +1 -0
  39. package/dist/internal/authorizer.d.ts +30 -0
  40. package/dist/internal/authorizer.d.ts.map +1 -0
  41. package/dist/internal/authorizer.js +221 -0
  42. package/dist/internal/authorizer.js.map +1 -0
  43. package/dist/internal/bind.js +286 -0
  44. package/dist/internal/bind.js.map +1 -0
  45. package/dist/internal/catalog.d.ts +56 -0
  46. package/dist/internal/catalog.d.ts.map +1 -0
  47. package/dist/internal/catalog.js +292 -0
  48. package/dist/internal/catalog.js.map +1 -0
  49. package/dist/internal/decide.js +166 -0
  50. package/dist/internal/decide.js.map +1 -0
  51. package/dist/internal/domain.js +32 -0
  52. package/dist/internal/domain.js.map +1 -0
  53. package/dist/internal/json.js +134 -0
  54. package/dist/internal/json.js.map +1 -0
  55. package/dist/internal/limits.d.ts +14 -0
  56. package/dist/internal/limits.d.ts.map +1 -0
  57. package/dist/internal/limits.js +43 -0
  58. package/dist/internal/limits.js.map +1 -0
  59. package/dist/internal/nodes.d.ts +121 -0
  60. package/dist/internal/nodes.d.ts.map +1 -0
  61. package/dist/internal/nodes.js +277 -0
  62. package/dist/internal/nodes.js.map +1 -0
  63. package/dist/internal/patterns.js +110 -0
  64. package/dist/internal/patterns.js.map +1 -0
  65. package/dist/internal/policy-json.js +255 -0
  66. package/dist/internal/policy-json.js.map +1 -0
  67. package/dist/internal/policy-nodes.d.ts +61 -0
  68. package/dist/internal/policy-nodes.d.ts.map +1 -0
  69. package/dist/internal/policy-nodes.js +146 -0
  70. package/dist/internal/policy-nodes.js.map +1 -0
  71. package/dist/internal/policy.d.ts +42 -0
  72. package/dist/internal/policy.d.ts.map +1 -0
  73. package/dist/internal/policy.js +124 -0
  74. package/dist/internal/policy.js.map +1 -0
  75. package/dist/internal/read.js +39 -0
  76. package/dist/internal/read.js.map +1 -0
  77. package/dist/internal/resource.js +115 -0
  78. package/dist/internal/resource.js.map +1 -0
  79. package/dist/internal/rule.d.ts +26 -0
  80. package/dist/internal/rule.d.ts.map +1 -0
  81. package/dist/internal/rule.js +93 -0
  82. package/dist/internal/rule.js.map +1 -0
  83. package/dist/internal/search.d.ts +22 -0
  84. package/dist/internal/search.d.ts.map +1 -0
  85. package/dist/internal/search.js +274 -0
  86. package/dist/internal/search.js.map +1 -0
  87. package/dist/internal/suggest.js +46 -0
  88. package/dist/internal/suggest.js.map +1 -0
  89. package/package.json +3 -4
  90. package/src/Authorizer.ts +67 -583
  91. package/src/Condition.ts +339 -2095
  92. package/src/Decision.ts +6 -5
  93. package/src/Domain.ts +12 -69
  94. package/src/HttpApi.ts +72 -10
  95. package/src/Limits.ts +132 -0
  96. package/src/Policy.ts +386 -2772
  97. package/src/Resource.ts +7 -1548
  98. package/src/index.ts +3 -9
  99. package/src/internal/algebra.ts +482 -0
  100. package/src/internal/analysis.ts +478 -0
  101. package/src/internal/authorizer.ts +418 -0
  102. package/src/internal/bind.ts +596 -0
  103. package/src/internal/catalog.ts +534 -0
  104. package/src/internal/decide.ts +402 -0
  105. package/src/internal/domain.ts +66 -0
  106. package/src/internal/json.ts +260 -0
  107. package/src/internal/limits.ts +78 -0
  108. package/src/internal/nodes.ts +404 -0
  109. package/src/internal/patterns.ts +211 -0
  110. package/src/internal/policy-json.ts +484 -0
  111. package/src/internal/policy-nodes.ts +207 -0
  112. package/src/internal/policy.ts +264 -0
  113. package/src/internal/read.ts +55 -0
  114. package/src/internal/resource.ts +180 -0
  115. package/src/internal/rule.ts +181 -0
  116. package/src/internal/search.ts +541 -0
  117. package/src/internal/suggest.ts +104 -0
  118. package/dist/Principal.d.ts +0 -35
  119. package/dist/Principal.d.ts.map +0 -1
  120. package/dist/Principal.js +0 -37
  121. package/dist/Principal.js.map +0 -1
  122. package/dist/Rule.d.ts +0 -275
  123. package/dist/Rule.d.ts.map +0 -1
  124. package/dist/Rule.js +0 -257
  125. package/dist/Rule.js.map +0 -1
  126. package/src/Principal.ts +0 -41
  127. package/src/Rule.ts +0 -683
@@ -0,0 +1,286 @@
1
+ import { isNode } from "./nodes.js";
2
+ import { accepts, anyKinds, lookup } from "./catalog.js";
3
+ import { fitsCatalog, parse } from "./patterns.js";
4
+ import { withSuggestion } from "./suggest.js";
5
+ import * as Result from "effect/Result";
6
+ import * as Arr from "effect/Array";
7
+ import * as Option from "effect/Option";
8
+ import * as HashMap from "effect/HashMap";
9
+ import * as MutableHashMap from "effect/MutableHashMap";
10
+ // src/internal/bind.ts
11
+ /**
12
+ * The binding of rules to a domain. It is the one check of a rule, for a rule
13
+ * from the builder, from `Policy.rule`, and from JSON alike. It reads the
14
+ * catalogs of the resources and of the principal, and it gives one issue for
15
+ * each problem, with the path of the problem and, for a name with a mistake,
16
+ * the name that it may mean.
17
+ */
18
+ const makeMemo = () => ({
19
+ suggestions: MutableHashMap.empty(),
20
+ fits: MutableHashMap.empty()
21
+ });
22
+ /**
23
+ * The most names that one binding makes a suggestion for. A suggestion
24
+ * compares the name with every name of a catalog, so a policy with very many
25
+ * wrong names would take a long time.
26
+ */
27
+ const maxSuggestions = 32;
28
+ /** Adds a suggestion to a message, with the memo of the binding. */
29
+ const suggestIn = (memo, message, name, scope, names) => {
30
+ const key = `${scope}\u0000${name}`;
31
+ const known = MutableHashMap.get(memo.suggestions, key);
32
+ if (Option.isSome(known)) return known.value;
33
+ if (MutableHashMap.size(memo.suggestions) >= maxSuggestions) return message;
34
+ const made = withSuggestion(message, name, names());
35
+ MutableHashMap.set(memo.suggestions, key, made);
36
+ return made;
37
+ };
38
+ /**
39
+ * Tests if a field may hold a value of the type of a fixed value. A test with
40
+ * `null` is a test that the field is missing, so `null` always fits. A number
41
+ * fits any field of numbers: a test of an integer field with `1.5` or
42
+ * `Infinity` has a meaning, as `lt` shows.
43
+ */
44
+ const fitsLiteral = (kinds, value) => value === null || typeof value === "number" && kinds.number !== "none" || accepts(kinds, value);
45
+ /**
46
+ * Tests if two fields may hold one scalar. A principal field that is `null`
47
+ * or that holds an object is a missing value, and a test with a missing value
48
+ * is false, so only scalars count.
49
+ */
50
+ const meet = (left, right) => left.string && right.string || left.boolean && right.boolean || left.number !== "none" && right.number !== "none" || Arr.some(left.literals, (value) => accepts(right, value)) || Arr.some(right.literals, (value) => accepts(left, value));
51
+ const hasStrings = (kinds) => kinds.string || Arr.some(kinds.literals, (value) => typeof value === "string");
52
+ const hasNumbers = (kinds) => kinds.number !== "none" || Arr.some(kinds.literals, (value) => typeof value === "number");
53
+ /** Tests if a field may hold a value with an order: a number or a string. */
54
+ const isOrdered = (kinds) => hasStrings(kinds) || hasNumbers(kinds);
55
+ /** Tests if two fields may hold values of one ordered type. */
56
+ const orderedMeet = (left, right) => hasStrings(left) && hasStrings(right) || hasNumbers(left) && hasNumbers(right);
57
+ /** The texts of the fields of a catalog that have only names, for suggestions. */
58
+ const namesOf = (catalog) => Arr.map(Arr.filter(catalog.fields, (field) => Arr.every(field.path, (segment) => typeof segment === "string")), (field) => field.text);
59
+ /** The issue of a field that is not a field of every resource of the rule. */
60
+ const fieldIssue = (context, field, at) => {
61
+ const message = `The field "${field}" is not a field of every resource of the rule`;
62
+ if (context.all || context.named.length === 0) return [{
63
+ path: at,
64
+ issue: message
65
+ }];
66
+ const without = Arr.filter(context.named, (descriptor) => lookup(descriptor.catalog, field) === void 0);
67
+ return without.length === 0 ? [] : [{
68
+ path: at,
69
+ issue: suggestIn(context.memo, message, field, without[0].kind, () => namesOf(without[0].catalog))
70
+ }];
71
+ };
72
+ /**
73
+ * The issues of a field pattern: a pattern that does not parse, or that
74
+ * matches no field of the resources of the rule, as a pattern with a mistake
75
+ * in a name does.
76
+ */
77
+ const patternIssues = (memo, known, pattern, at) => Result.match(parse(pattern), {
78
+ onFailure: (issue) => [{
79
+ path: at,
80
+ issue
81
+ }],
82
+ onSuccess: (parsed) => known.length === 0 || Arr.some(known, (descriptor) => {
83
+ const key = `${descriptor.kind}\u0000${pattern}`;
84
+ return Option.getOrElse(MutableHashMap.get(memo.fits, key), () => {
85
+ const fits = fitsCatalog(parsed, descriptor.catalog);
86
+ MutableHashMap.set(memo.fits, key, fits);
87
+ return fits;
88
+ });
89
+ }) ? [] : [{
90
+ path: at,
91
+ issue: `The field pattern "${pattern}" matches no field of the resources of the rule`
92
+ }]
93
+ });
94
+ /** The fields of the resources that a test of the rule may read. */
95
+ const fieldsAt = (context, field) => Arr.flatMap(context.applicable, (descriptor) => {
96
+ const found = lookup(descriptor.catalog, field);
97
+ return found === void 0 ? [] : [found];
98
+ });
99
+ /** The field of the principal that an operand names, or the issue when there is none. */
100
+ const principalField = (context, ref, at) => {
101
+ const found = lookup(context.domain.principalCatalog, ref);
102
+ return found !== void 0 ? found : {
103
+ path: at,
104
+ issue: suggestIn(context.memo, `The principal has no field "${ref}"`, ref, "~principal", () => namesOf(context.domain.principalCatalog))
105
+ };
106
+ };
107
+ const isIssue = (value) => "issue" in value;
108
+ /** The issues of one operand of a test of a field. */
109
+ const operandIssues = (context, field, ordered, operand, at) => {
110
+ const fields = fieldsAt(context, field);
111
+ if (operand._tag === "Literal") {
112
+ const value = operand.value;
113
+ if (ordered && typeof value !== "string" && typeof value !== "number") return [{
114
+ path: at,
115
+ issue: `The field "${field}" has an ordered test with a value that has no order`
116
+ }];
117
+ return [...Arr.some(fields, (found) => !fitsLiteral(found.kinds, value)) ? [{
118
+ path: at,
119
+ issue: `The field "${field}" can not hold the value ${JSON.stringify(value)}`
120
+ }] : [], ...ordered && Arr.some(fields, (found) => !isOrdered(found.kinds)) ? [{
121
+ path: at,
122
+ issue: `The field "${field}" has an ordered test with a value that has no order`
123
+ }] : []];
124
+ }
125
+ const principal = principalField(context, operand.path, at);
126
+ if (isIssue(principal)) return [principal];
127
+ return [...Arr.some(fields, (found) => !meet(found.kinds, principal.kinds)) ? [{
128
+ path: at,
129
+ issue: `The field "${field}" can not hold a value of the principal field "${operand.path}"`
130
+ }] : [], ...ordered && Arr.some(fields, (found) => !orderedMeet(found.kinds, principal.kinds)) ? [{
131
+ path: at,
132
+ issue: `The field "${field}" has an ordered test with a value that has no order`
133
+ }] : []];
134
+ };
135
+ /**
136
+ * Tests if the path of a resource may start with a prefix: it is as long as
137
+ * the prefix, and each fixed string of the prefix is the string of the path
138
+ * at its place, when the path has a string there.
139
+ */
140
+ const mayStartWith = (descriptor, strings) => (descriptor.path?.length ?? 0) >= strings.length && Arr.every(strings, (string, index) => {
141
+ const segment = descriptor.path?.[index];
142
+ return string === null || typeof segment !== "string" || segment === string;
143
+ });
144
+ /** The kinds of the part of the path of a resource at a place. */
145
+ const pathPartKinds = (descriptor, index) => {
146
+ const segment = descriptor.path?.[index];
147
+ return segment === void 0 || typeof segment === "string" ? segment : lookup(descriptor.catalog, segment.field)?.kinds ?? anyKinds;
148
+ };
149
+ /** The issues of the prefix of a path test. */
150
+ const underIssues = (context, prefix, at) => {
151
+ const strings = Arr.map(prefix, (operand) => operand._tag === "Literal" && typeof operand.value === "string" ? operand.value : null);
152
+ const candidates = Arr.filter(context.paths, (descriptor) => mayStartWith(descriptor, strings));
153
+ return Arr.flatMap(prefix, (operand, index) => {
154
+ const place = [
155
+ ...at,
156
+ "prefix",
157
+ index
158
+ ];
159
+ if (operand._tag === "Literal") return Arr.some(candidates, (descriptor) => {
160
+ const kinds = pathPartKinds(descriptor, index);
161
+ return typeof kinds === "object" && !fitsLiteral(kinds, operand.value);
162
+ }) ? [{
163
+ path: place,
164
+ issue: `The path part ${String(index)} can not hold the value ${JSON.stringify(operand.value)}`
165
+ }] : [];
166
+ const principal = principalField(context, operand.path, place);
167
+ if (isIssue(principal)) return [principal];
168
+ return Arr.some(candidates, (descriptor) => {
169
+ const kinds = pathPartKinds(descriptor, index);
170
+ return kinds === void 0 ? true : typeof kinds === "string" ? !accepts(principal.kinds, kinds) : !meet(kinds, principal.kinds);
171
+ }) ? [{
172
+ path: place,
173
+ issue: `The principal field "${operand.path}" can not hold a part of the path of the resource`
174
+ }] : [];
175
+ });
176
+ };
177
+ const notWhole = (at) => ({
178
+ path: at,
179
+ issue: "The condition is not a whole condition tree"
180
+ });
181
+ /**
182
+ * The issues of a condition. The constructors of the nodes checked the shape
183
+ * of each node, so this checks the names and the types against the domain. A
184
+ * node that the condition uses in many places is checked one time. The nodes
185
+ * it has seen are in a native `Set`, because it must find a node by the node
186
+ * itself: `Equal` compares nodes by structure.
187
+ */
188
+ const conditionIssues = (context, node, at, seen) => {
189
+ if (seen.has(node)) return [];
190
+ seen.add(node);
191
+ switch (node._tag) {
192
+ case "Compare": {
193
+ const exists = fieldIssue(context, node.field, [...at, "field"]);
194
+ return exists.length > 0 ? exists : operandIssues(context, node.field, node.op !== "eq" && node.op !== "ne", node.value, [...at, "value"]);
195
+ }
196
+ case "In": {
197
+ const exists = fieldIssue(context, node.field, [...at, "field"]);
198
+ return exists.length > 0 ? exists : Arr.flatMap(node.values, (operand, index) => operandIssues(context, node.field, false, operand, [
199
+ ...at,
200
+ "values",
201
+ index
202
+ ]));
203
+ }
204
+ case "Exists": return fieldIssue(context, node.field, [...at, "field"]);
205
+ case "Under": return underIssues(context, node.prefix, at);
206
+ case "And":
207
+ case "Or": return Arr.flatMap(node.conditions, (child, index) => conditionIssues(context, child, [
208
+ ...at,
209
+ "conditions",
210
+ index
211
+ ], seen));
212
+ case "Not": return conditionIssues(context, node.condition, [...at, "condition"], seen);
213
+ default: return [];
214
+ }
215
+ };
216
+ const hasAction = (descriptor, actions) => actions.includes("*") || Arr.some(actions, (action) => descriptor.actions.includes(action));
217
+ /**
218
+ * Checks one rule against a domain. It gives one issue for each problem. `at`
219
+ * is the path of the rule, for the paths of the issues.
220
+ */
221
+ const ruleIssues = (domain, rule, at, memo = makeMemo()) => {
222
+ const all = rule.resources.includes("*");
223
+ const names = Arr.dedupe(Arr.filter(rule.resources, (kind) => kind !== "*"));
224
+ const kinds = Array.from(HashMap.keys(domain.resources));
225
+ const named = Arr.flatMap(names, (kind) => Option.toArray(HashMap.get(domain.resources, kind)));
226
+ const known = all ? Array.from(HashMap.values(domain.resources)) : named;
227
+ const applicable = Arr.filter(named, (descriptor) => hasAction(descriptor, rule.actions));
228
+ const context = {
229
+ domain,
230
+ memo,
231
+ all,
232
+ named,
233
+ applicable,
234
+ paths: all ? Arr.filter(known, (descriptor) => hasAction(descriptor, rule.actions)) : applicable
235
+ };
236
+ const actions = Arr.dedupe(Arr.flatMap(known, (descriptor) => descriptor.actions));
237
+ return [
238
+ ...Arr.map(Arr.filter(names, (kind) => !HashMap.has(domain.resources, kind)), (kind) => ({
239
+ path: [...at, "resources"],
240
+ issue: suggestIn(memo, `There is no resource with the kind "${kind}"`, kind, "~kinds", () => kinds)
241
+ })),
242
+ ...Arr.map(Arr.filter(rule.actions, (action) => action !== "*" && !actions.includes(action)), (action) => ({
243
+ path: [...at, "actions"],
244
+ issue: suggestIn(memo, `The action "${action}" is not an action of these resources`, action, `~actions\u0000${names.join(",")}`, () => actions)
245
+ })),
246
+ ...Arr.flatMap(rule.fields ?? [], (field, index) => field.includes("*") ? patternIssues(memo, known, field, [
247
+ ...at,
248
+ "fields",
249
+ index
250
+ ]) : fieldIssue(context, field, [
251
+ ...at,
252
+ "fields",
253
+ index
254
+ ])),
255
+ ...rule.when === void 0 ? [] : isNode(rule.when) ? conditionIssues(context, rule.when, [...at, "when"], /* @__PURE__ */ new Set()) : [notWhole([...at, "when"])]
256
+ ];
257
+ };
258
+ /** Checks each rule of a policy tree against a domain. */
259
+ const treeIssues = (domain, root) => {
260
+ const rulesOf = (node, path, level) => {
261
+ if (level > 256) return [{
262
+ path,
263
+ issue: `A policy tree has more than ${String(256)} levels. The most is ${String(256)}.`
264
+ }];
265
+ switch (node._tag) {
266
+ case "Rules": return Arr.map(node.rules, (rule, index) => [rule, [
267
+ ...path,
268
+ "rules",
269
+ index
270
+ ]]);
271
+ case "All":
272
+ case "Any":
273
+ case "FirstOf": return Arr.flatMap(node.policies, (child, index) => rulesOf(child, [
274
+ ...path,
275
+ "policies",
276
+ index
277
+ ], level + 1));
278
+ case "Ceiling": return [...rulesOf(node.cap, [...path, "cap"], level + 1), ...rulesOf(node.policy, [...path, "policy"], level + 1)];
279
+ }
280
+ };
281
+ const memo = makeMemo();
282
+ return Arr.flatMap(rulesOf(root, [], 1), (found) => Array.isArray(found) ? ruleIssues(domain, found[0], found[1], memo) : [found]);
283
+ };
284
+ export { ruleIssues, treeIssues };
285
+
286
+ //# sourceMappingURL=bind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind.js","names":["Catalog.accepts","Catalog.lookup","Patterns.parse","Patterns.fitsCatalog","Catalog.anyKinds","Nodes.isNode"],"sources":["../../src/internal/bind.ts"],"sourcesContent":["/**\n * The binding of rules to a domain. It is the one check of a rule, for a rule\n * from the builder, from `Policy.rule`, and from JSON alike. It reads the\n * catalogs of the resources and of the principal, and it gives one issue for\n * each problem, with the path of the problem and, for a name with a mistake,\n * the name that it may mean.\n */\nimport * as Arr from \"effect/Array\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as MutableHashMap from \"effect/MutableHashMap\"\nimport * as Option from \"effect/Option\"\nimport * as Result from \"effect/Result\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Patterns from \"./patterns.ts\"\nimport { withSuggestion } from \"./suggest.ts\"\n\n/** One problem of a rule or a policy, with its path. */\nexport interface Issue {\n\treadonly path: ReadonlyArray<PropertyKey>\n\treadonly issue: string\n}\n\n/** The data of a rule that the binding reads. */\nexport interface RuleData {\n\treadonly actions: ReadonlyArray<string>\n\treadonly resources: ReadonlyArray<string>\n\treadonly when?: unknown\n\treadonly fields?: ReadonlyArray<string>\n}\n\n/**\n * The memo of one binding, for all its rules. A policy may name one wrong\n * field or one pattern in very many places, so the work on each is done one\n * time. It goes away when the binding ends.\n */\ninterface Memo {\n\t/** The suggestion for each name, by the place where it was looked for. */\n\treadonly suggestions: MutableHashMap.MutableHashMap<string, string>\n\t/** If a pattern matches a field of a kind, by the kind and the pattern. */\n\treadonly fits: MutableHashMap.MutableHashMap<string, boolean>\n}\n\nconst makeMemo = (): Memo => ({\n\tsuggestions: MutableHashMap.empty(),\n\tfits: MutableHashMap.empty(),\n})\n\n/**\n * The most names that one binding makes a suggestion for. A suggestion\n * compares the name with every name of a catalog, so a policy with very many\n * wrong names would take a long time.\n */\nconst maxSuggestions = 32\n\n/** Adds a suggestion to a message, with the memo of the binding. */\nconst suggestIn = (\n\tmemo: Memo,\n\tmessage: string,\n\tname: string,\n\tscope: string,\n\tnames: () => ReadonlyArray<string>,\n): string => {\n\tconst key = `${scope}\\u0000${name}`\n\tconst known = MutableHashMap.get(memo.suggestions, key)\n\tif (Option.isSome(known)) {\n\t\treturn known.value\n\t}\n\tif (MutableHashMap.size(memo.suggestions) >= maxSuggestions) {\n\t\treturn message\n\t}\n\tconst made = withSuggestion(message, name, names())\n\tMutableHashMap.set(memo.suggestions, key, made)\n\treturn made\n}\n\n/** The kinds that a rule may run on, from the view of its checks. */\ninterface Context {\n\treadonly domain: Domain.Any\n\treadonly memo: Memo\n\t/** The rule is for every resource (`\"*\"`). */\n\treadonly all: boolean\n\t/** The resources that the rule names and that the domain has. */\n\treadonly named: ReadonlyArray<Resource.Descriptor>\n\t/** The named resources that have one of the actions of the rule. */\n\treadonly applicable: ReadonlyArray<Resource.Descriptor>\n\t/**\n\t * The resources whose paths a path test may read: every resource with one\n\t * of the actions, for a rule for every resource.\n\t */\n\treadonly paths: ReadonlyArray<Resource.Descriptor>\n}\n\ntype Operand = Nodes.Operand\n\n/**\n * Tests if a field may hold a value of the type of a fixed value. A test with\n * `null` is a test that the field is missing, so `null` always fits. A number\n * fits any field of numbers: a test of an integer field with `1.5` or\n * `Infinity` has a meaning, as `lt` shows.\n */\nconst fitsLiteral = (kinds: Catalog.Kinds, value: unknown): boolean =>\n\tvalue === null ||\n\t(typeof value === \"number\" && kinds.number !== \"none\") ||\n\tCatalog.accepts(kinds, value)\n\n/**\n * Tests if two fields may hold one scalar. A principal field that is `null`\n * or that holds an object is a missing value, and a test with a missing value\n * is false, so only scalars count.\n */\nconst meet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(left.string && right.string) ||\n\t(left.boolean && right.boolean) ||\n\t(left.number !== \"none\" && right.number !== \"none\") ||\n\tArr.some(left.literals, (value) => Catalog.accepts(right, value)) ||\n\tArr.some(right.literals, (value) => Catalog.accepts(left, value))\n\nconst hasStrings = (kinds: Catalog.Kinds): boolean =>\n\tkinds.string || Arr.some(kinds.literals, (value) => typeof value === \"string\")\n\nconst hasNumbers = (kinds: Catalog.Kinds): boolean =>\n\tkinds.number !== \"none\" ||\n\tArr.some(kinds.literals, (value) => typeof value === \"number\")\n\n/** Tests if a field may hold a value with an order: a number or a string. */\nconst isOrdered = (kinds: Catalog.Kinds): boolean =>\n\thasStrings(kinds) || hasNumbers(kinds)\n\n/** Tests if two fields may hold values of one ordered type. */\nconst orderedMeet = (left: Catalog.Kinds, right: Catalog.Kinds): boolean =>\n\t(hasStrings(left) && hasStrings(right)) ||\n\t(hasNumbers(left) && hasNumbers(right))\n\n/** The texts of the fields of a catalog that have only names, for suggestions. */\nconst namesOf = (catalog: Catalog.Catalog): ReadonlyArray<string> =>\n\tArr.map(\n\t\tArr.filter(catalog.fields, (field) =>\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t\t),\n\t\t(field) => field.text,\n\t)\n\n/** The issue of a field that is not a field of every resource of the rule. */\nconst fieldIssue = (\n\tcontext: Context,\n\tfield: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst message = `The field \"${field}\" is not a field of every resource of the rule`\n\tif (context.all || context.named.length === 0) {\n\t\treturn [{ path: at, issue: message }]\n\t}\n\tconst without = Arr.filter(\n\t\tcontext.named,\n\t\t(descriptor) => Catalog.lookup(descriptor.catalog, field) === undefined,\n\t)\n\treturn without.length === 0\n\t\t? []\n\t\t: [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: suggestIn(context.memo, message, field, without[0]!.kind, () =>\n\t\t\t\t\t\tnamesOf(without[0]!.catalog),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t]\n}\n\n/**\n * The issues of a field pattern: a pattern that does not parse, or that\n * matches no field of the resources of the rule, as a pattern with a mistake\n * in a name does.\n */\nconst patternIssues = (\n\tmemo: Memo,\n\tknown: ReadonlyArray<Resource.Descriptor>,\n\tpattern: string,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> =>\n\tResult.match(Patterns.parse(pattern), {\n\t\tonFailure: (issue) => [{ path: at, issue }],\n\t\tonSuccess: (parsed) =>\n\t\t\t// A rule with no known resource has an issue for that already.\n\t\t\tknown.length === 0 ||\n\t\t\tArr.some(known, (descriptor) => {\n\t\t\t\tconst key = `${descriptor.kind}\\u0000${pattern}`\n\t\t\t\treturn Option.getOrElse(MutableHashMap.get(memo.fits, key), () => {\n\t\t\t\t\tconst fits = Patterns.fitsCatalog(parsed, descriptor.catalog)\n\t\t\t\t\tMutableHashMap.set(memo.fits, key, fits)\n\t\t\t\t\treturn fits\n\t\t\t\t})\n\t\t\t})\n\t\t\t\t? []\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field pattern \"${pattern}\" matches no field of the resources of the rule`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t})\n\n/** The fields of the resources that a test of the rule may read. */\nconst fieldsAt = (\n\tcontext: Context,\n\tfield: string,\n): ReadonlyArray<Catalog.Field> =>\n\tArr.flatMap(context.applicable, (descriptor) => {\n\t\tconst found = Catalog.lookup(descriptor.catalog, field)\n\t\treturn found === undefined ? [] : [found]\n\t})\n\n/** The field of the principal that an operand names, or the issue when there is none. */\nconst principalField = (\n\tcontext: Context,\n\tref: string,\n\tat: ReadonlyArray<PropertyKey>,\n): Catalog.Field | Issue => {\n\tconst found = Catalog.lookup(context.domain.principalCatalog, ref)\n\treturn found !== undefined\n\t\t? found\n\t\t: {\n\t\t\t\tpath: at,\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tcontext.memo,\n\t\t\t\t\t`The principal has no field \"${ref}\"`,\n\t\t\t\t\tref,\n\t\t\t\t\t\"~principal\",\n\t\t\t\t\t() => namesOf(context.domain.principalCatalog),\n\t\t\t\t),\n\t\t\t}\n}\n\nconst isIssue = (value: Catalog.Field | Issue): value is Issue =>\n\t\"issue\" in value\n\n/** The issues of one operand of a test of a field. */\nconst operandIssues = (\n\tcontext: Context,\n\tfield: string,\n\tordered: boolean,\n\toperand: Operand,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst fields = fieldsAt(context, field)\n\tif (operand._tag === \"Literal\") {\n\t\tconst value = operand.value\n\t\tif (ordered && typeof value !== \"string\" && typeof value !== \"number\") {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath: at,\n\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\treturn [\n\t\t\t...(Arr.some(fields, (found) => !fitsLiteral(found.kinds, value))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" can not hold the value ${JSON.stringify(value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t\t...(ordered && Arr.some(fields, (found) => !isOrdered(found.kinds))\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t\t]\n\t}\n\tconst principal = principalField(context, operand.path, at)\n\tif (isIssue(principal)) {\n\t\treturn [principal]\n\t}\n\treturn [\n\t\t...(Arr.some(fields, (found) => !meet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" can not hold a value of the principal field \"${operand.path}\"`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t\t...(ordered &&\n\t\tArr.some(fields, (found) => !orderedMeet(found.kinds, principal.kinds))\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: at,\n\t\t\t\t\t\tissue: `The field \"${field}\" has an ordered test with a value that has no order`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t]\n}\n\n/**\n * Tests if the path of a resource may start with a prefix: it is as long as\n * the prefix, and each fixed string of the prefix is the string of the path\n * at its place, when the path has a string there.\n */\nconst mayStartWith = (\n\tdescriptor: Resource.Descriptor,\n\tstrings: ReadonlyArray<string | null>,\n): boolean =>\n\t(descriptor.path?.length ?? 0) >= strings.length &&\n\tArr.every(strings, (string, index) => {\n\t\tconst segment = descriptor.path?.[index]\n\t\treturn string === null || typeof segment !== \"string\" || segment === string\n\t})\n\n/** The kinds of the part of the path of a resource at a place. */\nconst pathPartKinds = (\n\tdescriptor: Resource.Descriptor,\n\tindex: number,\n): Catalog.Kinds | string | undefined => {\n\tconst segment = descriptor.path?.[index]\n\treturn segment === undefined || typeof segment === \"string\"\n\t\t? segment\n\t\t: (Catalog.lookup(descriptor.catalog, segment.field)?.kinds ??\n\t\t\t\tCatalog.anyKinds)\n}\n\n/** The issues of the prefix of a path test. */\nconst underIssues = (\n\tcontext: Context,\n\tprefix: ReadonlyArray<Operand>,\n\tat: ReadonlyArray<PropertyKey>,\n): ReadonlyArray<Issue> => {\n\tconst strings = Arr.map(prefix, (operand) =>\n\t\toperand._tag === \"Literal\" && typeof operand.value === \"string\"\n\t\t\t? operand.value\n\t\t\t: null,\n\t)\n\t// The test can match only a path that starts with the fixed strings of\n\t// the prefix, so the other resources are left out.\n\tconst candidates = Arr.filter(context.paths, (descriptor) =>\n\t\tmayStartWith(descriptor, strings),\n\t)\n\treturn Arr.flatMap(prefix, (operand, index): ReadonlyArray<Issue> => {\n\t\tconst place = [...at, \"prefix\", index]\n\t\tif (operand._tag === \"Literal\") {\n\t\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\t\treturn typeof kinds === \"object\" && !fitsLiteral(kinds, operand.value)\n\t\t\t})\n\t\t\t\t? [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\t\tissue: `The path part ${String(index)} can not hold the value ${JSON.stringify(operand.value)}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t: []\n\t\t}\n\t\tconst principal = principalField(context, operand.path, place)\n\t\tif (isIssue(principal)) {\n\t\t\treturn [principal]\n\t\t}\n\t\treturn Arr.some(candidates, (descriptor) => {\n\t\t\tconst kinds = pathPartKinds(descriptor, index)\n\t\t\treturn kinds === undefined\n\t\t\t\t? true\n\t\t\t\t: typeof kinds === \"string\"\n\t\t\t\t\t? !Catalog.accepts(principal.kinds, kinds)\n\t\t\t\t\t: !meet(kinds, principal.kinds)\n\t\t})\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: place,\n\t\t\t\t\t\tissue: `The principal field \"${operand.path}\" can not hold a part of the path of the resource`,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []\n\t})\n}\n\nconst notWhole = (at: ReadonlyArray<PropertyKey>): Issue => ({\n\tpath: at,\n\tissue: \"The condition is not a whole condition tree\",\n})\n\n/**\n * The issues of a condition. The constructors of the nodes checked the shape\n * of each node, so this checks the names and the types against the domain. A\n * node that the condition uses in many places is checked one time. The nodes\n * it has seen are in a native `Set`, because it must find a node by the node\n * itself: `Equal` compares nodes by structure.\n */\nconst conditionIssues = (\n\tcontext: Context,\n\tnode: Nodes.Node,\n\tat: ReadonlyArray<PropertyKey>,\n\tseen: Set<Nodes.Node>,\n): ReadonlyArray<Issue> => {\n\tif (seen.has(node)) {\n\t\treturn []\n\t}\n\tseen.add(node)\n\tswitch (node._tag) {\n\t\tcase \"Compare\": {\n\t\t\tconst exists = fieldIssue(context, node.field, [...at, \"field\"])\n\t\t\treturn exists.length > 0\n\t\t\t\t? exists\n\t\t\t\t: operandIssues(\n\t\t\t\t\t\tcontext,\n\t\t\t\t\t\tnode.field,\n\t\t\t\t\t\tnode.op !== \"eq\" && node.op !== \"ne\",\n\t\t\t\t\t\tnode.value,\n\t\t\t\t\t\t[...at, \"value\"],\n\t\t\t\t\t)\n\t\t}\n\t\tcase \"In\": {\n\t\t\tconst exists = fieldIssue(context, node.field, [...at, \"field\"])\n\t\t\treturn exists.length > 0\n\t\t\t\t? exists\n\t\t\t\t: Arr.flatMap(node.values, (operand, index) =>\n\t\t\t\t\t\toperandIssues(context, node.field, false, operand, [\n\t\t\t\t\t\t\t...at,\n\t\t\t\t\t\t\t\"values\",\n\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t]),\n\t\t\t\t\t)\n\t\t}\n\t\tcase \"Exists\":\n\t\t\treturn fieldIssue(context, node.field, [...at, \"field\"])\n\t\tcase \"Under\":\n\t\t\treturn underIssues(context, node.prefix, at)\n\t\tcase \"And\":\n\t\tcase \"Or\":\n\t\t\treturn Arr.flatMap(node.conditions, (child, index) =>\n\t\t\t\tconditionIssues(context, child, [...at, \"conditions\", index], seen),\n\t\t\t)\n\t\tcase \"Not\":\n\t\t\treturn conditionIssues(\n\t\t\t\tcontext,\n\t\t\t\tnode.condition,\n\t\t\t\t[...at, \"condition\"],\n\t\t\t\tseen,\n\t\t\t)\n\t\tdefault:\n\t\t\treturn []\n\t}\n}\n\nconst hasAction = (\n\tdescriptor: Resource.Descriptor,\n\tactions: ReadonlyArray<string>,\n): boolean =>\n\tactions.includes(\"*\") ||\n\tArr.some(actions, (action) => descriptor.actions.includes(action))\n\n/**\n * Checks one rule against a domain. It gives one issue for each problem. `at`\n * is the path of the rule, for the paths of the issues.\n */\nexport const ruleIssues = (\n\tdomain: Domain.Any,\n\trule: RuleData,\n\tat: ReadonlyArray<PropertyKey>,\n\tmemo: Memo = makeMemo(),\n): ReadonlyArray<Issue> => {\n\tconst all = rule.resources.includes(\"*\")\n\t// Each kind is checked one time, also when the list names it again.\n\tconst names = Arr.dedupe(Arr.filter(rule.resources, (kind) => kind !== \"*\"))\n\tconst kinds = Array.from(HashMap.keys(domain.resources))\n\tconst named = Arr.flatMap(names, (kind) =>\n\t\tOption.toArray(HashMap.get(domain.resources, kind)),\n\t)\n\tconst known = all ? Array.from(HashMap.values(domain.resources)) : named\n\t// The rule never runs on a kind without one of its actions, so the types\n\t// of its tests are checked only on the kinds with one.\n\tconst applicable = Arr.filter(named, (descriptor) =>\n\t\thasAction(descriptor, rule.actions),\n\t)\n\tconst context: Context = {\n\t\tdomain,\n\t\tmemo,\n\t\tall,\n\t\tnamed,\n\t\tapplicable,\n\t\tpaths: all\n\t\t\t? Arr.filter(known, (descriptor) => hasAction(descriptor, rule.actions))\n\t\t\t: applicable,\n\t}\n\tconst actions = Arr.dedupe(\n\t\tArr.flatMap(known, (descriptor) => descriptor.actions),\n\t)\n\treturn [\n\t\t...Arr.map(\n\t\t\tArr.filter(names, (kind) => !HashMap.has(domain.resources, kind)),\n\t\t\t(kind): Issue => ({\n\t\t\t\tpath: [...at, \"resources\"],\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tmemo,\n\t\t\t\t\t`There is no resource with the kind \"${kind}\"`,\n\t\t\t\t\tkind,\n\t\t\t\t\t\"~kinds\",\n\t\t\t\t\t() => kinds,\n\t\t\t\t),\n\t\t\t}),\n\t\t),\n\t\t...Arr.map(\n\t\t\tArr.filter(\n\t\t\t\trule.actions,\n\t\t\t\t(action) => action !== \"*\" && !actions.includes(action),\n\t\t\t),\n\t\t\t(action): Issue => ({\n\t\t\t\tpath: [...at, \"actions\"],\n\t\t\t\tissue: suggestIn(\n\t\t\t\t\tmemo,\n\t\t\t\t\t`The action \"${action}\" is not an action of these resources`,\n\t\t\t\t\taction,\n\t\t\t\t\t`~actions\\u0000${names.join(\",\")}`,\n\t\t\t\t\t() => actions,\n\t\t\t\t),\n\t\t\t}),\n\t\t),\n\t\t...Arr.flatMap(rule.fields ?? [], (field, index) =>\n\t\t\tfield.includes(\"*\")\n\t\t\t\t? patternIssues(memo, known, field, [...at, \"fields\", index])\n\t\t\t\t: fieldIssue(context, field, [...at, \"fields\", index]),\n\t\t),\n\t\t...(rule.when === undefined\n\t\t\t? []\n\t\t\t: Nodes.isNode(rule.when)\n\t\t\t\t? conditionIssues(context, rule.when, [...at, \"when\"], new Set())\n\t\t\t\t: [notWhole([...at, \"when\"])]),\n\t]\n}\n\n/** The JSON form of a policy tree, or a policy tree, as the binding reads it. */\ntype TreeNode =\n\t| { readonly _tag: \"Rules\"; readonly rules: ReadonlyArray<RuleData> }\n\t| {\n\t\t\treadonly _tag: \"All\" | \"Any\" | \"FirstOf\"\n\t\t\treadonly policies: ReadonlyArray<TreeNode>\n\t }\n\t| {\n\t\t\treadonly _tag: \"Ceiling\"\n\t\t\treadonly cap: TreeNode\n\t\t\treadonly policy: TreeNode\n\t }\n\n/** Checks each rule of a policy tree against a domain. */\nexport const treeIssues = (\n\tdomain: Domain.Any,\n\troot: TreeNode,\n): ReadonlyArray<Issue> => {\n\t// The rules come out of the tree first, and each one is checked after.\n\t// A check of a condition goes down its levels, so a check inside the walk\n\t// of the tree would go down the levels of both.\n\tconst rulesOf = (\n\t\tnode: TreeNode,\n\t\tpath: ReadonlyArray<PropertyKey>,\n\t\tlevel: number,\n\t): ReadonlyArray<Issue | readonly [RuleData, ReadonlyArray<PropertyKey>]> => {\n\t\tif (level > Limits.policyDepth) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tpath,\n\t\t\t\t\tissue: `A policy tree has more than ${String(Limits.policyDepth)} levels. The most is ${String(Limits.policyDepth)}.`,\n\t\t\t\t},\n\t\t\t]\n\t\t}\n\t\tswitch (node._tag) {\n\t\t\tcase \"Rules\":\n\t\t\t\treturn Arr.map(\n\t\t\t\t\tnode.rules,\n\t\t\t\t\t(rule, index) => [rule, [...path, \"rules\", index]] as const,\n\t\t\t\t)\n\t\t\tcase \"All\":\n\t\t\tcase \"Any\":\n\t\t\tcase \"FirstOf\":\n\t\t\t\treturn Arr.flatMap(node.policies, (child, index) =>\n\t\t\t\t\trulesOf(child, [...path, \"policies\", index], level + 1),\n\t\t\t\t)\n\t\t\tcase \"Ceiling\":\n\t\t\t\treturn [\n\t\t\t\t\t...rulesOf(node.cap, [...path, \"cap\"], level + 1),\n\t\t\t\t\t...rulesOf(node.policy, [...path, \"policy\"], level + 1),\n\t\t\t\t]\n\t\t}\n\t}\n\tconst memo = makeMemo()\n\treturn Arr.flatMap(rulesOf(root, [], 1), (found) =>\n\t\tArray.isArray(found)\n\t\t\t? ruleIssues(domain, found[0], found[1], memo)\n\t\t\t: [found as Issue],\n\t)\n}\n"],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import * as HashMap from "effect/HashMap";
2
+ import * as SchemaAST from "effect/SchemaAST";
3
+ /** The kinds of values that a field may hold. */
4
+ export interface Kinds {
5
+ /** The field may be `null`, `undefined` or missing. */
6
+ readonly null: boolean;
7
+ readonly boolean: boolean;
8
+ /** The field may hold any string. */
9
+ readonly string: boolean;
10
+ /** The numbers that the field may hold. */
11
+ readonly number: "none" | "integer" | "finite" | "any";
12
+ /** The field may hold an object, or a value that is not a scalar. */
13
+ readonly object: boolean;
14
+ /** Single values that the field may hold, other than the ones above. */
15
+ readonly literals: ReadonlyArray<string | number | boolean>;
16
+ }
17
+ /** A part of a field path that stands for any key of a record. */
18
+ export interface Key {
19
+ readonly _tag: "Key";
20
+ /** The Schemas of the keys. A key must fit one of them. */
21
+ readonly parameters: ReadonlyArray<SchemaAST.AST>;
22
+ }
23
+ /** One part of a field path: a name, or any key of a record. */
24
+ export type Segment = string | Key;
25
+ /** One field of a catalog. */
26
+ export interface Field {
27
+ readonly path: ReadonlyArray<Segment>;
28
+ /** The path with dots, and `*` for a key of a record. */
29
+ readonly text: string;
30
+ readonly kinds: Kinds;
31
+ /**
32
+ * The field holds a value that a list of fields shows as one field: a
33
+ * scalar, a record, an array, or a struct at the last part a path may have.
34
+ */
35
+ readonly listed: boolean;
36
+ /** The field may hold parts that the Schema does not name, as a record may. */
37
+ readonly open: boolean;
38
+ }
39
+ /** A place in the tree of a catalog, with the places below it. */
40
+ export interface Branch {
41
+ /** The field at this place. The root has none. */
42
+ readonly field: Field | undefined;
43
+ /** The places below, by name, in the order of the Schema. */
44
+ readonly children: ReadonlyArray<readonly [string, Branch]>;
45
+ readonly names: HashMap.HashMap<string, Branch>;
46
+ /** The place for the keys of a record, when the value may be a record. */
47
+ readonly key: Key | undefined;
48
+ readonly keyBranch: Branch | undefined;
49
+ }
50
+ /** The catalog of the fields of a Schema. */
51
+ export interface Catalog {
52
+ /** Every field, a field before the fields below it. */
53
+ readonly fields: ReadonlyArray<Field>;
54
+ readonly root: Branch;
55
+ }
56
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","names":[],"sources":["../../src/internal/catalog.ts"],"mappings":";;;iBAsBiB;;WAEP;WACA;;WAEA;;WAEA;;WAEA;;WAEA,UAAU;;;iBA4EH;WACP;;WAEA,YAAY,cAAc,UAAU;;;YAIlC,mBAAmB;;iBAGd;WACP,MAAM,cAAc;;WAEpB;WACA,OAAO;;;;;WAKP;;WAEA;;;iBAIO;;WAEP,OAAO;;WAEP,UAAU,gCAAgC;WAC1C,OAAO,QAAQ,gBAAgB;;WAE/B,KAAK;WACL,WAAW;;;iBAIJ;;WAEP,QAAQ,cAAc;WACtB,MAAM"}