Skip to content

definePermissions

definePermissions<TRoles>(config): Permissions<TRoles>

Defined in: packages/permissions/src/define-permissions.ts:206

Creates a permission configuration that can be shared between server-side enforcement (@cfast/db) and client-side introspection (@cfast/actions).

Supports four calling styles:

  1. Direct: definePermissions(config) — when no custom user type and no schema map is needed.

  2. Curried (user only): definePermissions<MyUser>()(config) — types the user parameter inside where clauses. String subjects are still accepted but are not compile-time or runtime validated against a schema; they are stored as opaque strings and matched by getTableName.

  3. Curried (user + schema types only, no runtime schema): definePermissions<MyUser, typeof schema>()(config) — constrains string subjects to "jsKey" | "sql_name" at compile time, but at runtime still stores strings opaquely. Prefer style (4) when you can.

  4. Curried with runtime schema (RECOMMENDED): definePermissions<MyUser, typeof schema>({ schema })(config) — walks the schema at startup and resolves every string-form subject to the exact Drizzle table reference the schema uses. This is the only form that lets string-form grants work with Drizzle relational with queries, because Drizzle identifies tables by reference, not name (see issues #146, #175, #177).

    • Unknown strings throw PermissionRegistrationError at startup (fast, loud failure instead of silently matching nothing).
    • Both the JS key ("documentVersions") and the SQL table name ("document_versions") resolve to the same table reference.
    • Object-form subjects still work unchanged.

TRoles extends readonly string[]

PermissionsConfig<TRoles>

Permissions<TRoles>

A Permissions object, or a function that takes a config and returns one.

import { definePermissions, grant } from "@cfast/permissions";
import { eq } from "drizzle-orm";
import * as schema from "./schema";
const { posts, comments } = schema;
// Style 1 — direct, accepts table objects
const permissions = definePermissions({
roles: ["anonymous", "user", "admin"] as const,
grants: {
anonymous: [
grant("read", posts, { where: (p) => eq(p.published, true) }),
],
user: [grant("read", posts), grant("create", posts)],
admin: [grant("manage", "all")],
},
});
// Style 4 — recommended: runtime-resolved string subjects
type AuthUser = { id: string };
const perms = definePermissions<AuthUser, typeof schema>({ schema })({
roles: ["user", "admin"] as const,
grants: (grant) => ({
user: [
grant("read", "posts"), // JS key — resolves
grant("create", "post_versions"), // SQL name — also resolves
grant("update", posts, { // object form still works
where: (p, u) => eq(p.authorId, u.id),
}),
// grant("read", "unknownTable"), // throws at startup
],
admin: [grant("manage", "all")],
}),
});

definePermissions<TUser, TTables>(options): <TRoles>(config) => Permissions<TRoles>

Defined in: packages/permissions/src/define-permissions.ts:210

Creates a permission configuration that can be shared between server-side enforcement (@cfast/db) and client-side introspection (@cfast/actions).

Supports four calling styles:

  1. Direct: definePermissions(config) — when no custom user type and no schema map is needed.

  2. Curried (user only): definePermissions<MyUser>()(config) — types the user parameter inside where clauses. String subjects are still accepted but are not compile-time or runtime validated against a schema; they are stored as opaque strings and matched by getTableName.

  3. Curried (user + schema types only, no runtime schema): definePermissions<MyUser, typeof schema>()(config) — constrains string subjects to "jsKey" | "sql_name" at compile time, but at runtime still stores strings opaquely. Prefer style (4) when you can.

  4. Curried with runtime schema (RECOMMENDED): definePermissions<MyUser, typeof schema>({ schema })(config) — walks the schema at startup and resolves every string-form subject to the exact Drizzle table reference the schema uses. This is the only form that lets string-form grants work with Drizzle relational with queries, because Drizzle identifies tables by reference, not name (see issues #146, #175, #177).

    • Unknown strings throw PermissionRegistrationError at startup (fast, loud failure instead of silently matching nothing).
    • Both the JS key ("documentVersions") and the SQL table name ("document_versions") resolve to the same table reference.
    • Object-form subjects still work unchanged.

TUser

TTables extends SchemaMap

TTables

A Permissions object, or a function that takes a config and returns one.

<TRoles>(config): Permissions<TRoles>

TRoles extends readonly string[]

PermissionsConfig<TRoles, TUser, TTables>

Permissions<TRoles>

import { definePermissions, grant } from "@cfast/permissions";
import { eq } from "drizzle-orm";
import * as schema from "./schema";
const { posts, comments } = schema;
// Style 1 — direct, accepts table objects
const permissions = definePermissions({
roles: ["anonymous", "user", "admin"] as const,
grants: {
anonymous: [
grant("read", posts, { where: (p) => eq(p.published, true) }),
],
user: [grant("read", posts), grant("create", posts)],
admin: [grant("manage", "all")],
},
});
// Style 4 — recommended: runtime-resolved string subjects
type AuthUser = { id: string };
const perms = definePermissions<AuthUser, typeof schema>({ schema })({
roles: ["user", "admin"] as const,
grants: (grant) => ({
user: [
grant("read", "posts"), // JS key — resolves
grant("create", "post_versions"), // SQL name — also resolves
grant("update", posts, { // object form still works
where: (p, u) => eq(p.authorId, u.id),
}),
// grant("read", "unknownTable"), // throws at startup
],
admin: [grant("manage", "all")],
}),
});

definePermissions<TUser, TTables>(): <TRoles>(config) => Permissions<TRoles>

Defined in: packages/permissions/src/define-permissions.ts:219

Creates a permission configuration that can be shared between server-side enforcement (@cfast/db) and client-side introspection (@cfast/actions).

Supports four calling styles:

  1. Direct: definePermissions(config) — when no custom user type and no schema map is needed.

  2. Curried (user only): definePermissions<MyUser>()(config) — types the user parameter inside where clauses. String subjects are still accepted but are not compile-time or runtime validated against a schema; they are stored as opaque strings and matched by getTableName.

  3. Curried (user + schema types only, no runtime schema): definePermissions<MyUser, typeof schema>()(config) — constrains string subjects to "jsKey" | "sql_name" at compile time, but at runtime still stores strings opaquely. Prefer style (4) when you can.

  4. Curried with runtime schema (RECOMMENDED): definePermissions<MyUser, typeof schema>({ schema })(config) — walks the schema at startup and resolves every string-form subject to the exact Drizzle table reference the schema uses. This is the only form that lets string-form grants work with Drizzle relational with queries, because Drizzle identifies tables by reference, not name (see issues #146, #175, #177).

    • Unknown strings throw PermissionRegistrationError at startup (fast, loud failure instead of silently matching nothing).
    • Both the JS key ("documentVersions") and the SQL table name ("document_versions") resolve to the same table reference.
    • Object-form subjects still work unchanged.

TUser

TTables extends SchemaMap = SchemaMap

A Permissions object, or a function that takes a config and returns one.

<TRoles>(config): Permissions<TRoles>

TRoles extends readonly string[]

PermissionsConfig<TRoles, TUser, TTables>

Permissions<TRoles>

import { definePermissions, grant } from "@cfast/permissions";
import { eq } from "drizzle-orm";
import * as schema from "./schema";
const { posts, comments } = schema;
// Style 1 — direct, accepts table objects
const permissions = definePermissions({
roles: ["anonymous", "user", "admin"] as const,
grants: {
anonymous: [
grant("read", posts, { where: (p) => eq(p.published, true) }),
],
user: [grant("read", posts), grant("create", posts)],
admin: [grant("manage", "all")],
},
});
// Style 4 — recommended: runtime-resolved string subjects
type AuthUser = { id: string };
const perms = definePermissions<AuthUser, typeof schema>({ schema })({
roles: ["user", "admin"] as const,
grants: (grant) => ({
user: [
grant("read", "posts"), // JS key — resolves
grant("create", "post_versions"), // SQL name — also resolves
grant("update", posts, { // object form still works
where: (p, u) => eq(p.authorId, u.id),
}),
// grant("read", "unknownTable"), // throws at startup
],
admin: [grant("manage", "all")],
}),
});