@nmnmcc/ability 0.3.0 → 1.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 (63) hide show
  1. package/README.md +169 -14
  2. package/dist/Authorizer.d.ts +64 -39
  3. package/dist/Authorizer.d.ts.map +1 -1
  4. package/dist/Authorizer.js +291 -6
  5. package/dist/Authorizer.js.map +1 -1
  6. package/dist/Condition.d.ts +444 -65
  7. package/dist/Condition.d.ts.map +1 -1
  8. package/dist/Condition.js +1007 -25
  9. package/dist/Condition.js.map +1 -1
  10. package/dist/Domain.d.ts +27 -17
  11. package/dist/Domain.d.ts.map +1 -1
  12. package/dist/Domain.js +52 -10
  13. package/dist/Domain.js.map +1 -1
  14. package/dist/HttpApi.d.ts +84 -40
  15. package/dist/HttpApi.d.ts.map +1 -1
  16. package/dist/HttpApi.js +101 -11
  17. package/dist/HttpApi.js.map +1 -1
  18. package/dist/Policy.d.ts +311 -53
  19. package/dist/Policy.d.ts.map +1 -1
  20. package/dist/Policy.js +1212 -35
  21. package/dist/Policy.js.map +1 -1
  22. package/dist/Resource.d.ts +167 -16
  23. package/dist/Resource.d.ts.map +1 -1
  24. package/dist/Resource.js +784 -11
  25. package/dist/Resource.js.map +1 -1
  26. package/dist/Rule.d.ts +113 -27
  27. package/dist/Rule.d.ts.map +1 -1
  28. package/dist/Rule.js +217 -10
  29. package/dist/Rule.js.map +1 -1
  30. package/dist/index.js +4 -4
  31. package/package.json +1 -1
  32. package/src/Authorizer.ts +623 -59
  33. package/src/Condition.ts +2403 -112
  34. package/src/Domain.ts +79 -22
  35. package/src/HttpApi.ts +223 -23
  36. package/src/Policy.ts +2833 -93
  37. package/src/Resource.ts +1537 -22
  38. package/src/Rule.ts +461 -43
  39. package/dist/internal/authorizer.d.ts +0 -28
  40. package/dist/internal/authorizer.d.ts.map +0 -1
  41. package/dist/internal/authorizer.js +0 -153
  42. package/dist/internal/authorizer.js.map +0 -1
  43. package/dist/internal/condition.d.ts +0 -11
  44. package/dist/internal/condition.d.ts.map +0 -1
  45. package/dist/internal/condition.js +0 -393
  46. package/dist/internal/condition.js.map +0 -1
  47. package/dist/internal/domain.js +0 -34
  48. package/dist/internal/domain.js.map +0 -1
  49. package/dist/internal/httpApi.js +0 -41
  50. package/dist/internal/httpApi.js.map +0 -1
  51. package/dist/internal/policy.js +0 -524
  52. package/dist/internal/policy.js.map +0 -1
  53. package/dist/internal/resource.js +0 -66
  54. package/dist/internal/resource.js.map +0 -1
  55. package/dist/internal/rule.js +0 -131
  56. package/dist/internal/rule.js.map +0 -1
  57. package/src/internal/authorizer.ts +0 -271
  58. package/src/internal/condition.ts +0 -607
  59. package/src/internal/domain.ts +0 -56
  60. package/src/internal/httpApi.ts +0 -84
  61. package/src/internal/policy.ts +0 -815
  62. package/src/internal/resource.ts +0 -98
  63. package/src/internal/rule.ts +0 -203
