@cfast/permissions 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,9 @@
1
1
  // src/types.ts
2
2
  var DRIZZLE_NAME_SYMBOL = /* @__PURE__ */ Symbol.for("drizzle:Name");
3
3
  function getTableName(table) {
4
+ if (typeof table === "string") {
5
+ return table;
6
+ }
4
7
  const name = Reflect.get(table, DRIZZLE_NAME_SYMBOL);
5
8
  return typeof name === "string" ? name : "unknown";
6
9
  }
@@ -10,7 +13,7 @@ var CRUD_ACTIONS = ["read", "create", "update", "delete"];
10
13
  var ForbiddenError = class extends Error {
11
14
  /** The action that was denied (e.g., `"delete"`). */
12
15
  action;
13
- /** The Drizzle table the action targeted. */
16
+ /** The Drizzle table or string table name the action targeted. */
14
17
  table;
15
18
  /** The role that lacked the permission, or `undefined` if not specified. */
16
19
  role;
@@ -17,12 +17,46 @@ type DrizzleSQL = {
17
17
  getSQL(): unknown;
18
18
  };
19
19
  /**
20
- * Extracts the table name string from a Drizzle table reference.
20
+ * A schema map: an object mapping table names to Drizzle table references.
21
21
  *
22
- * @param table - A Drizzle table object containing the `drizzle:Name` symbol.
23
- * @returns The table name, or `"unknown"` if the symbol is not present.
22
+ * Typically the result of `import * as schema from "./schema"`. Used as the
23
+ * `TTables` generic parameter for {@link definePermissions}, {@link can}, and
24
+ * the curried {@link grant} callback so that string subjects (e.g.
25
+ * `"projects"`) are constrained to known table names at compile time.
24
26
  */
25
- declare function getTableName(table: DrizzleTable): string;
27
+ type SchemaMap = Record<string, DrizzleTable>;
28
+ /**
29
+ * Extracts the string-literal union of valid table names from a {@link SchemaMap}.
30
+ *
31
+ * Given `typeof schema` (where `schema` exports tables as named bindings),
32
+ * `TableName<typeof schema>` is the union of all exported table-key strings.
33
+ */
34
+ type TableName<TTables extends SchemaMap> = Extract<keyof TTables, string>;
35
+ /**
36
+ * The set of acceptable subject inputs for a grant or `can()` check.
37
+ *
38
+ * - A {@link DrizzleTable} object reference (the original form, always allowed).
39
+ * - A string-literal table name from {@link TableName}, constrained to the
40
+ * provided `TTables` schema map at compile time when one is supplied.
41
+ * - The literal `"all"` for grants that apply to every table.
42
+ */
43
+ type SubjectInput<TTables extends SchemaMap = SchemaMap> = DrizzleTable | TableName<TTables> | "all";
44
+ /**
45
+ * Extracts the table name string from a Drizzle table reference, or returns
46
+ * a string subject as-is.
47
+ *
48
+ * Accepts both:
49
+ * - A Drizzle table object containing the `drizzle:Name` symbol — the symbol
50
+ * is read via `Reflect.get`.
51
+ * - A bare string (e.g., a table-name literal like `"projects"`) — returned
52
+ * unchanged so callers can use the same key for grant matching whether the
53
+ * subject was declared as an object or a string.
54
+ *
55
+ * @param table - A Drizzle table object or a string subject key.
56
+ * @returns The table name, the input string, or `"unknown"` if a symbol-less
57
+ * object was provided.
58
+ */
59
+ declare function getTableName(table: DrizzleTable | string): string;
26
60
  /**
27
61
  * A permission action: one of the four CRUD operations, or `"manage"` for all.
28
62
  *
@@ -63,24 +97,37 @@ declare const CRUD_ACTIONS: readonly CrudAction[];
63
97
  type WhereClause = (columns: Record<string, unknown>, user: unknown) => DrizzleSQL | undefined;
64
98
  /**
65
99
  * A single permission grant: an action on a subject, optionally restricted by a `where` clause.
100
+ *
101
+ * `subject` may be a Drizzle table object, a string table name (e.g.
102
+ * `"projects"`), or the literal `"all"` for grants that apply to every table.
103
+ * String and object subjects are normalized to the same key by
104
+ * {@link getTableName} during matching, so the two forms are interchangeable
105
+ * at runtime.
66
106
  */
67
107
  type Grant = {
68
108
  /** The permitted operation. `"manage"` is shorthand for all four CRUD actions. */
69
109
  action: PermissionAction;
70
- /** The Drizzle table this grant applies to, or `"all"` for every table. */
71
- subject: DrizzleTable | "all";
110
+ /**
111
+ * The subject of the grant: a Drizzle table object, a string table name,
112
+ * or `"all"` for every table.
113
+ */
114
+ subject: DrizzleTable | string;
72
115
  /** Optional row-level filter that restricts which rows this grant covers. */
73
116
  where?: WhereClause;
74
117
  };
75
118
  /**
76
- * Type-safe grant builder function, parameterized by the user type.
119
+ * Type-safe grant builder function, parameterized by the user type and an
120
+ * optional schema map.
77
121
  *
78
122
  * Used when `grants` is provided as a callback in {@link PermissionsConfig}
79
- * so that `where` clauses receive a correctly typed `user` parameter.
123
+ * so that `where` clauses receive a correctly typed `user` parameter and
124
+ * string subjects are constrained to known table names from `TTables`.
80
125
  *
81
126
  * @typeParam TUser - The user type passed to `where` clause callbacks.
127
+ * @typeParam TTables - Optional schema map (e.g. `typeof schema`) used to
128
+ * constrain string subjects to known table-name literals.
82
129
  */
83
- type GrantFn<TUser> = (action: PermissionAction, subject: DrizzleTable | "all", options?: {
130
+ type GrantFn<TUser, TTables extends SchemaMap = SchemaMap> = (action: PermissionAction, subject: SubjectInput<TTables>, options?: {
84
131
  where?: (columns: Record<string, unknown>, user: TUser) => DrizzleSQL | undefined;
85
132
  }) => Grant;
86
133
  /**
@@ -90,19 +137,27 @@ type GrantFn<TUser> = (action: PermissionAction, subject: DrizzleTable | "all",
90
137
  * This is what makes client-side permission introspection possible: you can check whether a
91
138
  * role has the right grants without knowing the specific row being accessed.
92
139
  *
140
+ * The `table` field accepts either a Drizzle table object or a string table
141
+ * name; both forms are normalized to the same key by {@link getTableName}.
142
+ *
93
143
  * @example
94
144
  * ```typescript
95
145
  * const descriptor: PermissionDescriptor = {
96
146
  * action: "update",
97
- * table: posts,
147
+ * table: posts, // object form
148
+ * };
149
+ *
150
+ * const descriptor2: PermissionDescriptor = {
151
+ * action: "create",
152
+ * table: "posts", // string form
98
153
  * };
99
154
  * ```
100
155
  */
101
156
  type PermissionDescriptor = {
102
157
  /** The operation being checked. */
103
158
  action: PermissionAction;
104
- /** The Drizzle table the operation targets. */
105
- table: DrizzleTable;
159
+ /** The Drizzle table or string table name the operation targets. */
160
+ table: DrizzleTable | string;
106
161
  };
107
162
  /**
108
163
  * Result of a permission check via {@link checkPermissions}.
@@ -120,12 +175,14 @@ type PermissionCheckResult = {
120
175
  *
121
176
  * @typeParam TRoles - Tuple of role name string literals (use `as const`).
122
177
  * @typeParam TUser - The user type for typed `where` clauses (defaults to `unknown`).
178
+ * @typeParam TTables - Optional schema map used to constrain string subjects
179
+ * inside the `grants` callback to known table names.
123
180
  */
124
- type PermissionsConfig<TRoles extends readonly string[], TUser = unknown> = {
181
+ type PermissionsConfig<TRoles extends readonly string[], TUser = unknown, TTables extends SchemaMap = SchemaMap> = {
125
182
  /** All roles in the application, declared with `as const` for type inference. */
126
183
  roles: TRoles;
127
184
  /** A map from role to grant arrays, or a callback that receives a typed `grant` function. */
128
- grants: Record<TRoles[number], Grant[]> | ((grant: GrantFn<TUser>) => Record<TRoles[number], Grant[]>);
185
+ grants: Record<TRoles[number], Grant[]> | ((grant: GrantFn<TUser, TTables>) => Record<TRoles[number], Grant[]>);
129
186
  /** Optional role hierarchy declaring which roles inherit from which. */
130
187
  hierarchy?: Partial<Record<TRoles[number], TRoles[number][]>>;
131
188
  };
@@ -151,8 +208,8 @@ type Permissions<TRoles extends readonly string[] = readonly string[]> = {
151
208
  type ForbiddenErrorOptions = {
152
209
  /** The action that was denied. */
153
210
  action: PermissionAction;
154
- /** The Drizzle table the action targeted. */
155
- table: DrizzleTable;
211
+ /** The Drizzle table or string table name the action targeted. */
212
+ table: DrizzleTable | string;
156
213
  /** The role that lacked the permission, if known. */
157
214
  role?: string;
158
215
  /** The full list of permission descriptors that were checked. */
@@ -168,8 +225,8 @@ type ForbiddenErrorOptions = {
168
225
  declare class ForbiddenError extends Error {
169
226
  /** The action that was denied (e.g., `"delete"`). */
170
227
  readonly action: PermissionAction;
171
- /** The Drizzle table the action targeted. */
172
- readonly table: DrizzleTable;
228
+ /** The Drizzle table or string table name the action targeted. */
229
+ readonly table: DrizzleTable | string;
173
230
  /** The role that lacked the permission, or `undefined` if not specified. */
174
231
  readonly role: string | undefined;
175
232
  /** The full list of permission descriptors that were checked. */
@@ -194,4 +251,4 @@ declare class ForbiddenError extends Error {
194
251
  };
195
252
  }
196
253
 
197
- export { CRUD_ACTIONS as C, type DrizzleTable as D, ForbiddenError as F, type Grant as G, type PermissionsConfig as P, type WhereClause as W, type Permissions as a, type PermissionAction as b, type PermissionDescriptor as c, type PermissionCheckResult as d, type CrudAction as e, type GrantFn as f, getTableName as g };
254
+ export { CRUD_ACTIONS as C, type DrizzleTable as D, ForbiddenError as F, type Grant as G, type PermissionsConfig as P, type SchemaMap as S, type TableName as T, type WhereClause as W, type Permissions as a, type PermissionAction as b, type SubjectInput as c, type PermissionDescriptor as d, type PermissionCheckResult as e, type CrudAction as f, type GrantFn as g, getTableName as h };
package/dist/client.d.ts CHANGED
@@ -1 +1 @@
1
- export { e as CrudAction, F as ForbiddenError, b as PermissionAction, d as PermissionCheckResult, c as PermissionDescriptor } from './client-CJBFS0IS.js';
1
+ export { f as CrudAction, F as ForbiddenError, b as PermissionAction, e as PermissionCheckResult, d as PermissionDescriptor } from './client-C7xdwHTk.js';
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ForbiddenError
3
- } from "./chunk-I35WLWUH.js";
3
+ } from "./chunk-NCLN5YBQ.js";
4
4
  export {
5
5
  ForbiddenError
6
6
  };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,17 @@
1
- import { P as PermissionsConfig, a as Permissions, b as PermissionAction, D as DrizzleTable, W as WhereClause, G as Grant, c as PermissionDescriptor, d as PermissionCheckResult } from './client-CJBFS0IS.js';
2
- export { C as CRUD_ACTIONS, e as CrudAction, F as ForbiddenError, f as GrantFn, g as getTableName } from './client-CJBFS0IS.js';
1
+ import { P as PermissionsConfig, a as Permissions, S as SchemaMap, b as PermissionAction, c as SubjectInput, W as WhereClause, G as Grant, d as PermissionDescriptor, e as PermissionCheckResult } from './client-C7xdwHTk.js';
2
+ export { C as CRUD_ACTIONS, f as CrudAction, D as DrizzleTable, F as ForbiddenError, g as GrantFn, T as TableName, h as getTableName } from './client-C7xdwHTk.js';
3
3
 
4
4
  /**
5
5
  * Creates a permission configuration that can be shared between server-side
6
6
  * enforcement (`@cfast/db`) and client-side introspection (`@cfast/actions`).
7
7
  *
8
- * Supports two calling styles:
8
+ * Supports three calling styles:
9
9
  * - **Direct:** `definePermissions(config)` when no custom user type is needed.
10
- * - **Curried:** `definePermissions<MyUser>()(config)` to get typed `where` clause user parameters.
10
+ * - **Curried (user only):** `definePermissions<MyUser>()(config)` for typed
11
+ * `where` clause user parameters.
12
+ * - **Curried (user + tables):** `definePermissions<MyUser, typeof schema>()(config)`
13
+ * to additionally constrain string subjects passed to the `grant` callback
14
+ * to known table names from the schema map.
11
15
  *
12
16
  * @param config - The permissions configuration with roles, grants, and optional hierarchy.
13
17
  * @returns A {@link Permissions} object containing roles, raw grants, and hierarchy-expanded `resolvedGrants`.
@@ -16,8 +20,10 @@ export { C as CRUD_ACTIONS, e as CrudAction, F as ForbiddenError, f as GrantFn,
16
20
  * ```typescript
17
21
  * import { definePermissions, grant } from "@cfast/permissions";
18
22
  * import { eq } from "drizzle-orm";
19
- * import { posts, comments } from "./schema";
23
+ * import * as schema from "./schema";
24
+ * const { posts, comments } = schema;
20
25
  *
26
+ * // Direct form — accepts table objects
21
27
  * const permissions = definePermissions({
22
28
  * roles: ["anonymous", "user", "admin"] as const,
23
29
  * grants: {
@@ -27,15 +33,29 @@ export { C as CRUD_ACTIONS, e as CrudAction, F as ForbiddenError, f as GrantFn,
27
33
  * user: [
28
34
  * grant("read", posts),
29
35
  * grant("create", posts),
30
- * grant("update", posts, { where: (p, u) => eq(p.authorId, u.id) }),
31
36
  * ],
32
37
  * admin: [grant("manage", "all")],
33
38
  * },
34
39
  * });
40
+ *
41
+ * // Curried form — string subjects constrained to known tables
42
+ * type AuthUser = { id: string };
43
+ * const perms = definePermissions<AuthUser, typeof schema>()({
44
+ * roles: ["user", "admin"] as const,
45
+ * grants: (grant) => ({
46
+ * user: [
47
+ * grant("read", "posts"), // string form
48
+ * grant("update", posts, { // object form still works
49
+ * where: (p, u) => eq(p.authorId, u.id),
50
+ * }),
51
+ * ],
52
+ * admin: [grant("manage", "all")],
53
+ * }),
54
+ * });
35
55
  * ```
36
56
  */
37
57
  declare function definePermissions<TRoles extends readonly string[]>(config: PermissionsConfig<TRoles>): Permissions<TRoles>;
38
- declare function definePermissions<TUser>(): <TRoles extends readonly string[]>(config: PermissionsConfig<TRoles, TUser>) => Permissions<TRoles>;
58
+ declare function definePermissions<TUser, TTables extends SchemaMap = SchemaMap>(): <TRoles extends readonly string[]>(config: PermissionsConfig<TRoles, TUser, TTables>) => Permissions<TRoles>;
39
59
 
40
60
  /**
41
61
  * Declares that a role can perform an action on a subject, optionally restricted
@@ -44,8 +64,15 @@ declare function definePermissions<TUser>(): <TRoles extends readonly string[]>(
44
64
  * Used inside the `grants` map of {@link definePermissions} to build permission rules.
45
65
  * A grant without a `where` clause applies to all rows.
46
66
  *
67
+ * Subjects accept three forms (all interchangeable at runtime):
68
+ * - A {@link DrizzleTable} object reference (the original form).
69
+ * - A string table name (e.g. `"projects"`) — when called via the `grant`
70
+ * callback inside `definePermissions<User, typeof schema>()`, strings are
71
+ * constrained to known table names by TypeScript.
72
+ * - The literal `"all"` for grants that apply to every table.
73
+ *
47
74
  * @param action - The operation being permitted (`"read"`, `"create"`, `"update"`, `"delete"`, or `"manage"` for all four).
48
- * @param subject - A Drizzle table reference, or `"all"` to apply to every table.
75
+ * @param subject - A Drizzle table reference, a string table name, or `"all"` to apply to every table.
49
76
  * @param options - Optional configuration.
50
77
  * @param options.where - A Drizzle filter function `(columns, user) => SQL` that restricts which rows this grant covers.
51
78
  * @returns A {@link Grant} object for use in a permissions configuration.
@@ -56,9 +83,13 @@ declare function definePermissions<TUser>(): <TRoles extends readonly string[]>(
56
83
  * import { eq } from "drizzle-orm";
57
84
  * import { posts } from "./schema";
58
85
  *
59
- * // Unrestricted read on all posts
86
+ * // Object form (always works)
60
87
  * grant("read", posts);
61
88
  *
89
+ * // String form (works standalone; type-checked when called via the
90
+ * // `grant` callback inside `definePermissions<User, typeof schema>()`)
91
+ * grant("read", "posts");
92
+ *
62
93
  * // Only allow updating own posts
63
94
  * grant("update", posts, {
64
95
  * where: (post, user) => eq(post.authorId, user.id),
@@ -68,10 +99,46 @@ declare function definePermissions<TUser>(): <TRoles extends readonly string[]>(
68
99
  * grant("manage", "all");
69
100
  * ```
70
101
  */
71
- declare function grant(action: PermissionAction, subject: DrizzleTable | "all", options?: {
102
+ declare function grant<TTables extends SchemaMap = SchemaMap>(action: PermissionAction, subject: SubjectInput<TTables>, options?: {
72
103
  where?: WhereClause;
73
104
  }): Grant;
74
105
 
106
+ /**
107
+ * Checks whether a set of resolved grants permits a specific action on a table.
108
+ *
109
+ * Unlike {@link checkPermissions} (which takes a role string and the full permissions
110
+ * object), `can` works directly with the user's resolved grants — the same grants
111
+ * available in loaders, actions, and client-side via `useActions`.
112
+ *
113
+ * Accepts either a Drizzle table object or a string table name. The two forms
114
+ * are interchangeable and resolve to the same underlying key. To get
115
+ * compile-time validation that a string table name actually exists in your
116
+ * schema, supply the schema map as a generic argument: `can<typeof schema>(...)`.
117
+ *
118
+ * @typeParam TTables - Optional schema map (e.g. `typeof schema`) used to
119
+ * constrain string subjects to known table-name literals.
120
+ * @param grants - The user's resolved permission grants.
121
+ * @param action - The permission action to check (e.g., `"create"`, `"read"`).
122
+ * @param table - The Drizzle table object or string table name to check against.
123
+ * @returns `true` if any grant permits the action on the table.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * import { can } from "@cfast/permissions";
128
+ * import * as schema from "../db/schema";
129
+ *
130
+ * // Object form (always works)
131
+ * if (!can(ctx.auth.grants, "create", schema.posts)) throw redirect("/");
132
+ *
133
+ * // String form (always allowed; type-checked when a schema generic is supplied)
134
+ * if (!can<typeof schema>(ctx.auth.grants, "create", "posts")) throw redirect("/");
135
+ *
136
+ * // In a component
137
+ * {can(grants, "update", schema.posts) && <Button>Edit</Button>}
138
+ * ```
139
+ */
140
+ declare function can<TTables extends SchemaMap = SchemaMap>(grants: Grant[], action: PermissionAction, table: SubjectInput<TTables>): boolean;
141
+
75
142
  /**
76
143
  * Checks whether a role satisfies a set of permission descriptors.
77
144
  *
@@ -106,6 +173,14 @@ declare function grant(action: PermissionAction, subject: DrizzleTable | "all",
106
173
  */
107
174
  declare function checkPermissions(role: string, permissions: Permissions, descriptors: PermissionDescriptor[]): PermissionCheckResult;
108
175
 
176
+ /**
177
+ * Minimal user shape accepted by {@link resolveGrants}: any object with a
178
+ * `roles` array. The function reads `.roles` and forwards them to the
179
+ * underlying merge logic.
180
+ */
181
+ type UserWithRoles = {
182
+ roles: readonly string[];
183
+ };
109
184
  /**
110
185
  * Resolves and merges grants for multiple roles into a single flat array.
111
186
  *
@@ -117,10 +192,27 @@ declare function checkPermissions(role: string, permissions: Permissions, descri
117
192
  *
118
193
  * This is used when a user has multiple roles and their grants need to be combined.
119
194
  *
195
+ * Two calling styles are supported (both equivalent):
196
+ * - **User object (preferred):** `resolveGrants(permissions, user)` where
197
+ * `user` has a `roles` field. The function extracts roles internally.
198
+ * - **Roles array (legacy):** `resolveGrants(permissions, roles)` where
199
+ * `roles` is a string array. Still supported for backwards compatibility.
200
+ *
120
201
  * @param permissions - The permissions object from {@link definePermissions}.
121
- * @param roles - Array of role names whose grants should be merged.
202
+ * @param userOrRoles - Either a user object with a `roles` field, or an
203
+ * array of role names whose grants should be merged.
122
204
  * @returns A deduplicated array of {@link Grant} objects with merged `where` clauses.
205
+ *
206
+ * @example
207
+ * ```ts
208
+ * // Preferred: pass the user directly
209
+ * const grants = resolveGrants(permissions, user);
210
+ *
211
+ * // Legacy: pass roles array (still works)
212
+ * const grants = resolveGrants(permissions, user.roles);
213
+ * ```
123
214
  */
124
- declare function resolveGrants(permissions: Permissions, roles: string[]): Grant[];
215
+ declare function resolveGrants(permissions: Permissions, user: UserWithRoles): Grant[];
216
+ declare function resolveGrants(permissions: Permissions, roles: readonly string[]): Grant[];
125
217
 
126
- export { DrizzleTable, Grant, PermissionAction, PermissionCheckResult, PermissionDescriptor, Permissions, PermissionsConfig, WhereClause, checkPermissions, definePermissions, grant, resolveGrants };
218
+ export { Grant, PermissionAction, PermissionCheckResult, PermissionDescriptor, Permissions, PermissionsConfig, SchemaMap, SubjectInput, type UserWithRoles, WhereClause, can, checkPermissions, definePermissions, grant, resolveGrants };
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  CRUD_ACTIONS,
3
3
  ForbiddenError,
4
4
  getTableName
5
- } from "./chunk-I35WLWUH.js";
5
+ } from "./chunk-NCLN5YBQ.js";
6
6
 
7
7
  // src/grant.ts
8
8
  function grant(action, subject, options) {
@@ -58,11 +58,23 @@ function resolveHierarchy(roles, grants, hierarchy) {
58
58
  return resolved;
59
59
  }
60
60
 
61
+ // src/can.ts
62
+ function can(grants, action, table) {
63
+ const targetKey = getTableName(table);
64
+ return grants.some((g) => {
65
+ const actionOk = g.action === action || g.action === "manage";
66
+ if (!actionOk) return false;
67
+ if (g.subject === "all") return true;
68
+ return getTableName(g.subject) === targetKey;
69
+ });
70
+ }
71
+
61
72
  // src/check.ts
62
73
  function grantMatches(g, action, table) {
63
74
  const actionOk = g.action === action || g.action === "manage";
64
- const subjectOk = g.subject === "all" || g.subject === table || getTableName(g.subject) === getTableName(table);
65
- return actionOk && subjectOk;
75
+ if (!actionOk) return false;
76
+ if (g.subject === "all") return true;
77
+ return getTableName(g.subject) === getTableName(table);
66
78
  }
67
79
  function hasGrantFor(grants, action, table) {
68
80
  return grants.some((g) => grantMatches(g, action, table));
@@ -101,7 +113,11 @@ import { or } from "drizzle-orm";
101
113
  function toSQLWrapper(value) {
102
114
  return value;
103
115
  }
104
- function resolveGrants(permissions, roles) {
116
+ function isUserWithRoles(value) {
117
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Array.isArray(value.roles);
118
+ }
119
+ function resolveGrants(permissions, userOrRoles) {
120
+ const roles = isUserWithRoles(userOrRoles) ? userOrRoles.roles : userOrRoles;
105
121
  const allGrants = [];
106
122
  for (const role of roles) {
107
123
  const roleGrants = permissions.resolvedGrants[role];
@@ -113,16 +129,18 @@ function resolveGrants(permissions, roles) {
113
129
  const groups = /* @__PURE__ */ new Map();
114
130
  const subjectIds = /* @__PURE__ */ new Map();
115
131
  let nextId = 0;
116
- function getSubjectId(subject) {
132
+ function getSubjectKey(subject) {
133
+ if (subject === "all") return "all";
134
+ if (typeof subject === "string") return `s:${subject}`;
117
135
  let id = subjectIds.get(subject);
118
136
  if (id === void 0) {
119
137
  id = nextId++;
120
138
  subjectIds.set(subject, id);
121
139
  }
122
- return id;
140
+ return `o:${id}`;
123
141
  }
124
142
  for (const g of allGrants) {
125
- const key = `${g.action}:${getSubjectId(g.subject)}`;
143
+ const key = `${g.action}:${getSubjectKey(g.subject)}`;
126
144
  let group = groups.get(key);
127
145
  if (!group) {
128
146
  group = { action: g.action, subject: g.subject, wheres: [] };
@@ -162,6 +180,7 @@ function resolveGrants(permissions, roles) {
162
180
  export {
163
181
  CRUD_ACTIONS,
164
182
  ForbiddenError,
183
+ can,
165
184
  checkPermissions,
166
185
  definePermissions,
167
186
  getTableName,