@kutur-kit/access 0.0.0-stage → 0.1.0-alpha.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +224 -2
  3. package/dist/Condition.d.ts +76 -0
  4. package/dist/Condition.d.ts.map +1 -0
  5. package/dist/Condition.js +43 -0
  6. package/dist/Condition.js.map +1 -0
  7. package/dist/Decision.d.ts +39 -0
  8. package/dist/Decision.d.ts.map +1 -0
  9. package/dist/Decision.js +11 -0
  10. package/dist/Decision.js.map +1 -0
  11. package/dist/Permission.d.ts +82 -0
  12. package/dist/Permission.d.ts.map +1 -0
  13. package/dist/Permission.js +114 -0
  14. package/dist/Permission.js.map +1 -0
  15. package/dist/PermissionFilter.d.ts +25 -0
  16. package/dist/PermissionFilter.d.ts.map +1 -0
  17. package/dist/PermissionFilter.js +33 -0
  18. package/dist/PermissionFilter.js.map +1 -0
  19. package/dist/audit.d.ts +27 -0
  20. package/dist/audit.d.ts.map +1 -0
  21. package/dist/audit.js +46 -0
  22. package/dist/audit.js.map +1 -0
  23. package/dist/drizzle/postgres.d.ts +18 -0
  24. package/dist/drizzle/postgres.d.ts.map +1 -0
  25. package/dist/drizzle/postgres.js +65 -0
  26. package/dist/drizzle/postgres.js.map +1 -0
  27. package/dist/index.d.ts +5 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +5 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/testing/PermissionTesting.d.ts +17 -0
  32. package/dist/testing/PermissionTesting.d.ts.map +1 -0
  33. package/dist/testing/PermissionTesting.js +19 -0
  34. package/dist/testing/PermissionTesting.js.map +1 -0
  35. package/dist/testing/index.d.ts +2 -0
  36. package/dist/testing/index.d.ts.map +1 -0
  37. package/dist/testing/index.js +2 -0
  38. package/dist/testing/index.js.map +1 -0
  39. package/package.json +88 -3
  40. package/src/Condition.ts +93 -0
  41. package/src/Decision.ts +45 -0
  42. package/src/Permission.ts +311 -0
  43. package/src/PermissionFilter.ts +93 -0
  44. package/src/audit.ts +82 -0
  45. package/src/drizzle/postgres.ts +108 -0
  46. package/src/index.ts +7 -0
  47. package/src/testing/PermissionTesting.ts +47 -0
  48. package/src/testing/index.ts +1 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kutur Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,225 @@