@@ -1,607 +0,0 @@
1
- // A predicate keeps its error type and service type only in the public types.
2
- // Inside this file they are erased to `unknown`, so the language service check
3
- // for `unknown` in an Effect is off here.
4
- // @effect-diagnostics anyUnknownInErrorContext:off
5
- import * as Effect from "effect/Effect"
6
- import * as Equal from "effect/Equal"
7
- import { dual } from "effect/Function"
8
- import * as Predicate from "effect/Predicate"
9
- import * as Schema from "effect/Schema"
10
- import type * as Condition from "../Condition.ts"
11
- import type * as Resource from "../Resource.ts"
12
-
13
- type Ast = Condition.Ast
14
- type Scalar = Condition.Scalar
15
-
16
- /** @internal */
17
- export const always: Condition.True = Object.freeze({ _tag: "True" })
18
-
19
- /** @internal */
20
- export const never: Condition.False = Object.freeze({ _tag: "False" })
21
-
22
- const literal = (value: Scalar): Condition.Literal =>
23
- Object.freeze({ _tag: "Literal", value })
24
-
25
- const isOperand = (u: unknown): u is Condition.Operand =>
26
- Predicate.hasProperty(u, "_tag") &&
27
- (u._tag === "Literal" || u._tag === "Principal")
28
-
29
- const toOperand = (value: unknown): Condition.Operand =>
30
- isOperand(value) ? value : literal(value as Scalar)
31
-
32
- const compare =
33
- (op: Condition.CompareOperator) =>
34
- (field: string, value: unknown): Condition.Compare =>
35
- Object.freeze({ _tag: "Compare", op, field, value: toOperand(value) })
36
-
37
- /** @internal */
38
- export const and = (...conditions: ReadonlyArray<Ast>): Condition.And =>
39
- Object.freeze({ _tag: "And", conditions: Object.freeze([...conditions]) })
40
-
41
- /** @internal */
42
- export const or = (...conditions: ReadonlyArray<Ast>): Condition.Or =>
43
- Object.freeze({ _tag: "Or", conditions: Object.freeze([...conditions]) })
44
-
45
- /** @internal */
46
- export const not = (condition: Ast): Condition.Not =>
47
- Object.freeze({ _tag: "Not", condition })
48
-
49
- /** @internal */
50
- export const builder = (): any =>
51
- Object.freeze({
52
- eq: compare("eq"),
53
- ne: compare("ne"),
54
- lt: compare("lt"),
55
- lte: compare("lte"),
56
- gt: compare("gt"),
57
- gte: compare("gte"),
58
- in: (field: string, values: ReadonlyArray<unknown>): Condition.In =>
59
- Object.freeze({
60
- _tag: "In",
61
- field,
62
- values: Object.freeze(values.map(toOperand)),
63
- }),
64
- exists: (field: string): Condition.Exists =>
65
- Object.freeze({ _tag: "Exists", field }),
66
- under: (...prefix: ReadonlyArray<unknown>): Condition.Under =>
67
- Object.freeze({
68
- _tag: "Under",
69
- prefix: Object.freeze(prefix.map(toOperand)),
70
- }),
71
- principal: (path: string): Condition.PrincipalRef =>
72
- Object.freeze({ _tag: "Principal", path }),
73
- and,
74
- or,
75
- not,
76
- predicate: (
77
- name: string,
78
- test: (
79
- value: unknown,
80
- principal: unknown,
81
- ) => boolean | Effect.Effect<boolean, unknown, unknown>,
82
- ): Condition.Predicate =>
83
- Object.freeze({
84
- _tag: "Predicate",
85
- name,
86
- run: (value: unknown, principal: unknown) =>
87
- Effect.suspend(() => {
88
- const result = test(value, principal)
89
- return Effect.isEffect(result) ? result : Effect.succeed(result)
90
- }),
91
- }),
92
- })
93
-
94
- /**
95
- * Gets a field of an object by a path with dots. A missing field, or a field
96
- * under a value that is not an object, gives `null`.
97
- *
98
- * @internal
99
- */
100
- export const getField = (value: unknown, path: string): unknown => {
101
- let cursor: unknown = value
102
- for (const key of path.split(".")) {
103
- if (!Predicate.isObject(cursor) || !Object.hasOwn(cursor, key)) {
104
- return null
105
- }
106
- cursor = (cursor as Record<string, unknown>)[key]
107
- }
108
- return cursor === undefined ? null : cursor
109
- }
110
-
111
- /**
112
- * Gives the value of an operand. A principal field that is `null` or missing
113
- * gives `undefined`. No field of a resource is `undefined`, so a test with it
114
- * is never true. This keeps the check closed: a principal with no id never
115
- * matches a resource with no owner.
116
- */
117
- const resolve = (operand: Condition.Operand, principal: unknown): unknown =>
118
- operand._tag === "Literal"
119
- ? operand.value
120
- : (getField(principal, operand.path) ?? undefined)
121
-
122
- const testCompare = (
123
- op: Condition.CompareOperator,
124
- left: unknown,
125
- right: unknown,
126
- ): boolean => {
127
- if (right === undefined) {
128
- return false
129
- }
130
- switch (op) {
131
- case "eq":
132
- return left === right
133
- case "ne":
134
- return left !== right
135
- default: {
136
- const ordered =
137
- (typeof left === "number" && typeof right === "number") ||
138
- (typeof left === "string" && typeof right === "string")
139
- if (!ordered) {
140
- return false
141
- }
142
- const a = left as number | string
143
- const b = right as number | string
144
- switch (op) {
145
- case "lt":
146
- return a < b
147
- case "lte":
148
- return a <= b
149
- case "gt":
150
- return a > b
151
- case "gte":
152
- return a >= b
153
- }
154
- }
155
- }
156
- }
157
-
158
- /**
159
- * Gives the values of a resource path. It gives `undefined` when the resource
160
- * has no path.
161
- */
162
- const pathValues = (
163
- value: unknown,
164
- path: ReadonlyArray<Resource.PathSegment> | undefined,
165
- ): ReadonlyArray<unknown> | undefined =>
166
- path?.map((segment) =>
167
- typeof segment === "string" ? segment : getField(value, segment.field),
168
- )
169
-
170
- const testUnder = (
171
- prefix: ReadonlyArray<Condition.Operand>,
172
- input: Condition.Input,
173
- ): boolean => {
174
- const values = pathValues(input.value, input.path)
175
- if (values === undefined || prefix.length > values.length) {
176
- return false
177
- }
178
- return prefix.every(
179
- (operand, index) => resolve(operand, input.principal) === values[index],
180
- )
181
- }
182
-
183
- /** @internal */
184
- export const hasPredicate = (ast: Ast): boolean => {
185
- switch (ast._tag) {
186
- case "Predicate":
187
- return true
188
- case "And":
189
- case "Or":
190
- return ast.conditions.some(hasPredicate)
191
- case "Not":
192
- return hasPredicate(ast.condition)
193
- default:
194
- return false
195
- }
196
- }
197
-
198
- /** @internal */
199
- export const evaluateSyncOf = (ast: Ast, input: Condition.Input): boolean => {
200
- switch (ast._tag) {
201
- case "True":
202
- return true
203
- case "False":
204
- return false
205
- case "Compare":
206
- return testCompare(
207
- ast.op,
208
- getField(input.value, ast.field),
209
- resolve(ast.value, input.principal),
210
- )
211
- case "In": {
212
- const field = getField(input.value, ast.field)
213
- return ast.values.some(
214
- (operand) => resolve(operand, input.principal) === field,
215
- )
216
- }
217
- case "Exists":
218
- return getField(input.value, ast.field) !== null
219
- case "Under":
220
- return testUnder(ast.prefix, input)
221
- case "And":
222
- return ast.conditions.every((child) => evaluateSyncOf(child, input))
223
- case "Or":
224
- return ast.conditions.some((child) => evaluateSyncOf(child, input))
225
- case "Not":
226
- return !evaluateSyncOf(ast.condition, input)
227
- case "Predicate":
228
- // The types stop this. A predicate needs `evaluate`.
229
- throw new Error(
230
- `The predicate "${ast.name}" needs an Effect. Use Condition.evaluate.`,
231
- )
232
- }
233
- }
234
-
235
- /** @internal */
236
- export const evaluateOf = (
237
- ast: Ast,
238
- input: Condition.Input,
239
- ): Effect.Effect<boolean, unknown, unknown> => {
240
- if (!hasPredicate(ast)) {
241
- return Effect.sync(() => evaluateSyncOf(ast, input))
242
- }
243
- switch (ast._tag) {
244
- case "Predicate":
245
- return ast.run(input.value, input.principal)
246
- case "Not":
247
- return Effect.map(evaluateOf(ast.condition, input), (value) => !value)
248
- case "And":
249
- return Effect.gen(function* () {
250
- for (const child of ast.conditions) {
251
- if (!(yield* evaluateOf(child, input))) {
252
- return false
253
- }
254
- }
255
- return true
256
- })
257
- case "Or":
258
- return Effect.gen(function* () {
259
- for (const child of ast.conditions) {
260
- if (yield* evaluateOf(child, input)) {
261
- return true
262
- }
263
- }
264
- return false
265
- })
266
- default:
267
- return Effect.sync(() => evaluateSyncOf(ast, input))
268
- }
269
- }
270
-
271
- const dedupe = (conditions: ReadonlyArray<Ast>): ReadonlyArray<Ast> => {
272
- const output: Array<Ast> = []
273
- for (const condition of conditions) {
274
- if (!output.some((other) => Equal.equals(other, condition))) {
275
- output.push(condition)
276
- }
277
- }
278
- return output
279
- }
280
-
281
- /**
282
- * Removes the children that the other children make useless. In an `Or`,
283
- * `a OR (a AND b)` is `a`. In an `And`, `a AND (a OR b)` is `a`.
284
- */
285
- const absorb = (
286
- inner: "And" | "Or",
287
- conditions: ReadonlyArray<Ast>,
288
- ): ReadonlyArray<Ast> =>
289
- conditions.filter(
290
- (condition) =>
291
- condition._tag !== inner ||
292
- !conditions.some(
293
- (other) =>
294
- other !== condition &&
295
- condition.conditions.some((part) => Equal.equals(part, other)),
296
- ),
297
- )
298
-
299
- const flatten = (
300
- tag: "And" | "Or",
301
- conditions: ReadonlyArray<Ast>,
302
- ): ReadonlyArray<Ast> =>
303
- conditions.flatMap((child) => {
304
- const simple = simplify(child)
305
- return simple._tag === tag ? simple.conditions : [simple]
306
- })
307
-
308
- /** @internal */
309
- export const simplify = (ast: Ast): Ast => {
310
- switch (ast._tag) {
311
- case "And": {
312
- const children = flatten("And", ast.conditions).filter(
313
- (child) => child._tag !== "True",
314
- )
315
- if (children.some((child) => child._tag === "False")) {
316
- return never
317
- }
318
- const unique = absorb("Or", dedupe(children))
319
- if (unique.length === 0) {
320
- return always
321
- }
322
- return unique.length === 1 ? unique[0]! : and(...unique)
323
- }
324
- case "Or": {
325
- const children = flatten("Or", ast.conditions).filter(
326
- (child) => child._tag !== "False",
327
- )
328
- if (children.some((child) => child._tag === "True")) {
329
- return always
330
- }
331
- const unique = absorb("And", dedupe(children))
332
- if (unique.length === 0) {
333
- return never
334
- }
335
- return unique.length === 1 ? unique[0]! : or(...unique)
336
- }
337
- case "Not": {
338
- const child = simplify(ast.condition)
339
- switch (child._tag) {
340
- case "True":
341
- return never
342
- case "False":
343
- return always
344
- case "Not":
345
- return child.condition
346
- case "Compare":
347
- // A principal field with no value makes `eq` and `ne` both false,
348
- // so only a fixed value lets `not eq` become `ne`.
349
- if (
350
- child.value._tag === "Literal" &&
351
- (child.op === "eq" || child.op === "ne")
352
- ) {
353
- return Object.freeze({
354
- ...child,
355
- op: child.op === "eq" ? "ne" : "eq",
356
- })
357
- }
358
- return not(child)
359
- default:
360
- return not(child)
361
- }
362
- }
363
- case "In":
364
- return ast.values.length === 0 ? never : ast
365
- default:
366
- return ast
367
- }
368
- }
369
-
370
- /**
371
- * Puts the value of the principal into an operand. It gives `undefined` for a
372
- * principal field with no value. See {@link resolve}.
373
- */
374
- const bindOperand = (
375
- operand: Condition.Operand,
376
- principal: unknown,
377
- ): Condition.Literal | undefined => {
378
- if (operand._tag === "Literal") {
379
- return operand
380
- }
381
- const value = resolve(operand, principal)
382
- return value === undefined ? undefined : literal(value as Scalar)
383
- }
384
-
385
- const expandUnder = (
386
- prefix: ReadonlyArray<Condition.Operand>,
387
- principal: unknown,
388
- path: ReadonlyArray<Resource.PathSegment> | undefined,
389
- ): Ast => {
390
- if (path === undefined || prefix.length > path.length) {
391
- return never
392
- }
393
- const parts: Array<Ast> = []
394
- prefix.forEach((operand, index) => {
395
- const value = resolve(operand, principal)
396
- const segment = path[index]!
397
- if (value === undefined) {
398
- parts.push(never)
399
- } else if (typeof segment === "string") {
400
- parts.push(segment === value ? always : never)
401
- } else {
402
- parts.push(
403
- Object.freeze({
404
- _tag: "Compare",
405
- op: "eq",
406
- field: segment.field,
407
- value: literal(value as Scalar),
408
- }),
409
- )
410
- }
411
- })
412
- return and(...parts)
413
- }
414
-
415
- const bind = (
416
- ast: Ast,
417
- principal: unknown,
418
- path: ReadonlyArray<Resource.PathSegment> | undefined,
419
- ): Ast => {
420
- switch (ast._tag) {
421
- case "Compare": {
422
- const value = bindOperand(ast.value, principal)
423
- return value === undefined ? never : Object.freeze({ ...ast, value })
424
- }
425
- case "In":
426
- return Object.freeze({
427
- ...ast,
428
- values: Object.freeze(
429
- ast.values.flatMap((operand) => {
430
- const value = bindOperand(operand, principal)
431
- return value === undefined ? [] : [value]
432
- }),
433
- ),
434
- })
435
- case "Under":
436
- return expandUnder(ast.prefix, principal, path)
437
- case "And":
438
- return and(...ast.conditions.map((child) => bind(child, principal, path)))
439
- case "Or":
440
- return or(...ast.conditions.map((child) => bind(child, principal, path)))
441
- case "Not":
442
- return not(bind(ast.condition, principal, path))
443
- case "Predicate":
444
- return Object.freeze({
445
- _tag: "Predicate",
446
- name: ast.name,
447
- run: (value: unknown) => ast.run(value, principal),
448
- })
449
- default:
450
- return ast
451
- }
452
- }
453
-
454
- /** @internal */
455
- export const partialOf = (
456
- ast: Ast,
457
- input: {
458
- readonly principal?: object | undefined
459
- readonly path?: ReadonlyArray<Resource.PathSegment> | undefined
460
- },
461
- ): Ast => simplify(bind(ast, input.principal, input.path))
462
-
463
- /** @internal */
464
- export const evaluate = dual(2, evaluateOf)
465
-
466
- /** @internal */
467
- export const evaluateSync = dual(2, evaluateSyncOf)
468
-
469
- /** @internal */
470
- export const partial = dual(2, partialOf)
471
-
472
- /** @internal */
473
- export class Untranslatable extends Schema.TaggedError<Untranslatable>(
474
- "@nmnmcc/ability/Condition/Untranslatable",
475
- )("Untranslatable", { reason: Schema.String }) {}
476
-
477
- const foldNode = <A>(ast: Ast, interpreter: Condition.Interpreter<A>): A => {
478
- switch (ast._tag) {
479
- case "True":
480
- return interpreter.true()
481
- case "False":
482
- return interpreter.false()
483
- case "Compare":
484
- if (ast.value._tag !== "Literal") {
485
- throw Untranslatable.make({
486
- reason: `The field "${ast.field}" is compared with a principal field. Call Condition.partial first.`,
487
- })
488
- }
489
- return interpreter.compare(ast.op, ast.field, ast.value.value)
490
- case "In":
491
- return interpreter.in(
492
- ast.field,
493
- ast.values.map((operand) => {
494
- if (operand._tag !== "Literal") {
495
- throw Untranslatable.make({
496
- reason: `The field "${ast.field}" uses a principal field. Call Condition.partial first.`,
497
- })
498
- }
499
- return operand.value
500
- }),
501
- )
502
- case "Exists":
503
- return interpreter.exists(ast.field)
504
- case "Under":
505
- throw Untranslatable.make({
506
- reason:
507
- "An Under node needs the resource path. Call Condition.partial first.",
508
- })
509
- case "And":
510
- return interpreter.and(
511
- ast.conditions.map((child) => foldNode(child, interpreter)),
512
- )
513
- case "Or":
514
- return interpreter.or(
515
- ast.conditions.map((child) => foldNode(child, interpreter)),
516
- )
517
- case "Not":
518
- return interpreter.not(foldNode(ast.condition, interpreter))
519
- case "Predicate":
520
- if (interpreter.predicate === undefined) {
521
- throw Untranslatable.make({
522
- reason: `The predicate "${ast.name}" can not become a query`,
523
- })
524
- }
525
- return interpreter.predicate(ast.name)
526
- }
527
- }
528
-
529
- /** @internal */
530
- export const fold = dual(
531
- 2,
532
- <A>(
533
- ast: Ast,
534
- interpreter: Condition.Interpreter<A>,
535
- ): Effect.Effect<A, Untranslatable> =>
536
- Effect.try({
537
- try: () => foldNode(ast, interpreter),
538
- catch: (cause) => {
539
- if (cause instanceof Untranslatable) {
540
- return cause
541
- }
542
- throw cause
543
- },
544
- }),
545
- )
546
-
547
- /** @internal */
548
- export const fields = (ast: Ast): ReadonlyArray<string> => {
549
- const output = new Set<string>()
550
- const visit = (node: Ast): void => {
551
- switch (node._tag) {
552
- case "Compare":
553
- case "In":
554
- case "Exists":
555
- output.add(node.field)
556
- return
557
- case "And":
558
- case "Or":
559
- node.conditions.forEach(visit)
560
- return
561
- case "Not":
562
- visit(node.condition)
563
- return
564
- default:
565
- return
566
- }
567
- }
568
- visit(ast)
569
- return Object.freeze(Array.from(output))
570
- }
571
-
572
- const ScalarSchema = Schema.Union([
573
- Schema.String,
574
- Schema.Finite,
575
- Schema.Boolean,
576
- Schema.Null,
577
- ])
578
-
579
- const OperandSchema = Schema.Union([
580
- Schema.TaggedStruct("Literal", { value: ScalarSchema }),
581
- Schema.TaggedStruct("Principal", { path: Schema.String }),
582
- ])
583
-
584
- const Recursive = Schema.suspend(
585
- (): Schema.Codec<Condition.Condition, Condition.Encoded> => schema,
586
- )
587
-
588
- /** @internal */
589
- export const schema: Schema.Codec<Condition.Condition, Condition.Encoded> =
590
- Schema.Union([
591
- Schema.TaggedStruct("True", {}),
592
- Schema.TaggedStruct("False", {}),
593
- Schema.TaggedStruct("Compare", {
594
- op: Schema.Literals(["eq", "ne", "lt", "lte", "gt", "gte"]),
595
- field: Schema.String,
596
- value: OperandSchema,
597
- }),
598
- Schema.TaggedStruct("In", {
599
- field: Schema.String,
600
- values: Schema.Array(OperandSchema),
601
- }),
602
- Schema.TaggedStruct("Exists", { field: Schema.String }),
603
- Schema.TaggedStruct("Under", { prefix: Schema.Array(OperandSchema) }),
604
- Schema.TaggedStruct("And", { conditions: Schema.Array(Recursive) }),
605
- Schema.TaggedStruct("Or", { conditions: Schema.Array(Recursive) }),
606
- Schema.TaggedStruct("Not", { condition: Recursive }),
607
- ]) as never
@@ -1,56 +0,0 @@
1
- import * as Predicate from "effect/Predicate"
2
- import type * as Schema from "effect/Schema"
3
- import type * as Domain from "../Domain.ts"
4
- import type * as Principal from "../Principal.ts"
5
- import type * as Resource from "../Resource.ts"
6
- import { descriptor } from "./resource.ts"
7
-
8
- /** @internal */
9
- export const TypeId = "~@nmnmcc/ability/Domain" as const
10
-
11
- /** @internal */
12
- export const make = (options: {
13
- readonly principal: Principal.Any
14
- readonly resources: ReadonlyArray<Resource.Any>
15
- readonly meta?: Schema.Top
16
- }): any => {
17
- const resources = new Map<string, Resource.Descriptor>()
18
- for (const resource of options.resources) {
19
- const value = descriptor(resource)
20
- if (resources.has(value.kind)) {
21
- // Two resources with one kind name is a mistake in the code, not in the
22
- // data, so it is a defect.
23
- throw new Error(`Two resources have the kind name "${value.kind}"`)
24
- }
25
- if (value.kind === "*") {
26
- throw new Error(`A resource may not have the kind name "*"`)
27
- }
28
- resources.set(value.kind, value)
29
- }
30
- return Object.freeze({
31
- [TypeId]: TypeId,
32
- principal: options.principal,
33
- resources,
34
- meta: options.meta,
35
- })
36
- }
37
-
38
- /** @internal */
39
- export const isDomain = (u: unknown): u is Domain.Any =>
40
- Predicate.hasProperty(u, TypeId)
41
-
42
- /** @internal */
43
- export const getDescriptor = (
44
- domain: Domain.Any,
45
- kind: string,
46
- ): Resource.Descriptor | undefined => domain.resources.get(kind)
47
-
48
- /**
49
- * Gives the kind name of a resource value, from its `_tag` field.
50
- *
51
- * @internal
52
- */
53
- export const kindOf = (value: unknown): string | undefined =>
54
- Predicate.hasProperty(value, "_tag") && typeof value._tag === "string"
55
- ? value._tag
56
- : undefined