@nmnmcc/ability 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Authorization tools for Effect programs. Authorization means the work of
4
4
  deciding if a principal may do an act. A principal is the one who asks: a
5
- person, a user token, or a Bot.
5
+ person, an API token, or another service.
6
6
 
7
7
  The library is made with [Effect](https://effect.website) only:
8
8
 
@@ -140,35 +140,35 @@ There are two ways to make a resource:
140
140
 
141
141
  ```ts
142
142
  // A Schema class. Its body may have its own getters and methods.
143
- class Bot extends Resource.Class<Bot>()("Bot", {
143
+ class Document extends Resource.Class<Document>()("Document", {
144
144
  actions: ["read", "update"],
145
- fields: { id: Schema.String, ownerUserId: Schema.NullOr(Schema.String) },
145
+ fields: { id: Schema.String, ownerId: Schema.NullOr(Schema.String) },
146
146
  }) {
147
- get shared() {
148
- return this.ownerUserId === null
147
+ get orphaned() {
148
+ return this.ownerId === null
149
149
  }
150
150
  }
151
151
 
152
152
  // Plain objects with a `_tag` field.
153
- const Trigger = Resource.make("Trigger", {
153
+ const Comment = Resource.make("Comment", {
154
154
  actions: ["read", "write"],
155
- fields: { id: Schema.String, enabled: Schema.Boolean },
155
+ fields: { id: Schema.String, hidden: Schema.Boolean },
156
156
  })
157
- const trigger = Trigger.make({ id: "t1", enabled: true })
157
+ const comment = Comment.make({ id: "c1", hidden: false })
158
158
  ```
159
159
 
160
160
  A resource may have a path. The path puts the resource in a tree, so one rule
161
161
  can cover a part of the tree:
162
162
 
163
163
  ```ts
164
- class Bot extends Resource.Class<Bot>()("Bot", {
164
+ class Document extends Resource.Class<Document>()("Document", {
165
165
  actions: ["read", "update"],
166
166
  fields: {
167
167
  id: Schema.String,
168
168
  organizationId: Schema.String,
169
- ownerUserId: Schema.NullOr(Schema.String),
169
+ teamId: Schema.NullOr(Schema.String),
170
170
  },
171
- path: (f) => ["org", f.organizationId, "user", f.ownerUserId, "bot", f.id],
171
+ path: (f) => ["org", f.organizationId, "team", f.teamId, "doc", f.id],
172
172
  }) {}
173
173
 
174
174
  // Every resource of the organization of the principal.
@@ -216,8 +216,8 @@ The builder in `when` has these functions:
216
216
 
217
217
  A missing field of a resource has the value `null`. A field of the principal
218
218
  that is `null` or missing is different: a test with it is always false, also
219
- with `ne`. So a guest with no `userId` never matches a Bot with no owner. To
220
- test for `null`, use a fixed value, as in `$.eq("ownerUserId", null)`.
219
+ with `ne`. So a guest with no `userId` never matches a document with no owner.
220
+ To test for `null`, use a fixed value, as in `$.eq("ownerId", null)`.
221
221
 
222
222
  `predicate` is the one way out of plain data. Its test may give a boolean or an
223
223
  `Effect`. The error type and the service type of the `Effect` go into the types
@@ -225,8 +225,9 @@ of the policy and of every check. A policy with a predicate can not become JSON
225
225
  or a query, and the type system stops you when you try.
226
226
 
227
227
  A predicate also keeps the types of the value and the principal that it reads.
228
- So a predicate for a Bot can not go into a rule for a Trigger. Make conditions
229
- with the builder: a node made by hand is not a condition in the types.
228
+ So a predicate for a Document can not go into a rule for a Comment. Make
229
+ conditions with the builder: a node made by hand is not a condition in the
230
+ types.
230
231
 
231
232
  ## Policies
232
233
 
@@ -400,7 +401,7 @@ function too. Take care with `null`: in this library `ne` is true when the field
400
401
  is `null`, but in SQL `x <> 'a'` is not true when `x` is `NULL`. Use
401
402
  `IS DISTINCT FROM` or a similar form. Give the values to the database as bound
402
403
  parameters. A value from a principal may have a quote in it, and in the SQL text
403
- it could change the query. `examples/openbot-rbac.ts` shows how.
404
+ it could change the query. `examples/multi-tenant.ts` shows how.
404
405
 
405
406
  ## JSON
406
407
 
@@ -493,8 +494,8 @@ The errors of a check:
493
494
  ### Deciders
494
495
 
495
496
  When no rule gives an answer, the authorizer asks the decider, if there is one.
496
- A decider may answer at once, or fail with `Pending` when a person or a Bot must
497
- answer later:
497
+ A decider may answer at once, or fail with `Pending` when a person or another
498
+ service must answer later:
498
499
 
499
500
  ```ts
500
501
  Authorizer.layer(Auth, {
@@ -582,8 +583,20 @@ status codes: 403, 503, and 409.
582
583
 
583
584
  ## Examples
584
585
 
586
+ Each example is a small program. Run it with Node.js, which runs `.ts` files
587
+ itself:
588
+
585
589
  ```sh
586
590
  node examples/basic.ts
587
- node examples/authorizer.ts
588
- node examples/openbot-rbac.ts
589
591
  ```
592
+
593
+ | Example | What it shows |
594
+ | ----------------------------- | ------------------------------------------------------------------------------------------------ |
595
+ | `examples/basic.ts` | A blog: a domain, a policy, field rules and a decision with a reason. |
596
+ | `examples/authorizer.ts` | Team documents: an authorizer service with a cache, `guard`, `filter`, coverage and a decider. |
597
+ | `examples/multi-tenant.ts` | A task tracker for many teams: roles, API tokens, a platform limit, scopes and SQL list queries. |
598
+ | `examples/field-access.ts` | A staff list: rules for some fields, field patterns and `permittedFields`. |
599
+ | `examples/stored-policies.ts` | A help desk: policies kept as JSON, `meta` on rules, checks when a policy loads, and `explain`. |
600
+ | `examples/predicates.ts` | Meeting rooms: a predicate that asks an Effect service, its error type, and `provideContext`. |
601
+ | `examples/algorithms.ts` | File sharing: the four combining algorithms, `any`, `firstOf` and `ceiling`. |
602
+ | `examples/http-api.ts` | A notes API: the `effect/http-api` middleware, one-value checks, list scopes and closed routes. |
@@ -37,7 +37,7 @@ import * as Schema from "effect/Schema";
37
37
  * Then use the service in a handler:
38
38
  *
39
39
  * ```ts
40
- * yield* Auth.check({ action: "update", resource: bot })
40
+ * yield* Auth.check({ action: "update", resource: document })
41
41
  * ```
42
42
  *
43
43
  * A "no" answer goes into the error channel as {@link Forbidden}. When the
@@ -46,7 +46,7 @@ import * as Schema from "effect/Schema";
46
46
  *
47
47
  * When no rule gives an answer, the authorizer asks the decider, if there is
48
48
  * one. A decider may answer at once, or fail with {@link Pending} when a person
49
- * or a Bot must answer later.
49
+ * or another service must answer later.
50
50
  *
51
51
  * @stability stable
52
52
  * @since 0.3.0
@@ -1 +1 @@
1
- {"version":3,"file":"Authorizer.js","names":["Policy.provideContext","Policy.decide","decide","Policy.coverage","Decision.Allow","Policy.scopeHasValue","Policy.residuals","Domain.descriptorOf","Condition.satisfiable","Decision.NotApplicable","Decision.Deny","Resource.kindOf","Policy.fitsKind","Decision.reason","Policy.residual","coverage","permittedFields","Policy.permittedFields"],"sources":["../src/Authorizer.ts"],"sourcesContent":["/**\n * An authorizer is an Effect service that makes decisions for the principal\n * of the current request.\n *\n * You make the service class, in the same way as `Context.Service`. Give the\n * services that the principal and the decider need as `requires`, and their\n * errors as `error`:\n *\n * ```ts\n * class Auth extends Authorizer.Service<Auth, { requires: Identity }>()(\n * \"app/Auth\",\n * App,\n * ) {}\n * ```\n *\n * Then give a layer with {@link layer}. It says where the principal comes\n * from and how to load its policy. The policy of each principal is loaded\n * once and kept in a cache for a short time.\n *\n * Then use the service in a handler:\n *\n * ```ts\n * yield* Auth.check({ action: \"update\", resource: bot })\n * ```\n *\n * A \"no\" answer goes into the error channel as {@link Forbidden}. When the\n * policy can not be loaded, the check fails with {@link PolicyUnavailable}.\n * So the check fails closed: an error is never a \"yes\".\n *\n * When no rule gives an answer, the authorizer asks the decider, if there is\n * one. A decider may answer at once, or fail with {@link Pending} when a person\n * or a Bot must answer later.\n *\n * @stability stable\n * @since 0.3.0\n */\n// The service erases the error type and the service type of predicates,\n// principals and deciders to `unknown`. The public types keep them, so the\n// language service check for `unknown` in an Effect is off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Cache from \"effect/Cache\"\nimport * as Context from \"effect/Context\"\nimport type * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport * as Equal from \"effect/Equal\"\nimport * as Hash from \"effect/Hash\"\nimport * as Layer from \"effect/Layer\"\nimport * as MutableHashMap from \"effect/MutableHashMap\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as Condition from \"./Condition.ts\"\nimport * as Decision from \"./Decision.ts\"\nimport * as Domain from \"./Domain.ts\"\nimport * as Policy from \"./Policy.ts\"\nimport * as Resource from \"./Resource.ts\"\n\n/**\n * The error when a policy says no.\n *\n * @stability stable\n * @category errors\n * @since 0.3.0\n */\nexport class Forbidden extends Schema.TaggedError<Forbidden>(\n\t\"@nmnmcc/ability/Authorizer/Forbidden\",\n)(\"Forbidden\", {\n\taction: Schema.String,\n\tkind: Schema.String,\n\tfield: Schema.optionalKey(Schema.String),\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/**\n * The error when the policy of the principal can not be loaded. The cause is\n * kept, but it is not shown to the client.\n *\n * @stability stable\n * @category errors\n * @since 0.3.0\n */\nexport class PolicyUnavailable extends Schema.TaggedError<PolicyUnavailable>(\n\t\"@nmnmcc/ability/Authorizer/PolicyUnavailable\",\n)(\"PolicyUnavailable\", { cause: Schema.Defect() }) {}\n\n/**\n * The error when a decider must wait for an answer. `ticket` names the\n * request that waits, so the client can come back for the answer.\n *\n * @stability stable\n * @category errors\n * @since 0.3.0\n */\nexport class Pending extends Schema.TaggedError<Pending>(\n\t\"@nmnmcc/ability/Authorizer/Pending\",\n)(\"Pending\", {\n\tticket: Schema.String,\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/**\n * The services and the errors of the principal and the decider of an\n * authorizer.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Config {\n\treadonly requires?: unknown\n\treadonly error?: unknown\n}\n\ntype RequiresOf<C> = C extends { readonly requires: infer R } ? R : never\ntype ErrorOf<C> = C extends { readonly error: infer E } ? E : never\n\n/**\n * What a check is about: an act on a resource value, or an act on a kind of\n * resource. A check on a kind passes only when the policy allows the act on\n * every value of the kind. With a scope (`within`), it passes when the policy\n * allows the act on every value in the scope, and the scope is not empty.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Target<D> = Policy.Target<D> | Policy.KindTarget<D>\n\n/**\n * A request to the decider.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type DeciderRequest<D> = Policy.Request<D> | Policy.KindRequest<D>\n\n/**\n * The options of a guard.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type GuardOptions<D, E2, K extends string = Domain.Kind<D>> = {\n\t/** The error when the principal may not see the resource. */\n\treadonly hide: () => E2\n} & (\"read\" extends Domain.CommonAction<D, K>\n\t? {\n\t\t\t/**\n\t\t\t * The action that lets the principal see the resource. It must be an\n\t\t\t * action of every kind in `K`. The default is `\"read\"`.\n\t\t\t */\n\t\t\treadonly read?: Domain.CommonAction<D, K>\n\t\t}\n\t: {\n\t\t\t/**\n\t\t\t * The action that lets the principal see the resource. It must be an\n\t\t\t * action of every kind in `K`. Some kind in `K` has no `\"read\"`\n\t\t\t * action, so you must give it.\n\t\t\t */\n\t\t\treadonly read: Domain.CommonAction<D, K>\n\t\t})\n\n/**\n * The service.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Authorizer<D extends Domain.Any, E, R> {\n\treadonly domain: D\n\t/** The principal of the current request. */\n\treadonly principal: Effect.Effect<Domain.Principal<D>, E, R>\n\t/**\n\t * The policy of the principal of the current request. Its predicates have\n\t * the services of the layer. It may have predicates, so it is opaque.\n\t */\n\treadonly policy: Effect.Effect<\n\t\tPolicy.Policy<D, E, never, Condition.Opaque>,\n\t\tPolicyUnavailable | E,\n\t\tR\n\t>\n\t/** Gives the decision of the policy. It does not ask the decider. */\n\treadonly decide: (\n\t\ttarget: Policy.Target<D>,\n\t) => Effect.Effect<Decision.Decision, PolicyUnavailable | E, R>\n\t/** Succeeds when the principal may do the act, and fails when it may not. */\n\treadonly check: (\n\t\ttarget: Target<D>,\n\t) => Effect.Effect<void, Forbidden | Pending | PolicyUnavailable | E, R>\n\t/**\n\t * Checks the read action first, and fails with the error of `hide` when the\n\t * principal may not see the resource. Then it checks the act. Use it to\n\t * answer \"not found\" to a principal that may not know that a resource\n\t * exists.\n\t */\n\treadonly guard: <const T extends Policy.Target<D>, E2>(\n\t\ttarget: T,\n\t\toptions: GuardOptions<D, E2, T[\"resource\"][\"_tag\"]>,\n\t) => Effect.Effect<void, E2 | Forbidden | Pending | PolicyUnavailable | E, R>\n\t/**\n\t * Gives the condition on resource values that the principal may act on,\n\t * inside the scope of the target when it has one. Change it into a query\n\t * with `Condition.fold`. It is `False` when the principal may act on no\n\t * value, so the list is empty: it is not an error.\n\t */\n\treadonly filter: <const T extends Policy.KindTarget<D>>(\n\t\ttarget: T,\n\t) => Effect.Effect<\n\t\tCondition.Condition<\n\t\t\tE,\n\t\t\tnever,\n\t\t\tCondition.Opaque,\n\t\t\tPolicy.ResidualReads<D, T[\"kind\"]>\n\t\t>,\n\t\tPolicyUnavailable | E,\n\t\tR\n\t>\n\t/**\n\t * Tells if the principal may act on all, none or some of the values in\n\t * the scope of the target. See `Policy.coverage`. Use it to show a button\n\t * only when the answer is not `None`.\n\t */\n\treadonly coverage: <const T extends Policy.KindTarget<D>>(\n\t\ttarget: T,\n\t) => Effect.Effect<\n\t\tPolicy.Coverage<\n\t\t\tE,\n\t\t\tnever,\n\t\t\tCondition.Opaque,\n\t\t\tPolicy.ResidualReads<D, T[\"kind\"]>\n\t\t>,\n\t\tPolicyUnavailable | E,\n\t\tR\n\t>\n\t/** Gives the fields of a value that the principal may use. */\n\treadonly permittedFields: (\n\t\ttarget: Policy.FieldlessTarget<D>,\n\t) => Effect.Effect<ReadonlyArray<string>, PolicyUnavailable | E, R>\n\t/**\n\t * Removes the cached policy of a principal, or of every principal. Call it\n\t * after a change to the rules or roles of a principal.\n\t */\n\treadonly invalidate: (principal?: Domain.Principal<D>) => Effect.Effect<void>\n}\n\n/**\n * The class made by {@link Service}. The static functions use the service\n * from the context.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface ServiceClass<\n\tSelf,\n\tId extends string,\n\tD extends Domain.Any,\n\tE,\n\tR,\n> extends Context.ServiceClass<Self, Id, Authorizer<D, E, R>> {\n\treadonly domain: D\n\treadonly decide: (\n\t\ttarget: Policy.Target<D>,\n\t) => Effect.Effect<Decision.Decision, PolicyUnavailable | E, R | Self>\n\treadonly check: (\n\t\ttarget: Target<D>,\n\t) => Effect.Effect<\n\t\tvoid,\n\t\tForbidden | Pending | PolicyUnavailable | E,\n\t\tR | Self\n\t>\n\treadonly guard: <const T extends Policy.Target<D>, E2>(\n\t\ttarget: T,\n\t\toptions: GuardOptions<D, E2, T[\"resource\"][\"_tag\"]>,\n\t) => Effect.Effect<\n\t\tvoid,\n\t\tE2 | Forbidden | Pending | PolicyUnavailable | E,\n\t\tR | Self\n\t>\n\treadonly filter: <const T extends Policy.KindTarget<D>>(\n\t\ttarget: T,\n\t) => Effect.Effect<\n\t\tCondition.Condition<\n\t\t\tE,\n\t\t\tnever,\n\t\t\tCondition.Opaque,\n\t\t\tPolicy.ResidualReads<D, T[\"kind\"]>\n\t\t>,\n\t\tPolicyUnavailable | E,\n\t\tR | Self\n\t>\n\treadonly coverage: <const T extends Policy.KindTarget<D>>(\n\t\ttarget: T,\n\t) => Effect.Effect<\n\t\tPolicy.Coverage<\n\t\t\tE,\n\t\t\tnever,\n\t\t\tCondition.Opaque,\n\t\t\tPolicy.ResidualReads<D, T[\"kind\"]>\n\t\t>,\n\t\tPolicyUnavailable | E,\n\t\tR | Self\n\t>\n\treadonly permittedFields: (\n\t\ttarget: Policy.FieldlessTarget<D>,\n\t) => Effect.Effect<ReadonlyArray<string>, PolicyUnavailable | E, R | Self>\n}\n\n/**\n * Any service class made by {@link Service}.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type AnyServiceClass = Context.Key<any, any> & {\n\treadonly domain: Domain.Any\n}\n\n/**\n * Makes an authorizer service class for a domain.\n *\n * @stability stable\n * @category constructors\n * @since 0.3.0\n */\nexport const Service: <Self, C extends Config = {}>() => <\n\tconst Id extends string,\n\tD extends Domain.Any,\n>(\n\tid: Id,\n\tdomain: D,\n) => ServiceClass<Self, Id, D, ErrorOf<C>, RequiresOf<C>> = (() =>\n\t(id: string, domain: Domain.Any) => {\n\t\tconst Base = Context.Service<any, any>()(id)\n\t\treturn class extends Base {\n\t\t\tstatic readonly domain = domain\n\t\t\tstatic readonly decide = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.decide(target))\n\t\t\tstatic readonly check = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.check(target))\n\t\t\tstatic readonly guard = (target: AnyTarget, options: unknown) =>\n\t\t\t\tthis.use((service: any) => service.guard(target, options))\n\t\t\tstatic readonly filter = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.filter(target))\n\t\t\tstatic readonly coverage = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.coverage(target))\n\t\t\tstatic readonly permittedFields = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.permittedFields(target))\n\t\t}\n\t}) as never\n\ntype AnyTarget = {\n\treadonly action: string\n\treadonly resource?: object\n\treadonly kind?: string\n\treadonly field?: string\n\treadonly within?: unknown\n}\n\n/**\n * The options of {@link layer}.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Options<D extends Domain.Any, E, R, EL, RL, RP> {\n\t/** Gives the principal of the current request. */\n\treadonly principal: Effect.Effect<Domain.Principal<D>, E, R>\n\t/**\n\t * Loads the policy of a principal. A failure becomes\n\t * {@link PolicyUnavailable}. The services of the predicates of the policy\n\t * are taken when the layer is built.\n\t */\n\treadonly policy: (\n\t\tprincipal: Domain.Principal<D>,\n\t) => Effect.Effect<Policy.Policy<D, E, RP, any>, EL, RL>\n\t/**\n\t * Answers when no rule gives an answer. Without a decider, such a request\n\t * fails with {@link Forbidden}.\n\t */\n\treadonly decider?: (\n\t\trequest: DeciderRequest<D>,\n\t) => Effect.Effect<Decision.Decision, Pending | E, R>\n\t/**\n\t * How the policies are kept. The default keeps 1000 policies for 30 seconds.\n\t * Give `false` to load the policy for each check.\n\t *\n\t * Two principals with the same data get one policy from the cache. So the\n\t * loader must decide by the data of the principal only. When it tests that\n\t * an object of the principal is a given object, as `role === admin` does,\n\t * give `false`. A principal with objects in an array, a set or the keys of\n\t * a map is not kept, because such tests are usual there.\n\t */\n\treadonly cache?:\n\t\t| {\n\t\t\t\treadonly capacity?: number\n\t\t\t\treadonly timeToLive?: Duration.Input\n\t\t }\n\t\t| false\n}\n\n/**\n * A cache key for a principal. Two keys are equal when they have the same\n * text, so two principals with the same value share one policy. The key also\n * keeps the principal, so the cache can load its policy. A plain `Data` value\n * can not do both, so the key gives its own equality with `Equal.Equal`.\n */\nclass PrincipalKey implements Equal.Equal {\n\treadonly principal: object\n\treadonly text: string\n\tconstructor(principal: object, text: string) {\n\t\tthis.principal = principal\n\t\tthis.text = text\n\t}\n\t[Equal.symbol](that: unknown): boolean {\n\t\treturn that instanceof PrincipalKey && that.text === this.text\n\t}\n\t[Hash.symbol](): number {\n\t\treturn Hash.string(this.text)\n\t}\n}\n\n/**\n * Tests if an array has a plain field at each index below its length. With\n * one more own key for `length`, it then has no other own keys.\n */\nconst hasEveryIndex = (value: Array<unknown>): boolean =>\n\tArr.every(\n\t\tArray.from({ length: value.length }, (_, index) => index),\n\t\t(index) => isPlainField(value, String(index)),\n\t)\n\n/**\n * Tests if an own key of an object holds plain data that the text shows: it\n * can be seen in a loop over the keys, and it is not a getter.\n */\nconst isPlainField = (value: object, key: string): boolean => {\n\tconst descriptor = Object.getOwnPropertyDescriptor(value, key)\n\treturn (\n\t\tdescriptor !== undefined &&\n\t\tdescriptor.enumerable === true &&\n\t\t\"value\" in descriptor\n\t)\n}\n\nconst isObjectLike = (value: unknown): boolean =>\n\t(typeof value === \"object\" && value !== null) || typeof value === \"function\"\n\n/**\n * The number of each class that a key has seen. Each layer has its own, so\n * no state is kept for the whole program. A class is a function, and `Equal`\n * compares functions by the function itself.\n */\ntype ClassIds = MutableHashMap.MutableHashMap<object, number>\n\n/** Gives a number that is only for this class, in this layer. */\nconst classId = (classes: ClassIds, constructor: object): string => {\n\tconst id = Option.getOrElse(MutableHashMap.get(classes, constructor), () => {\n\t\tconst fresh = MutableHashMap.size(classes)\n\t\tMutableHashMap.set(classes, constructor, fresh)\n\t\treturn fresh\n\t})\n\treturn `#${String(id)}`\n}\n\n/** The most levels of objects in a principal that the cache takes. */\nconst maxKeyDepth = 256\n\n/**\n * Gives a text that names a principal value. Two values get one text only when\n * they have the same types and the same data. So a string `\"1\"` and a bigint\n * `1n` get two texts. The text keeps the class of each object, by a number\n * that is its own, because two classes may have one name.\n *\n * It reads only the fields of plain objects and of `Schema.Class` values, and\n * only when each own key is a plain field that a loop over the keys can see.\n * An object of another class may keep its data where the text can not see it,\n * as a `URL` does. So it gives `undefined` for such an object, unless it is a\n * plain `Date`, `URL`, array, `Map` or `Set` with no extra own keys. It also\n * gives `undefined` for a function, a symbol, or a value with one object in two\n * places. The text can not show that two places hold the same object, and a\n * loader may test it, as `set.has(value)` does.\n * The private fields (`#name`) of a class can not be seen, so a principal\n * class must not keep its data in them.\n *\n * It also gives `undefined` for a value with more than {@link maxKeyDepth}\n * levels of objects. The walk calls itself for each level, so a deeper value\n * could fill the stack. Such a principal is not cached.\n */\nconst keyText = (\n\tvalue: unknown,\n\tseen: Set<object>,\n\tclasses: ClassIds,\n\tdepth: number = 0,\n): string | undefined => {\n\tconst plain = (text: string): string => text\n\tswitch (typeof value) {\n\t\tcase \"string\":\n\t\t\treturn plain(`s${JSON.stringify(value)}`)\n\t\tcase \"number\":\n\t\t\treturn plain(Object.is(value, -0) ? \"n-0\" : `n${String(value)}`)\n\t\tcase \"bigint\":\n\t\t\treturn plain(`b${String(value)}`)\n\t\tcase \"boolean\":\n\t\t\treturn plain(value ? \"true\" : \"false\")\n\t\tcase \"undefined\":\n\t\t\treturn plain(\"undefined\")\n\t\tcase \"object\":\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn undefined\n\t}\n\tif (value === null) {\n\t\treturn plain(\"null\")\n\t}\n\tif (seen.has(value) || depth >= maxKeyDepth) {\n\t\treturn undefined\n\t}\n\tseen.add(value)\n\tconst list = (\n\t\titems: ReadonlyArray<unknown>,\n\t\twrap: (texts: ReadonlyArray<string>) => string,\n\t): string | undefined => {\n\t\tconst texts = Arr.map(items, (item) =>\n\t\t\tkeyText(item, seen, classes, depth + 1),\n\t\t)\n\t\treturn Arr.every(texts, (text) => text !== undefined)\n\t\t\t? wrap(texts as ReadonlyArray<string>)\n\t\t\t: undefined\n\t}\n\tconst prototype: unknown = Object.getPrototypeOf(value)\n\t// The own keys of the value. A built-in value keeps its data inside, so it\n\t// may have no own keys, and the elements of an array are its own keys. An\n\t// extra own key is data that the text does not show.\n\tconst own = Reflect.ownKeys(value)\n\t// The entries of a map or a set keep their order, because a loader may read\n\t// the first one.\n\tif (prototype === Date.prototype && own.length === 0) {\n\t\treturn `d${String((value as Date).getTime())}`\n\t}\n\tif (prototype === URL.prototype && own.length === 0) {\n\t\treturn `u${JSON.stringify((value as URL).href)}`\n\t}\n\tif (\n\t\tprototype === Array.prototype &&\n\t\town.length === (value as Array<unknown>).length + 1 &&\n\t\thasEveryIndex(value as Array<unknown>) &&\n\t\t// An array finds an object by the object itself, as in\n\t\t// `roles.includes(admin)`.\n\t\t!Arr.some(value as Array<unknown>, isObjectLike)\n\t) {\n\t\treturn list(value as Array<unknown>, (texts) => `[${texts.join(\",\")}]`)\n\t}\n\tif (\n\t\tprototype === Map.prototype &&\n\t\town.length === 0 &&\n\t\t// A map finds an object key by the object itself, not by its data, as\n\t\t// in `map.get(role)`. The text can not show which object it is.\n\t\t!Arr.some(Array.from((value as Map<unknown, unknown>).keys()), isObjectLike)\n\t) {\n\t\t// The keys and the values go in one list, so a value that is an object\n\t\t// does not go through the test of the elements of an array.\n\t\treturn list(\n\t\t\tArr.flatMap(Array.from(value as Map<unknown, unknown>), ([key, item]) => [\n\t\t\t\tkey,\n\t\t\t\titem,\n\t\t\t]),\n\t\t\t(texts) => `Map{${texts.join(\",\")}}`,\n\t\t)\n\t}\n\tif (\n\t\tprototype === Set.prototype &&\n\t\town.length === 0 &&\n\t\t// A set finds an object by the object itself, as in `set.has(role)`.\n\t\t!Arr.some(Array.from(value as Set<unknown>), isObjectLike)\n\t) {\n\t\treturn list(\n\t\t\tArray.from(value as Set<unknown>),\n\t\t\t(texts) => `Set{${texts.join(\",\")}}`,\n\t\t)\n\t}\n\tif (\n\t\t(prototype === null ||\n\t\t\tprototype === Object.prototype ||\n\t\t\t// The prototype must be the prototype of the class itself. A value\n\t\t\t// made with `Object.create(user)` has `user` as its prototype, and\n\t\t\t// the data of `user` is not in the own keys. Such a prototype may\n\t\t\t// have its own `constructor` key, so the test compares the\n\t\t\t// prototype with the `prototype` of the class.\n\t\t\t(Schema.isSchema((prototype as object).constructor) &&\n\t\t\t\t(prototype as object).constructor.prototype === prototype)) &&\n\t\tArr.every(own, (key) => typeof key === \"string\" && isPlainField(value, key))\n\t) {\n\t\t// The keys keep their order, because a loader may read the first one.\n\t\tconst keys = own as ReadonlyArray<string>\n\t\tconst name =\n\t\t\tprototype === null\n\t\t\t\t? \"\"\n\t\t\t\t: classId(classes, (prototype as object).constructor)\n\t\treturn list(\n\t\t\tArr.map(keys, (key) => (value as Record<string, unknown>)[key]),\n\t\t\t(texts) =>\n\t\t\t\t`${name}{${Arr.map(keys, (key, index) => `${JSON.stringify(key)}:${texts[index]!}`).join(\",\")}}`,\n\t\t)\n\t}\n\treturn undefined\n}\n\nconst forbidden = (\n\ttarget: AnyTarget,\n\tkind: string,\n\treason: string | undefined,\n): Forbidden =>\n\tForbidden.make({\n\t\taction: target.action,\n\t\tkind,\n\t\t...(target.field === undefined ? {} : { field: target.field }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t})\n\nconst makeLayer = (\n\tservice: AnyServiceClass,\n\toptions: {\n\t\treadonly principal: Effect.Effect<object, unknown, unknown>\n\t\treadonly policy: (\n\t\t\tprincipal: object,\n\t\t) => Effect.Effect<Policy.Any, unknown, unknown>\n\t\treadonly decider?: (\n\t\t\trequest: object,\n\t\t) => Effect.Effect<Decision.Decision, unknown, unknown>\n\t\treadonly cache?:\n\t\t\t| { readonly capacity?: number; readonly timeToLive?: Duration.Input }\n\t\t\t| false\n\t},\n) => {\n\tconst domain: Domain.Any = service.domain\n\t// It gives `undefined` when the principal has no key. Such a principal does\n\t// not use the cache.\n\tconst classes: ClassIds = MutableHashMap.empty()\n\tconst keyOf = (principal: object): PrincipalKey | undefined => {\n\t\t// One set of seen objects for the walk: the walk adds each object to it,\n\t\t// and the set goes away when the key is made.\n\t\tconst text = keyText(principal, new Set(), classes)\n\t\treturn text === undefined ? undefined : new PrincipalKey(principal, text)\n\t}\n\treturn Layer.effect(\n\t\tservice,\n\t\tEffect.gen(function* () {\n\t\t\t// The services of the policy loader and of the predicates come from the\n\t\t\t// layer, so a check does not need them.\n\t\t\tconst services = yield* Effect.context<never>()\n\n\t\t\t// The predicates of the policy get the services of the layer, so the\n\t\t\t// policy and its residual conditions need no services.\n\t\t\t// A policy of another domain may have the same types, but its resources\n\t\t\t// may have other paths. So it fails closed, as a policy that can not be\n\t\t\t// loaded.\n\t\t\tconst load = (principal: object) =>\n\t\t\t\toptions.policy(principal).pipe(\n\t\t\t\t\tEffect.provideContext(services),\n\t\t\t\t\tEffect.filterOrFail(\n\t\t\t\t\t\t(value) => value.domain === domain,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew globalThis.Error(\n\t\t\t\t\t\t\t\t\"The loaded policy is not a policy of this domain\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((cause) => PolicyUnavailable.make({ cause })),\n\t\t\t\t\tEffect.map((value) =>\n\t\t\t\t\t\tPolicy.provideContext(value, services as never),\n\t\t\t\t\t),\n\t\t\t\t)\n\n\t\t\tconst cache =\n\t\t\t\toptions.cache === false\n\t\t\t\t\t? undefined\n\t\t\t\t\t: yield* Cache.make({\n\t\t\t\t\t\t\tcapacity: options.cache?.capacity ?? 1000,\n\t\t\t\t\t\t\ttimeToLive: options.cache?.timeToLive ?? \"30 seconds\",\n\t\t\t\t\t\t\tlookup: (key: PrincipalKey) => load(key.principal),\n\t\t\t\t\t\t})\n\n\t\t\tconst policyFor = (principal: object) => {\n\t\t\t\tconst key = cache === undefined ? undefined : keyOf(principal)\n\t\t\t\tif (cache === undefined || key === undefined) {\n\t\t\t\t\treturn load(principal)\n\t\t\t\t}\n\t\t\t\t// A failed lookup is kept in the cache. Remove it, so the next check\n\t\t\t\t// tries again.\n\t\t\t\treturn Cache.get(cache, key).pipe(\n\t\t\t\t\tEffect.tapError(() => Cache.invalidate(cache, key)),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst withServices = <A, E>(\n\t\t\t\teffect: Effect.Effect<A, E, unknown>,\n\t\t\t): Effect.Effect<A, E, unknown> => Effect.provideContext(effect, services)\n\n\t\t\tconst current = Effect.gen(function* () {\n\t\t\t\tconst principal = yield* options.principal\n\t\t\t\tconst value = yield* policyFor(principal)\n\t\t\t\treturn { principal, policy: value }\n\t\t\t})\n\n\t\t\t// The principal and its policy, loaded one time. A check that asks\n\t\t\t// the policy two times, such as `guard`, uses one snapshot for both,\n\t\t\t// so the two answers come from one principal.\n\t\t\ttype Snapshot = Effect.Success<typeof current>\n\n\t\t\tconst decideWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst decision: Decision.Decision = yield* withServices(\n\t\t\t\t\tPolicy.decide(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t\treturn decision\n\t\t\t})\n\n\t\t\tconst decide = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => decideWith(snapshot, target))\n\n\t\t\tconst kindDecision = (\n\t\t\t\tvalue: Policy.Any,\n\t\t\t\tprincipal: object,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Decision.Decision => {\n\t\t\t\tconst request = { ...target, principal } as never\n\t\t\t\tconst covered = Policy.coverage(value, request)\n\t\t\t\tif (covered._tag === \"All\") {\n\t\t\t\t\treturn Decision.Allow({})\n\t\t\t\t}\n\t\t\t\t// The decider may answer for the scope only when no value in it is\n\t\t\t\t// denied, and when no predicate may fail. Else some value may get a\n\t\t\t\t// \"no\" from the policy, so the answer for the scope is \"no\". An\n\t\t\t\t// unknown answer may hide a denied value, so it is a \"no\" too.\n\t\t\t\t// An empty scope never passes, so the decider is not asked for it.\n\t\t\t\t// A scope that the search can not say has a value may be empty, so\n\t\t\t\t// the decider is not asked for it either.\n\t\t\t\tif (\n\t\t\t\t\tcovered._tag !== \"Unknown\" &&\n\t\t\t\t\tPolicy.scopeHasValue(value, request)\n\t\t\t\t) {\n\t\t\t\t\tconst deny: Condition.Ast = Policy.residuals(value, request).deny\n\t\t\t\t\tconst schema = Domain.descriptorOf(\n\t\t\t\t\t\tvalue.domain,\n\t\t\t\t\t\ttarget.kind ?? \"\",\n\t\t\t\t\t)?.schema\n\t\t\t\t\tif (\n\t\t\t\t\t\tdeny._tag === \"False\" ||\n\t\t\t\t\t\tCondition.satisfiable(deny as Condition.Condition, { schema })\n\t\t\t\t\t\t\t._tag === \"Unsatisfiable\"\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn Decision.NotApplicable\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn Decision.Deny({})\n\t\t\t}\n\n\t\t\tconst checkWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst kind =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? (target.kind ?? \"\")\n\t\t\t\t\t\t: (Resource.kindOf(target.resource) ?? \"\")\n\t\t\t\t// A request that does not fit its kind gets a \"no\" at once. The\n\t\t\t\t// decider must not say yes to an action that the kind does not have.\n\t\t\t\tconst descriptor = Domain.descriptorOf(domain, kind)\n\t\t\t\tif (descriptor === undefined || !Policy.fitsKind(descriptor, target)) {\n\t\t\t\t\treturn yield* forbidden(target, kind, undefined)\n\t\t\t\t}\n\t\t\t\tconst fromPolicy: Decision.Decision =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? kindDecision(value, principal, target)\n\t\t\t\t\t\t: yield* withServices(\n\t\t\t\t\t\t\t\tPolicy.decide(value, { ...target, principal } as never),\n\t\t\t\t\t\t\t)\n\t\t\t\t// When no rule gives an answer, the decider gives it.\n\t\t\t\tconst decision: Decision.Decision =\n\t\t\t\t\tfromPolicy._tag === \"NotApplicable\" && options.decider !== undefined\n\t\t\t\t\t\t? yield* options.decider({ ...target, principal })\n\t\t\t\t\t\t: fromPolicy\n\t\t\t\tif (decision._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* forbidden(target, kind, Decision.reason(decision))\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst check = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => checkWith(snapshot, target))\n\n\t\t\tconst guard = Effect.fnUntraced(function* (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t\tguardOptions: {\n\t\t\t\t\treadonly read?: string\n\t\t\t\t\treadonly hide: () => unknown\n\t\t\t\t},\n\t\t\t) {\n\t\t\t\tconst snapshot = yield* current\n\t\t\t\tconst read = guardOptions.read ?? \"read\"\n\t\t\t\tconst { field: _field, ...rest } = target\n\t\t\t\tconst visible: Decision.Decision = yield* decideWith(snapshot, {\n\t\t\t\t\t...rest,\n\t\t\t\t\taction: read,\n\t\t\t\t})\n\t\t\t\tif (visible._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* Effect.fail(guardOptions.hide())\n\t\t\t\t}\n\t\t\t\tif (target.action !== read || target.field !== undefined) {\n\t\t\t\t\tyield* checkWith(snapshot, target)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// The filter is the condition of the coverage. When the coverage is\n\t\t\t// not known, it is the residual condition in the scope, which throws\n\t\t\t// an error when there is none, so the filter fails.\n\t\t\tconst filter = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Condition.Ast, unknown, unknown> =>\n\t\t\t\tEffect.map(current, ({ principal, policy: value }): Condition.Ast => {\n\t\t\t\t\tconst request = { ...target, principal } as never\n\t\t\t\t\tconst covered = Policy.coverage(value, request)\n\t\t\t\t\treturn covered._tag === \"Unknown\"\n\t\t\t\t\t\t? Policy.residual(value, request)\n\t\t\t\t\t\t: (covered.condition as Condition.Ast)\n\t\t\t\t})\n\n\t\t\tconst coverage = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Policy.Coverage<any, any, any, any>, unknown, unknown> =>\n\t\t\t\tEffect.map(current, ({ principal, policy: value }) =>\n\t\t\t\t\tPolicy.coverage(value, { ...target, principal } as never),\n\t\t\t\t)\n\n\t\t\tconst permittedFields = Effect.fnUntraced(function* (target: AnyTarget) {\n\t\t\t\tconst { principal, policy: value } = yield* current\n\t\t\t\treturn yield* withServices(\n\t\t\t\t\tPolicy.permittedFields(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t})\n\n\t\t\tconst invalidate = (principal?: object) => {\n\t\t\t\tif (cache === undefined) {\n\t\t\t\t\treturn Effect.void\n\t\t\t\t}\n\t\t\t\tif (principal === undefined) {\n\t\t\t\t\treturn Cache.invalidateAll(cache)\n\t\t\t\t}\n\t\t\t\tconst key = keyOf(principal)\n\t\t\t\treturn key === undefined ? Effect.void : Cache.invalidate(cache, key)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tdomain,\n\t\t\t\tprincipal: options.principal,\n\t\t\t\tpolicy: Effect.map(current, ({ policy: value }) => value),\n\t\t\t\tdecide,\n\t\t\t\tcheck,\n\t\t\t\tguard,\n\t\t\t\tfilter,\n\t\t\t\tcoverage,\n\t\t\t\tpermittedFields,\n\t\t\t\tinvalidate,\n\t\t\t} as never\n\t\t}),\n\t)\n}\n\n/**\n * Makes the layer of an authorizer.\n *\n * @stability stable\n * @category layers\n * @since 0.3.0\n */\nexport const layer: <\n\tSelf,\n\tId extends string,\n\tD extends Domain.Any,\n\tE,\n\tR,\n\tEL = never,\n\tRL = never,\n\tRP = never,\n>(\n\tservice: ServiceClass<Self, Id, D, E, R>,\n\toptions: Options<D, NoInfer<E>, NoInfer<R>, EL, RL, RP>,\n) => Layer.Layer<Self, never, RL | RP> = makeLayer as never\n"],"mappings":"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"}
1
+ {"version":3,"file":"Authorizer.js","names":["Policy.provideContext","Policy.decide","decide","Policy.coverage","Decision.Allow","Policy.scopeHasValue","Policy.residuals","Domain.descriptorOf","Condition.satisfiable","Decision.NotApplicable","Decision.Deny","Resource.kindOf","Policy.fitsKind","Decision.reason","Policy.residual","coverage","permittedFields","Policy.permittedFields"],"sources":["../src/Authorizer.ts"],"sourcesContent":["/**\n * An authorizer is an Effect service that makes decisions for the principal\n * of the current request.\n *\n * You make the service class, in the same way as `Context.Service`. Give the\n * services that the principal and the decider need as `requires`, and their\n * errors as `error`:\n *\n * ```ts\n * class Auth extends Authorizer.Service<Auth, { requires: Identity }>()(\n * \"app/Auth\",\n * App,\n * ) {}\n * ```\n *\n * Then give a layer with {@link layer}. It says where the principal comes\n * from and how to load its policy. The policy of each principal is loaded\n * once and kept in a cache for a short time.\n *\n * Then use the service in a handler:\n *\n * ```ts\n * yield* Auth.check({ action: \"update\", resource: document })\n * ```\n *\n * A \"no\" answer goes into the error channel as {@link Forbidden}. When the\n * policy can not be loaded, the check fails with {@link PolicyUnavailable}.\n * So the check fails closed: an error is never a \"yes\".\n *\n * When no rule gives an answer, the authorizer asks the decider, if there is\n * one. A decider may answer at once, or fail with {@link Pending} when a person\n * or another service must answer later.\n *\n * @stability stable\n * @since 0.3.0\n */\n// The service erases the error type and the service type of predicates,\n// principals and deciders to `unknown`. The public types keep them, so the\n// language service check for `unknown` in an Effect is off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Cache from \"effect/Cache\"\nimport * as Context from \"effect/Context\"\nimport type * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport * as Equal from \"effect/Equal\"\nimport * as Hash from \"effect/Hash\"\nimport * as Layer from \"effect/Layer\"\nimport * as MutableHashMap from \"effect/MutableHashMap\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as Condition from \"./Condition.ts\"\nimport * as Decision from \"./Decision.ts\"\nimport * as Domain from \"./Domain.ts\"\nimport * as Policy from \"./Policy.ts\"\nimport * as Resource from \"./Resource.ts\"\n\n/**\n * The error when a policy says no.\n *\n * @stability stable\n * @category errors\n * @since 0.3.0\n */\nexport class Forbidden extends Schema.TaggedError<Forbidden>(\n\t\"@nmnmcc/ability/Authorizer/Forbidden\",\n)(\"Forbidden\", {\n\taction: Schema.String,\n\tkind: Schema.String,\n\tfield: Schema.optionalKey(Schema.String),\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/**\n * The error when the policy of the principal can not be loaded. The cause is\n * kept, but it is not shown to the client.\n *\n * @stability stable\n * @category errors\n * @since 0.3.0\n */\nexport class PolicyUnavailable extends Schema.TaggedError<PolicyUnavailable>(\n\t\"@nmnmcc/ability/Authorizer/PolicyUnavailable\",\n)(\"PolicyUnavailable\", { cause: Schema.Defect() }) {}\n\n/**\n * The error when a decider must wait for an answer. `ticket` names the\n * request that waits, so the client can come back for the answer.\n *\n * @stability stable\n * @category errors\n * @since 0.3.0\n */\nexport class Pending extends Schema.TaggedError<Pending>(\n\t\"@nmnmcc/ability/Authorizer/Pending\",\n)(\"Pending\", {\n\tticket: Schema.String,\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/**\n * The services and the errors of the principal and the decider of an\n * authorizer.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Config {\n\treadonly requires?: unknown\n\treadonly error?: unknown\n}\n\ntype RequiresOf<C> = C extends { readonly requires: infer R } ? R : never\ntype ErrorOf<C> = C extends { readonly error: infer E } ? E : never\n\n/**\n * What a check is about: an act on a resource value, or an act on a kind of\n * resource. A check on a kind passes only when the policy allows the act on\n * every value of the kind. With a scope (`within`), it passes when the policy\n * allows the act on every value in the scope, and the scope is not empty.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Target<D> = Policy.Target<D> | Policy.KindTarget<D>\n\n/**\n * A request to the decider.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type DeciderRequest<D> = Policy.Request<D> | Policy.KindRequest<D>\n\n/**\n * The options of a guard.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type GuardOptions<D, E2, K extends string = Domain.Kind<D>> = {\n\t/** The error when the principal may not see the resource. */\n\treadonly hide: () => E2\n} & (\"read\" extends Domain.CommonAction<D, K>\n\t? {\n\t\t\t/**\n\t\t\t * The action that lets the principal see the resource. It must be an\n\t\t\t * action of every kind in `K`. The default is `\"read\"`.\n\t\t\t */\n\t\t\treadonly read?: Domain.CommonAction<D, K>\n\t\t}\n\t: {\n\t\t\t/**\n\t\t\t * The action that lets the principal see the resource. It must be an\n\t\t\t * action of every kind in `K`. Some kind in `K` has no `\"read\"`\n\t\t\t * action, so you must give it.\n\t\t\t */\n\t\t\treadonly read: Domain.CommonAction<D, K>\n\t\t})\n\n/**\n * The service.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Authorizer<D extends Domain.Any, E, R> {\n\treadonly domain: D\n\t/** The principal of the current request. */\n\treadonly principal: Effect.Effect<Domain.Principal<D>, E, R>\n\t/**\n\t * The policy of the principal of the current request. Its predicates have\n\t * the services of the layer. It may have predicates, so it is opaque.\n\t */\n\treadonly policy: Effect.Effect<\n\t\tPolicy.Policy<D, E, never, Condition.Opaque>,\n\t\tPolicyUnavailable | E,\n\t\tR\n\t>\n\t/** Gives the decision of the policy. It does not ask the decider. */\n\treadonly decide: (\n\t\ttarget: Policy.Target<D>,\n\t) => Effect.Effect<Decision.Decision, PolicyUnavailable | E, R>\n\t/** Succeeds when the principal may do the act, and fails when it may not. */\n\treadonly check: (\n\t\ttarget: Target<D>,\n\t) => Effect.Effect<void, Forbidden | Pending | PolicyUnavailable | E, R>\n\t/**\n\t * Checks the read action first, and fails with the error of `hide` when the\n\t * principal may not see the resource. Then it checks the act. Use it to\n\t * answer \"not found\" to a principal that may not know that a resource\n\t * exists.\n\t */\n\treadonly guard: <const T extends Policy.Target<D>, E2>(\n\t\ttarget: T,\n\t\toptions: GuardOptions<D, E2, T[\"resource\"][\"_tag\"]>,\n\t) => Effect.Effect<void, E2 | Forbidden | Pending | PolicyUnavailable | E, R>\n\t/**\n\t * Gives the condition on resource values that the principal may act on,\n\t * inside the scope of the target when it has one. Change it into a query\n\t * with `Condition.fold`. It is `False` when the principal may act on no\n\t * value, so the list is empty: it is not an error.\n\t */\n\treadonly filter: <const T extends Policy.KindTarget<D>>(\n\t\ttarget: T,\n\t) => Effect.Effect<\n\t\tCondition.Condition<\n\t\t\tE,\n\t\t\tnever,\n\t\t\tCondition.Opaque,\n\t\t\tPolicy.ResidualReads<D, T[\"kind\"]>\n\t\t>,\n\t\tPolicyUnavailable | E,\n\t\tR\n\t>\n\t/**\n\t * Tells if the principal may act on all, none or some of the values in\n\t * the scope of the target. See `Policy.coverage`. Use it to show a button\n\t * only when the answer is not `None`.\n\t */\n\treadonly coverage: <const T extends Policy.KindTarget<D>>(\n\t\ttarget: T,\n\t) => Effect.Effect<\n\t\tPolicy.Coverage<\n\t\t\tE,\n\t\t\tnever,\n\t\t\tCondition.Opaque,\n\t\t\tPolicy.ResidualReads<D, T[\"kind\"]>\n\t\t>,\n\t\tPolicyUnavailable | E,\n\t\tR\n\t>\n\t/** Gives the fields of a value that the principal may use. */\n\treadonly permittedFields: (\n\t\ttarget: Policy.FieldlessTarget<D>,\n\t) => Effect.Effect<ReadonlyArray<string>, PolicyUnavailable | E, R>\n\t/**\n\t * Removes the cached policy of a principal, or of every principal. Call it\n\t * after a change to the rules or roles of a principal.\n\t */\n\treadonly invalidate: (principal?: Domain.Principal<D>) => Effect.Effect<void>\n}\n\n/**\n * The class made by {@link Service}. The static functions use the service\n * from the context.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface ServiceClass<\n\tSelf,\n\tId extends string,\n\tD extends Domain.Any,\n\tE,\n\tR,\n> extends Context.ServiceClass<Self, Id, Authorizer<D, E, R>> {\n\treadonly domain: D\n\treadonly decide: (\n\t\ttarget: Policy.Target<D>,\n\t) => Effect.Effect<Decision.Decision, PolicyUnavailable | E, R | Self>\n\treadonly check: (\n\t\ttarget: Target<D>,\n\t) => Effect.Effect<\n\t\tvoid,\n\t\tForbidden | Pending | PolicyUnavailable | E,\n\t\tR | Self\n\t>\n\treadonly guard: <const T extends Policy.Target<D>, E2>(\n\t\ttarget: T,\n\t\toptions: GuardOptions<D, E2, T[\"resource\"][\"_tag\"]>,\n\t) => Effect.Effect<\n\t\tvoid,\n\t\tE2 | Forbidden | Pending | PolicyUnavailable | E,\n\t\tR | Self\n\t>\n\treadonly filter: <const T extends Policy.KindTarget<D>>(\n\t\ttarget: T,\n\t) => Effect.Effect<\n\t\tCondition.Condition<\n\t\t\tE,\n\t\t\tnever,\n\t\t\tCondition.Opaque,\n\t\t\tPolicy.ResidualReads<D, T[\"kind\"]>\n\t\t>,\n\t\tPolicyUnavailable | E,\n\t\tR | Self\n\t>\n\treadonly coverage: <const T extends Policy.KindTarget<D>>(\n\t\ttarget: T,\n\t) => Effect.Effect<\n\t\tPolicy.Coverage<\n\t\t\tE,\n\t\t\tnever,\n\t\t\tCondition.Opaque,\n\t\t\tPolicy.ResidualReads<D, T[\"kind\"]>\n\t\t>,\n\t\tPolicyUnavailable | E,\n\t\tR | Self\n\t>\n\treadonly permittedFields: (\n\t\ttarget: Policy.FieldlessTarget<D>,\n\t) => Effect.Effect<ReadonlyArray<string>, PolicyUnavailable | E, R | Self>\n}\n\n/**\n * Any service class made by {@link Service}.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type AnyServiceClass = Context.Key<any, any> & {\n\treadonly domain: Domain.Any\n}\n\n/**\n * Makes an authorizer service class for a domain.\n *\n * @stability stable\n * @category constructors\n * @since 0.3.0\n */\nexport const Service: <Self, C extends Config = {}>() => <\n\tconst Id extends string,\n\tD extends Domain.Any,\n>(\n\tid: Id,\n\tdomain: D,\n) => ServiceClass<Self, Id, D, ErrorOf<C>, RequiresOf<C>> = (() =>\n\t(id: string, domain: Domain.Any) => {\n\t\tconst Base = Context.Service<any, any>()(id)\n\t\treturn class extends Base {\n\t\t\tstatic readonly domain = domain\n\t\t\tstatic readonly decide = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.decide(target))\n\t\t\tstatic readonly check = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.check(target))\n\t\t\tstatic readonly guard = (target: AnyTarget, options: unknown) =>\n\t\t\t\tthis.use((service: any) => service.guard(target, options))\n\t\t\tstatic readonly filter = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.filter(target))\n\t\t\tstatic readonly coverage = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.coverage(target))\n\t\t\tstatic readonly permittedFields = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.permittedFields(target))\n\t\t}\n\t}) as never\n\ntype AnyTarget = {\n\treadonly action: string\n\treadonly resource?: object\n\treadonly kind?: string\n\treadonly field?: string\n\treadonly within?: unknown\n}\n\n/**\n * The options of {@link layer}.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Options<D extends Domain.Any, E, R, EL, RL, RP> {\n\t/** Gives the principal of the current request. */\n\treadonly principal: Effect.Effect<Domain.Principal<D>, E, R>\n\t/**\n\t * Loads the policy of a principal. A failure becomes\n\t * {@link PolicyUnavailable}. The services of the predicates of the policy\n\t * are taken when the layer is built.\n\t */\n\treadonly policy: (\n\t\tprincipal: Domain.Principal<D>,\n\t) => Effect.Effect<Policy.Policy<D, E, RP, any>, EL, RL>\n\t/**\n\t * Answers when no rule gives an answer. Without a decider, such a request\n\t * fails with {@link Forbidden}.\n\t */\n\treadonly decider?: (\n\t\trequest: DeciderRequest<D>,\n\t) => Effect.Effect<Decision.Decision, Pending | E, R>\n\t/**\n\t * How the policies are kept. The default keeps 1000 policies for 30 seconds.\n\t * Give `false` to load the policy for each check.\n\t *\n\t * Two principals with the same data get one policy from the cache. So the\n\t * loader must decide by the data of the principal only. When it tests that\n\t * an object of the principal is a given object, as `role === admin` does,\n\t * give `false`. A principal with objects in an array, a set or the keys of\n\t * a map is not kept, because such tests are usual there.\n\t */\n\treadonly cache?:\n\t\t| {\n\t\t\t\treadonly capacity?: number\n\t\t\t\treadonly timeToLive?: Duration.Input\n\t\t }\n\t\t| false\n}\n\n/**\n * A cache key for a principal. Two keys are equal when they have the same\n * text, so two principals with the same value share one policy. The key also\n * keeps the principal, so the cache can load its policy. A plain `Data` value\n * can not do both, so the key gives its own equality with `Equal.Equal`.\n */\nclass PrincipalKey implements Equal.Equal {\n\treadonly principal: object\n\treadonly text: string\n\tconstructor(principal: object, text: string) {\n\t\tthis.principal = principal\n\t\tthis.text = text\n\t}\n\t[Equal.symbol](that: unknown): boolean {\n\t\treturn that instanceof PrincipalKey && that.text === this.text\n\t}\n\t[Hash.symbol](): number {\n\t\treturn Hash.string(this.text)\n\t}\n}\n\n/**\n * Tests if an array has a plain field at each index below its length. With\n * one more own key for `length`, it then has no other own keys.\n */\nconst hasEveryIndex = (value: Array<unknown>): boolean =>\n\tArr.every(\n\t\tArray.from({ length: value.length }, (_, index) => index),\n\t\t(index) => isPlainField(value, String(index)),\n\t)\n\n/**\n * Tests if an own key of an object holds plain data that the text shows: it\n * can be seen in a loop over the keys, and it is not a getter.\n */\nconst isPlainField = (value: object, key: string): boolean => {\n\tconst descriptor = Object.getOwnPropertyDescriptor(value, key)\n\treturn (\n\t\tdescriptor !== undefined &&\n\t\tdescriptor.enumerable === true &&\n\t\t\"value\" in descriptor\n\t)\n}\n\nconst isObjectLike = (value: unknown): boolean =>\n\t(typeof value === \"object\" && value !== null) || typeof value === \"function\"\n\n/**\n * The number of each class that a key has seen. Each layer has its own, so\n * no state is kept for the whole program. A class is a function, and `Equal`\n * compares functions by the function itself.\n */\ntype ClassIds = MutableHashMap.MutableHashMap<object, number>\n\n/** Gives a number that is only for this class, in this layer. */\nconst classId = (classes: ClassIds, constructor: object): string => {\n\tconst id = Option.getOrElse(MutableHashMap.get(classes, constructor), () => {\n\t\tconst fresh = MutableHashMap.size(classes)\n\t\tMutableHashMap.set(classes, constructor, fresh)\n\t\treturn fresh\n\t})\n\treturn `#${String(id)}`\n}\n\n/** The most levels of objects in a principal that the cache takes. */\nconst maxKeyDepth = 256\n\n/**\n * Gives a text that names a principal value. Two values get one text only when\n * they have the same types and the same data. So a string `\"1\"` and a bigint\n * `1n` get two texts. The text keeps the class of each object, by a number\n * that is its own, because two classes may have one name.\n *\n * It reads only the fields of plain objects and of `Schema.Class` values, and\n * only when each own key is a plain field that a loop over the keys can see.\n * An object of another class may keep its data where the text can not see it,\n * as a `URL` does. So it gives `undefined` for such an object, unless it is a\n * plain `Date`, `URL`, array, `Map` or `Set` with no extra own keys. It also\n * gives `undefined` for a function, a symbol, or a value with one object in two\n * places. The text can not show that two places hold the same object, and a\n * loader may test it, as `set.has(value)` does.\n * The private fields (`#name`) of a class can not be seen, so a principal\n * class must not keep its data in them.\n *\n * It also gives `undefined` for a value with more than {@link maxKeyDepth}\n * levels of objects. The walk calls itself for each level, so a deeper value\n * could fill the stack. Such a principal is not cached.\n */\nconst keyText = (\n\tvalue: unknown,\n\tseen: Set<object>,\n\tclasses: ClassIds,\n\tdepth: number = 0,\n): string | undefined => {\n\tconst plain = (text: string): string => text\n\tswitch (typeof value) {\n\t\tcase \"string\":\n\t\t\treturn plain(`s${JSON.stringify(value)}`)\n\t\tcase \"number\":\n\t\t\treturn plain(Object.is(value, -0) ? \"n-0\" : `n${String(value)}`)\n\t\tcase \"bigint\":\n\t\t\treturn plain(`b${String(value)}`)\n\t\tcase \"boolean\":\n\t\t\treturn plain(value ? \"true\" : \"false\")\n\t\tcase \"undefined\":\n\t\t\treturn plain(\"undefined\")\n\t\tcase \"object\":\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn undefined\n\t}\n\tif (value === null) {\n\t\treturn plain(\"null\")\n\t}\n\tif (seen.has(value) || depth >= maxKeyDepth) {\n\t\treturn undefined\n\t}\n\tseen.add(value)\n\tconst list = (\n\t\titems: ReadonlyArray<unknown>,\n\t\twrap: (texts: ReadonlyArray<string>) => string,\n\t): string | undefined => {\n\t\tconst texts = Arr.map(items, (item) =>\n\t\t\tkeyText(item, seen, classes, depth + 1),\n\t\t)\n\t\treturn Arr.every(texts, (text) => text !== undefined)\n\t\t\t? wrap(texts as ReadonlyArray<string>)\n\t\t\t: undefined\n\t}\n\tconst prototype: unknown = Object.getPrototypeOf(value)\n\t// The own keys of the value. A built-in value keeps its data inside, so it\n\t// may have no own keys, and the elements of an array are its own keys. An\n\t// extra own key is data that the text does not show.\n\tconst own = Reflect.ownKeys(value)\n\t// The entries of a map or a set keep their order, because a loader may read\n\t// the first one.\n\tif (prototype === Date.prototype && own.length === 0) {\n\t\treturn `d${String((value as Date).getTime())}`\n\t}\n\tif (prototype === URL.prototype && own.length === 0) {\n\t\treturn `u${JSON.stringify((value as URL).href)}`\n\t}\n\tif (\n\t\tprototype === Array.prototype &&\n\t\town.length === (value as Array<unknown>).length + 1 &&\n\t\thasEveryIndex(value as Array<unknown>) &&\n\t\t// An array finds an object by the object itself, as in\n\t\t// `roles.includes(admin)`.\n\t\t!Arr.some(value as Array<unknown>, isObjectLike)\n\t) {\n\t\treturn list(value as Array<unknown>, (texts) => `[${texts.join(\",\")}]`)\n\t}\n\tif (\n\t\tprototype === Map.prototype &&\n\t\town.length === 0 &&\n\t\t// A map finds an object key by the object itself, not by its data, as\n\t\t// in `map.get(role)`. The text can not show which object it is.\n\t\t!Arr.some(Array.from((value as Map<unknown, unknown>).keys()), isObjectLike)\n\t) {\n\t\t// The keys and the values go in one list, so a value that is an object\n\t\t// does not go through the test of the elements of an array.\n\t\treturn list(\n\t\t\tArr.flatMap(Array.from(value as Map<unknown, unknown>), ([key, item]) => [\n\t\t\t\tkey,\n\t\t\t\titem,\n\t\t\t]),\n\t\t\t(texts) => `Map{${texts.join(\",\")}}`,\n\t\t)\n\t}\n\tif (\n\t\tprototype === Set.prototype &&\n\t\town.length === 0 &&\n\t\t// A set finds an object by the object itself, as in `set.has(role)`.\n\t\t!Arr.some(Array.from(value as Set<unknown>), isObjectLike)\n\t) {\n\t\treturn list(\n\t\t\tArray.from(value as Set<unknown>),\n\t\t\t(texts) => `Set{${texts.join(\",\")}}`,\n\t\t)\n\t}\n\tif (\n\t\t(prototype === null ||\n\t\t\tprototype === Object.prototype ||\n\t\t\t// The prototype must be the prototype of the class itself. A value\n\t\t\t// made with `Object.create(user)` has `user` as its prototype, and\n\t\t\t// the data of `user` is not in the own keys. Such a prototype may\n\t\t\t// have its own `constructor` key, so the test compares the\n\t\t\t// prototype with the `prototype` of the class.\n\t\t\t(Schema.isSchema((prototype as object).constructor) &&\n\t\t\t\t(prototype as object).constructor.prototype === prototype)) &&\n\t\tArr.every(own, (key) => typeof key === \"string\" && isPlainField(value, key))\n\t) {\n\t\t// The keys keep their order, because a loader may read the first one.\n\t\tconst keys = own as ReadonlyArray<string>\n\t\tconst name =\n\t\t\tprototype === null\n\t\t\t\t? \"\"\n\t\t\t\t: classId(classes, (prototype as object).constructor)\n\t\treturn list(\n\t\t\tArr.map(keys, (key) => (value as Record<string, unknown>)[key]),\n\t\t\t(texts) =>\n\t\t\t\t`${name}{${Arr.map(keys, (key, index) => `${JSON.stringify(key)}:${texts[index]!}`).join(\",\")}}`,\n\t\t)\n\t}\n\treturn undefined\n}\n\nconst forbidden = (\n\ttarget: AnyTarget,\n\tkind: string,\n\treason: string | undefined,\n): Forbidden =>\n\tForbidden.make({\n\t\taction: target.action,\n\t\tkind,\n\t\t...(target.field === undefined ? {} : { field: target.field }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t})\n\nconst makeLayer = (\n\tservice: AnyServiceClass,\n\toptions: {\n\t\treadonly principal: Effect.Effect<object, unknown, unknown>\n\t\treadonly policy: (\n\t\t\tprincipal: object,\n\t\t) => Effect.Effect<Policy.Any, unknown, unknown>\n\t\treadonly decider?: (\n\t\t\trequest: object,\n\t\t) => Effect.Effect<Decision.Decision, unknown, unknown>\n\t\treadonly cache?:\n\t\t\t| { readonly capacity?: number; readonly timeToLive?: Duration.Input }\n\t\t\t| false\n\t},\n) => {\n\tconst domain: Domain.Any = service.domain\n\t// It gives `undefined` when the principal has no key. Such a principal does\n\t// not use the cache.\n\tconst classes: ClassIds = MutableHashMap.empty()\n\tconst keyOf = (principal: object): PrincipalKey | undefined => {\n\t\t// One set of seen objects for the walk: the walk adds each object to it,\n\t\t// and the set goes away when the key is made.\n\t\tconst text = keyText(principal, new Set(), classes)\n\t\treturn text === undefined ? undefined : new PrincipalKey(principal, text)\n\t}\n\treturn Layer.effect(\n\t\tservice,\n\t\tEffect.gen(function* () {\n\t\t\t// The services of the policy loader and of the predicates come from the\n\t\t\t// layer, so a check does not need them.\n\t\t\tconst services = yield* Effect.context<never>()\n\n\t\t\t// The predicates of the policy get the services of the layer, so the\n\t\t\t// policy and its residual conditions need no services.\n\t\t\t// A policy of another domain may have the same types, but its resources\n\t\t\t// may have other paths. So it fails closed, as a policy that can not be\n\t\t\t// loaded.\n\t\t\tconst load = (principal: object) =>\n\t\t\t\toptions.policy(principal).pipe(\n\t\t\t\t\tEffect.provideContext(services),\n\t\t\t\t\tEffect.filterOrFail(\n\t\t\t\t\t\t(value) => value.domain === domain,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew globalThis.Error(\n\t\t\t\t\t\t\t\t\"The loaded policy is not a policy of this domain\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((cause) => PolicyUnavailable.make({ cause })),\n\t\t\t\t\tEffect.map((value) =>\n\t\t\t\t\t\tPolicy.provideContext(value, services as never),\n\t\t\t\t\t),\n\t\t\t\t)\n\n\t\t\tconst cache =\n\t\t\t\toptions.cache === false\n\t\t\t\t\t? undefined\n\t\t\t\t\t: yield* Cache.make({\n\t\t\t\t\t\t\tcapacity: options.cache?.capacity ?? 1000,\n\t\t\t\t\t\t\ttimeToLive: options.cache?.timeToLive ?? \"30 seconds\",\n\t\t\t\t\t\t\tlookup: (key: PrincipalKey) => load(key.principal),\n\t\t\t\t\t\t})\n\n\t\t\tconst policyFor = (principal: object) => {\n\t\t\t\tconst key = cache === undefined ? undefined : keyOf(principal)\n\t\t\t\tif (cache === undefined || key === undefined) {\n\t\t\t\t\treturn load(principal)\n\t\t\t\t}\n\t\t\t\t// A failed lookup is kept in the cache. Remove it, so the next check\n\t\t\t\t// tries again.\n\t\t\t\treturn Cache.get(cache, key).pipe(\n\t\t\t\t\tEffect.tapError(() => Cache.invalidate(cache, key)),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst withServices = <A, E>(\n\t\t\t\teffect: Effect.Effect<A, E, unknown>,\n\t\t\t): Effect.Effect<A, E, unknown> => Effect.provideContext(effect, services)\n\n\t\t\tconst current = Effect.gen(function* () {\n\t\t\t\tconst principal = yield* options.principal\n\t\t\t\tconst value = yield* policyFor(principal)\n\t\t\t\treturn { principal, policy: value }\n\t\t\t})\n\n\t\t\t// The principal and its policy, loaded one time. A check that asks\n\t\t\t// the policy two times, such as `guard`, uses one snapshot for both,\n\t\t\t// so the two answers come from one principal.\n\t\t\ttype Snapshot = Effect.Success<typeof current>\n\n\t\t\tconst decideWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst decision: Decision.Decision = yield* withServices(\n\t\t\t\t\tPolicy.decide(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t\treturn decision\n\t\t\t})\n\n\t\t\tconst decide = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => decideWith(snapshot, target))\n\n\t\t\tconst kindDecision = (\n\t\t\t\tvalue: Policy.Any,\n\t\t\t\tprincipal: object,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Decision.Decision => {\n\t\t\t\tconst request = { ...target, principal } as never\n\t\t\t\tconst covered = Policy.coverage(value, request)\n\t\t\t\tif (covered._tag === \"All\") {\n\t\t\t\t\treturn Decision.Allow({})\n\t\t\t\t}\n\t\t\t\t// The decider may answer for the scope only when no value in it is\n\t\t\t\t// denied, and when no predicate may fail. Else some value may get a\n\t\t\t\t// \"no\" from the policy, so the answer for the scope is \"no\". An\n\t\t\t\t// unknown answer may hide a denied value, so it is a \"no\" too.\n\t\t\t\t// An empty scope never passes, so the decider is not asked for it.\n\t\t\t\t// A scope that the search can not say has a value may be empty, so\n\t\t\t\t// the decider is not asked for it either.\n\t\t\t\tif (\n\t\t\t\t\tcovered._tag !== \"Unknown\" &&\n\t\t\t\t\tPolicy.scopeHasValue(value, request)\n\t\t\t\t) {\n\t\t\t\t\tconst deny: Condition.Ast = Policy.residuals(value, request).deny\n\t\t\t\t\tconst schema = Domain.descriptorOf(\n\t\t\t\t\t\tvalue.domain,\n\t\t\t\t\t\ttarget.kind ?? \"\",\n\t\t\t\t\t)?.schema\n\t\t\t\t\tif (\n\t\t\t\t\t\tdeny._tag === \"False\" ||\n\t\t\t\t\t\tCondition.satisfiable(deny as Condition.Condition, { schema })\n\t\t\t\t\t\t\t._tag === \"Unsatisfiable\"\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn Decision.NotApplicable\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn Decision.Deny({})\n\t\t\t}\n\n\t\t\tconst checkWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst kind =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? (target.kind ?? \"\")\n\t\t\t\t\t\t: (Resource.kindOf(target.resource) ?? \"\")\n\t\t\t\t// A request that does not fit its kind gets a \"no\" at once. The\n\t\t\t\t// decider must not say yes to an action that the kind does not have.\n\t\t\t\tconst descriptor = Domain.descriptorOf(domain, kind)\n\t\t\t\tif (descriptor === undefined || !Policy.fitsKind(descriptor, target)) {\n\t\t\t\t\treturn yield* forbidden(target, kind, undefined)\n\t\t\t\t}\n\t\t\t\tconst fromPolicy: Decision.Decision =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? kindDecision(value, principal, target)\n\t\t\t\t\t\t: yield* withServices(\n\t\t\t\t\t\t\t\tPolicy.decide(value, { ...target, principal } as never),\n\t\t\t\t\t\t\t)\n\t\t\t\t// When no rule gives an answer, the decider gives it.\n\t\t\t\tconst decision: Decision.Decision =\n\t\t\t\t\tfromPolicy._tag === \"NotApplicable\" && options.decider !== undefined\n\t\t\t\t\t\t? yield* options.decider({ ...target, principal })\n\t\t\t\t\t\t: fromPolicy\n\t\t\t\tif (decision._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* forbidden(target, kind, Decision.reason(decision))\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst check = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => checkWith(snapshot, target))\n\n\t\t\tconst guard = Effect.fnUntraced(function* (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t\tguardOptions: {\n\t\t\t\t\treadonly read?: string\n\t\t\t\t\treadonly hide: () => unknown\n\t\t\t\t},\n\t\t\t) {\n\t\t\t\tconst snapshot = yield* current\n\t\t\t\tconst read = guardOptions.read ?? \"read\"\n\t\t\t\tconst { field: _field, ...rest } = target\n\t\t\t\tconst visible: Decision.Decision = yield* decideWith(snapshot, {\n\t\t\t\t\t...rest,\n\t\t\t\t\taction: read,\n\t\t\t\t})\n\t\t\t\tif (visible._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* Effect.fail(guardOptions.hide())\n\t\t\t\t}\n\t\t\t\tif (target.action !== read || target.field !== undefined) {\n\t\t\t\t\tyield* checkWith(snapshot, target)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// The filter is the condition of the coverage. When the coverage is\n\t\t\t// not known, it is the residual condition in the scope, which throws\n\t\t\t// an error when there is none, so the filter fails.\n\t\t\tconst filter = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Condition.Ast, unknown, unknown> =>\n\t\t\t\tEffect.map(current, ({ principal, policy: value }): Condition.Ast => {\n\t\t\t\t\tconst request = { ...target, principal } as never\n\t\t\t\t\tconst covered = Policy.coverage(value, request)\n\t\t\t\t\treturn covered._tag === \"Unknown\"\n\t\t\t\t\t\t? Policy.residual(value, request)\n\t\t\t\t\t\t: (covered.condition as Condition.Ast)\n\t\t\t\t})\n\n\t\t\tconst coverage = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Policy.Coverage<any, any, any, any>, unknown, unknown> =>\n\t\t\t\tEffect.map(current, ({ principal, policy: value }) =>\n\t\t\t\t\tPolicy.coverage(value, { ...target, principal } as never),\n\t\t\t\t)\n\n\t\t\tconst permittedFields = Effect.fnUntraced(function* (target: AnyTarget) {\n\t\t\t\tconst { principal, policy: value } = yield* current\n\t\t\t\treturn yield* withServices(\n\t\t\t\t\tPolicy.permittedFields(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t})\n\n\t\t\tconst invalidate = (principal?: object) => {\n\t\t\t\tif (cache === undefined) {\n\t\t\t\t\treturn Effect.void\n\t\t\t\t}\n\t\t\t\tif (principal === undefined) {\n\t\t\t\t\treturn Cache.invalidateAll(cache)\n\t\t\t\t}\n\t\t\t\tconst key = keyOf(principal)\n\t\t\t\treturn key === undefined ? Effect.void : Cache.invalidate(cache, key)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tdomain,\n\t\t\t\tprincipal: options.principal,\n\t\t\t\tpolicy: Effect.map(current, ({ policy: value }) => value),\n\t\t\t\tdecide,\n\t\t\t\tcheck,\n\t\t\t\tguard,\n\t\t\t\tfilter,\n\t\t\t\tcoverage,\n\t\t\t\tpermittedFields,\n\t\t\t\tinvalidate,\n\t\t\t} as never\n\t\t}),\n\t)\n}\n\n/**\n * Makes the layer of an authorizer.\n *\n * @stability stable\n * @category layers\n * @since 0.3.0\n */\nexport const layer: <\n\tSelf,\n\tId extends string,\n\tD extends Domain.Any,\n\tE,\n\tR,\n\tEL = never,\n\tRL = never,\n\tRP = never,\n>(\n\tservice: ServiceClass<Self, Id, D, E, R>,\n\toptions: Options<D, NoInfer<E>, NoInfer<R>, EL, RL, RP>,\n) => Layer.Layer<Self, never, RL | RP> = makeLayer as never\n"],"mappings":"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"}
package/dist/Domain.js CHANGED
@@ -15,7 +15,7 @@ import * as HashMap from "effect/HashMap";
15
15
  * ```ts
16
16
  * import { Domain } from "@nmnmcc/ability"
17
17
  *
18
- * const App = Domain.make({ principal: Caller, resources: [Bot, Trigger] })
18
+ * const App = Domain.make({ principal: Member, resources: [Document, Comment] })
19
19
  * ```
20
20
  *
21
21
  * @stability stable
@@ -1 +1 @@
1
- {"version":3,"file":"Domain.js","names":["Resource.descriptor"],"sources":["../src/Domain.ts"],"sourcesContent":["/**\n * A domain puts together the principal and all the resources of an\n * application. All other modules get their types from the domain. A rule may\n * only use the actions of its resource. A condition may only use the fields of\n * its resource and of the principal.\n *\n * @example\n * ```ts\n * import { Domain } from \"@nmnmcc/ability\"\n *\n * const App = Domain.make({ principal: Caller, resources: [Bot, Trigger] })\n * ```\n *\n * @stability stable\n * @since 0.3.0\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 PrincipalSchema from \"./Principal.ts\"\nimport * as Resource from \"./Resource.ts\"\n\n/**\n * A domain. `P` is the type of the principal, `Resources` is the union of the\n * resources, and `Meta` is the type of the extra data that a rule may keep.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Domain<\n\tP extends object,\n\tResources extends Resource.Any,\n\tMeta,\n> {\n\treadonly principal: PrincipalSchema.Any\n\t/** The descriptor of each resource, by its kind name. */\n\treadonly resources: HashMap.HashMap<string, Resource.Descriptor>\n\treadonly meta: Schema.Top | undefined\n\treadonly \"~Domain\"?: {\n\t\treadonly Principal: P\n\t\treadonly Resources: Resources\n\t\treadonly Meta: Meta\n\t}\n}\n\n/**\n * Any domain.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Any = Domain<any, any, any>\n\n/**\n * The type of the principal of a domain.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type Principal<D> = D extends Domain<infer P, any, any> ? P : never\n\n/**\n * The union of the resources of a domain.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type Resources<D> = D extends Domain<any, infer R, any> ? R : never\n\n/**\n * The type of the extra data that a rule of the domain may keep.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type Meta<D> = D extends Domain<any, any, infer M> ? M : never\n\n/**\n * The kind names of a domain.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type Kind<D> = Resource.Kind<Resources<D>>\n\n/**\n * The resource of a domain with the kind `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ResourceOf<D, K extends string> =\n\tResources<D> extends infer R\n\t\t? R extends Resource.Resource<K, any, any, any>\n\t\t\t? R\n\t\t\t: never\n\t\t: never\n\n/**\n * The actions of the resource with the kind `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ActionOf<D, K extends string> = Resource.Action<ResourceOf<D, K>>\n\n/**\n * The actions that every kind in `K` has. For one kind, they are its actions.\n * For a union of kinds, an action of only some of them is left out, so a\n * value of the union type can not get an action that its kind does not have.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type CommonAction<D, K extends string> = (\n\tK extends unknown ? (action: ActionOf<D, K>) => void : never\n) extends (action: infer A) => void\n\t? A\n\t: never\n\n/**\n * All the actions of all the resources of a domain.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type AnyAction<D> = Resource.Action<Resources<D>>\n\n/**\n * The data type of the resource with the kind `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type DataOf<D, K extends string> = Resource.Data<ResourceOf<D, K>>\n\n/**\n * The value type of the resource with the kind `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ValueOf<D, K extends string> = Resource.Value<ResourceOf<D, K>>\n\n/**\n * The union of the value types of all the resources of a domain.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type AnyValue<D> = Resource.Value<Resources<D>>\n\n/**\n * The class of every domain value. {@link isDomain} uses it to know a domain.\n */\nclass DomainData extends Data.Class<{\n\treadonly principal: PrincipalSchema.Any\n\treadonly resources: HashMap.HashMap<string, Resource.Descriptor>\n\treadonly meta: Schema.Top | undefined\n}> {}\n\n/**\n * Makes a domain. Each resource must have its own kind name. Give `meta` when\n * rules must keep extra data, such as the scope of a grant. The `meta` Schema\n * may not need services, because `Policy.schema` gives a Schema that needs\n * none.\n *\n * @stability stable\n * @category constructors\n * @since 0.3.0\n */\nexport const make: <\n\tP extends PrincipalSchema.Any,\n\tconst Resources extends ReadonlyArray<Resource.Any>,\n\tM extends Schema.Codec<any, any, never, never> = Schema.Never,\n>(options: {\n\treadonly principal: P\n\treadonly resources: Resources\n\treadonly meta?: M\n}) => Domain<P[\"Type\"], Resources[number], M[\"Type\"]> = (options) => {\n\tconst descriptors = Arr.map(options.resources, Resource.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// This checks the field names of the principal. See `Resource.fieldPaths`.\n\tResource.fieldPaths(options.principal)\n\treturn new DomainData({\n\t\tprincipal: options.principal,\n\t\tresources,\n\t\tmeta: options.meta,\n\t})\n}\n\n/**\n * Gives the descriptor of the resource with a kind name, or `undefined` when\n * the domain has no such resource.\n *\n * @stability stable\n * @category getters\n * @since 1.0.0\n */\nexport const descriptorOf = (\n\tdomain: Any,\n\tkind: string,\n): Resource.Descriptor | undefined =>\n\tOption.getOrUndefined(HashMap.get(domain.resources, kind))\n\n/**\n * Tests if a value is a domain.\n *\n * @stability stable\n * @category guards\n * @since 0.3.0\n */\nexport const isDomain = (u: unknown): u is Any => u instanceof DomainData\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0KA,IAAM,aAAN,cAAyB,KAAK,MAI3B,CAAC;;;;;;;;;;;AAYJ,MAAa,QAQ4C,YAAY;CACpE,MAAM,cAAc,IAAI,IAAI,QAAQ,WAAWA,UAAmB;CAClE,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;CAEA,WAAoB,QAAQ,SAAS;CACrC,OAAO,IAAI,WAAW;EACrB,WAAW,QAAQ;EACnB;EACA,MAAM,QAAQ;CACf,CAAC;AACF;;;;;;;;;AAUA,MAAa,gBACZ,QACA,SAEA,OAAO,eAAe,QAAQ,IAAI,OAAO,WAAW,IAAI,CAAC;;;;;;;;AAS1D,MAAa,YAAY,MAAyB,aAAa"}
1
+ {"version":3,"file":"Domain.js","names":["Resource.descriptor"],"sources":["../src/Domain.ts"],"sourcesContent":["/**\n * A domain puts together the principal and all the resources of an\n * application. All other modules get their types from the domain. A rule may\n * only use the actions of its resource. A condition may only use the fields of\n * its resource and of the principal.\n *\n * @example\n * ```ts\n * import { Domain } from \"@nmnmcc/ability\"\n *\n * const App = Domain.make({ principal: Member, resources: [Document, Comment] })\n * ```\n *\n * @stability stable\n * @since 0.3.0\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 PrincipalSchema from \"./Principal.ts\"\nimport * as Resource from \"./Resource.ts\"\n\n/**\n * A domain. `P` is the type of the principal, `Resources` is the union of the\n * resources, and `Meta` is the type of the extra data that a rule may keep.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport interface Domain<\n\tP extends object,\n\tResources extends Resource.Any,\n\tMeta,\n> {\n\treadonly principal: PrincipalSchema.Any\n\t/** The descriptor of each resource, by its kind name. */\n\treadonly resources: HashMap.HashMap<string, Resource.Descriptor>\n\treadonly meta: Schema.Top | undefined\n\treadonly \"~Domain\"?: {\n\t\treadonly Principal: P\n\t\treadonly Resources: Resources\n\t\treadonly Meta: Meta\n\t}\n}\n\n/**\n * Any domain.\n *\n * @stability stable\n * @category models\n * @since 0.3.0\n */\nexport type Any = Domain<any, any, any>\n\n/**\n * The type of the principal of a domain.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type Principal<D> = D extends Domain<infer P, any, any> ? P : never\n\n/**\n * The union of the resources of a domain.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type Resources<D> = D extends Domain<any, infer R, any> ? R : never\n\n/**\n * The type of the extra data that a rule of the domain may keep.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type Meta<D> = D extends Domain<any, any, infer M> ? M : never\n\n/**\n * The kind names of a domain.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type Kind<D> = Resource.Kind<Resources<D>>\n\n/**\n * The resource of a domain with the kind `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ResourceOf<D, K extends string> =\n\tResources<D> extends infer R\n\t\t? R extends Resource.Resource<K, any, any, any>\n\t\t\t? R\n\t\t\t: never\n\t\t: never\n\n/**\n * The actions of the resource with the kind `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ActionOf<D, K extends string> = Resource.Action<ResourceOf<D, K>>\n\n/**\n * The actions that every kind in `K` has. For one kind, they are its actions.\n * For a union of kinds, an action of only some of them is left out, so a\n * value of the union type can not get an action that its kind does not have.\n *\n * @stability stable\n * @category utility types\n * @since 1.0.0\n */\nexport type CommonAction<D, K extends string> = (\n\tK extends unknown ? (action: ActionOf<D, K>) => void : never\n) extends (action: infer A) => void\n\t? A\n\t: never\n\n/**\n * All the actions of all the resources of a domain.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type AnyAction<D> = Resource.Action<Resources<D>>\n\n/**\n * The data type of the resource with the kind `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type DataOf<D, K extends string> = Resource.Data<ResourceOf<D, K>>\n\n/**\n * The value type of the resource with the kind `K`.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type ValueOf<D, K extends string> = Resource.Value<ResourceOf<D, K>>\n\n/**\n * The union of the value types of all the resources of a domain.\n *\n * @stability stable\n * @category utility types\n * @since 0.3.0\n */\nexport type AnyValue<D> = Resource.Value<Resources<D>>\n\n/**\n * The class of every domain value. {@link isDomain} uses it to know a domain.\n */\nclass DomainData extends Data.Class<{\n\treadonly principal: PrincipalSchema.Any\n\treadonly resources: HashMap.HashMap<string, Resource.Descriptor>\n\treadonly meta: Schema.Top | undefined\n}> {}\n\n/**\n * Makes a domain. Each resource must have its own kind name. Give `meta` when\n * rules must keep extra data, such as the scope of a grant. The `meta` Schema\n * may not need services, because `Policy.schema` gives a Schema that needs\n * none.\n *\n * @stability stable\n * @category constructors\n * @since 0.3.0\n */\nexport const make: <\n\tP extends PrincipalSchema.Any,\n\tconst Resources extends ReadonlyArray<Resource.Any>,\n\tM extends Schema.Codec<any, any, never, never> = Schema.Never,\n>(options: {\n\treadonly principal: P\n\treadonly resources: Resources\n\treadonly meta?: M\n}) => Domain<P[\"Type\"], Resources[number], M[\"Type\"]> = (options) => {\n\tconst descriptors = Arr.map(options.resources, Resource.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// This checks the field names of the principal. See `Resource.fieldPaths`.\n\tResource.fieldPaths(options.principal)\n\treturn new DomainData({\n\t\tprincipal: options.principal,\n\t\tresources,\n\t\tmeta: options.meta,\n\t})\n}\n\n/**\n * Gives the descriptor of the resource with a kind name, or `undefined` when\n * the domain has no such resource.\n *\n * @stability stable\n * @category getters\n * @since 1.0.0\n */\nexport const descriptorOf = (\n\tdomain: Any,\n\tkind: string,\n): Resource.Descriptor | undefined =>\n\tOption.getOrUndefined(HashMap.get(domain.resources, kind))\n\n/**\n * Tests if a value is a domain.\n *\n * @stability stable\n * @category guards\n * @since 0.3.0\n */\nexport const isDomain = (u: unknown): u is Any => u instanceof DomainData\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0KA,IAAM,aAAN,cAAyB,KAAK,MAI3B,CAAC;;;;;;;;;;;AAYJ,MAAa,QAQ4C,YAAY;CACpE,MAAM,cAAc,IAAI,IAAI,QAAQ,WAAWA,UAAmB;CAClE,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;CAEA,WAAoB,QAAQ,SAAS;CACrC,OAAO,IAAI,WAAW;EACrB,WAAW,QAAQ;EACnB;EACA,MAAM,QAAQ;CACf,CAAC;AACF;;;;;;;;;AAUA,MAAa,gBACZ,QACA,SAEA,OAAO,eAAe,QAAQ,IAAI,OAAO,WAAW,IAAI,CAAC;;;;;;;;AAS1D,MAAa,YAAY,MAAyB,aAAa"}
package/dist/HttpApi.js CHANGED
@@ -26,12 +26,12 @@ import { HttpApiSchema } from "effect/http-api";
26
26
  * { requires: Identity }
27
27
  * >()("app/Authorization", { error: HttpApi.errors }) {}
28
28
  *
29
- * const endpoint = HttpApiEndpoint.patch("update", "/bots/:id", { ... })
29
+ * const endpoint = HttpApiEndpoint.patch("update", "/notes/:id", { ... })
30
30
  * .annotate(HttpApi.Required, [
31
- * HttpApi.requires(Auth, "update", "Bot", (params) => Bots.find(params.id)),
31
+ * HttpApi.requires(Auth, "update", "Note", (params) => Notes.find(params.id)),
32
32
  * ])
33
33
  *
34
- * const bots = HttpApiGroup.make("bots").add(endpoint).middleware(Authorization)
34
+ * const notes = HttpApiGroup.make("notes").add(endpoint).middleware(Authorization)
35
35
  *
36
36
  * const AuthorizationLive = HttpApi.layer(Authorization, Auth)
37
37
  * ```
@@ -1 +1 @@
1
- {"version":3,"file":"HttpApi.js","names":["Authorizer.PolicyUnavailable","Resource.kindOf"],"sources":["../src/HttpApi.ts"],"sourcesContent":["/**\n * Tools to check authorization in an `effect/http-api` server.\n *\n * Each endpoint says what it needs with the {@link Required} annotation. A\n * group can give a default with {@link GroupRequired}: one list for GET\n * requests and one list for all other methods. A middleware checks the\n * requirements before the handler runs, in each group that uses it. An\n * endpoint with no annotation is a defect, so a new endpoint is closed until\n * you say what it needs. An empty list means the endpoint is open. An endpoint\n * in a group without the middleware is not checked at all.\n *\n * ```ts\n * class Authorization extends HttpApiMiddleware.Service<\n * Authorization,\n * { requires: Identity }\n * >()(\"app/Authorization\", { error: HttpApi.errors }) {}\n *\n * const endpoint = HttpApiEndpoint.patch(\"update\", \"/bots/:id\", { ... })\n * .annotate(HttpApi.Required, [\n * HttpApi.requires(Auth, \"update\", \"Bot\", (params) => Bots.find(params.id)),\n * ])\n *\n * const bots = HttpApiGroup.make(\"bots\").add(endpoint).middleware(Authorization)\n *\n * const AuthorizationLive = HttpApi.layer(Authorization, Auth)\n * ```\n *\n * This module is a separate entry point, `@nmnmcc/ability/HttpApi`, because\n * `effect/http-api` is not stable yet.\n *\n * @stability unstable\n * @since 0.3.0\n */\n// A `load` function of a requirement is kept in an annotation, so its error\n// type and its service type are erased to `unknown`. The language service\n// check for `unknown` in an Effect is off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as SchemaTransformation from \"effect/SchemaTransformation\"\nimport { HttpRouter } from \"effect/http\"\nimport { HttpApiSchema } from \"effect/http-api\"\nimport * as Authorizer from \"./Authorizer.ts\"\nimport type * as Domain from \"./Domain.ts\"\nimport type * as Policy from \"./Policy.ts\"\nimport * as Resource from \"./Resource.ts\"\n\n/**\n * The path parameters of a request.\n *\n * @stability unstable\n * @category models\n * @since 1.0.0\n */\nexport type Params = Readonly<Record<string, string | undefined>>\n\n/**\n * One check that an endpoint needs: an action on a kind of resource.\n * - With `load`, the middleware loads the resource value from the path\n * parameters and checks the action on that value.\n * - With `within`, it makes a scope from the path parameters and checks the\n * action on every value in the scope.\n * - With `list`, the endpoint gives a list. The handler gets the list query\n * with `Authorizer.filter`, so the middleware checks nothing, unless\n * `nonEmpty` is true: then it fails when the principal may act on no value\n * in the scope.\n * - With none of them, it checks the action on every value of the kind.\n *\n * @stability unstable\n * @category models\n * @since 0.3.0\n */\nexport interface Requirement {\n\treadonly action: string\n\treadonly kind: string\n\treadonly load?: (params: Params) => Effect.Effect<object, unknown, unknown>\n\treadonly within?: (params: Params) => unknown\n\treadonly list?: { readonly nonEmpty: boolean }\n}\n\n/**\n * The checks that an endpoint needs.\n *\n * @stability unstable\n * @category annotations\n * @since 0.3.0\n */\nexport class Required extends Context.Service<\n\tRequired,\n\tReadonlyArray<Requirement>\n>()(\"@nmnmcc/ability/HttpApi/Required\") {}\n\n/**\n * The checks for every endpoint of a group with no {@link Required}\n * annotation. `read` is for GET requests. `write` is for all other methods.\n *\n * @stability unstable\n * @category annotations\n * @since 0.3.0\n */\nexport class GroupRequired extends Context.Service<\n\tGroupRequired,\n\t{\n\t\treadonly read: ReadonlyArray<Requirement>\n\t\treadonly write: ReadonlyArray<Requirement>\n\t}\n>()(\"@nmnmcc/ability/HttpApi/GroupRequired\") {}\n\n/**\n * Makes a {@link Requirement} with the types of an authorizer. The action must\n * be an action of the kind, also when the kind has a union type.\n *\n * The last argument is one of:\n * - a `load` function, which must give a value of the kind. It may fail. Its\n * errors must be errors of the middleware, and the services that it needs\n * must be given to {@link layer}.\n * - `{ within }`, a function from the path parameters to a scope. Use it for\n * an act on a part of the kind, such as a new value with no id yet:\n * `{ within: (params) => ($) => $.under(\"org\", params.org ?? null) }`. A path\n * parameter may be `undefined`, and a scope with `undefined` in it is a\n * defect, so give a value for it.\n *\n * @stability unstable\n * @category constructors\n * @since 0.3.0\n */\nexport const requires: <\n\tA extends { readonly domain: Domain.Any },\n\tconst K extends Domain.Kind<A[\"domain\"]>,\n>(\n\tauthorizer: A,\n\taction: Domain.CommonAction<A[\"domain\"], K>,\n\tkind: K,\n\thow?:\n\t\t| ((\n\t\t\t\tparams: Params,\n\t\t ) => Effect.Effect<Domain.ValueOf<A[\"domain\"], K>, unknown, unknown>)\n\t\t| { readonly within: (params: Params) => Policy.Scope<A[\"domain\"], K> },\n) => Requirement = (_authorizer, action, kind, how) =>\n\thow === undefined\n\t\t? { action: action as string, kind }\n\t\t: typeof how === \"function\"\n\t\t\t? { action: action as string, kind, load: how }\n\t\t\t: { action: action as string, kind, within: how.within }\n\n/**\n * Makes a {@link Requirement} for an endpoint that gives a list. The middleware\n * does not check that the principal may act on every value, because the list\n * shows only the values that it may act on. The handler gets the list query\n * with `Authorizer.filter`, with the same scope.\n *\n * When the principal may act on no value, the list is empty: that is not an\n * error, so a client does not learn that hidden values are there. With\n * `nonEmpty: true`, the middleware fails with `Forbidden` in that case. It\n * also fails when the coverage is `Unknown`, because then it can not say that\n * the list has a value.\n *\n * @stability unstable\n * @category constructors\n * @since 1.0.0\n */\nexport const lists: <\n\tA extends { readonly domain: Domain.Any },\n\tconst K extends Domain.Kind<A[\"domain\"]>,\n>(\n\tauthorizer: A,\n\taction: Domain.CommonAction<A[\"domain\"], K>,\n\tkind: K,\n\toptions?: {\n\t\treadonly within?: (params: Params) => Policy.Scope<A[\"domain\"], K>\n\t\treadonly nonEmpty?: boolean\n\t},\n) => Requirement = (_authorizer, action, kind, options) => ({\n\taction: action as string,\n\tkind,\n\tlist: { nonEmpty: options?.nonEmpty ?? false },\n\t...(options?.within === undefined ? {} : { within: options.within }),\n})\n\n/**\n * The Schema of a `PolicyUnavailable` response. It encodes only the `_tag`, so\n * the cause does not go to the client.\n */\nconst PolicyUnavailableResponse = Schema.TaggedStruct(\n\t\"PolicyUnavailable\",\n\t{},\n).pipe(\n\tSchema.decodeTo(\n\t\tSchema.declare(\n\t\t\t(u): u is Authorizer.PolicyUnavailable =>\n\t\t\t\tu instanceof Authorizer.PolicyUnavailable,\n\t\t),\n\t\tSchemaTransformation.transform({\n\t\t\tdecode: () => Authorizer.PolicyUnavailable.make({ cause: undefined }),\n\t\t\tencode: () => ({ _tag: \"PolicyUnavailable\" as const }),\n\t\t}),\n\t),\n)\n\n/**\n * The errors of the middleware, with HTTP status codes: 403 for\n * `Forbidden`, 503 for `PolicyUnavailable`, and 409 for `Pending`.\n *\n * The body of a `PolicyUnavailable` response has only its `_tag`. The cause\n * may have private details, such as an error from the database, so it stays\n * on the server.\n *\n * @stability unstable\n * @category errors\n * @since 0.3.0\n */\nexport const errors = [\n\tAuthorizer.Forbidden.pipe(HttpApiSchema.status(403)),\n\tPolicyUnavailableResponse.pipe(HttpApiSchema.status(503)),\n\tAuthorizer.Pending.pipe(HttpApiSchema.status(409)),\n] as const\n\nconst makeLayer = (\n\tmiddleware: Context.ServiceClass<any, string, any>,\n\tauthorizer: Authorizer.AnyServiceClass,\n) =>\n\tLayer.effect(\n\t\tmiddleware,\n\t\tEffect.gen(function* () {\n\t\t\tconst service = yield* authorizer\n\t\t\tconst services = yield* Effect.context<never>()\n\t\t\treturn Effect.fnUntraced(function* (\n\t\t\t\thttpEffect: Effect.Effect<unknown, unknown, unknown>,\n\t\t\t\toptions: {\n\t\t\t\t\treadonly endpoint: {\n\t\t\t\t\t\treadonly name: string\n\t\t\t\t\t\treadonly method: string\n\t\t\t\t\t\treadonly annotations: Context.Context<never>\n\t\t\t\t\t}\n\t\t\t\t\treadonly group: {\n\t\t\t\t\t\treadonly identifier: string\n\t\t\t\t\t\treadonly annotations: Context.Context<never>\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t) {\n\t\t\t\tconst requirements = Option.orElse(\n\t\t\t\t\tContext.getOption(options.endpoint.annotations, Required),\n\t\t\t\t\t() =>\n\t\t\t\t\t\tOption.map(\n\t\t\t\t\t\t\tContext.getOption(options.group.annotations, GroupRequired),\n\t\t\t\t\t\t\t(rules) =>\n\t\t\t\t\t\t\t\toptions.endpoint.method === \"GET\" ? rules.read : rules.write,\n\t\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\tif (Option.isNone(requirements)) {\n\t\t\t\t\treturn yield* Effect.die(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`The endpoint ${options.group.identifier}.${options.endpoint.name} does not say what it needs. Give it the HttpApi.Required annotation.`,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst endpoint = `${options.group.identifier}.${options.endpoint.name}`\n\t\t\t\t// The requirements are checked in order. The first one that fails\n\t\t\t\t// stops the request.\n\t\t\t\tyield* Effect.forEach(\n\t\t\t\t\trequirements.value,\n\t\t\t\t\t({ action, kind, load, within, list }) => {\n\t\t\t\t\t\tif (load !== undefined) {\n\t\t\t\t\t\t\treturn HttpRouter.params.pipe(\n\t\t\t\t\t\t\t\tEffect.flatMap((params) =>\n\t\t\t\t\t\t\t\t\tEffect.provideContext(load(params), services),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t// The check is for the kind of the requirement. A value of\n\t\t\t\t\t\t\t\t// another kind is a mistake in `load`, and the check must\n\t\t\t\t\t\t\t\t// not pass with the rules of that other kind.\n\t\t\t\t\t\t\t\tEffect.filterOrElse(\n\t\t\t\t\t\t\t\t\t(value) => Resource.kindOf(value) === kind,\n\t\t\t\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t\t\t\tEffect.die(\n\t\t\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\t\t`The load function of ${endpoint} gave a value that is not a ${kind}`,\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tEffect.flatMap((value) =>\n\t\t\t\t\t\t\t\t\tservice.check({ action, resource: value } as never),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst target =\n\t\t\t\t\t\t\twithin === undefined\n\t\t\t\t\t\t\t\t? Effect.succeed({ action, kind } as never)\n\t\t\t\t\t\t\t\t: Effect.map(\n\t\t\t\t\t\t\t\t\t\tHttpRouter.params,\n\t\t\t\t\t\t\t\t\t\t(params) =>\n\t\t\t\t\t\t\t\t\t\t\t({ action, kind, within: within(params) }) as never,\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\tif (list === undefined) {\n\t\t\t\t\t\t\treturn Effect.flatMap(target, (value) => service.check(value))\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// A list endpoint may give an empty list, unless it must not.\n\t\t\t\t\t\t// Only `All` and `Some` say that the list has a value. `Unknown`\n\t\t\t\t\t\t// is not a \"yes\".\n\t\t\t\t\t\treturn list.nonEmpty\n\t\t\t\t\t\t\t? target.pipe(\n\t\t\t\t\t\t\t\t\tEffect.flatMap((value) => service.coverage(value)),\n\t\t\t\t\t\t\t\t\tEffect.filterOrElse(\n\t\t\t\t\t\t\t\t\t\t(covered) =>\n\t\t\t\t\t\t\t\t\t\t\t(covered as { readonly _tag: string })._tag === \"All\" ||\n\t\t\t\t\t\t\t\t\t\t\t(covered as { readonly _tag: string })._tag === \"Some\",\n\t\t\t\t\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t\t\t\t\tEffect.fail(Authorizer.Forbidden.make({ action, kind })),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: Effect.void\n\t\t\t\t\t},\n\t\t\t\t\t{ discard: true },\n\t\t\t\t)\n\t\t\t\treturn yield* httpEffect\n\t\t\t})\n\t\t}),\n\t)\n\n/**\n * Makes the layer of an authorization middleware. The middleware reads the\n * {@link Required} and {@link GroupRequired} annotations and checks each\n * requirement with the authorizer.\n *\n * The layer takes the authorizer when it is built. The middleware must\n * require the services of the authorizer, such as the identity of the\n * caller. `R` is the type of the services that the `load` functions need. The\n * layer takes them when it is built too.\n *\n * @stability unstable\n * @category layers\n * @since 0.3.0\n */\nexport const layer: <\n\tSelf,\n\tId extends string,\n\tA extends Authorizer.AnyServiceClass,\n\tR = never,\n>(\n\tmiddleware: Context.ServiceClass<Self, Id, any>,\n\tauthorizer: A,\n) => Layer.Layer<\n\tSelf,\n\tnever,\n\t(A extends Context.Key<infer I, any> ? I : never) | R\n> = makeLayer as never\n"],"mappings":"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"}
1
+ {"version":3,"file":"HttpApi.js","names":["Authorizer.PolicyUnavailable","Resource.kindOf"],"sources":["../src/HttpApi.ts"],"sourcesContent":["/**\n * Tools to check authorization in an `effect/http-api` server.\n *\n * Each endpoint says what it needs with the {@link Required} annotation. A\n * group can give a default with {@link GroupRequired}: one list for GET\n * requests and one list for all other methods. A middleware checks the\n * requirements before the handler runs, in each group that uses it. An\n * endpoint with no annotation is a defect, so a new endpoint is closed until\n * you say what it needs. An empty list means the endpoint is open. An endpoint\n * in a group without the middleware is not checked at all.\n *\n * ```ts\n * class Authorization extends HttpApiMiddleware.Service<\n * Authorization,\n * { requires: Identity }\n * >()(\"app/Authorization\", { error: HttpApi.errors }) {}\n *\n * const endpoint = HttpApiEndpoint.patch(\"update\", \"/notes/:id\", { ... })\n * .annotate(HttpApi.Required, [\n * HttpApi.requires(Auth, \"update\", \"Note\", (params) => Notes.find(params.id)),\n * ])\n *\n * const notes = HttpApiGroup.make(\"notes\").add(endpoint).middleware(Authorization)\n *\n * const AuthorizationLive = HttpApi.layer(Authorization, Auth)\n * ```\n *\n * This module is a separate entry point, `@nmnmcc/ability/HttpApi`, because\n * `effect/http-api` is not stable yet.\n *\n * @stability unstable\n * @since 0.3.0\n */\n// A `load` function of a requirement is kept in an annotation, so its error\n// type and its service type are erased to `unknown`. The language service\n// check for `unknown` in an Effect is off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Option from \"effect/Option\"\nimport * as Schema from \"effect/Schema\"\nimport * as SchemaTransformation from \"effect/SchemaTransformation\"\nimport { HttpRouter } from \"effect/http\"\nimport { HttpApiSchema } from \"effect/http-api\"\nimport * as Authorizer from \"./Authorizer.ts\"\nimport type * as Domain from \"./Domain.ts\"\nimport type * as Policy from \"./Policy.ts\"\nimport * as Resource from \"./Resource.ts\"\n\n/**\n * The path parameters of a request.\n *\n * @stability unstable\n * @category models\n * @since 1.0.0\n */\nexport type Params = Readonly<Record<string, string | undefined>>\n\n/**\n * One check that an endpoint needs: an action on a kind of resource.\n * - With `load`, the middleware loads the resource value from the path\n * parameters and checks the action on that value.\n * - With `within`, it makes a scope from the path parameters and checks the\n * action on every value in the scope.\n * - With `list`, the endpoint gives a list. The handler gets the list query\n * with `Authorizer.filter`, so the middleware checks nothing, unless\n * `nonEmpty` is true: then it fails when the principal may act on no value\n * in the scope.\n * - With none of them, it checks the action on every value of the kind.\n *\n * @stability unstable\n * @category models\n * @since 0.3.0\n */\nexport interface Requirement {\n\treadonly action: string\n\treadonly kind: string\n\treadonly load?: (params: Params) => Effect.Effect<object, unknown, unknown>\n\treadonly within?: (params: Params) => unknown\n\treadonly list?: { readonly nonEmpty: boolean }\n}\n\n/**\n * The checks that an endpoint needs.\n *\n * @stability unstable\n * @category annotations\n * @since 0.3.0\n */\nexport class Required extends Context.Service<\n\tRequired,\n\tReadonlyArray<Requirement>\n>()(\"@nmnmcc/ability/HttpApi/Required\") {}\n\n/**\n * The checks for every endpoint of a group with no {@link Required}\n * annotation. `read` is for GET requests. `write` is for all other methods.\n *\n * @stability unstable\n * @category annotations\n * @since 0.3.0\n */\nexport class GroupRequired extends Context.Service<\n\tGroupRequired,\n\t{\n\t\treadonly read: ReadonlyArray<Requirement>\n\t\treadonly write: ReadonlyArray<Requirement>\n\t}\n>()(\"@nmnmcc/ability/HttpApi/GroupRequired\") {}\n\n/**\n * Makes a {@link Requirement} with the types of an authorizer. The action must\n * be an action of the kind, also when the kind has a union type.\n *\n * The last argument is one of:\n * - a `load` function, which must give a value of the kind. It may fail. Its\n * errors must be errors of the middleware, and the services that it needs\n * must be given to {@link layer}.\n * - `{ within }`, a function from the path parameters to a scope. Use it for\n * an act on a part of the kind, such as a new value with no id yet:\n * `{ within: (params) => ($) => $.under(\"org\", params.org ?? null) }`. A path\n * parameter may be `undefined`, and a scope with `undefined` in it is a\n * defect, so give a value for it.\n *\n * @stability unstable\n * @category constructors\n * @since 0.3.0\n */\nexport const requires: <\n\tA extends { readonly domain: Domain.Any },\n\tconst K extends Domain.Kind<A[\"domain\"]>,\n>(\n\tauthorizer: A,\n\taction: Domain.CommonAction<A[\"domain\"], K>,\n\tkind: K,\n\thow?:\n\t\t| ((\n\t\t\t\tparams: Params,\n\t\t ) => Effect.Effect<Domain.ValueOf<A[\"domain\"], K>, unknown, unknown>)\n\t\t| { readonly within: (params: Params) => Policy.Scope<A[\"domain\"], K> },\n) => Requirement = (_authorizer, action, kind, how) =>\n\thow === undefined\n\t\t? { action: action as string, kind }\n\t\t: typeof how === \"function\"\n\t\t\t? { action: action as string, kind, load: how }\n\t\t\t: { action: action as string, kind, within: how.within }\n\n/**\n * Makes a {@link Requirement} for an endpoint that gives a list. The middleware\n * does not check that the principal may act on every value, because the list\n * shows only the values that it may act on. The handler gets the list query\n * with `Authorizer.filter`, with the same scope.\n *\n * When the principal may act on no value, the list is empty: that is not an\n * error, so a client does not learn that hidden values are there. With\n * `nonEmpty: true`, the middleware fails with `Forbidden` in that case. It\n * also fails when the coverage is `Unknown`, because then it can not say that\n * the list has a value.\n *\n * @stability unstable\n * @category constructors\n * @since 1.0.0\n */\nexport const lists: <\n\tA extends { readonly domain: Domain.Any },\n\tconst K extends Domain.Kind<A[\"domain\"]>,\n>(\n\tauthorizer: A,\n\taction: Domain.CommonAction<A[\"domain\"], K>,\n\tkind: K,\n\toptions?: {\n\t\treadonly within?: (params: Params) => Policy.Scope<A[\"domain\"], K>\n\t\treadonly nonEmpty?: boolean\n\t},\n) => Requirement = (_authorizer, action, kind, options) => ({\n\taction: action as string,\n\tkind,\n\tlist: { nonEmpty: options?.nonEmpty ?? false },\n\t...(options?.within === undefined ? {} : { within: options.within }),\n})\n\n/**\n * The Schema of a `PolicyUnavailable` response. It encodes only the `_tag`, so\n * the cause does not go to the client.\n */\nconst PolicyUnavailableResponse = Schema.TaggedStruct(\n\t\"PolicyUnavailable\",\n\t{},\n).pipe(\n\tSchema.decodeTo(\n\t\tSchema.declare(\n\t\t\t(u): u is Authorizer.PolicyUnavailable =>\n\t\t\t\tu instanceof Authorizer.PolicyUnavailable,\n\t\t),\n\t\tSchemaTransformation.transform({\n\t\t\tdecode: () => Authorizer.PolicyUnavailable.make({ cause: undefined }),\n\t\t\tencode: () => ({ _tag: \"PolicyUnavailable\" as const }),\n\t\t}),\n\t),\n)\n\n/**\n * The errors of the middleware, with HTTP status codes: 403 for\n * `Forbidden`, 503 for `PolicyUnavailable`, and 409 for `Pending`.\n *\n * The body of a `PolicyUnavailable` response has only its `_tag`. The cause\n * may have private details, such as an error from the database, so it stays\n * on the server.\n *\n * @stability unstable\n * @category errors\n * @since 0.3.0\n */\nexport const errors = [\n\tAuthorizer.Forbidden.pipe(HttpApiSchema.status(403)),\n\tPolicyUnavailableResponse.pipe(HttpApiSchema.status(503)),\n\tAuthorizer.Pending.pipe(HttpApiSchema.status(409)),\n] as const\n\nconst makeLayer = (\n\tmiddleware: Context.ServiceClass<any, string, any>,\n\tauthorizer: Authorizer.AnyServiceClass,\n) =>\n\tLayer.effect(\n\t\tmiddleware,\n\t\tEffect.gen(function* () {\n\t\t\tconst service = yield* authorizer\n\t\t\tconst services = yield* Effect.context<never>()\n\t\t\treturn Effect.fnUntraced(function* (\n\t\t\t\thttpEffect: Effect.Effect<unknown, unknown, unknown>,\n\t\t\t\toptions: {\n\t\t\t\t\treadonly endpoint: {\n\t\t\t\t\t\treadonly name: string\n\t\t\t\t\t\treadonly method: string\n\t\t\t\t\t\treadonly annotations: Context.Context<never>\n\t\t\t\t\t}\n\t\t\t\t\treadonly group: {\n\t\t\t\t\t\treadonly identifier: string\n\t\t\t\t\t\treadonly annotations: Context.Context<never>\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t) {\n\t\t\t\tconst requirements = Option.orElse(\n\t\t\t\t\tContext.getOption(options.endpoint.annotations, Required),\n\t\t\t\t\t() =>\n\t\t\t\t\t\tOption.map(\n\t\t\t\t\t\t\tContext.getOption(options.group.annotations, GroupRequired),\n\t\t\t\t\t\t\t(rules) =>\n\t\t\t\t\t\t\t\toptions.endpoint.method === \"GET\" ? rules.read : rules.write,\n\t\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\tif (Option.isNone(requirements)) {\n\t\t\t\t\treturn yield* Effect.die(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`The endpoint ${options.group.identifier}.${options.endpoint.name} does not say what it needs. Give it the HttpApi.Required annotation.`,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst endpoint = `${options.group.identifier}.${options.endpoint.name}`\n\t\t\t\t// The requirements are checked in order. The first one that fails\n\t\t\t\t// stops the request.\n\t\t\t\tyield* Effect.forEach(\n\t\t\t\t\trequirements.value,\n\t\t\t\t\t({ action, kind, load, within, list }) => {\n\t\t\t\t\t\tif (load !== undefined) {\n\t\t\t\t\t\t\treturn HttpRouter.params.pipe(\n\t\t\t\t\t\t\t\tEffect.flatMap((params) =>\n\t\t\t\t\t\t\t\t\tEffect.provideContext(load(params), services),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t// The check is for the kind of the requirement. A value of\n\t\t\t\t\t\t\t\t// another kind is a mistake in `load`, and the check must\n\t\t\t\t\t\t\t\t// not pass with the rules of that other kind.\n\t\t\t\t\t\t\t\tEffect.filterOrElse(\n\t\t\t\t\t\t\t\t\t(value) => Resource.kindOf(value) === kind,\n\t\t\t\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t\t\t\tEffect.die(\n\t\t\t\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t\t\t\t`The load function of ${endpoint} gave a value that is not a ${kind}`,\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tEffect.flatMap((value) =>\n\t\t\t\t\t\t\t\t\tservice.check({ action, resource: value } as never),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst target =\n\t\t\t\t\t\t\twithin === undefined\n\t\t\t\t\t\t\t\t? Effect.succeed({ action, kind } as never)\n\t\t\t\t\t\t\t\t: Effect.map(\n\t\t\t\t\t\t\t\t\t\tHttpRouter.params,\n\t\t\t\t\t\t\t\t\t\t(params) =>\n\t\t\t\t\t\t\t\t\t\t\t({ action, kind, within: within(params) }) as never,\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\tif (list === undefined) {\n\t\t\t\t\t\t\treturn Effect.flatMap(target, (value) => service.check(value))\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// A list endpoint may give an empty list, unless it must not.\n\t\t\t\t\t\t// Only `All` and `Some` say that the list has a value. `Unknown`\n\t\t\t\t\t\t// is not a \"yes\".\n\t\t\t\t\t\treturn list.nonEmpty\n\t\t\t\t\t\t\t? target.pipe(\n\t\t\t\t\t\t\t\t\tEffect.flatMap((value) => service.coverage(value)),\n\t\t\t\t\t\t\t\t\tEffect.filterOrElse(\n\t\t\t\t\t\t\t\t\t\t(covered) =>\n\t\t\t\t\t\t\t\t\t\t\t(covered as { readonly _tag: string })._tag === \"All\" ||\n\t\t\t\t\t\t\t\t\t\t\t(covered as { readonly _tag: string })._tag === \"Some\",\n\t\t\t\t\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\t\t\t\t\tEffect.fail(Authorizer.Forbidden.make({ action, kind })),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t: Effect.void\n\t\t\t\t\t},\n\t\t\t\t\t{ discard: true },\n\t\t\t\t)\n\t\t\t\treturn yield* httpEffect\n\t\t\t})\n\t\t}),\n\t)\n\n/**\n * Makes the layer of an authorization middleware. The middleware reads the\n * {@link Required} and {@link GroupRequired} annotations and checks each\n * requirement with the authorizer.\n *\n * The layer takes the authorizer when it is built. The middleware must\n * require the services of the authorizer, such as the identity of the\n * caller. `R` is the type of the services that the `load` functions need. The\n * layer takes them when it is built too.\n *\n * @stability unstable\n * @category layers\n * @since 0.3.0\n */\nexport const layer: <\n\tSelf,\n\tId extends string,\n\tA extends Authorizer.AnyServiceClass,\n\tR = never,\n>(\n\tmiddleware: Context.ServiceClass<Self, Id, any>,\n\tauthorizer: A,\n) => Layer.Layer<\n\tSelf,\n\tnever,\n\t(A extends Context.Key<infer I, any> ? I : never) | R\n> = makeLayer as never\n"],"mappings":"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"}
package/dist/Policy.d.ts CHANGED
@@ -309,9 +309,9 @@ export declare const rules: <D extends Any$1, Rules extends Rule<any, any, any,
309
309
  * @example
310
310
  * ```ts
311
311
  * const policy = Policy.define(App)(function* (_) {
312
- * yield* _.allow("read", Bot)
313
- * yield* _.allow(["update", "delete"], Bot, {
314
- * when: ($) => $.eq("ownerUserId", $.principal("userId")),
312
+ * yield* _.allow("read", Document)
313
+ * yield* _.allow(["update", "delete"], Document, {
314
+ * when: ($) => $.eq("ownerId", $.principal("id")),
315
315
  * })
316
316
  * })
317
317
  * ```
package/dist/Policy.js CHANGED
@@ -169,9 +169,9 @@ const rules = (domain, values, options) => {
169
169
  * @example
170
170
  * ```ts
171
171
  * const policy = Policy.define(App)(function* (_) {
172
- * yield* _.allow("read", Bot)
173
- * yield* _.allow(["update", "delete"], Bot, {
174
- * when: ($) => $.eq("ownerUserId", $.principal("userId")),
172
+ * yield* _.allow("read", Document)
173
+ * yield* _.allow(["update", "delete"], Document, {
174
+ * when: ($) => $.eq("ownerId", $.principal("id")),
175
175
  * })
176
176
  * })
177
177
  * ```