1
- # Temporary Holding Version
1
+ # @kutur-kit/access
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Permissions for Effect, defined as services. Each permission declares what it decides about, and its policy is a Layer. Checks are typed and traced, can be restricted per caller (for example to an agent token's scopes) and can be audited. A permission can also describe the targets it allows as a condition for list queries.
4
+
5
+ Tested with Effect 4.0.5.
6
+
7
+ ```sh
8
+ pnpm add @kutur-kit/access@alpha effect
9
+ ```
10
+
11
+ ## Permissions
12
+
13
+ Define a permission with a key, an optional public `id` and the schema of its `target`. Declare the services its policy may read with `requires`:
14
+
15
+ ```ts
16
+ import { Permission } from "@kutur-kit/access";
17
+ import { Schema } from "effect";
18
+
19
+ import { CurrentUser, Invoice, MembershipUnavailable } from "./domain.js";
20
+
21
+ export class ViewInvoice extends Permission.Service<ViewInvoice, { requires: CurrentUser }>()(
22
+ "billing/ViewInvoice",
23
+ { id: "invoices.view", target: Invoice, error: MembershipUnavailable },
24
+ ) {}
25
+ ```
26
+
27
+ The definition holds only schemas, so it can be shared with clients. Implement the policy with `toLayer`, usually in server-only code. A policy returns a `Decision`, directly or as an Effect, and may read the services declared in `requires`:
28
+
29
+ ```ts
30
+ import { Decision } from "@kutur-kit/access";
31
+ import { Effect, Option } from "effect";
32
+
33
+ export const ViewInvoiceLayer = ViewInvoice.toLayer(
34
+ Effect.gen(function* () {
35
+ const memberships = yield* Memberships;
36
+
37
+ return Effect.fn(function* (invoice) {
38
+ const user = yield* CurrentUser;
39
+ const role = yield* memberships.roleIn(user.id, invoice.orgId);
40
+
41
+ return Option.isSome(role) ? Decision.allow() : Decision.deny();
42
+ });
43
+ }),
44
+ );
45
+ ```
46
+
47
+ Services the Layer reads while it is built, such as `Memberships`, are requirements of the Layer. Services read inside the policy, such as `CurrentUser`, are requirements of every check.
48
+
49
+ | Method | Result |
50
+ | ----------------------------- | -------------------------------------------------------------- |
51
+ | `ViewInvoice.check(target)` | The `Decision`. Fails only with the policy's own errors. |
52
+ | `ViewInvoice.require(target)` | Succeeds when allowed; otherwise fails with `Decision.Denied`. |
53
+
54
+ Both are also available on the service itself (`const view = yield* ViewInvoice`), which is how other services and policies capture a permission when their Layer is built. A permission without a `target` takes no arguments.
55
+
56
+ `Decision.Denied` carries the refusing permission's `id` and encodes with HTTP status 403. Each permission's `Denied` schema matches only its own refusals, so a caller can treat one refusal differently:
57
+
58
+ ```ts
59
+ const approve = (invoice: Invoice) =>
60
+ approvePayout
61
+ .require(invoice)
62
+ .pipe(Effect.catchIf(Schema.is(ViewInvoice.Denied), () => Effect.fail(new InvoiceNotFound())));
63
+ ```
64
+
65
+ ### Decisions
66
+
67
+ | Constructor | Meaning |
68
+ | ---------------------------- | ----------------------------------------------------------- |
69
+ | `Decision.allow()` | Allowed. |
70
+ | `Decision.deny(reason?)` | Refused by the policy. |
71
+ | `Decision.restricted()` | Refused by the caller's restriction, before the policy ran. |
72
+ | `Decision.unmet(permission)` | Refused because a permission the policy required refused. |
73
+
74
+ `Deny.by` tells these apart: `"policy"`, `"restriction"` or `"requirement"`.
75
+
76
+ ### Permissions that require others
77
+
78
+ A policy can require another permission. Declare that permission's refusals in `error`:
79
+
80
+ ```ts
81
+ export class ApprovePayout extends Permission.Service<ApprovePayout, { requires: CurrentUser }>()(
82
+ "billing/ApprovePayout",
83
+ {
84
+ id: "payouts.approve",
85
+ target: Invoice,
86
+ error: Schema.Union([ViewInvoice.Denied, MembershipUnavailable]),
87
+ },
88
+ ) {}
89
+
90
+ export const ApprovePayoutLayer = ApprovePayout.toLayer(
91
+ Effect.gen(function* () {
92
+ const view = yield* ViewInvoice;
93
+
94
+ return Effect.fn(function* (invoice) {
95
+ yield* view.require(invoice);
96
+ // ...
97
+ });
98
+ }),
99
+ ).pipe(Layer.provide(ViewInvoiceLayer));
100
+ ```
101
+
102
+ When the required permission refuses, `check` answers `Decision.unmet("invoices.view")` and `require` fails with the required permission's refusal, so callers can still tell which one refused. Refusals never appear in a check's error type.
103
+
104
+ ## Restrictions
105
+
106
+ Restrict the permissions a caller may use, for example to the scopes of an agent token. Every other permission refuses with `Decision.restricted()`, and restrictions only narrow when nested:
107
+
108
+ ```ts
109
+ program.pipe(Permission.restrictTo([ViewInvoice, ApprovePayout]));
110
+ ```
111
+
112
+ To store scopes, define the set of permissions a token may hold. Its schema decodes stored ids and drops ids of permissions that no longer exist:
113
+
114
+ ```ts
115
+ export const AgentScopes = Permission.set([ViewInvoice, ApprovePayout]);
116
+
117
+ const asAgent = Effect.gen(function* () {
118
+ const scopes = yield* Schema.decodeEffect(AgentScopes.schema)(token.scopes);
119
+
120
+ return yield* program.pipe(AgentScopes.restrictTo(scopes));
121
+ });
122
+ ```
123
+
124
+ Ids name permissions outside the code, so `Permission.set` rejects duplicates. Restrictions hold service keys, so two permissions that share an id still restrict separately.
125
+
126
+ ## Lists
127
+
128
+ Checking every row of a list one by one runs the policy, and its lookups, once per row. A `PermissionFilter` instead states the permission's rule as a `Condition` that a query applies, so a list is one query and pages stay complete:
129
+
130
+ ```ts
131
+ import { PermissionFilter } from "@kutur-kit/access";
132
+
133
+ export class ViewInvoiceFilter extends PermissionFilter.Service<ViewInvoiceFilter>()(ViewInvoice) {}
134
+
135
+ export const ViewInvoiceFilterLayer = ViewInvoiceFilter.toLayer(
136
+ Effect.gen(function* () {
137
+ const memberships = yield* Memberships;
138
+
139
+ return Effect.fn(function* (where) {
140
+ const user = yield* CurrentUser;
141
+
142
+ return where.oneOf("orgId", yield* memberships.orgsOf(user.id));
143
+ });
144
+ }),
145
+ );
146
+ ```
147
+
148
+ The filter takes its target, errors and requirements from the permission, which must have a target. Name it after the permission: its key is the permission's key with `/Filter`. Outside the caller's restriction its condition matches nothing.
149
+
150
+ `where` builds conditions over the target's fields: `eq`, `oneOf`, `and`, `or`, `not`, `all` and `none`. Fields and values are checked against the target's type. `Condition.matches(condition, target)` evaluates a condition in memory.
151
+
152
+ ### PostgreSQL with Drizzle
153
+
154
+ `@kutur-kit/access/drizzle/postgres` translates a condition into a `where` clause:
155
+
156
+ ```ts
157
+ import * as Drizzle from "@kutur-kit/access/drizzle/postgres";
158
+
159
+ const page = Effect.gen(function* () {
160
+ const viewInvoiceFilter = yield* ViewInvoiceFilter;
161
+ const condition = yield* viewInvoiceFilter.condition;
162
+
163
+ return yield* database
164
+ .select()
165
+ .from(invoices)
166
+ .where(and(Drizzle.where(invoices, condition), gt(invoices.id, cursor)))
167
+ .orderBy(invoices.id)
168
+ .limit(100);
169
+ });
170
+ ```
171
+
172
+ The query selects exactly the rows `Condition.matches` accepts, including rows with `NULL` values. To keep that true, only columns whose SQL equality matches JavaScript's are accepted: `text`, `varchar`, enums, `uuid`, `smallint`, `integer`, `bigint`, `boolean`, `real` and `double precision`. `numeric`, `char`, `citext` and other custom types are rejected, and `uuid` values must be lowercase and canonical. `oneOf` sends its values as one array parameter, however many there are.
173
+
174
+ Pagination is the query's own: the condition combines with a cursor as above, or with `LIMIT` and `OFFSET`.
175
+
176
+ ### Keeping filters and policies in agreement
177
+
178
+ A filter and its policy state the same rule twice. `PermissionTesting.assertFilterAgrees` from `@kutur-kit/access/testing` dies on the first target where they disagree:
179
+
180
+ ```ts
181
+ it.effect("the invoice filter agrees with its policy", () =>
182
+ PermissionTesting.assertFilterAgrees(ViewInvoice, ViewInvoiceFilter, invoices),
183
+ );
184
+ ```
185
+
186
+ Run it in tests for each kind of user over representative targets.
187
+
188
+ ## Audit
189
+
190
+ `@kutur-kit/access/audit` records a permission's decisions with [`@kutur-kit/audit`](https://github.com/kutur-labs/kit/tree/main/packages/audit):
191
+
192
+ ```ts
193
+ import * as PermissionAudit from "@kutur-kit/access/audit";
194
+
195
+ export const ApprovePayoutLayer = ApprovePayout.toLayer(policy).pipe(
196
+ PermissionAudit.record(ApprovePayout, {
197
+ view: Schema.Struct({ id: InvoiceId, orgId: OrgId, amount: Schema.Int }),
198
+ }),
199
+ );
200
+ ```
201
+
202
+ Every `require` records an audit event named by the permission's id, with outcome `Allow` or `Deny`, the policy's reason and details of what refused. Checks only answer questions and are not recorded, nor are the requires their policies make. Writes are isolated from the caller's transaction, so refusals stay recorded when the request rolls back. The recorded Layer requires an `AuditLog`.
203
+
204
+ `view` chooses what is recorded of the target, as for any audit event. Without it, the fields marked with `AuditKey.key` are recorded.
205
+
206
+ ## Tracing
207
+
208
+ Checks run in spans named `<key>.check` and `<key>.require`, and conditions in `<key>/Filter.condition`. Spans carry `access.permission`, `access.decision`, `access.denied_by`, `access.reason` and `access.requirement`.
209
+
210
+ `Permission.tap(f)` returns a permission service that also runs `f` after each decision, for other kinds of recording.
211
+
212
+ ## Requirements
213
+
214
+ - `effect` 4.0.5 or later 4.x.
215
+ - `@kutur-kit/access/audit` needs `@kutur-kit/audit`.
216
+ - `@kutur-kit/access/drizzle/postgres` needs `drizzle-orm` 1.0.0-rc.4 with the Effect 4 patch described in the [`@kutur-kit/audit` README](https://github.com/kutur-labs/kit/tree/main/packages/audit#requirements).
217
+
218
+ ## Limitations
219
+
220
+ - Conditions compare fields with values. They have no ranges, patterns, date fields, optional or nested fields and no conditions over other tables, so lists such as shared ids are passed as values.
221
+ - `Drizzle.where` needs a comparable column for every field of the target a condition could compare, even unused ones. Map columns with an object when the table's names differ.
222
+ - The SQL translation supports PostgreSQL only.
223
+ - Each `eq` and `oneOf` is one statement parameter, and the PostgreSQL driver sends at most 32,767. Prefer `oneOf` over many `eq`s.
224
+ - Translation and evaluation recurse once per level of nesting. SQL translation overflows the stack below 2,000 levels.
225
+ - Nothing enforces that a filter agrees with its policy except `assertFilterAgrees` in tests.
@@ -0,0 +1,76 @@
1
+ /** The values conditions compare. */
2
+ export type Value = string | number | boolean | bigint | null;
3
+ /** The fields of `T` a condition can compare: those holding primitive values. */
4
+ export type Field<T> = {
5
+ readonly [K in keyof T & string]: T[K] extends Value ? K : never;
6
+ }[keyof T & string];
7
+ declare const All_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
8
+ readonly _tag: "All";
9
+ } & import("effect/Pipeable").Pipeable;
10
+ export declare class All extends All_base<{}> {
11
+ }
12
+ declare const None_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
13
+ readonly _tag: "None";
14
+ } & import("effect/Pipeable").Pipeable;
15
+ export declare class None extends None_base<{}> {
16
+ }
17
+ declare const Eq_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
18
+ readonly _tag: "Eq";
19
+ } & import("effect/Pipeable").Pipeable;
20
+ export declare class Eq extends Eq_base<{
21
+ readonly field: string;
22
+ readonly value: Value;
23
+ }> {
24
+ }
25
+ declare const OneOf_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
26
+ readonly _tag: "OneOf";
27
+ } & import("effect/Pipeable").Pipeable;
28
+ export declare class OneOf extends OneOf_base<{
29
+ readonly field: string;
30
+ readonly values: ReadonlyArray<Value>;
31
+ }> {
32
+ }
33
+ declare const And_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
34
+ readonly _tag: "And";
35
+ } & import("effect/Pipeable").Pipeable;
36
+ export declare class And extends And_base<{
37
+ readonly conditions: ReadonlyArray<Node>;
38
+ }> {
39
+ }
40
+ declare const Or_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
41
+ readonly _tag: "Or";
42
+ } & import("effect/Pipeable").Pipeable;
43
+ export declare class Or extends Or_base<{
44
+ readonly conditions: ReadonlyArray<Node>;
45
+ }> {
46
+ }
47
+ declare const Not_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
48
+ readonly _tag: "Not";
49
+ } & import("effect/Pipeable").Pipeable;
50
+ export declare class Not extends Not_base<{
51
+ readonly condition: Node;
52
+ }> {
53
+ }
54
+ export type Node = All | None | Eq | OneOf | And | Or | Not;
55
+ /**
56
+ * A condition over targets of type `T`. The phantom member keeps conditions
57
+ * over one target type from being used for another.
58
+ */
59
+ export type Condition<T> = Node & {
60
+ readonly "~target"?: (target: T) => T;
61
+ };
62
+ /** Builds conditions over one target type; PermissionFilter policies receive it. */
63
+ export interface Builder<T> {
64
+ readonly all: () => Condition<T>;
65
+ readonly none: () => Condition<T>;
66
+ readonly eq: <K extends Field<T>>(field: K, value: T[K] & Value) => Condition<T>;
67
+ readonly oneOf: <K extends Field<T>>(field: K, values: ReadonlyArray<T[K] & Value>) => Condition<T>;
68
+ readonly and: (...conditions: ReadonlyArray<Condition<T>>) => Condition<T>;
69
+ readonly or: (...conditions: ReadonlyArray<Condition<T>>) => Condition<T>;
70
+ readonly not: (condition: Condition<T>) => Condition<T>;
71
+ }
72
+ export declare const make: <T>() => Builder<T>;
73
+ /** Whether a target satisfies a condition, as a query applying it would decide. */
74
+ export declare const matches: <T extends object>(condition: Condition<T>, target: T) => boolean;
75
+ export {};
76
+ //# sourceMappingURL=Condition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Condition.d.ts","sourceRoot":"","sources":["../src/Condition.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { Data, Match, Predicate } from "effect";
2
+ export class All extends Data.TaggedClass("All") {
3
+ }
4
+ export class None extends Data.TaggedClass("None") {
5
+ }
6
+ export class Eq extends Data.TaggedClass("Eq") {
7
+ }
8
+ export class OneOf extends Data.TaggedClass("OneOf") {
9
+ }
10
+ export class And extends Data.TaggedClass("And") {
11
+ }
12
+ export class Or extends Data.TaggedClass("Or") {
13
+ }
14
+ export class Not extends Data.TaggedClass("Not") {
15
+ }
16
+ export const make = () => ({
17
+ all: () => new All(),
18
+ none: () => new None(),
19
+ eq: (field, value) => new Eq({ field, value }),
20
+ oneOf: (field, values) => new OneOf({ field, values }),
21
+ and: (...conditions) => new And({ conditions }),
22
+ or: (...conditions) => new Or({ conditions }),
23
+ not: (condition) => new Not({ condition }),
24
+ });
25
+ // NaN equals NaN and 0 equals -0, as in SQL.
26
+ const same = (actual, value) => actual === value || Object.is(actual, value);
27
+ const isValue = (value) => value === null ||
28
+ Predicate.isString(value) ||
29
+ Predicate.isNumber(value) ||
30
+ Predicate.isBoolean(value) ||
31
+ Predicate.isBigInt(value);
32
+ const evaluate = (fields) => Match.type().pipe(Match.tagsExhaustive({
33
+ All: () => true,
34
+ None: () => false,
35
+ Eq: ({ field, value }) => same(fields.get(field), value),
36
+ OneOf: ({ field, values }) => values.some((value) => same(fields.get(field), value)),
37
+ And: ({ conditions }) => conditions.every(evaluate(fields)),
38
+ Or: ({ conditions }) => conditions.some(evaluate(fields)),
39
+ Not: ({ condition }) => !evaluate(fields)(condition),
40
+ }));
41
+ /** Whether a target satisfies a condition, as a query applying it would decide. */
42
+ export const matches = (condition, target) => evaluate(new Map(Object.entries(target).flatMap(([field, value]) => (isValue(value) ? [[field, value]] : []))))(condition);
43
+ //# sourceMappingURL=Condition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Condition.js","sourceRoot":"","sources":["../src/Condition.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { Data, Schema } from "effect";
2
+ export type Decision = Data.TaggedEnum<{
3
+ Allow: {};
4
+ /**
5
+ * `restriction` when the caller's restriction refused the permission before
6
+ * its policy ran; `requirement` when the policy required a permission, named
7
+ * by `requirement`, that refused.
8
+ */
9
+ Deny: {
10
+ readonly by: "restriction" | "policy" | "requirement";
11
+ readonly reason?: string;
12
+ readonly requirement?: string;
13
+ };
14
+ }>;
15
+ export type Allow = Extract<Decision, {
16
+ readonly _tag: "Allow";
17
+ }>;
18
+ export type Deny = Extract<Decision, {
19
+ readonly _tag: "Deny";
20
+ }>;
21
+ export declare const allow: () => Allow;
22
+ export declare const deny: (reason?: string) => Deny;
23
+ export declare const restricted: () => Deny;
24
+ export declare const unmet: (requirement: string) => Deny;
25
+ declare const Denied_base: Schema.Class<Denied, Schema.TaggedStruct<"AccessDenied", {
26
+ readonly permission: Schema.String;
27
+ }>, import("effect/Cause").YieldableError & {
28
+ readonly brand: unique symbol;
29
+ }>;
30
+ export declare class Denied extends Denied_base {
31
+ }
32
+ export type DeniedBy<P extends string> = Denied & {
33
+ readonly permission: P;
34
+ };
35
+ export type DeniedBySchema<P extends string> = Schema.refine<DeniedBy<P>, typeof Denied>;
36
+ /** The refusals of one permission, from its id alone. */
37
+ export declare const deniedBy: <P extends string>(permission: P) => DeniedBySchema<P>;
38
+ export {};
39
+ //# sourceMappingURL=Decision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Decision.d.ts","sourceRoot":"","sources":["../src/Decision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC;IACV;;;;OAIG;IACH,IAAI,EAAE;QACJ,QAAQ,CAAC,EAAE,EAAE,aAAa,GAAG,QAAQ,GAAG,aAAa,CAAC;QACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;KAC/B,CAAC;CACH,CAAC,CAAC;AAEH,MAAM,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAElE,MAAM,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAIhE,eAAO,MAAM,KAAK,QAAO,KAA6B,CAAC;AAEvD,eAAO,MAAM,IAAI,YAAa,MAAM,KAAG,IACgD,CAAC;AAExF,eAAO,MAAM,UAAU,QAAO,IAAgD,CAAC;AAE/E,eAAO,MAAM,KAAK,gBAAiB,MAAM,KAAG,IACW,CAAC;;;;oBAEiB,OAAO,MAAM;;AAAtF,qBAAa,MAAO,SAAQ,WAI3B;CAAG;AAEJ,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,IAAI,MAAM,GAAG;IAAE,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;CAAE,CAAC;AAE7E,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,MAAM,CAAC,CAAC;AAEzF,yDAAyD;AACzD,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,MAAM,cAAc,CAAC,KAAG,cAAc,CAAC,CAAC,CACsB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { Data, Schema } from "effect";
2
+ const constructors = Data.taggedEnum();
3
+ export const allow = () => constructors.Allow();
4
+ export const deny = (reason) => constructors.Deny(reason === undefined ? { by: "policy" } : { by: "policy", reason });
5
+ export const restricted = () => constructors.Deny({ by: "restriction" });
6
+ export const unmet = (requirement) => constructors.Deny({ by: "requirement", requirement });
7
+ export class Denied extends Schema.TaggedError()("AccessDenied", { permission: Schema.String }, { httpApiStatus: 403 }) {
8
+ }
9
+ /** The refusals of one permission, from its id alone. */
10
+ export const deniedBy = (permission) => Denied.pipe(Schema.refine((denied) => denied.permission === permission));
11
+ //# sourceMappingURL=Decision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Decision.js","sourceRoot":"","sources":["../src/Decision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAoBtC,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,EAAY,CAAC;AAEjD,MAAM,CAAC,MAAM,KAAK,GAAG,GAAU,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;AAEvD,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,MAAe,EAAQ,EAAE,CAC5C,YAAY,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AAExF,MAAM,CAAC,MAAM,UAAU,GAAG,GAAS,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;AAE/E,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,WAAmB,EAAQ,EAAE,CACjD,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC,CAAC;AAExD,MAAM,OAAO,MAAO,SAAQ,MAAM,CAAC,WAAW,EAA6C,CACzF,cAAc,EACd,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,EAC7B,EAAE,aAAa,EAAE,GAAG,EAAE,CACvB;CAAG;AAMJ,yDAAyD;AACzD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAmB,UAAa,EAAqB,EAAE,CAC7E,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAyB,EAAE,CAAC,MAAM,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC"}
@@ -0,0 +1,82 @@
1
+ import type { Scope } from "effect";
2
+ import { Context, Effect, Layer, Schema } from "effect";
3
+ import * as Decision from "./Decision.ts";
4
+ export type Codec = Schema.Codec<unknown, unknown>;
5
+ export interface Config {
6
+ readonly requires?: unknown;
7
+ }
8
+ export interface Definition {
9
+ readonly id?: string;
10
+ readonly target?: Codec;
11
+ readonly error?: Codec;
12
+ }
13
+ export type IdOf<Key extends string, D extends Definition> = D extends {
14
+ readonly id: infer I extends string;
15
+ } ? I : Key;
16
+ export type Requires<C extends Config> = C extends {
17
+ readonly requires: infer R;
18
+ } ? R : never;
19
+ export type TargetOf<D extends Definition> = D extends {
20
+ readonly target: infer S extends Codec;
21
+ } ? S["Type"] : never;
22
+ export type TargetArgs<D extends Definition> = D extends {
23
+ readonly target: Codec;
24
+ } ? [target: TargetOf<D>] : [];
25
+ export type PolicyError<D extends Definition> = D extends {
26
+ readonly error: infer S extends Codec;
27
+ } ? S["Type"] : never;
28
+ /** The errors a check can fail with: refusals of required permissions become decisions. */
29
+ export type CheckError<D extends Definition> = Exclude<PolicyError<D>, Refusal<D>>;
30
+ type Refusal<D extends Definition> = Extract<PolicyError<D>, Decision.Denied>;
31
+ export type Policy<D extends Definition, R> = (...target: TargetArgs<D>) => Decision.Decision | Effect.Effect<Decision.Decision, PolicyError<D>, R>;
32
+ export interface Api<I extends string, D extends Definition, R> {
33
+ readonly check: (...target: TargetArgs<D>) => Effect.Effect<Decision.Decision, CheckError<D>, R>;
34
+ readonly require: (...target: TargetArgs<D>) => Effect.Effect<void, Decision.DeniedBy<I> | PolicyError<D>, R>;
35
+ }
36
+ export interface Statics<Self, I extends string, D extends Definition, R> {
37
+ readonly id: I;
38
+ readonly definition: D;
39
+ readonly Denied: Decision.DeniedBySchema<I>;
40
+ readonly check: (...target: TargetArgs<D>) => Effect.Effect<Decision.Decision, CheckError<D>, Self | R>;
41
+ readonly require: (...target: TargetArgs<D>) => Effect.Effect<void, Decision.DeniedBy<I> | PolicyError<D>, Self | R>;
42
+ readonly toLayer: <E = never, RL = never>(policy: Policy<D, R> | Effect.Effect<Policy<D, R>, E, RL>) => Layer.Layer<Self, E, Exclude<RL, Scope.Scope>>;
43
+ }
44
+ export type Permission<Self, Key extends string, D extends Definition, R> = Context.ServiceClass<Self, Key, Api<IdOf<Key, D>, D, R>> & Statics<Self, IdOf<Key, D>, D, R>;
45
+ export interface Any {
46
+ readonly id: string;
47
+ /** The service key: unique per permission, as Effect requires of service keys. */
48
+ readonly key: string;
49
+ }
50
+ export interface Tag<Self, I extends string, D extends Definition, R> extends Context.Service<Self, Api<I, D, R>> {
51
+ readonly id: I;
52
+ readonly definition: D;
53
+ }
54
+ declare const Restriction_base: Context.Reference<ReadonlySet<string> | undefined>;
55
+ /**
56
+ * The keys of the permissions the caller may use, or undefined for all.
57
+ * Keys rather than ids, so permissions sharing an id stay apart.
58
+ */
59
+ export declare class Restriction extends Restriction_base {
60
+ }
61
+ export interface Decided<D extends Definition> {
62
+ readonly permission: string;
63
+ readonly via: "check" | "require";
64
+ readonly target: TargetArgs<D>;
65
+ readonly decision: Decision.Decision;
66
+ }
67
+ /**
68
+ * Runs `f` after every decision of a permission service built by `toLayer`,
69
+ * inside the check's span and before `require` fails.
70
+ */
71
+ export declare const tap: <D extends Definition>(f: (decided: Decided<D>) => Effect.Effect<void>) => <I extends string, R>(service: Api<I, D, R>) => Api<I, D, R>;
72
+ /** Whether the caller's restriction excludes a permission, so it must refuse. */
73
+ export declare const isRestricted: (permission: Any) => Effect.Effect<boolean, never, never>;
74
+ export declare const restrictTo: (permissions: Iterable<Any>) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
75
+ export interface PermissionSet<P extends Any> {
76
+ readonly schema: Schema.Codec<ReadonlyArray<P["id"]>, ReadonlyArray<string>>;
77
+ readonly restrictTo: (ids: Iterable<P["id"]>) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
78
+ }
79
+ export declare const set: <const Ps extends readonly [Any, ...ReadonlyArray<Any>]>(permissions: Ps) => PermissionSet<Ps[number]>;
80
+ export declare const Service: <Self, C extends Config = {}>() => <const Key extends string, const D extends Definition>(key: Key, definition: D) => Permission<Self, Key, D, Requires<C>>;
81
+ export {};
82
+ //# sourceMappingURL=Permission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Permission.d.ts","sourceRoot":"","sources":["../src/Permission.ts"],"names":[],"mappings":"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"}