@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,292 @@
1
+ import { catalogFields, exceeded } from "./limits.js";
2
+ import * as Schema from "effect/Schema";
3
+ import * as Arr from "effect/Array";
4
+ import { hasProperty } from "effect/Predicate";
5
+ import * as HashSet from "effect/HashSet";
6
+ import * as Option from "effect/Option";
7
+ import * as HashMap from "effect/HashMap";
8
+ import * as SchemaAST from "effect/SchemaAST";
9
+ // src/internal/catalog.ts
10
+ /**
11
+ * The catalog of the fields of a Schema. One walk over the Schema makes it,
12
+ * and every question about the fields of a resource or of the principal reads
13
+ * it: which names a rule may use, which values a field may hold, which fields
14
+ * a pattern fits, and which fields a list of permitted fields shows.
15
+ *
16
+ * The catalog describes the shape of the values, not the checks of the
17
+ * Schema. A check, such as a minimum length, only keeps some values out, so a
18
+ * catalog that leaves the checks out holds every real value and more. An
19
+ * answer that holds for all of these values, as `All` and `None` of a
20
+ * coverage do, then holds for the real values too.
21
+ */
22
+ /** No value: the kinds of a field of `Schema.Never`. */
23
+ const noKinds = {
24
+ null: false,
25
+ boolean: false,
26
+ string: false,
27
+ number: "none",
28
+ object: false,
29
+ literals: []
30
+ };
31
+ /** Any value. */
32
+ const anyKinds = {
33
+ null: true,
34
+ boolean: true,
35
+ string: true,
36
+ number: "any",
37
+ object: true,
38
+ literals: []
39
+ };
40
+ const nullKinds = {
41
+ ...noKinds,
42
+ null: true
43
+ };
44
+ const objectKinds = {
45
+ ...noKinds,
46
+ object: true
47
+ };
48
+ const numberOrder = [
49
+ "none",
50
+ "integer",
51
+ "finite",
52
+ "any"
53
+ ];
54
+ /** The kinds of a field that may hold a value of `left` or of `right`. */
55
+ const joinKinds = (left, right) => ({
56
+ null: left.null || right.null,
57
+ boolean: left.boolean || right.boolean,
58
+ string: left.string || right.string,
59
+ number: numberOrder.indexOf(left.number) >= numberOrder.indexOf(right.number) ? left.number : right.number,
60
+ object: left.object || right.object,
61
+ literals: Arr.dedupe([...left.literals, ...right.literals])
62
+ });
63
+ /** Tests if a number fits the numbers of a field. */
64
+ const fitsNumber = (number, value) => {
65
+ switch (number) {
66
+ case "none": return false;
67
+ case "integer": return Number.isInteger(value);
68
+ case "finite": return Number.isFinite(value);
69
+ case "any": return true;
70
+ }
71
+ };
72
+ /**
73
+ * Tests if a field with these kinds may hold a value. A missing value and
74
+ * `undefined` are `null`.
75
+ */
76
+ const accepts = (kinds, value) => {
77
+ if (value === null || value === void 0) return kinds.null;
78
+ switch (typeof value) {
79
+ case "boolean": return kinds.boolean || kinds.literals.includes(value);
80
+ case "string": return kinds.string || kinds.literals.includes(value);
81
+ case "number": return fitsNumber(kinds.number, value) || kinds.literals.includes(value);
82
+ default: return kinds.object;
83
+ }
84
+ };
85
+ /**
86
+ * Tests if a key of an object fits the Schemas of the keys of a record. A key
87
+ * of an object is a string, also a key that a number Schema takes: `{ 1: "a" }`
88
+ * has the key `"1"`. So a key that is the text of a number is tried as that
89
+ * number too.
90
+ */
91
+ const keyFits = (key, name) => Arr.some(key.parameters, (parameter) => {
92
+ const is = Schema.is(Schema.make(parameter));
93
+ return is(name) || name !== "" && String(Number(name)) === name && is(Number(name));
94
+ });
95
+ /**
96
+ * Gives the struct of a `Schema.Class`. A class is a declaration with one
97
+ * struct as its type parameter, and Effect marks it with a `"~constructor"`
98
+ * annotation, also through more codec steps, as in
99
+ * `Schema.fromJsonString(User)`. Other declarations, such as `Schema.Option`
100
+ * or `Schema.ReadonlyMap`, have no such mark, and the fields of their type
101
+ * parameter are not fields of the value.
102
+ */
103
+ const classStruct = (ast) => {
104
+ const [inner] = ast.typeParameters;
105
+ return ast.annotations?.["~constructor"] !== void 0 && ast.typeParameters.length === 1 && inner?._tag === "Objects" ? inner : void 0;
106
+ };
107
+ /**
108
+ * Gives the Schemas that a value at one place may fit: it opens unions,
109
+ * `Schema.suspend` and classes. The open suspend nodes are in a native `Set`,
110
+ * because the walk must find a node by the node itself: `Equal` compares
111
+ * Schema nodes by structure, and a recursive Schema has no end. A suspend
112
+ * node that gives itself again, with no step on the path, gives nothing.
113
+ */
114
+ const alternatives = (ast, open) => {
115
+ switch (ast._tag) {
116
+ case "Union": return Arr.flatMap(ast.types, (type) => alternatives(type, open));
117
+ case "Suspend": return open.has(ast) ? [] : alternatives(ast.thunk(), /* @__PURE__ */ new Set([...open, ast]));
118
+ case "Declaration": {
119
+ const inner = classStruct(ast);
120
+ return inner === void 0 ? [ast] : alternatives(inner, open);
121
+ }
122
+ default: return [ast];
123
+ }
124
+ };
125
+ const hasCheck = (checks, id) => Arr.some(checks, (check) => check.annotations?.representation?.id === id || check._tag === "FilterGroup" && hasCheck(check.checks, id));
126
+ /** A struct with named fields, or with the keys of a record. */
127
+ const isStruct = (ast) => ast._tag === "Objects" && (ast.propertySignatures.length > 0 || ast.indexSignatures.length > 0);
128
+ /** The kinds of the values of a Schema that is not a struct. */
129
+ const kindsOf = (ast) => {
130
+ switch (ast._tag) {
131
+ case "Null":
132
+ case "Undefined": return nullKinds;
133
+ case "Never": return noKinds;
134
+ case "String":
135
+ case "TemplateLiteral": return {
136
+ ...noKinds,
137
+ string: true
138
+ };
139
+ case "Boolean": return {
140
+ ...noKinds,
141
+ boolean: true
142
+ };
143
+ case "Number": {
144
+ const checks = ast.checks ?? [];
145
+ return {
146
+ ...noKinds,
147
+ number: hasCheck(checks, "effect/schema/isInt") ? "integer" : hasCheck(checks, "effect/schema/isFinite") ? "finite" : "any"
148
+ };
149
+ }
150
+ case "Literal": return typeof ast.literal === "bigint" ? objectKinds : {
151
+ ...noKinds,
152
+ literals: [ast.literal]
153
+ };
154
+ case "Enum": return {
155
+ ...noKinds,
156
+ literals: Arr.map(ast.enums, ([, value]) => value)
157
+ };
158
+ case "Arrays":
159
+ case "ObjectKeyword":
160
+ case "BigInt":
161
+ case "Symbol":
162
+ case "UniqueSymbol": return objectKinds;
163
+ case "Declaration": return HashSet.has(objectDeclarations, declarationId(ast)) ? objectKinds : anyKinds;
164
+ default: return anyKinds;
165
+ }
166
+ };
167
+ /**
168
+ * The ids of the declarations of Effect that hold only objects. Effect puts
169
+ * the id in the `representation` annotation. A declaration that is not in
170
+ * this list may hold a scalar, as `Schema.Json` may, so its kinds are any
171
+ * value.
172
+ */
173
+ const objectDeclarations = HashSet.make("effect/schema/Date", "effect/schema/URL", "effect/schema/Uint8Array", "effect/schema/Duration", "effect/schema/DateTimeUtc", "effect/schema/DateTimeZoned", "effect/schema/BigDecimal", "effect/schema/Redacted", "effect/schema/Cause", "effect/schema/Chunk", "effect/schema/HashMap", "effect/schema/HashSet", "effect/schema/ReadonlyMap", "effect/schema/ReadonlySet", "effect/schema/Option", "effect/schema/Result", "effect/schema/Exit", "effect/schema/File", "effect/schema/FormData", "effect/schema/RegExp", "effect/schema/URLSearchParams");
174
+ const declarationId = (ast) => {
175
+ const representation = ast.annotations?.["representation"];
176
+ return hasProperty(representation, "id") && typeof representation.id === "string" ? representation.id : void 0;
177
+ };
178
+ const textOf = (path) => Arr.map(path, (segment) => typeof segment === "string" ? segment : "*").join(".");
179
+ /** Gives the property of a struct with a name, if it has one. */
180
+ const propertyOf = (struct, name) => Option.getOrUndefined(Arr.findFirst(struct.propertySignatures, (signature) => signature.name === name));
181
+ /** The names of the fields of the structs, in the order that they first come. */
182
+ const namesOf = (structs) => Arr.dedupe(Arr.flatMap(structs, (struct) => Arr.flatMap(struct.propertySignatures, (signature) => {
183
+ const name = signature.name;
184
+ if (typeof name !== "string") return [];
185
+ if (name.includes(".")) throw new Error(`A field name may not have a dot: "${name}"`);
186
+ if (name === "") throw new Error("A field name may not be empty");
187
+ return [name];
188
+ })));
189
+ /**
190
+ * Makes the branch of a place, with the branches below it. `count` is the
191
+ * number of fields so far. It belongs to one walk, and it stops a Schema with
192
+ * very many fields before the walk is done.
193
+ */
194
+ const branchOf = (place, count) => {
195
+ const structs = Arr.filter(place.schemas, isStruct);
196
+ const values = Arr.filter(place.schemas, (schema) => !isStruct(schema));
197
+ const valueKinds = Arr.reduce(values, noKinds, (kinds, schema) => joinKinds(kinds, kindsOf(schema)));
198
+ const kinds = place.top ? anyKinds : joinKinds(joinKinds(valueKinds, structs.length > 0 ? objectKinds : noKinds), place.missing ? nullKinds : noKinds);
199
+ const records = Arr.filter(structs, (struct) => struct.indexSignatures.length > 0);
200
+ const last = place.path.length === 5;
201
+ const field = place.path.length === 0 ? void 0 : {
202
+ path: place.path,
203
+ text: textOf(place.path),
204
+ kinds,
205
+ listed: values.length > 0 || records.length > 0 || last && structs.length > 0,
206
+ open: records.length > 0 || structs.length > 0 || valueKinds.object || place.top
207
+ };
208
+ if (field !== void 0) {
209
+ count.size += 1;
210
+ if (count.size > 1e4) throw exceeded("A Schema", "catalogFields", "fields", count.size, catalogFields);
211
+ }
212
+ if (last) return {
213
+ field,
214
+ children: [],
215
+ names: HashMap.empty(),
216
+ key: void 0,
217
+ keyBranch: void 0
218
+ };
219
+ const children = Arr.map(namesOf(structs), (name) => {
220
+ const properties = Arr.flatMap(structs, (struct) => {
221
+ const property = propertyOf(struct, name);
222
+ return property === void 0 ? [] : [property];
223
+ });
224
+ const below = {
225
+ path: [...place.path, name],
226
+ schemas: Arr.flatMap(properties, (property) => alternatives(property.type, /* @__PURE__ */ new Set())),
227
+ missing: place.missing || values.length > 0 || Arr.some(properties, (property) => SchemaAST.isOptional(property.type)),
228
+ top: place.top || valueKinds.object || Arr.some(structs, (struct) => propertyOf(struct, name) === void 0)
229
+ };
230
+ return [name, branchOf(below, count)];
231
+ });
232
+ const names = HashMap.fromIterable(children);
233
+ const signatures = Arr.flatMap(records, (struct) => struct.indexSignatures);
234
+ if (signatures.length === 0) return {
235
+ field,
236
+ children,
237
+ names,
238
+ key: void 0,
239
+ keyBranch: void 0
240
+ };
241
+ const key = {
242
+ _tag: "Key",
243
+ parameters: Arr.map(signatures, (signature) => signature.parameter)
244
+ };
245
+ return {
246
+ field,
247
+ children,
248
+ names,
249
+ key,
250
+ keyBranch: branchOf({
251
+ path: [...place.path, key],
252
+ schemas: Arr.flatMap(signatures, (signature) => alternatives(signature.type, /* @__PURE__ */ new Set())),
253
+ missing: true,
254
+ top: place.top || valueKinds.object || records.length < structs.length
255
+ }, count)
256
+ };
257
+ };
258
+ const fieldsOf = (branch) => [
259
+ ...branch.field === void 0 ? [] : [branch.field],
260
+ ...Arr.flatMap(branch.children, ([, child]) => fieldsOf(child)),
261
+ ...branch.keyBranch === void 0 ? [] : fieldsOf(branch.keyBranch)
262
+ ];
263
+ /**
264
+ * Makes the catalog of a Schema. It throws when a field name has a dot or is
265
+ * empty, and when the Schema has more fields than `catalogFields`.
266
+ */
267
+ const catalogOf = (ast) => {
268
+ const root = branchOf({
269
+ path: [],
270
+ schemas: alternatives(ast, /* @__PURE__ */ new Set()),
271
+ missing: false,
272
+ top: false
273
+ }, { size: 0 });
274
+ return {
275
+ fields: fieldsOf(root),
276
+ root
277
+ };
278
+ };
279
+ /** Gives the field of a catalog at a path with dots, if the catalog has it. */
280
+ const lookup = (catalog, path) => {
281
+ const names = path.split(".", 6);
282
+ if (names.length > 5) return;
283
+ return Arr.reduce(names, catalog.root, (current, name) => {
284
+ if (current === void 0) return;
285
+ const named = HashMap.get(current.names, name);
286
+ if (Option.isSome(named)) return named.value;
287
+ return current.key !== void 0 && keyFits(current.key, name) ? current.keyBranch : void 0;
288
+ })?.field;
289
+ };
290
+ export { accepts, anyKinds, catalogOf, fitsNumber, joinKinds, keyFits, lookup, noKinds };
291
+
292
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","names":["Limits.exceeded","Limits.catalogFields"],"sources":["../../src/internal/catalog.ts"],"sourcesContent":["/**\n * The catalog of the fields of a Schema. One walk over the Schema makes it,\n * and every question about the fields of a resource or of the principal reads\n * it: which names a rule may use, which values a field may hold, which fields\n * a pattern fits, and which fields a list of permitted fields shows.\n *\n * The catalog describes the shape of the values, not the checks of the\n * Schema. A check, such as a minimum length, only keeps some values out, so a\n * catalog that leaves the checks out holds every real value and more. An\n * answer that holds for all of these values, as `All` and `None` of a\n * coverage do, then holds for the real values too.\n */\nimport * as Arr from \"effect/Array\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as HashSet from \"effect/HashSet\"\nimport * as Option from \"effect/Option\"\nimport { hasProperty } from \"effect/Predicate\"\nimport * as Schema from \"effect/Schema\"\nimport * as SchemaAST from \"effect/SchemaAST\"\nimport * as Limits from \"./limits.ts\"\n\n/** The kinds of values that a field may hold. */\nexport interface Kinds {\n\t/** The field may be `null`, `undefined` or missing. */\n\treadonly null: boolean\n\treadonly boolean: boolean\n\t/** The field may hold any string. */\n\treadonly string: boolean\n\t/** The numbers that the field may hold. */\n\treadonly number: \"none\" | \"integer\" | \"finite\" | \"any\"\n\t/** The field may hold an object, or a value that is not a scalar. */\n\treadonly object: boolean\n\t/** Single values that the field may hold, other than the ones above. */\n\treadonly literals: ReadonlyArray<string | number | boolean>\n}\n\n/** No value: the kinds of a field of `Schema.Never`. */\nexport const noKinds: Kinds = {\n\tnull: false,\n\tboolean: false,\n\tstring: false,\n\tnumber: \"none\",\n\tobject: false,\n\tliterals: [],\n}\n\n/** Any value. */\nexport const anyKinds: Kinds = {\n\tnull: true,\n\tboolean: true,\n\tstring: true,\n\tnumber: \"any\",\n\tobject: true,\n\tliterals: [],\n}\n\nconst nullKinds: Kinds = { ...noKinds, null: true }\nconst objectKinds: Kinds = { ...noKinds, object: true }\n\nconst numberOrder = [\"none\", \"integer\", \"finite\", \"any\"] as const\n\n/** The kinds of a field that may hold a value of `left` or of `right`. */\nexport const joinKinds = (left: Kinds, right: Kinds): Kinds => ({\n\tnull: left.null || right.null,\n\tboolean: left.boolean || right.boolean,\n\tstring: left.string || right.string,\n\tnumber:\n\t\tnumberOrder.indexOf(left.number) >= numberOrder.indexOf(right.number)\n\t\t\t? left.number\n\t\t\t: right.number,\n\tobject: left.object || right.object,\n\tliterals: Arr.dedupe([...left.literals, ...right.literals]),\n})\n\n/** Tests if a number fits the numbers of a field. */\nexport const fitsNumber = (number: Kinds[\"number\"], value: number): boolean => {\n\tswitch (number) {\n\t\tcase \"none\":\n\t\t\treturn false\n\t\tcase \"integer\":\n\t\t\treturn Number.isInteger(value)\n\t\tcase \"finite\":\n\t\t\treturn Number.isFinite(value)\n\t\tcase \"any\":\n\t\t\treturn true\n\t}\n}\n\n/**\n * Tests if a field with these kinds may hold a value. A missing value and\n * `undefined` are `null`.\n */\nexport const accepts = (kinds: Kinds, value: unknown): boolean => {\n\tif (value === null || value === undefined) {\n\t\treturn kinds.null\n\t}\n\tswitch (typeof value) {\n\t\tcase \"boolean\":\n\t\t\treturn kinds.boolean || kinds.literals.includes(value)\n\t\tcase \"string\":\n\t\t\treturn kinds.string || kinds.literals.includes(value)\n\t\tcase \"number\":\n\t\t\treturn fitsNumber(kinds.number, value) || kinds.literals.includes(value)\n\t\tdefault:\n\t\t\treturn kinds.object\n\t}\n}\n\n/** A part of a field path that stands for any key of a record. */\nexport interface Key {\n\treadonly _tag: \"Key\"\n\t/** The Schemas of the keys. A key must fit one of them. */\n\treadonly parameters: ReadonlyArray<SchemaAST.AST>\n}\n\n/** One part of a field path: a name, or any key of a record. */\nexport type Segment = string | Key\n\n/** One field of a catalog. */\nexport interface Field {\n\treadonly path: ReadonlyArray<Segment>\n\t/** The path with dots, and `*` for a key of a record. */\n\treadonly text: string\n\treadonly kinds: Kinds\n\t/**\n\t * The field holds a value that a list of fields shows as one field: a\n\t * scalar, a record, an array, or a struct at the last part a path may have.\n\t */\n\treadonly listed: boolean\n\t/** The field may hold parts that the Schema does not name, as a record may. */\n\treadonly open: boolean\n}\n\n/** A place in the tree of a catalog, with the places below it. */\nexport interface Branch {\n\t/** The field at this place. The root has none. */\n\treadonly field: Field | undefined\n\t/** The places below, by name, in the order of the Schema. */\n\treadonly children: ReadonlyArray<readonly [string, Branch]>\n\treadonly names: HashMap.HashMap<string, Branch>\n\t/** The place for the keys of a record, when the value may be a record. */\n\treadonly key: Key | undefined\n\treadonly keyBranch: Branch | undefined\n}\n\n/** The catalog of the fields of a Schema. */\nexport interface Catalog {\n\t/** Every field, a field before the fields below it. */\n\treadonly fields: ReadonlyArray<Field>\n\treadonly root: Branch\n}\n\n/**\n * Tests if a key of an object fits the Schemas of the keys of a record. A key\n * of an object is a string, also a key that a number Schema takes: `{ 1: \"a\" }`\n * has the key `\"1\"`. So a key that is the text of a number is tried as that\n * number too.\n */\nexport const keyFits = (key: Key, name: string): boolean =>\n\tArr.some(key.parameters, (parameter) => {\n\t\tconst is = Schema.is(Schema.make(parameter))\n\t\treturn (\n\t\t\tis(name) ||\n\t\t\t(name !== \"\" && String(Number(name)) === name && is(Number(name)))\n\t\t)\n\t})\n\n/**\n * Gives the struct of a `Schema.Class`. A class is a declaration with one\n * struct as its type parameter, and Effect marks it with a `\"~constructor\"`\n * annotation, also through more codec steps, as in\n * `Schema.fromJsonString(User)`. Other declarations, such as `Schema.Option`\n * or `Schema.ReadonlyMap`, have no such mark, and the fields of their type\n * parameter are not fields of the value.\n */\nconst classStruct = (\n\tast: SchemaAST.Declaration,\n): SchemaAST.Objects | undefined => {\n\tconst [inner] = ast.typeParameters\n\treturn ast.annotations?.[\"~constructor\"] !== undefined &&\n\t\tast.typeParameters.length === 1 &&\n\t\tinner?._tag === \"Objects\"\n\t\t? inner\n\t\t: undefined\n}\n\n/**\n * Gives the Schemas that a value at one place may fit: it opens unions,\n * `Schema.suspend` and classes. The open suspend nodes are in a native `Set`,\n * because the walk must find a node by the node itself: `Equal` compares\n * Schema nodes by structure, and a recursive Schema has no end. A suspend\n * node that gives itself again, with no step on the path, gives nothing.\n */\nconst alternatives = (\n\tast: SchemaAST.AST,\n\topen: ReadonlySet<SchemaAST.AST>,\n): ReadonlyArray<SchemaAST.AST> => {\n\tswitch (ast._tag) {\n\t\tcase \"Union\":\n\t\t\treturn Arr.flatMap(ast.types, (type) => alternatives(type, open))\n\t\tcase \"Suspend\":\n\t\t\treturn open.has(ast)\n\t\t\t\t? []\n\t\t\t\t: alternatives(ast.thunk(), new Set([...open, ast]))\n\t\tcase \"Declaration\": {\n\t\t\tconst inner = classStruct(ast)\n\t\t\treturn inner === undefined ? [ast] : alternatives(inner, open)\n\t\t}\n\t\tdefault:\n\t\t\treturn [ast]\n\t}\n}\n\nconst hasCheck = (\n\tchecks: ReadonlyArray<SchemaAST.Check<never>>,\n\tid: string,\n): boolean =>\n\tArr.some(\n\t\tchecks,\n\t\t(check) =>\n\t\t\tcheck.annotations?.representation?.id === id ||\n\t\t\t(check._tag === \"FilterGroup\" && hasCheck(check.checks, id)),\n\t)\n\n/** A struct with named fields, or with the keys of a record. */\nconst isStruct = (ast: SchemaAST.AST): ast is SchemaAST.Objects =>\n\tast._tag === \"Objects\" &&\n\t(ast.propertySignatures.length > 0 || ast.indexSignatures.length > 0)\n\n/** The kinds of the values of a Schema that is not a struct. */\nconst kindsOf = (ast: SchemaAST.AST): Kinds => {\n\tswitch (ast._tag) {\n\t\tcase \"Null\":\n\t\tcase \"Undefined\":\n\t\t\treturn nullKinds\n\t\tcase \"Never\":\n\t\t\treturn noKinds\n\t\tcase \"String\":\n\t\tcase \"TemplateLiteral\":\n\t\t\treturn { ...noKinds, string: true }\n\t\tcase \"Boolean\":\n\t\t\treturn { ...noKinds, boolean: true }\n\t\tcase \"Number\": {\n\t\t\t// The two checks that a search uses. Effect names a number with\n\t\t\t// them, as `Schema.Int` and `Schema.Finite`; other checks are left\n\t\t\t// out, as all checks are.\n\t\t\tconst checks = ast.checks ?? []\n\t\t\treturn {\n\t\t\t\t...noKinds,\n\t\t\t\tnumber: hasCheck(checks, \"effect/schema/isInt\")\n\t\t\t\t\t? \"integer\"\n\t\t\t\t\t: hasCheck(checks, \"effect/schema/isFinite\")\n\t\t\t\t\t\t? \"finite\"\n\t\t\t\t\t\t: \"any\",\n\t\t\t}\n\t\t}\n\t\tcase \"Literal\":\n\t\t\treturn typeof ast.literal === \"bigint\"\n\t\t\t\t? objectKinds\n\t\t\t\t: { ...noKinds, literals: [ast.literal] }\n\t\tcase \"Enum\":\n\t\t\treturn { ...noKinds, literals: Arr.map(ast.enums, ([, value]) => value) }\n\t\tcase \"Arrays\":\n\t\tcase \"ObjectKeyword\":\n\t\tcase \"BigInt\":\n\t\tcase \"Symbol\":\n\t\tcase \"UniqueSymbol\":\n\t\t\treturn objectKinds\n\t\tcase \"Declaration\":\n\t\t\t// A declaration of Effect in `objectDeclarations` holds an object,\n\t\t\t// such as a `Date`. Another declaration may hold any value, as\n\t\t\t// `Schema.Json` or one from `Schema.declare` may.\n\t\t\treturn HashSet.has(objectDeclarations, declarationId(ast))\n\t\t\t\t? objectKinds\n\t\t\t\t: anyKinds\n\t\tdefault:\n\t\t\t// `Schema.Unknown`, `Schema.Void`, and `Struct({})`, which takes a\n\t\t\t// string too: these may hold any value.\n\t\t\treturn anyKinds\n\t}\n}\n\n/**\n * The ids of the declarations of Effect that hold only objects. Effect puts\n * the id in the `representation` annotation. A declaration that is not in\n * this list may hold a scalar, as `Schema.Json` may, so its kinds are any\n * value.\n */\nconst objectDeclarations = HashSet.make(\n\t\"effect/schema/Date\",\n\t\"effect/schema/URL\",\n\t\"effect/schema/Uint8Array\",\n\t\"effect/schema/Duration\",\n\t\"effect/schema/DateTimeUtc\",\n\t\"effect/schema/DateTimeZoned\",\n\t\"effect/schema/BigDecimal\",\n\t\"effect/schema/Redacted\",\n\t\"effect/schema/Cause\",\n\t\"effect/schema/Chunk\",\n\t\"effect/schema/HashMap\",\n\t\"effect/schema/HashSet\",\n\t\"effect/schema/ReadonlyMap\",\n\t\"effect/schema/ReadonlySet\",\n\t\"effect/schema/Option\",\n\t\"effect/schema/Result\",\n\t\"effect/schema/Exit\",\n\t\"effect/schema/File\",\n\t\"effect/schema/FormData\",\n\t\"effect/schema/RegExp\",\n\t\"effect/schema/URLSearchParams\",\n)\n\nconst declarationId = (ast: SchemaAST.Declaration): string | undefined => {\n\tconst representation: unknown = ast.annotations?.[\"representation\"]\n\treturn hasProperty(representation, \"id\") &&\n\t\ttypeof representation.id === \"string\"\n\t\t? representation.id\n\t\t: undefined\n}\n\n/** The things that a walk of the catalog knows at one place. */\ninterface Place {\n\treadonly path: ReadonlyArray<Segment>\n\treadonly schemas: ReadonlyArray<SchemaAST.AST>\n\t/** The value may be missing. */\n\treadonly missing: boolean\n\t/** The value may be anything, as an extra field of a struct may be. */\n\treadonly top: boolean\n}\n\nconst textOf = (path: ReadonlyArray<Segment>): string =>\n\tArr.map(path, (segment) =>\n\t\ttypeof segment === \"string\" ? segment : \"*\",\n\t).join(\".\")\n\n/** Gives the property of a struct with a name, if it has one. */\nconst propertyOf = (\n\tstruct: SchemaAST.Objects,\n\tname: string,\n): SchemaAST.PropertySignature | undefined =>\n\tOption.getOrUndefined(\n\t\tArr.findFirst(\n\t\t\tstruct.propertySignatures,\n\t\t\t(signature) => signature.name === name,\n\t\t),\n\t)\n\n/** The names of the fields of the structs, in the order that they first come. */\nconst namesOf = (\n\tstructs: ReadonlyArray<SchemaAST.Objects>,\n): ReadonlyArray<string> =>\n\tArr.dedupe(\n\t\tArr.flatMap(structs, (struct) =>\n\t\t\tArr.flatMap(struct.propertySignatures, (signature) => {\n\t\t\t\tconst name = signature.name\n\t\t\t\tif (typeof name !== \"string\") {\n\t\t\t\t\treturn []\n\t\t\t\t}\n\t\t\t\t// A dot in a path is the step into a nested field. A field name with\n\t\t\t\t// a dot would make two fields have one path, so it is a defect.\n\t\t\t\tif (name.includes(\".\")) {\n\t\t\t\t\tthrow new Error(`A field name may not have a dot: \"${name}\"`)\n\t\t\t\t}\n\t\t\t\t// An empty name would make an empty part in a path, such as\n\t\t\t\t// `.secret`.\n\t\t\t\tif (name === \"\") {\n\t\t\t\t\tthrow new Error(\"A field name may not be empty\")\n\t\t\t\t}\n\t\t\t\treturn [name]\n\t\t\t}),\n\t\t),\n\t)\n\n/**\n * Makes the branch of a place, with the branches below it. `count` is the\n * number of fields so far. It belongs to one walk, and it stops a Schema with\n * very many fields before the walk is done.\n */\nconst branchOf = (place: Place, count: { size: number }): Branch => {\n\tconst structs = Arr.filter(place.schemas, isStruct)\n\tconst values = Arr.filter(place.schemas, (schema) => !isStruct(schema))\n\tconst valueKinds = Arr.reduce(values, noKinds, (kinds, schema) =>\n\t\tjoinKinds(kinds, kindsOf(schema)),\n\t)\n\tconst kinds = place.top\n\t\t? anyKinds\n\t\t: joinKinds(\n\t\t\t\tjoinKinds(valueKinds, structs.length > 0 ? objectKinds : noKinds),\n\t\t\t\tplace.missing ? nullKinds : noKinds,\n\t\t\t)\n\tconst records = Arr.filter(\n\t\tstructs,\n\t\t(struct) => struct.indexSignatures.length > 0,\n\t)\n\t// The last part that a path may have: the walk does not go below it.\n\tconst last = place.path.length === Limits.fieldPathParts\n\tconst field: Field | undefined =\n\t\tplace.path.length === 0\n\t\t\t? undefined\n\t\t\t: {\n\t\t\t\t\tpath: place.path,\n\t\t\t\t\ttext: textOf(place.path),\n\t\t\t\t\tkinds,\n\t\t\t\t\tlisted:\n\t\t\t\t\t\tvalues.length > 0 ||\n\t\t\t\t\t\trecords.length > 0 ||\n\t\t\t\t\t\t(last && structs.length > 0),\n\t\t\t\t\t// A field that is a scalar in one branch of a union and a struct\n\t\t\t\t\t// in another holds parts too.\n\t\t\t\t\topen:\n\t\t\t\t\t\trecords.length > 0 ||\n\t\t\t\t\t\tstructs.length > 0 ||\n\t\t\t\t\t\tvalueKinds.object ||\n\t\t\t\t\t\tplace.top,\n\t\t\t\t}\n\tif (field !== undefined) {\n\t\tcount.size += 1\n\t\tif (count.size > Limits.catalogFields) {\n\t\t\tthrow Limits.exceeded(\n\t\t\t\t\"A Schema\",\n\t\t\t\t\"catalogFields\",\n\t\t\t\t\"fields\",\n\t\t\t\tcount.size,\n\t\t\t\tLimits.catalogFields,\n\t\t\t)\n\t\t}\n\t}\n\tif (last) {\n\t\treturn {\n\t\t\tfield,\n\t\t\tchildren: [],\n\t\t\tnames: HashMap.empty(),\n\t\t\tkey: undefined,\n\t\t\tkeyBranch: undefined,\n\t\t}\n\t}\n\tconst children = Arr.map(namesOf(structs), (name) => {\n\t\tconst properties = Arr.flatMap(structs, (struct) => {\n\t\t\tconst property = propertyOf(struct, name)\n\t\t\treturn property === undefined ? [] : [property]\n\t\t})\n\t\tconst below: Place = {\n\t\t\tpath: [...place.path, name],\n\t\t\tschemas: Arr.flatMap(properties, (property) =>\n\t\t\t\talternatives(property.type, new Set()),\n\t\t\t),\n\t\t\t// A field below a value that is not a struct reads `null`, and so\n\t\t\t// does a field that may be missing.\n\t\t\tmissing:\n\t\t\t\tplace.missing ||\n\t\t\t\tvalues.length > 0 ||\n\t\t\t\tArr.some(properties, (property) => SchemaAST.isOptional(property.type)),\n\t\t\t// A struct without the name may still have it as an extra field,\n\t\t\t// with any value: only a decode takes extra fields away. A value\n\t\t\t// that may be any object, as one of `Schema.Unknown` may, may have\n\t\t\t// the name with any value too.\n\t\t\ttop:\n\t\t\t\tplace.top ||\n\t\t\t\tvalueKinds.object ||\n\t\t\t\tArr.some(structs, (struct) => propertyOf(struct, name) === undefined),\n\t\t}\n\t\treturn [name, branchOf(below, count)] as const\n\t})\n\tconst names = HashMap.fromIterable(children)\n\tconst signatures = Arr.flatMap(records, (struct) => struct.indexSignatures)\n\tif (signatures.length === 0) {\n\t\treturn { field, children, names, key: undefined, keyBranch: undefined }\n\t}\n\tconst key: Key = {\n\t\t_tag: \"Key\",\n\t\tparameters: Arr.map(signatures, (signature) => signature.parameter),\n\t}\n\tconst keyBranch = branchOf(\n\t\t{\n\t\t\tpath: [...place.path, key],\n\t\t\tschemas: Arr.flatMap(signatures, (signature) =>\n\t\t\t\talternatives(signature.type, new Set()),\n\t\t\t),\n\t\t\t// A record may not have a given key.\n\t\t\tmissing: true,\n\t\t\ttop: place.top || valueKinds.object || records.length < structs.length,\n\t\t},\n\t\tcount,\n\t)\n\treturn { field, children, names, key, keyBranch }\n}\n\nconst fieldsOf = (branch: Branch): ReadonlyArray<Field> => [\n\t...(branch.field === undefined ? [] : [branch.field]),\n\t...Arr.flatMap(branch.children, ([, child]) => fieldsOf(child)),\n\t...(branch.keyBranch === undefined ? [] : fieldsOf(branch.keyBranch)),\n]\n\n/**\n * Makes the catalog of a Schema. It throws when a field name has a dot or is\n * empty, and when the Schema has more fields than `catalogFields`.\n */\nexport const catalogOf = (ast: SchemaAST.AST): Catalog => {\n\tconst root = branchOf(\n\t\t{\n\t\t\tpath: [],\n\t\t\tschemas: alternatives(ast, new Set()),\n\t\t\tmissing: false,\n\t\t\ttop: false,\n\t\t},\n\t\t{ size: 0 },\n\t)\n\treturn { fields: fieldsOf(root), root }\n}\n\n/** Gives the field of a catalog at a path with dots, if the catalog has it. */\nexport const lookup = (catalog: Catalog, path: string): Field | undefined => {\n\tconst names = path.split(\".\", Limits.fieldPathParts + 1)\n\tif (names.length > Limits.fieldPathParts) {\n\t\treturn undefined\n\t}\n\tconst branch = Arr.reduce(\n\t\tnames,\n\t\tcatalog.root as Branch | undefined,\n\t\t(current, name) => {\n\t\t\tif (current === undefined) {\n\t\t\t\treturn undefined\n\t\t\t}\n\t\t\tconst named = HashMap.get(current.names, name)\n\t\t\tif (Option.isSome(named)) {\n\t\t\t\treturn named.value\n\t\t\t}\n\t\t\treturn current.key !== undefined && keyFits(current.key, name)\n\t\t\t\t? current.keyBranch\n\t\t\t\t: undefined\n\t\t},\n\t)\n\treturn branch?.field\n}\n"],"mappings":"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"}
@@ -0,0 +1,166 @@
1
+ import { Allow, Deny, NotApplicable } from "../Decision.js";
2
+ import { lookup } from "./catalog.js";
3
+ import { kindOf } from "./resource.js";
4
+ import { evaluate } from "../Condition.js";
5
+ import { descriptorOf } from "./domain.js";
6
+ import { matchesInside, matchesTarget } from "./rule.js";
7
+ import { nodeOf } from "./policy.js";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Arr from "effect/Array";
10
+ import * as Option from "effect/Option";
11
+ import * as Chunk from "effect/Chunk";
12
+ import * as Ref from "effect/Ref";
13
+ // src/internal/decide.ts
14
+ /**
15
+ * The decision walk of a policy: `decide`, `explain` and `permittedFields`.
16
+ * The types are in `Policy.ts`.
17
+ */
18
+ const ruleApplies = (value, context, path) => {
19
+ if (!matchesTarget(value, context)) return Effect.succeed(false);
20
+ const test = value.when === void 0 ? Effect.succeed(true) : evaluate(value.when, context.input);
21
+ const trace = context.trace;
22
+ return trace === void 0 ? test : Effect.tap(test, (applies) => Ref.update(trace, Chunk.append({
23
+ path,
24
+ rule: typed(value),
25
+ applies
26
+ })));
27
+ };
28
+ /** A rule of a node, with the type of a rule of no domain. */
29
+ const typed = (value) => value;
30
+ const fromRule = (value) => value._tag === "Allow" ? Allow({ rule: typed(value) }) : value.reason === void 0 ? Deny({ rule: typed(value) }) : Deny({
31
+ rule: typed(value),
32
+ reason: value.reason
33
+ });
34
+ /** Gives the first rule that applies, in order. */
35
+ const firstApplying = (candidates, context, path) => Effect.map(Effect.findFirst(candidates, ([value, index]) => ruleApplies(value, context, [...path, index])), Option.map(([value]) => value));
36
+ /** Gives the first result that is found, and tests `next` only when none is. */
37
+ const orElse = (first, next) => Effect.filterOrElse(first, Option.isSome, next);
38
+ const decideRules = (node, context, path) => {
39
+ const indexed = Arr.map(node.rules, (value, index) => [value, index]);
40
+ const denies = Arr.filter(indexed, ([value]) => value._tag === "Deny");
41
+ const allows = Arr.filter(indexed, ([value]) => value._tag === "Allow");
42
+ const first = (candidates) => firstApplying(candidates, context, path);
43
+ const found = (() => {
44
+ switch (node.algorithm) {
45
+ case "denyOverrides": return orElse(first(denies), () => first(allows));
46
+ case "permitOverrides": return orElse(first(allows), () => first(denies));
47
+ case "firstApplicable": return first(indexed);
48
+ case "lastApplicable": return first(Arr.reverse(indexed));
49
+ }
50
+ })();
51
+ return Effect.map(found, Option.match({
52
+ onNone: () => NotApplicable,
53
+ onSome: fromRule
54
+ }));
55
+ };
56
+ /**
57
+ * Decides the children in order. After each child, `step` stops with a
58
+ * decision, or gives the state for the next child. After the last child,
59
+ * `end` gives the decision. A child after a stop is not decided, so its
60
+ * predicates do not run.
61
+ */
62
+ const walkChildren = (children, context, path, start, step, end) => {
63
+ const go = (index, state) => index === children.length ? Effect.succeed(end(state)) : Effect.flatMap(decideNode(children[index], context, [...path, index]), (decision) => {
64
+ const next = step(state, decision);
65
+ return next._tag === "Stop" ? Effect.succeed(next.decision) : go(index + 1, next.state);
66
+ });
67
+ return go(0, start);
68
+ };
69
+ const stop = (decision) => ({
70
+ _tag: "Stop",
71
+ decision
72
+ });
73
+ const next = (state) => ({
74
+ _tag: "Next",
75
+ state
76
+ });
77
+ /**
78
+ * Decides a node. A shared child is decided again for each place, so its
79
+ * predicates run each time, as the policy says. The limits of the nodes keep
80
+ * this work small.
81
+ */
82
+ const decideNode = (node, context, path) => {
83
+ switch (node._tag) {
84
+ case "Rules": return decideRules(node, context, path);
85
+ case "All": return walkChildren(node.policies, context, path, {
86
+ firstAllow: Option.none(),
87
+ allAllow: node.policies.length > 0
88
+ }, (state, decision) => decision._tag === "Deny" ? stop(decision) : next({
89
+ firstAllow: Option.orElse(state.firstAllow, () => decision._tag === "Allow" ? Option.some(decision) : Option.none()),
90
+ allAllow: state.allAllow && decision._tag === "Allow"
91
+ }), (state) => state.allAllow ? Option.getOrElse(state.firstAllow, () => NotApplicable) : NotApplicable);
92
+ case "Any": return walkChildren(node.policies, context, path, Option.none(), (firstDeny, decision) => decision._tag === "Allow" ? stop(decision) : next(Option.orElse(firstDeny, () => decision._tag === "Deny" ? Option.some(decision) : Option.none())), (firstDeny) => Option.getOrElse(firstDeny, () => NotApplicable));
93
+ case "FirstOf": return walkChildren(node.policies, context, path, void 0, (_, decision) => decision._tag === "NotApplicable" ? next(void 0) : stop(decision), () => NotApplicable);
94
+ case "Ceiling": return Effect.filterOrElse(decideNode(node.cap, context, [...path, 0]), (capped) => capped._tag === "Deny", () => decideNode(node.policy, context, [...path, 1]));
95
+ }
96
+ };
97
+ /**
98
+ * Tests if a request fits its kind: the action is an action of the kind, and
99
+ * the field, if there is one, is a field of the kind.
100
+ */
101
+ const fitsKind = (descriptor, request) => descriptor.actions.includes(request.action) && (request.field === void 0 || (lookup(descriptor.catalog, request.field)?.listed ?? false));
102
+ const makeContext = (domain, request, trace) => {
103
+ const kind = kindOf(request.resource);
104
+ const descriptor = kind === void 0 ? void 0 : descriptorOf(domain, kind);
105
+ if (kind === void 0 || descriptor === void 0 || !fitsKind(descriptor, request)) return;
106
+ return {
107
+ action: request.action,
108
+ kind,
109
+ field: request.field,
110
+ input: {
111
+ value: request.resource,
112
+ principal: request.principal,
113
+ path: descriptor.path
114
+ },
115
+ descriptor,
116
+ trace
117
+ };
118
+ };
119
+ const decideAny = (policy, request) => Effect.suspend(() => {
120
+ const context = makeContext(policy.domain, request, void 0);
121
+ return context === void 0 ? Effect.succeed(NotApplicable) : decideNode(nodeOf(policy), context, []);
122
+ });
123
+ /** Gives the decision of a policy with no predicates, at once. */
124
+ const decideSync = (policy, request) => Effect.runSync(decideAny(policy, request));
125
+ /** Gives the decision of a policy and the rules that were tested to make it. */
126
+ const explain = (policy, request) => Effect.gen(function* () {
127
+ const trace = yield* Ref.make(Chunk.empty());
128
+ const context = makeContext(policy.domain, request, trace);
129
+ if (context === void 0) return {
130
+ decision: NotApplicable,
131
+ steps: []
132
+ };
133
+ return {
134
+ decision: yield* decideNode(nodeOf(policy), context, []),
135
+ steps: Chunk.toReadonlyArray(yield* Ref.get(trace))
136
+ };
137
+ });
138
+ /**
139
+ * Tests if a deny rule of the request names a part inside `field`.
140
+ */
141
+ const deniesInside = (node, target, field) => {
142
+ switch (node._tag) {
143
+ case "Rules": return node.rules.some((value) => value._tag === "Deny" && value.fields !== void 0 && value.fields.some((pattern) => matchesInside(pattern, field)) && (value.actions.includes("*") || value.actions.includes(target.action)) && (value.resources.includes("*") || value.resources.includes(target.kind)));
144
+ case "All":
145
+ case "Any":
146
+ case "FirstOf": return node.policies.some((child) => deniesInside(child, target, field));
147
+ case "Ceiling": return deniesInside(node.cap, target, field) || deniesInside(node.policy, target, field);
148
+ }
149
+ };
150
+ /** Gives the fields of a resource value that the policy lets a request use. */
151
+ const permittedFields = (policy, request) => {
152
+ const kind = kindOf(request.resource);
153
+ const descriptor = kind === void 0 ? void 0 : descriptorOf(policy.domain, kind);
154
+ if (descriptor === void 0) return Effect.succeed([]);
155
+ const listed = Arr.filter(descriptor.catalog.fields, (field) => field.listed && Arr.every(field.path, (segment) => typeof segment === "string"));
156
+ return Effect.map(Effect.filter(listed, (field) => Effect.map(decideAny(policy, {
157
+ ...request,
158
+ field: field.text
159
+ }), (result) => result._tag === "Allow" && !(field.open && deniesInside(nodeOf(policy), {
160
+ ...request,
161
+ kind: descriptor.kind
162
+ }, field.text)))), Arr.map((field) => field.text));
163
+ };
164
+ export { decideAny, decideNode, decideSync, explain, fitsKind, permittedFields };
165
+
166
+ //# sourceMappingURL=decide.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decide.js","names":["RuleData.matchesTarget","Condition.evaluate","Decision.Allow","Decision.Deny","Decision.NotApplicable","Catalog.lookup","ResourceData.kindOf","DomainData.descriptorOf","RuleData.matchesInside"],"sources":["../../src/internal/decide.ts"],"sourcesContent":["/**\n * The decision walk of a policy: `decide`, `explain` and `permittedFields`.\n * The types are in `Policy.ts`.\n */\n// A policy keeps the error type and the service type of its predicates in the\n// public types. The code below works on the nodes with no types, where they\n// are `unknown`, so the language service check for `unknown` in an Effect is\n// off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Chunk from \"effect/Chunk\"\nimport * as Effect from \"effect/Effect\"\nimport * as Option from \"effect/Option\"\nimport * as Ref from \"effect/Ref\"\nimport * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport type * as PolicyNodes from \"./policy-nodes.ts\"\nimport { nodeOf } from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as RuleData from \"./rule.ts\"\n\ntype Any = Policy.Any\ntype AnyRequest = Policy.AnyRequest\ntype AnyRule = Policy.AnyRule\ntype Explanation = Policy.Explanation\ntype Step = Policy.Step\n\ninterface DecideContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly input: Condition.Input\n\t/** The resource of the kind, for the fields that a rule may grant. */\n\treadonly descriptor: Resource.Descriptor\n\t/** The rules that were tested, for {@link explain}. */\n\treadonly trace: Ref.Ref<Chunk.Chunk<Step>> | undefined\n}\n\nconst ruleApplies = (\n\tvalue: RuleData.Rule,\n\tcontext: DecideContext,\n\tpath: ReadonlyArray<number>,\n): Effect.Effect<boolean, unknown, unknown> => {\n\tif (!RuleData.matchesTarget(value, context)) {\n\t\treturn Effect.succeed(false)\n\t}\n\tconst test =\n\t\tvalue.when === undefined\n\t\t\t? Effect.succeed(true)\n\t\t\t: // A rule of a node keeps no type of what it reads. The decision gives\n\t\t\t\t// it the value and the principal of the request.\n\t\t\t\tCondition.evaluate(value.when as Condition.Any, context.input)\n\tconst trace = context.trace\n\treturn trace === undefined\n\t\t? test\n\t\t: Effect.tap(test, (applies) =>\n\t\t\t\t// A chunk adds a step with no copy of the steps before.\n\t\t\t\tRef.update(trace, Chunk.append({ path, rule: typed(value), applies })),\n\t\t\t)\n}\n\n/** A rule of a node, with the type of a rule of no domain. */\nconst typed = (value: RuleData.Rule): AnyRule => value as never\n\nconst fromRule = (value: RuleData.Rule): Decision.Decision =>\n\tvalue._tag === \"Allow\"\n\t\t? Decision.Allow({ rule: typed(value) })\n\t\t: value.reason === undefined\n\t\t\t? Decision.Deny({ rule: typed(value) })\n\t\t\t: Decision.Deny({ rule: typed(value), reason: value.reason })\n\n/** Gives the first rule that applies, in order. */\nconst firstApplying = (\n\tcandidates: ReadonlyArray<readonly [RuleData.Rule, number]>,\n\tcontext: DecideContext,\n\tpath: ReadonlyArray<number>,\n): Effect.Effect<Option.Option<RuleData.Rule>, unknown, unknown> =>\n\tEffect.map(\n\t\tEffect.findFirst(candidates, ([value, index]) =>\n\t\t\truleApplies(value, context, [...path, index]),\n\t\t),\n\t\tOption.map(([value]) => value),\n\t)\n\n/** Gives the first result that is found, and tests `next` only when none is. */\nconst orElse = <A, E, R>(\n\tfirst: Effect.Effect<Option.Option<A>, E, R>,\n\tnext: () => Effect.Effect<Option.Option<A>, E, R>,\n): Effect.Effect<Option.Option<A>, E, R> =>\n\tEffect.filterOrElse(first, Option.isSome, next)\n\nconst decideRules = (\n\tnode: PolicyNodes.Rules,\n\tcontext: DecideContext,\n\tpath: ReadonlyArray<number>,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst indexed = Arr.map(node.rules, (value, index) => [value, index] as const)\n\tconst denies = Arr.filter(indexed, ([value]) => value._tag === \"Deny\")\n\tconst allows = Arr.filter(indexed, ([value]) => value._tag === \"Allow\")\n\tconst first = (candidates: ReadonlyArray<readonly [RuleData.Rule, number]>) =>\n\t\tfirstApplying(candidates, context, path)\n\tconst found = (() => {\n\t\tswitch (node.algorithm) {\n\t\t\tcase \"denyOverrides\":\n\t\t\t\treturn orElse(first(denies), () => first(allows))\n\t\t\tcase \"permitOverrides\":\n\t\t\t\treturn orElse(first(allows), () => first(denies))\n\t\t\tcase \"firstApplicable\":\n\t\t\t\treturn first(indexed)\n\t\t\tcase \"lastApplicable\":\n\t\t\t\treturn first(Arr.reverse(indexed))\n\t\t}\n\t})()\n\treturn Effect.map(\n\t\tfound,\n\t\tOption.match({ onNone: () => Decision.NotApplicable, onSome: fromRule }),\n\t)\n}\n\n/** The next step of a walk over children: stop with a decision, or go on. */\ntype WalkStep<S> =\n\t| { readonly _tag: \"Stop\"; readonly decision: Decision.Decision }\n\t| { readonly _tag: \"Next\"; readonly state: S }\n\n/**\n * Decides the children in order. After each child, `step` stops with a\n * decision, or gives the state for the next child. After the last child,\n * `end` gives the decision. A child after a stop is not decided, so its\n * predicates do not run.\n */\nconst walkChildren = <S>(\n\tchildren: ReadonlyArray<PolicyNodes.Node>,\n\tcontext: DecideContext,\n\tpath: ReadonlyArray<number>,\n\tstart: S,\n\tstep: (state: S, decision: Decision.Decision) => WalkStep<S>,\n\tend: (state: S) => Decision.Decision,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tconst go = (\n\t\tindex: number,\n\t\tstate: S,\n\t): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t\tindex === children.length\n\t\t\t? Effect.succeed(end(state))\n\t\t\t: Effect.flatMap(\n\t\t\t\t\tdecideNode(children[index]!, context, [...path, index]),\n\t\t\t\t\t(decision) => {\n\t\t\t\t\t\tconst next = step(state, decision)\n\t\t\t\t\t\treturn next._tag === \"Stop\"\n\t\t\t\t\t\t\t? Effect.succeed(next.decision)\n\t\t\t\t\t\t\t: go(index + 1, next.state)\n\t\t\t\t\t},\n\t\t\t\t)\n\treturn go(0, start)\n}\n\nconst stop = (decision: Decision.Decision): WalkStep<never> => ({\n\t_tag: \"Stop\",\n\tdecision,\n})\n\nconst next = <S>(state: S): WalkStep<S> => ({ _tag: \"Next\", state })\n\n/**\n * Decides a node. A shared child is decided again for each place, so its\n * predicates run each time, as the policy says. The limits of the nodes keep\n * this work small.\n */\nexport const decideNode = (\n\tnode: PolicyNodes.Node,\n\tcontext: DecideContext,\n\tpath: ReadonlyArray<number>,\n): Effect.Effect<Decision.Decision, unknown, unknown> => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn decideRules(node, context, path)\n\t\tcase \"All\":\n\t\t\t// One deny is enough to deny. Every child must allow.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tpath,\n\t\t\t\t{\n\t\t\t\t\tfirstAllow: Option.none<Decision.Decision>(),\n\t\t\t\t\tallAllow: node.policies.length > 0,\n\t\t\t\t},\n\t\t\t\t(state, decision) =>\n\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next({\n\t\t\t\t\t\t\t\tfirstAllow: Option.orElse(state.firstAllow, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tallAllow: state.allAllow && decision._tag === \"Allow\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t(state) =>\n\t\t\t\t\tstate.allAllow\n\t\t\t\t\t\t? Option.getOrElse(state.firstAllow, () => Decision.NotApplicable)\n\t\t\t\t\t\t: Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Any\":\n\t\t\t// One allow is enough. Without one, the first deny denies.\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tpath,\n\t\t\t\tOption.none<Decision.Decision>(),\n\t\t\t\t(firstDeny, decision) =>\n\t\t\t\t\tdecision._tag === \"Allow\"\n\t\t\t\t\t\t? stop(decision)\n\t\t\t\t\t\t: next(\n\t\t\t\t\t\t\t\tOption.orElse(firstDeny, () =>\n\t\t\t\t\t\t\t\t\tdecision._tag === \"Deny\"\n\t\t\t\t\t\t\t\t\t\t? Option.some(decision)\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t(firstDeny) =>\n\t\t\t\t\tOption.getOrElse(firstDeny, () => Decision.NotApplicable),\n\t\t\t)\n\t\tcase \"FirstOf\":\n\t\t\treturn walkChildren(\n\t\t\t\tnode.policies,\n\t\t\t\tcontext,\n\t\t\t\tpath,\n\t\t\t\tundefined,\n\t\t\t\t(_, decision) =>\n\t\t\t\t\tdecision._tag === \"NotApplicable\" ? next(undefined) : stop(decision),\n\t\t\t\t() => Decision.NotApplicable,\n\t\t\t)\n\t\tcase \"Ceiling\":\n\t\t\t// A deny of the cap wins. Else the policy decides.\n\t\t\treturn Effect.filterOrElse(\n\t\t\t\tdecideNode(node.cap, context, [...path, 0]),\n\t\t\t\t(capped) => capped._tag === \"Deny\",\n\t\t\t\t() => decideNode(node.policy, context, [...path, 1]),\n\t\t\t)\n\t}\n}\n\n/**\n * Tests if a request fits its kind: the action is an action of the kind, and\n * the field, if there is one, is a field of the kind.\n */\nexport const fitsKind = (\n\tdescriptor: Resource.Descriptor,\n\trequest: { readonly action: string; readonly field?: string | undefined },\n): boolean =>\n\tdescriptor.actions.includes(request.action) &&\n\t(request.field === undefined ||\n\t\t(Catalog.lookup(descriptor.catalog, request.field)?.listed ?? false))\n\nconst makeContext = (\n\tdomain: Domain.Any,\n\trequest: AnyRequest,\n\ttrace: Ref.Ref<Chunk.Chunk<Step>> | undefined,\n): DecideContext | undefined => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor =\n\t\tkind === undefined ? undefined : DomainData.descriptorOf(domain, kind)\n\tif (\n\t\tkind === undefined ||\n\t\tdescriptor === undefined ||\n\t\t!fitsKind(descriptor, request)\n\t) {\n\t\treturn undefined\n\t}\n\treturn {\n\t\taction: request.action,\n\t\tkind,\n\t\tfield: request.field,\n\t\tinput: {\n\t\t\tvalue: request.resource,\n\t\t\tprincipal: request.principal,\n\t\t\tpath: descriptor.path,\n\t\t},\n\t\tdescriptor,\n\t\ttrace,\n\t}\n}\n\nexport const decideAny = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Decision.Decision, unknown, unknown> =>\n\t// The context reads the resource value, so it is made for each run.\n\tEffect.suspend(() => {\n\t\tconst context = makeContext(policy.domain, request, undefined)\n\t\treturn context === undefined\n\t\t\t? Effect.succeed(Decision.NotApplicable)\n\t\t\t: decideNode(nodeOf(policy), context, [])\n\t})\n\n/** Gives the decision of a policy with no predicates, at once. */\nexport const decideSync = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Decision.Decision =>\n\tEffect.runSync(\n\t\t// The public type of `decideSync` takes only policies with no\n\t\t// predicates, so this Effect has no error and needs no services.\n\t\t// @effect-diagnostics-next-line unsafeEffectTypeAssertion:off\n\t\tdecideAny(policy, request) as Effect.Effect<Decision.Decision>,\n\t)\n\n/** Gives the decision of a policy and the rules that were tested to make it. */\nexport const explain = (\n\tpolicy: Any,\n\trequest: AnyRequest,\n): Effect.Effect<Explanation, unknown, unknown> =>\n\t// Each run of the Effect needs its own trace, so the trace is made inside\n\t// the Effect.\n\tEffect.gen(function* () {\n\t\tconst trace = yield* Ref.make(Chunk.empty<Step>())\n\t\tconst context = makeContext(policy.domain, request, trace)\n\t\tif (context === undefined) {\n\t\t\treturn { decision: Decision.NotApplicable, steps: [] }\n\t\t}\n\t\tconst decision = yield* decideNode(nodeOf(policy), context, [])\n\t\treturn { decision, steps: Chunk.toReadonlyArray(yield* Ref.get(trace)) }\n\t})\n\n/**\n * Tests if a deny rule of the request names a part inside `field`.\n */\nconst deniesInside = (\n\tnode: PolicyNodes.Node,\n\ttarget: { readonly action: string; readonly kind: string },\n\tfield: string,\n): boolean => {\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn node.rules.some(\n\t\t\t\t(value) =>\n\t\t\t\t\tvalue._tag === \"Deny\" &&\n\t\t\t\t\tvalue.fields !== undefined &&\n\t\t\t\t\tvalue.fields.some((pattern) =>\n\t\t\t\t\t\tRuleData.matchesInside(pattern, field),\n\t\t\t\t\t) &&\n\t\t\t\t\t(value.actions.includes(\"*\") ||\n\t\t\t\t\t\tvalue.actions.includes(target.action)) &&\n\t\t\t\t\t(value.resources.includes(\"*\") ||\n\t\t\t\t\t\tvalue.resources.includes(target.kind)),\n\t\t\t)\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\":\n\t\t\treturn node.policies.some((child) => deniesInside(child, target, field))\n\t\tcase \"Ceiling\":\n\t\t\treturn (\n\t\t\t\tdeniesInside(node.cap, target, field) ||\n\t\t\t\tdeniesInside(node.policy, target, field)\n\t\t\t)\n\t}\n}\n\n/** Gives the fields of a resource value that the policy lets a request use. */\nexport const permittedFields = (\n\tpolicy: Any,\n\trequest: Omit<AnyRequest, \"field\">,\n): Effect.Effect<ReadonlyArray<string>, unknown, unknown> => {\n\tconst kind = ResourceData.kindOf(request.resource)\n\tconst descriptor: Resource.Descriptor | undefined =\n\t\tkind === undefined\n\t\t\t? undefined\n\t\t\t: DomainData.descriptorOf(policy.domain, kind)\n\tif (descriptor === undefined) {\n\t\treturn Effect.succeed([])\n\t}\n\t// The fields with names only: a list can not name every key of a record.\n\tconst listed = Arr.filter(\n\t\tdescriptor.catalog.fields,\n\t\t(field) =>\n\t\t\tfield.listed &&\n\t\t\tArr.every(field.path, (segment) => typeof segment === \"string\"),\n\t)\n\treturn Effect.map(\n\t\tEffect.filter(listed, (field) =>\n\t\t\tEffect.map(\n\t\t\t\tdecideAny(policy, { ...request, field: field.text }),\n\t\t\t\t(result) =>\n\t\t\t\t\tresult._tag === \"Allow\" &&\n\t\t\t\t\t!(\n\t\t\t\t\t\tfield.open &&\n\t\t\t\t\t\tdeniesInside(\n\t\t\t\t\t\t\tnodeOf(policy),\n\t\t\t\t\t\t\t{ ...request, kind: descriptor.kind },\n\t\t\t\t\t\t\tfield.text,\n\t\t\t\t\t\t)\n\t\t\t\t\t),\n\t\t\t),\n\t\t),\n\t\tArr.map((field) => field.text),\n\t)\n}\n"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { catalogOf } from "./catalog.js";
2
+ import { descriptor } from "./resource.js";
3
+ import * as Data from "effect/Data";
4
+ import * as Arr from "effect/Array";
5
+ import * as Option from "effect/Option";
6
+ import * as HashMap from "effect/HashMap";
7
+ // src/internal/domain.ts
8
+ /**
9
+ * The values of domains. The types are in `Domain.ts`.
10
+ */
11
+ /** The class of every domain value. `isDomain` uses it to know a domain. */
12
+ var DomainData = class extends Data.Class {};
13
+ /** Makes a domain. Each resource must have its own kind name. */
14
+ const make = (options) => {
15
+ const descriptors = Arr.map(options.resources, descriptor);
16
+ const kinds = Arr.map(descriptors, (descriptor) => descriptor.kind);
17
+ const twice = Arr.findFirst(kinds, (kind, index) => kinds.indexOf(kind) !== index);
18
+ if (Option.isSome(twice)) throw new Error(`Two resources have the kind name "${twice.value}"`);
19
+ if (kinds.includes("*")) throw new Error(`A resource may not have the kind name "*"`);
20
+ const resources = HashMap.fromIterable(Arr.map(descriptors, (descriptor) => [descriptor.kind, descriptor]));
21
+ return new DomainData({
22
+ principal: options.principal,
23
+ principalCatalog: catalogOf(options.principal.ast),
24
+ resources,
25
+ meta: options.meta
26
+ });
27
+ };
28
+ /** Gives the descriptor of the resource with a kind name. */
29
+ const descriptorOf = (domain, kind) => Option.getOrUndefined(HashMap.get(domain.resources, kind));
30
+ export { descriptorOf, make };
31
+
32
+ //# sourceMappingURL=domain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain.js","names":["ResourceData.descriptor","Catalog.catalogOf"],"sources":["../../src/internal/domain.ts"],"sourcesContent":["/**\n * The values of domains. The types are in `Domain.ts`.\n */\nimport * as Arr from \"effect/Array\"\nimport * as Data from \"effect/Data\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as Option from \"effect/Option\"\nimport type * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Catalog from \"./catalog.ts\"\nimport * as ResourceData from \"./resource.ts\"\n\n/** The class of every domain value. `isDomain` uses it to know a domain. */\nclass DomainData extends Data.Class<{\n\treadonly principal: Schema.Top\n\treadonly principalCatalog: Catalog.Catalog\n\treadonly resources: HashMap.HashMap<string, Resource.Descriptor>\n\treadonly meta: Schema.Top | undefined\n}> {}\n\n/** Makes a domain. Each resource must have its own kind name. */\nexport const make = (options: {\n\treadonly principal: Schema.Top\n\treadonly resources: ReadonlyArray<Resource.Any>\n\treadonly meta?: Schema.Top\n}): DomainData => {\n\tconst descriptors = Arr.map(options.resources, ResourceData.descriptor)\n\tconst kinds = Arr.map(descriptors, (descriptor) => descriptor.kind)\n\t// Two resources with one kind name is a mistake in the code, not in the\n\t// data, so it is a defect.\n\tconst twice = Arr.findFirst(\n\t\tkinds,\n\t\t(kind, index) => kinds.indexOf(kind) !== index,\n\t)\n\tif (Option.isSome(twice)) {\n\t\tthrow new Error(`Two resources have the kind name \"${twice.value}\"`)\n\t}\n\tif (kinds.includes(\"*\")) {\n\t\tthrow new Error(`A resource may not have the kind name \"*\"`)\n\t}\n\tconst resources = HashMap.fromIterable(\n\t\tArr.map(\n\t\t\tdescriptors,\n\t\t\t(descriptor) => [descriptor.kind, descriptor] as const,\n\t\t),\n\t)\n\t// The catalog checks the field names of the principal, so a bad name is a\n\t// defect when the domain is made.\n\treturn new DomainData({\n\t\tprincipal: options.principal,\n\t\tprincipalCatalog: Catalog.catalogOf(options.principal.ast),\n\t\tresources,\n\t\tmeta: options.meta,\n\t})\n}\n\n/** Gives the descriptor of the resource with a kind name. */\nexport const descriptorOf = (\n\tdomain: Domain.Any,\n\tkind: string,\n): Resource.Descriptor | undefined =>\n\tOption.getOrUndefined(HashMap.get(domain.resources, kind))\n\n/** Tests if a value is a domain. */\nexport const isDomain = (u: unknown): u is Domain.Any => u instanceof DomainData\n"],"mappings":";;;;;;;;;;;AAcA,IAAM,aAAN,cAAyB,KAAK,MAK3B,CAAC;;AAGJ,MAAa,QAAQ,YAIH;CACjB,MAAM,cAAc,IAAI,IAAI,QAAQ,WAAWA,UAAuB;CACtE,MAAM,QAAQ,IAAI,IAAI,cAAc,eAAe,WAAW,IAAI;CAGlE,MAAM,QAAQ,IAAI,UACjB,QACC,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,KAC1C;CACA,IAAI,OAAO,OAAO,KAAK,GACtB,MAAM,IAAI,MAAM,qCAAqC,MAAM,MAAM,EAAE;CAEpE,IAAI,MAAM,SAAS,GAAG,GACrB,MAAM,IAAI,MAAM,2CAA2C;CAE5D,MAAM,YAAY,QAAQ,aACzB,IAAI,IACH,cACC,eAAe,CAAC,WAAW,MAAM,UAAU,CAC7C,CACD;CAGA,OAAO,IAAI,WAAW;EACrB,WAAW,QAAQ;EACnB,kBAAkBC,UAAkB,QAAQ,UAAU,GAAG;EACzD;EACA,MAAM,QAAQ;CACf,CAAC;AACF;;AAGA,MAAa,gBACZ,QACA,SAEA,OAAO,eAAe,QAAQ,IAAI,OAAO,WAAW,IAAI,CAAC"}