@aooth/arbac 0.1.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.
@@ -0,0 +1,282 @@
1
+ import { TArbacRole, TArbacRule } from "@aooth/arbac-core";
2
+ export * from "@aooth/arbac-core";
3
+
4
+ //#region src/define-role.d.ts
5
+ /**
6
+ * A privilege function produces an array of rules when called.
7
+ * Created by `definePrivilege()` or manually.
8
+ */
9
+ type TPrivilegeFunction<TUserAttrs, TScope> = () => TArbacRule<TUserAttrs, TScope>[];
10
+ /**
11
+ * Fluent builder produced by {@link defineRole}.
12
+ *
13
+ * All methods return `this` so they can be chained. Rules are emitted in the order
14
+ * the corresponding `.allow()`, `.deny()`, and `.use()` calls were made — this matters
15
+ * for engines that respect rule order (deny-first / first-match-wins variants).
16
+ */
17
+ interface RoleBuilder<TUserAttrs, TScope> {
18
+ /**
19
+ * Set the role id (required). Calling `.id()` more than once overwrites the
20
+ * previous value — the last call wins.
21
+ */
22
+ id(id: string): RoleBuilder<TUserAttrs, TScope>;
23
+ /** Set the human-readable role name. Optional. */
24
+ name(name: string): RoleBuilder<TUserAttrs, TScope>;
25
+ /** Set the role description. Optional. */
26
+ describe(description: string): RoleBuilder<TUserAttrs, TScope>;
27
+ /**
28
+ * Append an allow rule for a (resource, action) pair, with an optional scope
29
+ * function that derives a scope object from user attrs.
30
+ */
31
+ allow(resource: string, action: string, scope?: (attrs: TUserAttrs, userId: string) => TScope): RoleBuilder<TUserAttrs, TScope>;
32
+ /** Append a deny rule (`effect: 'deny'`) for a (resource, action) pair. */
33
+ deny(resource: string, action: string): RoleBuilder<TUserAttrs, TScope>;
34
+ /**
35
+ * Splice in rules from one or more privilege functions (see
36
+ * {@link definePrivilege}). Privileges are expanded inline in call order.
37
+ *
38
+ * Two overloads:
39
+ * 1. **Strict** — all privileges share the role's `TScope`. Preserves
40
+ * bidirectional inference of `TUserAttrs` and `TScope` into bare-generic
41
+ * privilege calls like `allowTableWrite("tasks", { scope: (attrs) => … })`.
42
+ * 2. **Variadic tuple** — privileges may carry independent per-arg scope
43
+ * shapes (e.g. `ArbacDbScope<Task>` next to `ArbacDbScope<Comment>`).
44
+ * Used when typed-table privileges don't structurally match the role
45
+ * pin. Runtime storage type-erases through a cast.
46
+ */
47
+ use(...privileges: TPrivilegeFunction<TUserAttrs, TScope>[]): RoleBuilder<TUserAttrs, TScope>;
48
+ use<TScopes extends readonly unknown[]>(...privileges: { [K in keyof TScopes]: TPrivilegeFunction<TUserAttrs, TScopes[K]> }): RoleBuilder<TUserAttrs, TScope>;
49
+ /**
50
+ * Finalize and return a plain {@link TArbacRole} object suitable for
51
+ * `Arbac.registerRole`. Throws if `.id()` was never called.
52
+ */
53
+ build(): TArbacRole<TUserAttrs, TScope>;
54
+ }
55
+ /**
56
+ * Start building a new role.
57
+ *
58
+ * @example
59
+ * const editor = defineRole<MyAttrs, MyScope>()
60
+ * .id("editor")
61
+ * .name("Editor")
62
+ * .use(allowTableWrite("articles"))
63
+ * .deny("articles", "publish")
64
+ * .build();
65
+ */
66
+ declare function defineRole<TUserAttrs extends object = object, TScope extends object = object>(): RoleBuilder<TUserAttrs, TScope>;
67
+ //#endregion
68
+ //#region src/define-privilege.d.ts
69
+ /**
70
+ * Define a reusable privilege factory.
71
+ *
72
+ * Double-call pattern: first call pins TUserAttrs/TScope generics,
73
+ * second call infers TArgs from the factory function.
74
+ *
75
+ * @example
76
+ * const canManageUsers = definePrivilege<MyAttrs, MyScope>()(
77
+ * (scope: (attrs: MyAttrs, userId: string) => MyScope) => [
78
+ * { resource: "users", action: "read", scope },
79
+ * { resource: "users", action: "update", scope },
80
+ * ]
81
+ * );
82
+ *
83
+ * defineRole<MyAttrs, MyScope>()
84
+ * .id("manager")
85
+ * .use(canManageUsers((attrs) => ({ dept: attrs.dept })))
86
+ * .build();
87
+ */
88
+ declare function definePrivilege<TUserAttrs extends object = object, TScope extends object = object>(): <TArgs extends unknown[]>(factory: (...args: TArgs) => TArbacRule<TUserAttrs, TScope>[]) => ((...args: TArgs) => TPrivilegeFunction<TUserAttrs, TScope>);
89
+ //#endregion
90
+ //#region src/db-privileges.d.ts
91
+ type ScopeFn<TUserAttrs, TScope> = (attrs: TUserAttrs, userId: string) => TScope;
92
+ interface ScopeOpts<TUserAttrs, TScope> {
93
+ scope?: ScopeFn<TUserAttrs, TScope>;
94
+ }
95
+ /** Read-side actions on an `AsDbController` table: `query`, `pages`, `getOne`, `getOneComposite`, `meta`, `metaForm`. */
96
+ declare function allowTableRead<TUserAttrs extends object = object, TScope extends object = object>(resource: string, opts?: ScopeOpts<TUserAttrs, TScope>): TPrivilegeFunction<TUserAttrs, TScope>;
97
+ /** All actions on an `AsDbController` table: read + `insert`, `update`, `replace`, `remove`, `removeComposite`. */
98
+ declare function allowTableWrite<TUserAttrs extends object = object, TScope extends object = object>(resource: string, opts?: ScopeOpts<TUserAttrs, TScope>): TPrivilegeFunction<TUserAttrs, TScope>;
99
+ /** One or more declarative actions on the same table, sharing a scope. */
100
+ declare function allowTableAction<TUserAttrs extends object = object, TScope extends object = object>(resource: string, name: string | string[], opts?: ScopeOpts<TUserAttrs, TScope>): TPrivilegeFunction<TUserAttrs, TScope>;
101
+ //#endregion
102
+ //#region src/scope/types.d.ts
103
+ /**
104
+ * Mongo-style field projection: `{ field: 0 | 1 }`.
105
+ * - All 1s = inclusion mode (only listed fields allowed)
106
+ * - All 0s = exclusion mode (listed fields denied, rest allowed)
107
+ * - Mixing 0 and 1 in a single projection is invalid.
108
+ *
109
+ * Structurally compatible with @uniqu/core SelectExpr in object form.
110
+ */
111
+ type TProjection = Record<string, 0 | 1>;
112
+ /**
113
+ * A filter expression compatible with @uniqu/core FilterExpr.
114
+ * Comparison leaf: `{ field: value }` or `{ field: { $op: value } }`
115
+ * Logical branch: `{ $or: [...] }` | `{ $and: [...] }` | `{ $not: ... }`
116
+ */
117
+ type TScopeFilter = Record<string, unknown>;
118
+ /**
119
+ * Per-control policy in a resource scope.
120
+ *
121
+ * Used inside `ArbacDbScope.controls` (in `@aooth/arbac-moost`) to gate
122
+ * Uniquery URL controls (`$with`, `$groupBy`, `$having`, …) on a per-role basis.
123
+ *
124
+ * Semantics:
125
+ * - **silence** (key absent / `undefined`) — no opinion → allow.
126
+ * - `true` — explicit allow (same effect as silence; useful for documentation).
127
+ * - `false` — explicit deny; using the control fails 403.
128
+ * - `readonly string[]` — whitelist mode; only the listed values are allowed.
129
+ * E.g., `{ $with: ['comments'] }` permits `?$with=comments` but rejects
130
+ * `?$with=tasks` with 403.
131
+ *
132
+ * Whitelist arrays are supported only for controls whose value is a list of
133
+ * named entities (notably `$with` for relation names and `$groupBy` for
134
+ * column names). For other controls (e.g. `$having`) only boolean gates are
135
+ * supported in v1; supplying a whitelist throws at scope-evaluation time.
136
+ */
137
+ type ControlGate = boolean | readonly string[];
138
+ //#endregion
139
+ //#region src/scope/projection.d.ts
140
+ type TProjectionMode = "include" | "exclude" | "empty";
141
+ /**
142
+ * Determine whether a projection is in inclusion mode (all 1s),
143
+ * exclusion mode (all 0s), or empty (`{}`, no restriction).
144
+ *
145
+ * @throws when a single projection mixes 0 and 1 values
146
+ */
147
+ declare function getProjectionMode(proj: TProjection): TProjectionMode;
148
+ /**
149
+ * Check whether a dot-path field is allowed by a projection.
150
+ *
151
+ * Inclusion mode: a field is allowed if it, any of its parents, or any of its children
152
+ * is explicitly listed.
153
+ * Exclusion mode: a field is allowed unless it or any of its parents is excluded.
154
+ * Empty projection: every field is allowed.
155
+ */
156
+ declare function isFieldAllowed(field: string, projection: TProjection): boolean;
157
+ /**
158
+ * Combines projections from multiple RBAC role grants under additive semantics:
159
+ * more roles = broader access. Each projection represents a set of allowed fields:
160
+ * include-mode `{a:1}` allows `{a}`; exclude-mode `{a:0}` allows `universe \ {a}`;
161
+ * empty `{}` allows the universe.
162
+ *
163
+ * A field is effectively allowed if any input grants it. Equivalently, a field
164
+ * stays excluded only if (a) no include-mode role grants it explicitly, AND (b)
165
+ * every exclude-mode role excludes it.
166
+ *
167
+ * Output mode:
168
+ * - All-include input → include-mode result (union of include keys)
169
+ * - At least one exclude-mode → exclude-mode result (or `{}` if no fields excluded)
170
+ * - Empty input or any universal grant `{}` → `{}` (universe)
171
+ *
172
+ * Within a single projection, mixing 1 and 0 keys is an error (call sites should
173
+ * normalize first). Across projections, mixing modes is supported and resolves
174
+ * via the additive rule above.
175
+ *
176
+ * @throws when a single projection mixes 1 and 0 keys, or contains an invalid value
177
+ */
178
+ declare function unionProjections(...projections: TProjection[]): TProjection;
179
+ /**
180
+ * Restrict a desired projection to only fields allowed by an access-control projection.
181
+ *
182
+ * The result is the intersection of the two projections: only fields that pass both
183
+ * `desired` and `accessControl` survive. Either side may be empty (unrestricted), in
184
+ * which case the other side is returned. Mixed include/exclude modes are normalized
185
+ * to a single result projection.
186
+ *
187
+ * @param desired - the projection the caller asked for
188
+ * @param accessControl - the projection allowed by RBAC
189
+ */
190
+ declare function restrictProjection(desired: TProjection, accessControl: TProjection): TProjection;
191
+ //#endregion
192
+ //#region src/scope/filter.d.ts
193
+ /**
194
+ * Merge multiple scope filters into a single filter using `$or` semantics.
195
+ *
196
+ * In RBAC, if multiple roles grant access with different filters,
197
+ * the user can see records matching ANY of them — hence `$or`.
198
+ *
199
+ * Behaviour:
200
+ * - Empty input → `undefined` (no filter)
201
+ * - Any empty filter → `undefined` (one role grants unrestricted access)
202
+ * - Single filter → returned as-is
203
+ * - All filters single-keyed on the same primitive field → `{ field: { $in: [...] } }`
204
+ * - Otherwise → `{ $or: [...] }`
205
+ *
206
+ * @returns the merged filter, or `undefined` for unrestricted access
207
+ */
208
+ declare function mergeScopeFilters(scopes: TScopeFilter[]): TScopeFilter | undefined;
209
+ //#endregion
210
+ //#region src/scope/controls.d.ts
211
+ /**
212
+ * Union per-role `controls` policies. Additive RBAC semantics: more roles =
213
+ * broader access, mirroring how `mergeScopeFilters` and `unionProjections`
214
+ * combine other parts of an `ArbacDbScope`.
215
+ *
216
+ * Resolution per control key:
217
+ * 1. Collect each scope's value for the key (including `undefined` for
218
+ * silent scopes — i.e., scopes that have a `controls` map but no entry
219
+ * for this key).
220
+ * 2. If any value is `undefined` or `true` → return `true` (full allow).
221
+ * Silence and explicit allow both mean "this role has no objection".
222
+ * 3. Else (every scope is `false` or `string[]`):
223
+ * - If all are `false` → return `false` (full deny).
224
+ * - Else collect every `string[]` into a single sorted, de-duplicated
225
+ * array; return that array. (Roles that say `false` don't contribute
226
+ * positively; roles with whitelists do.)
227
+ *
228
+ * Scopes that omit the `controls` map entirely are treated as silent on
229
+ * EVERY key — i.e., they grant unrestricted control usage. As a result, if
230
+ * even a single scope in the input lacks a `controls` map, the result is
231
+ * always `{}` (no restrictions).
232
+ *
233
+ * Whitelist support is restricted to {@link WHITELISTABLE_CONTROLS}. Passing
234
+ * a whitelist for any other control throws — the gate cannot be enforced
235
+ * meaningfully in v1, so the misuse is rejected loudly at scope-merge time.
236
+ *
237
+ * @returns merged policy keyed by control name. Keys absent from the result
238
+ * mean "no opinion → fully allowed". The caller should treat a
239
+ * missing key the same as `true`.
240
+ *
241
+ * @throws when a non-whitelistable control is given a `readonly string[]` gate.
242
+ */
243
+ declare function unionControlsPolicy(scopes: ReadonlyArray<{
244
+ controls?: Record<string, ControlGate>;
245
+ }>): Record<string, ControlGate>;
246
+ //#endregion
247
+ //#region src/codegen/extract.d.ts
248
+ interface TResourceActionMap {
249
+ /** Map of resource string → Set of action strings */
250
+ resources: Map<string, Set<string>>;
251
+ /** All unique resource strings */
252
+ allResources: Set<string>;
253
+ /** All unique action strings */
254
+ allActions: Set<string>;
255
+ }
256
+ /**
257
+ * Extract all resource and action strings from role definitions.
258
+ *
259
+ * @param roles - Array of built role definitions
260
+ * @param options.includeWildcards - If false (default), skip entries containing `*` or `**`
261
+ */
262
+ declare function extractResourceActions(roles: TArbacRole<unknown, unknown>[], options?: {
263
+ includeWildcards?: boolean;
264
+ }): TResourceActionMap;
265
+ //#endregion
266
+ //#region src/codegen/generate.d.ts
267
+ interface TCodegenOptions {
268
+ /** Name for the resource union type. Default: "Resource" */
269
+ resourceTypeName?: string;
270
+ /** Name for the action union type. Default: "Action" */
271
+ actionTypeName?: string;
272
+ /** Whether to generate a resource-to-actions map type. Default: true */
273
+ resourceActionMap?: boolean;
274
+ /** Header comment. Default: "// Auto-generated by @aooth/arbac codegen" */
275
+ header?: string;
276
+ }
277
+ /**
278
+ * Generate TypeScript source defining union types for resources and actions.
279
+ */
280
+ declare function generateResourceTypes(map: TResourceActionMap, options?: TCodegenOptions): string;
281
+ //#endregion
282
+ export { type ControlGate, type RoleBuilder, type TCodegenOptions, type TPrivilegeFunction, type TProjection, type TProjectionMode, type TResourceActionMap, type TScopeFilter, allowTableAction, allowTableRead, allowTableWrite, definePrivilege, defineRole, extractResourceActions, generateResourceTypes, getProjectionMode, isFieldAllowed, mergeScopeFilters, restrictProjection, unionControlsPolicy, unionProjections };
@@ -0,0 +1,282 @@
1
+ import { TArbacRole, TArbacRule } from "@aooth/arbac-core";
2
+ export * from "@aooth/arbac-core";
3
+
4
+ //#region src/define-role.d.ts
5
+ /**
6
+ * A privilege function produces an array of rules when called.
7
+ * Created by `definePrivilege()` or manually.
8
+ */
9
+ type TPrivilegeFunction<TUserAttrs, TScope> = () => TArbacRule<TUserAttrs, TScope>[];
10
+ /**
11
+ * Fluent builder produced by {@link defineRole}.
12
+ *
13
+ * All methods return `this` so they can be chained. Rules are emitted in the order
14
+ * the corresponding `.allow()`, `.deny()`, and `.use()` calls were made — this matters
15
+ * for engines that respect rule order (deny-first / first-match-wins variants).
16
+ */
17
+ interface RoleBuilder<TUserAttrs, TScope> {
18
+ /**
19
+ * Set the role id (required). Calling `.id()` more than once overwrites the
20
+ * previous value — the last call wins.
21
+ */
22
+ id(id: string): RoleBuilder<TUserAttrs, TScope>;
23
+ /** Set the human-readable role name. Optional. */
24
+ name(name: string): RoleBuilder<TUserAttrs, TScope>;
25
+ /** Set the role description. Optional. */
26
+ describe(description: string): RoleBuilder<TUserAttrs, TScope>;
27
+ /**
28
+ * Append an allow rule for a (resource, action) pair, with an optional scope
29
+ * function that derives a scope object from user attrs.
30
+ */
31
+ allow(resource: string, action: string, scope?: (attrs: TUserAttrs, userId: string) => TScope): RoleBuilder<TUserAttrs, TScope>;
32
+ /** Append a deny rule (`effect: 'deny'`) for a (resource, action) pair. */
33
+ deny(resource: string, action: string): RoleBuilder<TUserAttrs, TScope>;
34
+ /**
35
+ * Splice in rules from one or more privilege functions (see
36
+ * {@link definePrivilege}). Privileges are expanded inline in call order.
37
+ *
38
+ * Two overloads:
39
+ * 1. **Strict** — all privileges share the role's `TScope`. Preserves
40
+ * bidirectional inference of `TUserAttrs` and `TScope` into bare-generic
41
+ * privilege calls like `allowTableWrite("tasks", { scope: (attrs) => … })`.
42
+ * 2. **Variadic tuple** — privileges may carry independent per-arg scope
43
+ * shapes (e.g. `ArbacDbScope<Task>` next to `ArbacDbScope<Comment>`).
44
+ * Used when typed-table privileges don't structurally match the role
45
+ * pin. Runtime storage type-erases through a cast.
46
+ */
47
+ use(...privileges: TPrivilegeFunction<TUserAttrs, TScope>[]): RoleBuilder<TUserAttrs, TScope>;
48
+ use<TScopes extends readonly unknown[]>(...privileges: { [K in keyof TScopes]: TPrivilegeFunction<TUserAttrs, TScopes[K]> }): RoleBuilder<TUserAttrs, TScope>;
49
+ /**
50
+ * Finalize and return a plain {@link TArbacRole} object suitable for
51
+ * `Arbac.registerRole`. Throws if `.id()` was never called.
52
+ */
53
+ build(): TArbacRole<TUserAttrs, TScope>;
54
+ }
55
+ /**
56
+ * Start building a new role.
57
+ *
58
+ * @example
59
+ * const editor = defineRole<MyAttrs, MyScope>()
60
+ * .id("editor")
61
+ * .name("Editor")
62
+ * .use(allowTableWrite("articles"))
63
+ * .deny("articles", "publish")
64
+ * .build();
65
+ */
66
+ declare function defineRole<TUserAttrs extends object = object, TScope extends object = object>(): RoleBuilder<TUserAttrs, TScope>;
67
+ //#endregion
68
+ //#region src/define-privilege.d.ts
69
+ /**
70
+ * Define a reusable privilege factory.
71
+ *
72
+ * Double-call pattern: first call pins TUserAttrs/TScope generics,
73
+ * second call infers TArgs from the factory function.
74
+ *
75
+ * @example
76
+ * const canManageUsers = definePrivilege<MyAttrs, MyScope>()(
77
+ * (scope: (attrs: MyAttrs, userId: string) => MyScope) => [
78
+ * { resource: "users", action: "read", scope },
79
+ * { resource: "users", action: "update", scope },
80
+ * ]
81
+ * );
82
+ *
83
+ * defineRole<MyAttrs, MyScope>()
84
+ * .id("manager")
85
+ * .use(canManageUsers((attrs) => ({ dept: attrs.dept })))
86
+ * .build();
87
+ */
88
+ declare function definePrivilege<TUserAttrs extends object = object, TScope extends object = object>(): <TArgs extends unknown[]>(factory: (...args: TArgs) => TArbacRule<TUserAttrs, TScope>[]) => ((...args: TArgs) => TPrivilegeFunction<TUserAttrs, TScope>);
89
+ //#endregion
90
+ //#region src/db-privileges.d.ts
91
+ type ScopeFn<TUserAttrs, TScope> = (attrs: TUserAttrs, userId: string) => TScope;
92
+ interface ScopeOpts<TUserAttrs, TScope> {
93
+ scope?: ScopeFn<TUserAttrs, TScope>;
94
+ }
95
+ /** Read-side actions on an `AsDbController` table: `query`, `pages`, `getOne`, `getOneComposite`, `meta`, `metaForm`. */
96
+ declare function allowTableRead<TUserAttrs extends object = object, TScope extends object = object>(resource: string, opts?: ScopeOpts<TUserAttrs, TScope>): TPrivilegeFunction<TUserAttrs, TScope>;
97
+ /** All actions on an `AsDbController` table: read + `insert`, `update`, `replace`, `remove`, `removeComposite`. */
98
+ declare function allowTableWrite<TUserAttrs extends object = object, TScope extends object = object>(resource: string, opts?: ScopeOpts<TUserAttrs, TScope>): TPrivilegeFunction<TUserAttrs, TScope>;
99
+ /** One or more declarative actions on the same table, sharing a scope. */
100
+ declare function allowTableAction<TUserAttrs extends object = object, TScope extends object = object>(resource: string, name: string | string[], opts?: ScopeOpts<TUserAttrs, TScope>): TPrivilegeFunction<TUserAttrs, TScope>;
101
+ //#endregion
102
+ //#region src/scope/types.d.ts
103
+ /**
104
+ * Mongo-style field projection: `{ field: 0 | 1 }`.
105
+ * - All 1s = inclusion mode (only listed fields allowed)
106
+ * - All 0s = exclusion mode (listed fields denied, rest allowed)
107
+ * - Mixing 0 and 1 in a single projection is invalid.
108
+ *
109
+ * Structurally compatible with @uniqu/core SelectExpr in object form.
110
+ */
111
+ type TProjection = Record<string, 0 | 1>;
112
+ /**
113
+ * A filter expression compatible with @uniqu/core FilterExpr.
114
+ * Comparison leaf: `{ field: value }` or `{ field: { $op: value } }`
115
+ * Logical branch: `{ $or: [...] }` | `{ $and: [...] }` | `{ $not: ... }`
116
+ */
117
+ type TScopeFilter = Record<string, unknown>;
118
+ /**
119
+ * Per-control policy in a resource scope.
120
+ *
121
+ * Used inside `ArbacDbScope.controls` (in `@aooth/arbac-moost`) to gate
122
+ * Uniquery URL controls (`$with`, `$groupBy`, `$having`, …) on a per-role basis.
123
+ *
124
+ * Semantics:
125
+ * - **silence** (key absent / `undefined`) — no opinion → allow.
126
+ * - `true` — explicit allow (same effect as silence; useful for documentation).
127
+ * - `false` — explicit deny; using the control fails 403.
128
+ * - `readonly string[]` — whitelist mode; only the listed values are allowed.
129
+ * E.g., `{ $with: ['comments'] }` permits `?$with=comments` but rejects
130
+ * `?$with=tasks` with 403.
131
+ *
132
+ * Whitelist arrays are supported only for controls whose value is a list of
133
+ * named entities (notably `$with` for relation names and `$groupBy` for
134
+ * column names). For other controls (e.g. `$having`) only boolean gates are
135
+ * supported in v1; supplying a whitelist throws at scope-evaluation time.
136
+ */
137
+ type ControlGate = boolean | readonly string[];
138
+ //#endregion
139
+ //#region src/scope/projection.d.ts
140
+ type TProjectionMode = "include" | "exclude" | "empty";
141
+ /**
142
+ * Determine whether a projection is in inclusion mode (all 1s),
143
+ * exclusion mode (all 0s), or empty (`{}`, no restriction).
144
+ *
145
+ * @throws when a single projection mixes 0 and 1 values
146
+ */
147
+ declare function getProjectionMode(proj: TProjection): TProjectionMode;
148
+ /**
149
+ * Check whether a dot-path field is allowed by a projection.
150
+ *
151
+ * Inclusion mode: a field is allowed if it, any of its parents, or any of its children
152
+ * is explicitly listed.
153
+ * Exclusion mode: a field is allowed unless it or any of its parents is excluded.
154
+ * Empty projection: every field is allowed.
155
+ */
156
+ declare function isFieldAllowed(field: string, projection: TProjection): boolean;
157
+ /**
158
+ * Combines projections from multiple RBAC role grants under additive semantics:
159
+ * more roles = broader access. Each projection represents a set of allowed fields:
160
+ * include-mode `{a:1}` allows `{a}`; exclude-mode `{a:0}` allows `universe \ {a}`;
161
+ * empty `{}` allows the universe.
162
+ *
163
+ * A field is effectively allowed if any input grants it. Equivalently, a field
164
+ * stays excluded only if (a) no include-mode role grants it explicitly, AND (b)
165
+ * every exclude-mode role excludes it.
166
+ *
167
+ * Output mode:
168
+ * - All-include input → include-mode result (union of include keys)
169
+ * - At least one exclude-mode → exclude-mode result (or `{}` if no fields excluded)
170
+ * - Empty input or any universal grant `{}` → `{}` (universe)
171
+ *
172
+ * Within a single projection, mixing 1 and 0 keys is an error (call sites should
173
+ * normalize first). Across projections, mixing modes is supported and resolves
174
+ * via the additive rule above.
175
+ *
176
+ * @throws when a single projection mixes 1 and 0 keys, or contains an invalid value
177
+ */
178
+ declare function unionProjections(...projections: TProjection[]): TProjection;
179
+ /**
180
+ * Restrict a desired projection to only fields allowed by an access-control projection.
181
+ *
182
+ * The result is the intersection of the two projections: only fields that pass both
183
+ * `desired` and `accessControl` survive. Either side may be empty (unrestricted), in
184
+ * which case the other side is returned. Mixed include/exclude modes are normalized
185
+ * to a single result projection.
186
+ *
187
+ * @param desired - the projection the caller asked for
188
+ * @param accessControl - the projection allowed by RBAC
189
+ */
190
+ declare function restrictProjection(desired: TProjection, accessControl: TProjection): TProjection;
191
+ //#endregion
192
+ //#region src/scope/filter.d.ts
193
+ /**
194
+ * Merge multiple scope filters into a single filter using `$or` semantics.
195
+ *
196
+ * In RBAC, if multiple roles grant access with different filters,
197
+ * the user can see records matching ANY of them — hence `$or`.
198
+ *
199
+ * Behaviour:
200
+ * - Empty input → `undefined` (no filter)
201
+ * - Any empty filter → `undefined` (one role grants unrestricted access)
202
+ * - Single filter → returned as-is
203
+ * - All filters single-keyed on the same primitive field → `{ field: { $in: [...] } }`
204
+ * - Otherwise → `{ $or: [...] }`
205
+ *
206
+ * @returns the merged filter, or `undefined` for unrestricted access
207
+ */
208
+ declare function mergeScopeFilters(scopes: TScopeFilter[]): TScopeFilter | undefined;
209
+ //#endregion
210
+ //#region src/scope/controls.d.ts
211
+ /**
212
+ * Union per-role `controls` policies. Additive RBAC semantics: more roles =
213
+ * broader access, mirroring how `mergeScopeFilters` and `unionProjections`
214
+ * combine other parts of an `ArbacDbScope`.
215
+ *
216
+ * Resolution per control key:
217
+ * 1. Collect each scope's value for the key (including `undefined` for
218
+ * silent scopes — i.e., scopes that have a `controls` map but no entry
219
+ * for this key).
220
+ * 2. If any value is `undefined` or `true` → return `true` (full allow).
221
+ * Silence and explicit allow both mean "this role has no objection".
222
+ * 3. Else (every scope is `false` or `string[]`):
223
+ * - If all are `false` → return `false` (full deny).
224
+ * - Else collect every `string[]` into a single sorted, de-duplicated
225
+ * array; return that array. (Roles that say `false` don't contribute
226
+ * positively; roles with whitelists do.)
227
+ *
228
+ * Scopes that omit the `controls` map entirely are treated as silent on
229
+ * EVERY key — i.e., they grant unrestricted control usage. As a result, if
230
+ * even a single scope in the input lacks a `controls` map, the result is
231
+ * always `{}` (no restrictions).
232
+ *
233
+ * Whitelist support is restricted to {@link WHITELISTABLE_CONTROLS}. Passing
234
+ * a whitelist for any other control throws — the gate cannot be enforced
235
+ * meaningfully in v1, so the misuse is rejected loudly at scope-merge time.
236
+ *
237
+ * @returns merged policy keyed by control name. Keys absent from the result
238
+ * mean "no opinion → fully allowed". The caller should treat a
239
+ * missing key the same as `true`.
240
+ *
241
+ * @throws when a non-whitelistable control is given a `readonly string[]` gate.
242
+ */
243
+ declare function unionControlsPolicy(scopes: ReadonlyArray<{
244
+ controls?: Record<string, ControlGate>;
245
+ }>): Record<string, ControlGate>;
246
+ //#endregion
247
+ //#region src/codegen/extract.d.ts
248
+ interface TResourceActionMap {
249
+ /** Map of resource string → Set of action strings */
250
+ resources: Map<string, Set<string>>;
251
+ /** All unique resource strings */
252
+ allResources: Set<string>;
253
+ /** All unique action strings */
254
+ allActions: Set<string>;
255
+ }
256
+ /**
257
+ * Extract all resource and action strings from role definitions.
258
+ *
259
+ * @param roles - Array of built role definitions
260
+ * @param options.includeWildcards - If false (default), skip entries containing `*` or `**`
261
+ */
262
+ declare function extractResourceActions(roles: TArbacRole<unknown, unknown>[], options?: {
263
+ includeWildcards?: boolean;
264
+ }): TResourceActionMap;
265
+ //#endregion
266
+ //#region src/codegen/generate.d.ts
267
+ interface TCodegenOptions {
268
+ /** Name for the resource union type. Default: "Resource" */
269
+ resourceTypeName?: string;
270
+ /** Name for the action union type. Default: "Action" */
271
+ actionTypeName?: string;
272
+ /** Whether to generate a resource-to-actions map type. Default: true */
273
+ resourceActionMap?: boolean;
274
+ /** Header comment. Default: "// Auto-generated by @aooth/arbac codegen" */
275
+ header?: string;
276
+ }
277
+ /**
278
+ * Generate TypeScript source defining union types for resources and actions.
279
+ */
280
+ declare function generateResourceTypes(map: TResourceActionMap, options?: TCodegenOptions): string;
281
+ //#endregion
282
+ export { type ControlGate, type RoleBuilder, type TCodegenOptions, type TPrivilegeFunction, type TProjection, type TProjectionMode, type TResourceActionMap, type TScopeFilter, allowTableAction, allowTableRead, allowTableWrite, definePrivilege, defineRole, extractResourceActions, generateResourceTypes, getProjectionMode, isFieldAllowed, mergeScopeFilters, restrictProjection, unionControlsPolicy, unionProjections };