@zeno-lib/db 0.3.2 → 0.4.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.
@@ -0,0 +1,87 @@
1
+ import { n as DrizzleClient, t as CreateClientConfig } from "./clients-BlUEH7QJ.mjs";
2
+ import { AnyRelations, EmptyRelations } from "drizzle-orm";
3
+ import { JwtPayload, SupabaseClient } from "@supabase/supabase-js";
4
+ //#region src/define-action.d.ts
5
+ /**
6
+ * The slice of a schema `defineAction` needs: a throwing `parse`, plus the
7
+ * Standard Schema `types` marker it reads the caller-facing input type from.
8
+ * Zod 4 schemas satisfy it as they are; so does anything else that implements
9
+ * Standard Schema and exposes `parse`.
10
+ */
11
+ interface ActionSchema<TInput, TOutput> {
12
+ readonly "~standard": {
13
+ readonly types?: {
14
+ readonly input: TInput;
15
+ readonly output: TOutput;
16
+ } | undefined;
17
+ };
18
+ parse(input: unknown): TOutput;
19
+ }
20
+ /** What a request context must carry for `defineAction` to hand it over. */
21
+ interface ActionContext<TDb> {
22
+ readonly db: TDb;
23
+ }
24
+ /** An action's body: the RLS-bound `db`, the parsed input, and the context. */
25
+ type ActionHandler<TContext extends ActionContext<unknown>, TOutput, TResult> = (db: TContext["db"], input: TOutput, context: TContext) => TResult | Promise<TResult>;
26
+ /**
27
+ * `defineAction(schema, handler)` returns the server action itself: an async
28
+ * function that parses its argument, resolves the request context, then calls
29
+ * `handler(db, input, context)`.
30
+ */
31
+ type DefineAction<TContext extends ActionContext<unknown>> = <TInput, TOutput, TResult>(schema: ActionSchema<TInput, TOutput>, handler: ActionHandler<TContext, TOutput, TResult>) => (input: TInput) => Promise<TResult>;
32
+ //#endregion
33
+ //#region src/next.d.ts
34
+ /**
35
+ * Thrown when a request carries no verified session. A named class so a caller
36
+ * with a legitimate answer for that case (a permission check, where "not signed
37
+ * in" means "no") can tell it apart from a real failure with `instanceof`.
38
+ */
39
+ declare class UnauthenticatedError extends Error {
40
+ constructor(message?: string);
41
+ }
42
+ /** The only part of a Supabase client the request context reads. */
43
+ type ClaimsSource = {
44
+ auth: Pick<SupabaseClient["auth"], "getClaims">;
45
+ };
46
+ /** A verified request: the full `getClaims()` payload and the RLS client. */
47
+ type RequestContext<TRelations extends AnyRelations = EmptyRelations> = {
48
+ readonly claims: JwtPayload;
49
+ readonly db: DrizzleClient<TRelations>;
50
+ };
51
+ type CreateRequestDbOptions<TRelations extends AnyRelations = EmptyRelations> = Omit<CreateClientConfig<TRelations>, "connectionString"> & {
52
+ /**
53
+ * Builds the request's Supabase client, e.g. `createClient` from
54
+ * `@zeno-lib/supabase/next-server` (cookie session via `next/headers`).
55
+ */
56
+ supabase: () => ClaimsSource | Promise<ClaimsSource>;
57
+ /**
58
+ * Overrides `SUPABASE_DATABASE_URL`. A function is resolved per request, not
59
+ * at import, so a build without server secrets can still import the module.
60
+ */
61
+ connectionString?: string | (() => string);
62
+ };
63
+ type RequestDb<TRelations extends AnyRelations = EmptyRelations> = {
64
+ /** Memoised per render: `{ claims, db }` for the verified caller. */
65
+ getRequestContext: () => Promise<RequestContext<TRelations>>;
66
+ /** The common case: just the RLS-bound client. */
67
+ getRequestDb: () => Promise<DrizzleClient<TRelations>>;
68
+ /** Wraps a handler into a `"use server"` export; see `DefineAction`. */
69
+ defineAction: DefineAction<RequestContext<TRelations>>;
70
+ };
71
+ /**
72
+ * Request-scoped, RLS-bound Drizzle access for Next.js. Verifies the session
73
+ * with `getClaims()` (which checks the signature) and installs the **whole**
74
+ * claims object, so custom claims reach `auth.jwt()` in policies. There is no
75
+ * fallback to `anon`: a caller without a verified `sub` gets
76
+ * `UnauthenticatedError` rather than the empty result of a policy it never
77
+ * satisfied.
78
+ *
79
+ * `getRequestContext` is wrapped in React's `cache`, so one server render shares
80
+ * one verification and one handle. A server action invoked from the browser
81
+ * runs outside a render and resolves afresh on every call.
82
+ *
83
+ * Do not `close()` the handle: pools are shared and reference-counted.
84
+ */
85
+ declare function createRequestDb<TRelations extends AnyRelations = EmptyRelations>(options: CreateRequestDbOptions<TRelations>): RequestDb<TRelations>;
86
+ //#endregion
87
+ export { type ActionContext, type ActionHandler, type ActionSchema, ClaimsSource, CreateRequestDbOptions, type DefineAction, RequestContext, RequestDb, UnauthenticatedError, createRequestDb };
package/dist/next.mjs ADDED
@@ -0,0 +1,69 @@
1
+ import { a as createSupabaseClient } from "./clients-CdPr3mCZ.mjs";
2
+ import { cache } from "react";
3
+ //#region src/define-action.ts
4
+ /**
5
+ * Binds `defineAction` to a request-context resolver. Deliberately not a
6
+ * `"use server"` module: it only returns a function, and the app file that
7
+ * exports the result is the one that carries the directive.
8
+ *
9
+ * Input is parsed before the context is resolved, so a malformed call fails
10
+ * without a session round trip, and the handler never sees an unparsed value.
11
+ */
12
+ function createDefineAction(getContext) {
13
+ return (schema, handler) => async (input) => {
14
+ const parsed = schema.parse(input);
15
+ const context = await getContext();
16
+ return await handler(context.db, parsed, context);
17
+ };
18
+ }
19
+ //#endregion
20
+ //#region src/next.ts
21
+ /**
22
+ * Thrown when a request carries no verified session. A named class so a caller
23
+ * with a legitimate answer for that case (a permission check, where "not signed
24
+ * in" means "no") can tell it apart from a real failure with `instanceof`.
25
+ */
26
+ var UnauthenticatedError = class extends Error {
27
+ constructor(message = "Unauthorized: no verified session on this request") {
28
+ super(message);
29
+ this.name = "UnauthenticatedError";
30
+ }
31
+ };
32
+ /**
33
+ * Request-scoped, RLS-bound Drizzle access for Next.js. Verifies the session
34
+ * with `getClaims()` (which checks the signature) and installs the **whole**
35
+ * claims object, so custom claims reach `auth.jwt()` in policies. There is no
36
+ * fallback to `anon`: a caller without a verified `sub` gets
37
+ * `UnauthenticatedError` rather than the empty result of a policy it never
38
+ * satisfied.
39
+ *
40
+ * `getRequestContext` is wrapped in React's `cache`, so one server render shares
41
+ * one verification and one handle. A server action invoked from the browser
42
+ * runs outside a render and resolves afresh on every call.
43
+ *
44
+ * Do not `close()` the handle: pools are shared and reference-counted.
45
+ */
46
+ function createRequestDb(options) {
47
+ const { connectionString, supabase, ...config } = options;
48
+ const getRequestContext = cache(async () => {
49
+ const { data, error } = await (await supabase()).auth.getClaims();
50
+ if (error) throw error;
51
+ if (!data?.claims.sub) throw new UnauthenticatedError();
52
+ const url = typeof connectionString === "function" ? connectionString() : connectionString;
53
+ const db = createSupabaseClient(data.claims, url === void 0 ? config : {
54
+ ...config,
55
+ connectionString: url
56
+ });
57
+ return {
58
+ claims: data.claims,
59
+ db
60
+ };
61
+ });
62
+ return {
63
+ defineAction: createDefineAction(getRequestContext),
64
+ getRequestContext,
65
+ getRequestDb: async () => (await getRequestContext()).db
66
+ };
67
+ }
68
+ //#endregion
69
+ export { UnauthenticatedError, createRequestDb };
@@ -0,0 +1,103 @@
1
+ import { t as FunctionPolicyOperation } from "./function-names-hv2mj_x1.mjs";
2
+ import { SQL } from "drizzle-orm";
3
+ import { PgTable } from "drizzle-orm/pg-core";
4
+ //#region src/query.d.ts
5
+ /**
6
+ * PostgREST's `.single()`: exactly one row, or an error.
7
+ *
8
+ * `findFirst()` returns `undefined` for no rows and silently drops the rest
9
+ * when there are several, and `.returning()` hands back an array.
10
+ */
11
+ declare const one: <T>(rows: readonly T[]) => T;
12
+ /**
13
+ * PostgREST's `.maybeSingle()`: one row or none, erroring on more than one.
14
+ * Returns `undefined` rather than `null`, which is what `findFirst()` does.
15
+ */
16
+ declare const maybeOne: <T>(rows: readonly T[]) => T | undefined;
17
+ type ColumnKey<TTable extends PgTable> = Extract<keyof TTable["_"]["columns"], string>;
18
+ type ExcludedSetOptions<TTable extends PgTable> = {
19
+ /**
20
+ * The conflict target's column keys, never written back from `excluded`.
21
+ * The first one that exists is also the empty-set fallback. Default `"id"`.
22
+ */
23
+ target?: ColumnKey<TTable> | readonly ColumnKey<TTable>[];
24
+ };
25
+ /**
26
+ * The `set` for `onConflictDoUpdate` that reproduces PostgREST's
27
+ * `.upsert(row, { onConflict })`: on conflict, every column the caller
28
+ * supplied is overwritten from the proposed row (`col = excluded.col`).
29
+ *
30
+ * - A key carrying `undefined` is skipped. Drizzle omits an undefined column
31
+ * from the INSERT, so `excluded.x` would be the column default, and on a
32
+ * conflict `x = excluded.x` would overwrite a stored value with it.
33
+ * - The conflict target (`id` unless `target` says otherwise) is excluded.
34
+ * - An empty result falls back to `<target> = excluded.<target>`. Drizzle
35
+ * throws `No values to set` while *building* the statement, so a payload
36
+ * holding nothing but the key would fail even on the plain insert path.
37
+ * Writing the key back to itself is a no-op that keeps `.returning()`
38
+ * yielding the row, which `onConflictDoNothing` does not.
39
+ *
40
+ * With no target column in the table (a junction keyed only by its foreign
41
+ * keys, with the default target) the fallback cannot apply and the result
42
+ * stays empty: such a table wants `onConflictDoNothing` anyway.
43
+ *
44
+ * Column names come from the schema, never from `values`.
45
+ */
46
+ declare const excludedSet: <TTable extends PgTable>(table: TTable, values: Record<string, unknown>, { target }?: ExcludedSetOptions<TTable>) => Record<string, SQL>;
47
+ /**
48
+ * The same values with every `undefined` key dropped, so an insert and its
49
+ * `excludedSet` agree on which columns were supplied.
50
+ */
51
+ declare const definedValues: <T extends Record<string, unknown>>(values: T) => T;
52
+ /** Table name to the schema its `<prefix>_<operation>_<table>` functions live in. */
53
+ type FunctionPermissionTables = ReadonlyMap<string, string>;
54
+ type FunctionPermissionTablesOptions = {
55
+ /** Schemas whose tables are included. Default `["public"]`. */
56
+ schemas?: readonly string[];
57
+ };
58
+ /**
59
+ * The allowlist `selectFunctionPermissions` resolves a table name against,
60
+ * built from a Drizzle schema object (`import * as schema`) or the result of
61
+ * `defineRelations`. Build it once at module scope. Views and non-table
62
+ * exports are ignored; a table name that appears in two included schemas
63
+ * throws, since a bare name could not tell them apart.
64
+ */
65
+ declare const functionPermissionTables: (schemaObject: Record<string, unknown>, { schemas }?: FunctionPermissionTablesOptions) => FunctionPermissionTables;
66
+ type FunctionPermissions = Record<FunctionPolicyOperation, boolean>;
67
+ /**
68
+ * Anything with Drizzle's `execute` returning rows: a postgres-js client from
69
+ * `@zeno-lib/db`, a `PgAsyncDatabase`, or a transaction.
70
+ */
71
+ type QueryExecutor = {
72
+ execute: (query: SQL) => PromiseLike<readonly Record<string, unknown>[]>;
73
+ };
74
+ type SelectFunctionPermissionsOptions = {
75
+ /** From `functionPermissionTables`. */
76
+ tables: FunctionPermissionTables;
77
+ /** The table asked about, checked against `tables`. */
78
+ table: string;
79
+ /**
80
+ * The row, passed to every function but `insert` (there is no row yet) as
81
+ * a named argument. Omit to call all four with none.
82
+ */
83
+ id?: number | string;
84
+ /** Name of the functions' row parameter. Default `"id"`. */
85
+ argumentName?: string;
86
+ /** Function prefix, as given to `functionPolicies`. Default `"can"`. */
87
+ prefix?: string;
88
+ /** Schema the functions live in. Default: the table's own. */
89
+ functionSchema?: string;
90
+ };
91
+ /**
92
+ * The four `<prefix>_<operation>_<table>()` answers for one table, in one
93
+ * statement: the read side of `functionPolicies`, for showing or hiding UI.
94
+ *
95
+ * `table` is only ever a map key: every identifier that reaches the statement
96
+ * comes from `tables` and is quoted with `sql.identifier`. A function missing
97
+ * or with another signature raises `42883` (`SqlState.undefinedFunction`).
98
+ * A `null` answer, which a function gives for a row the caller cannot see,
99
+ * counts as `false`, as does anything else that is not `true`.
100
+ */
101
+ declare const selectFunctionPermissions: (db: QueryExecutor, { argumentName, functionSchema, id, prefix, table, tables }: SelectFunctionPermissionsOptions) => Promise<FunctionPermissions>;
102
+ //#endregion
103
+ export { ExcludedSetOptions, FunctionPermissionTables, FunctionPermissionTablesOptions, FunctionPermissions, QueryExecutor, SelectFunctionPermissionsOptions, definedValues, excludedSet, functionPermissionTables, maybeOne, one, selectFunctionPermissions };
package/dist/query.mjs ADDED
@@ -0,0 +1,115 @@
1
+ import { n as functionPolicyName, t as FUNCTION_POLICY_OPERATIONS } from "./function-names-BDc-AuFm.mjs";
2
+ import { getColumns, is, sql } from "drizzle-orm";
3
+ import { PgTable, getTableConfig } from "drizzle-orm/pg-core";
4
+ //#region src/query.ts
5
+ /**
6
+ * PostgREST's `.single()`: exactly one row, or an error.
7
+ *
8
+ * `findFirst()` returns `undefined` for no rows and silently drops the rest
9
+ * when there are several, and `.returning()` hands back an array.
10
+ */
11
+ const one = (rows) => {
12
+ if (rows.length !== 1) throw new Error(`Expected exactly one row, received ${rows.length}`);
13
+ return rows[0];
14
+ };
15
+ /**
16
+ * PostgREST's `.maybeSingle()`: one row or none, erroring on more than one.
17
+ * Returns `undefined` rather than `null`, which is what `findFirst()` does.
18
+ */
19
+ const maybeOne = (rows) => {
20
+ if (rows.length > 1) throw new Error(`Expected at most one row, received ${rows.length}`);
21
+ return rows[0];
22
+ };
23
+ /**
24
+ * The `set` for `onConflictDoUpdate` that reproduces PostgREST's
25
+ * `.upsert(row, { onConflict })`: on conflict, every column the caller
26
+ * supplied is overwritten from the proposed row (`col = excluded.col`).
27
+ *
28
+ * - A key carrying `undefined` is skipped. Drizzle omits an undefined column
29
+ * from the INSERT, so `excluded.x` would be the column default, and on a
30
+ * conflict `x = excluded.x` would overwrite a stored value with it.
31
+ * - The conflict target (`id` unless `target` says otherwise) is excluded.
32
+ * - An empty result falls back to `<target> = excluded.<target>`. Drizzle
33
+ * throws `No values to set` while *building* the statement, so a payload
34
+ * holding nothing but the key would fail even on the plain insert path.
35
+ * Writing the key back to itself is a no-op that keeps `.returning()`
36
+ * yielding the row, which `onConflictDoNothing` does not.
37
+ *
38
+ * With no target column in the table (a junction keyed only by its foreign
39
+ * keys, with the default target) the fallback cannot apply and the result
40
+ * stays empty: such a table wants `onConflictDoNothing` anyway.
41
+ *
42
+ * Column names come from the schema, never from `values`.
43
+ */
44
+ const excludedSet = (table, values, { target = "id" } = {}) => {
45
+ const columns = getColumns(table);
46
+ const targets = typeof target === "string" ? [target] : target;
47
+ const set = {};
48
+ for (const [key, column] of Object.entries(columns)) if (key in values && values[key] !== void 0 && !targets.includes(key)) set[key] = sql`excluded.${sql.identifier(column.name)}`;
49
+ if (Object.keys(set).length > 0) return set;
50
+ const fallback = targets.find((key) => key in columns);
51
+ const fallbackColumn = fallback === void 0 ? void 0 : columns[fallback];
52
+ if (fallback !== void 0 && fallbackColumn) set[fallback] = sql`excluded.${sql.identifier(fallbackColumn.name)}`;
53
+ return set;
54
+ };
55
+ /**
56
+ * The same values with every `undefined` key dropped, so an insert and its
57
+ * `excludedSet` agree on which columns were supplied.
58
+ */
59
+ const definedValues = (values) => Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
60
+ const DEFAULT_SCHEMA = "public";
61
+ const tableOf = (value) => {
62
+ if (is(value, PgTable)) return value;
63
+ if (typeof value === "object" && value !== null && "table" in value) {
64
+ const { table } = value;
65
+ return is(table, PgTable) ? table : void 0;
66
+ }
67
+ };
68
+ /**
69
+ * The allowlist `selectFunctionPermissions` resolves a table name against,
70
+ * built from a Drizzle schema object (`import * as schema`) or the result of
71
+ * `defineRelations`. Build it once at module scope. Views and non-table
72
+ * exports are ignored; a table name that appears in two included schemas
73
+ * throws, since a bare name could not tell them apart.
74
+ */
75
+ const functionPermissionTables = (schemaObject, { schemas = [DEFAULT_SCHEMA] } = {}) => {
76
+ const tables = /* @__PURE__ */ new Map();
77
+ for (const value of Object.values(schemaObject)) {
78
+ const table = tableOf(value);
79
+ if (!table) continue;
80
+ const { name, schema = DEFAULT_SCHEMA } = getTableConfig(table);
81
+ if (!schemas.includes(schema)) continue;
82
+ const existing = tables.get(name);
83
+ if (existing !== void 0 && existing !== schema) throw new Error(`Table "${name}" exists in both "${existing}" and "${schema}"; include only one of those schemas`);
84
+ tables.set(name, schema);
85
+ }
86
+ return tables;
87
+ };
88
+ /**
89
+ * The four `<prefix>_<operation>_<table>()` answers for one table, in one
90
+ * statement: the read side of `functionPolicies`, for showing or hiding UI.
91
+ *
92
+ * `table` is only ever a map key: every identifier that reaches the statement
93
+ * comes from `tables` and is quoted with `sql.identifier`. A function missing
94
+ * or with another signature raises `42883` (`SqlState.undefinedFunction`).
95
+ * A `null` answer, which a function gives for a row the caller cannot see,
96
+ * counts as `false`, as does anything else that is not `true`.
97
+ */
98
+ const selectFunctionPermissions = async (db, { argumentName = "id", functionSchema, id, prefix = "can", table, tables }) => {
99
+ const tableSchema = tables.get(table);
100
+ if (tableSchema === void 0) throw new Error(`No permission functions for table "${table}"`);
101
+ const schema = functionSchema ?? tableSchema;
102
+ const call = (operation) => {
103
+ const callee = sql`${sql.identifier(schema)}.${sql.identifier(functionPolicyName(prefix, operation, table))}`;
104
+ return operation === "insert" || id === void 0 ? sql`${callee}()` : sql`${callee}(${sql.identifier(argumentName)} => ${id})`;
105
+ };
106
+ const row = one(await db.execute(sql`select ${sql.join(FUNCTION_POLICY_OPERATIONS.map((operation) => sql`${call(operation)} as ${sql.identifier(operation)}`), sql`, `)}`));
107
+ return {
108
+ delete: row.delete === true,
109
+ insert: row.insert === true,
110
+ select: row.select === true,
111
+ update: row.update === true
112
+ };
113
+ };
114
+ //#endregion
115
+ export { definedValues, excludedSet, functionPermissionTables, maybeOne, one, selectFunctionPermissions };
package/dist/schema.d.mts CHANGED
@@ -1,15 +1,26 @@
1
- import { AnyPgColumn, ExtraConfigColumn, HasIdentity, PgBigInt53Builder, PgBigInt64Builder, PgIntegerBuilder, PgPolicyConfig, PgUUIDBuilder, Precision, ReferenceConfig, SetHasDefault, SetIsPrimaryKey, SetNotNull, isPgEnum as isEnum, isPgMaterializedView as isMaterializedView, isPgSchema as isSchema, isPgSequence as isSequence, isPgView as isView, pgEnum as enum$1, pgMaterializedView as materializedView, pgPolicy as policy, pgRole as role, pgSequence as sequence, pgTableCreator as tableCreator, pgView as view } from "drizzle-orm/pg-core";
1
+ import { t as FunctionPolicyOperation } from "./function-names-hv2mj_x1.mjs";
2
+ import { AnyPgColumn, ExtraConfigColumn, HasIdentity, PgBigInt53Builder, PgBigInt64Builder, PgIntegerBuilder, PgPolicyConfig, PgTimestampBuilder, PgTimestampStringBuilder, PgUUIDBuilder, Precision, ReferenceConfig, SetHasDefault, SetIsPrimaryKey, SetNotNull, isPgEnum as isEnum, isPgMaterializedView as isMaterializedView, isPgSchema as isSchema, isPgSequence as isSequence, isPgView as isView, pgEnum as enum$1, pgMaterializedView as materializedView, pgPolicy as policy, pgRole as role, pgSequence as sequence, pgTableCreator as tableCreator, pgView as view } from "drizzle-orm/pg-core";
2
3
  import { anonRole, authUid, authenticatedRole, postgresRole, realtimeMessages, realtimeTopic, serviceRole, supabaseAuthAdminRole } from "drizzle-orm/supabase";
3
4
  //#region src/schema.d.ts
4
- type TimestampsOptions = {
5
+ /** How a timestamp column is read back: a `Date` (the default) or Postgres's own text. */
6
+ type TimestampMode = "date" | "string";
7
+ type TimestampsOptions<TMode extends TimestampMode = "date"> = {
8
+ /**
9
+ * `"date"` (the default) reads a `Date`; `"string"` keeps Postgres's text
10
+ * form, which also keeps the microseconds a `Date` truncates. A JavaScript
11
+ * mapping only: it emits no SQL.
12
+ */
13
+ mode?: TMode;
5
14
  withTimezone?: boolean;
6
15
  /** Fractional-second digits. Postgres allows 0 to 6. */
7
16
  precision?: Precision;
8
17
  };
9
- declare const timestamps: ({ precision, withTimezone }?: TimestampsOptions) => {
10
- createdAt: SetHasDefault<SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>>;
11
- updatedAt: SetHasDefault<SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>>;
18
+ type TimestampColumn<TMode extends TimestampMode> = SetHasDefault<SetNotNull<[TMode] extends ["string"] ? PgTimestampStringBuilder : PgTimestampBuilder>>;
19
+ type TimestampColumns<TMode extends TimestampMode = "date"> = {
20
+ createdAt: TimestampColumn<TMode>;
21
+ updatedAt: TimestampColumn<TMode>;
12
22
  };
23
+ declare const timestamps: <TMode extends TimestampMode = "date">({ mode, precision, withTimezone }?: TimestampsOptions<TMode>) => TimestampColumns<TMode>;
13
24
  type ReferenceActions = ReferenceConfig["config"];
14
25
  type AuthorColumn<TNotNull extends boolean> = [TNotNull] extends [true] ? SetNotNull<PgUUIDBuilder> : PgUUIDBuilder;
15
26
  type AuthorReference = {
@@ -36,9 +47,9 @@ declare const authorship: <TNotNull extends boolean = false>(options?: Authorshi
36
47
  createdBy: SetHasDefault<AuthorColumn<TNotNull>>;
37
48
  updatedBy: SetHasDefault<AuthorColumn<TNotNull>>;
38
49
  };
39
- declare const auditColumns: <TNotNull extends boolean = false>(options?: AuthorshipOptions<TNotNull> & TimestampsOptions) => {
40
- createdAt: SetHasDefault<SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>>;
41
- updatedAt: SetHasDefault<SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>>;
50
+ declare const auditColumns: <TNotNull extends boolean = false, TMode extends TimestampMode = "date">(options?: AuthorshipOptions<TNotNull> & TimestampsOptions<TMode>) => {
51
+ createdAt: TimestampColumn<TMode>;
52
+ updatedAt: TimestampColumn<TMode>;
42
53
  createdBy: SetHasDefault<AuthorColumn<TNotNull>>;
43
54
  updatedBy: SetHasDefault<AuthorColumn<TNotNull>>;
44
55
  };
@@ -78,8 +89,6 @@ declare const authenticatedInsertPolicy: (name: string, config?: AuthenticatedPo
78
89
  declare const authenticatedUpdatePolicy: (name: string, config?: AuthenticatedPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
79
90
  declare const authenticatedDeletePolicy: (name: string, config?: AuthenticatedPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
80
91
  declare const authenticatedAllPolicy: (name: string, config?: AuthenticatedPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
81
- declare const FUNCTION_POLICY_OPERATIONS: readonly ["select", "insert", "update", "delete"];
82
- type FunctionPolicyOperation = (typeof FUNCTION_POLICY_OPERATIONS)[number];
83
92
  /** Columns one function is called with. `null` calls it with none. */
84
93
  type FunctionArgument = AnyPgColumn | readonly AnyPgColumn[] | null;
85
94
  type FunctionPoliciesOptions = {
@@ -146,4 +155,4 @@ declare const schema: <TName extends string>(name: TName) => import("drizzle-orm
146
155
  unsecureTable: import("drizzle-orm/pg-core").PgTableFn<TName>;
147
156
  };
148
157
  //#endregion
149
- export { AuthorshipOptions, allPolicy, anonRole, assignedPrimaryId, auditColumns, authUid, authUserId, authUserOwns, authenticatedAllPolicy, authenticatedDeletePolicy, authenticatedInsertPolicy, authenticatedOwnerAllPolicy, authenticatedOwnerDeletePolicy, authenticatedOwnerInsertPolicy, authenticatedOwnerSelectPolicy, authenticatedOwnerUpdatePolicy, authenticatedRole, authenticatedSelectPolicy, authenticatedUpdatePolicy, authorship, createdBy, deletePolicy, enum$1 as enum, functionPolicies, insertPolicy, isEnum, isMaterializedView, isSchema, isSequence, isView, materializedView, policy, postgresRole, primaryId, realtimeMessages, realtimeTopic, role, schema, selectPolicy, sequence, sequentialPrimaryId, serviceRole, supabaseAuthAdminRole, table, tableCreator, timestamps, unsecureTable, updatePolicy, updatedBy, userId, uuidPrimaryId, view };
158
+ export { AuthorshipOptions, TimestampColumns, TimestampMode, TimestampsOptions, allPolicy, anonRole, assignedPrimaryId, auditColumns, authUid, authUserId, authUserOwns, authenticatedAllPolicy, authenticatedDeletePolicy, authenticatedInsertPolicy, authenticatedOwnerAllPolicy, authenticatedOwnerDeletePolicy, authenticatedOwnerInsertPolicy, authenticatedOwnerSelectPolicy, authenticatedOwnerUpdatePolicy, authenticatedRole, authenticatedSelectPolicy, authenticatedUpdatePolicy, authorship, createdBy, deletePolicy, enum$1 as enum, functionPolicies, insertPolicy, isEnum, isMaterializedView, isSchema, isSequence, isView, materializedView, policy, postgresRole, primaryId, realtimeMessages, realtimeTopic, role, schema, selectPolicy, sequence, sequentialPrimaryId, serviceRole, supabaseAuthAdminRole, table, tableCreator, timestamps, unsecureTable, updatePolicy, updatedBy, userId, uuidPrimaryId, view };
package/dist/schema.mjs CHANGED
@@ -1,17 +1,22 @@
1
1
  import { authUsers } from "./auth-schema.mjs";
2
+ import { n as functionPolicyName, t as FUNCTION_POLICY_OPERATIONS } from "./function-names-BDc-AuFm.mjs";
2
3
  import { Column, getColumnTable, getTableName, is, sql } from "drizzle-orm";
3
4
  import { bigint, integer, isPgEnum as isEnum, isPgMaterializedView as isMaterializedView, isPgSchema as isSchema, isPgSequence as isSequence, isPgView as isView, pgEnum as enum$1, pgMaterializedView as materializedView, pgPolicy, pgPolicy as policy, pgRole as role, pgSequence as sequence, pgTableCreator as tableCreator, pgView as view, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
4
5
  import { snakeCase } from "drizzle-orm/pg-core/casing";
5
6
  import { anonRole, authUid, authUid as authUid$1, authenticatedRole, authenticatedRole as authenticatedRole$1, postgresRole, realtimeMessages, realtimeTopic, serviceRole, supabaseAuthAdminRole } from "drizzle-orm/supabase";
6
7
  //#region src/schema.ts
7
- const timestamps = ({ precision, withTimezone = true } = {}) => {
8
- const config = {
8
+ const timestamps = ({ mode, precision, withTimezone = true } = {}) => {
9
+ const column = (name) => mode === "string" ? timestamp(name, {
10
+ mode: "string",
9
11
  precision,
10
12
  withTimezone
11
- };
13
+ }).notNull().defaultNow() : timestamp(name, {
14
+ precision,
15
+ withTimezone
16
+ }).notNull().defaultNow();
12
17
  return {
13
- createdAt: timestamp("created_at", config).notNull().defaultNow(),
14
- updatedAt: timestamp("updated_at", config).notNull().defaultNow()
18
+ createdAt: column("created_at"),
19
+ updatedAt: column("updated_at")
15
20
  };
16
21
  };
17
22
  const NULLABLE_AUTHOR_ACTIONS = {
@@ -118,12 +123,6 @@ const POLICY_BUILDERS = {
118
123
  select: selectPolicy,
119
124
  update: updatePolicy
120
125
  };
121
- const FUNCTION_POLICY_OPERATIONS = [
122
- "select",
123
- "insert",
124
- "update",
125
- "delete"
126
- ];
127
126
  const isWholeSetArgument = (argument) => is(argument, Column) || Array.isArray(argument);
128
127
  const argumentsFor = (argument, operation) => {
129
128
  if (!argument) return [];
@@ -163,7 +162,7 @@ const functionPolicies = (columns, { argument, name, prefix = "can", schema: fun
163
162
  if (!firstColumn) throw new Error("functionPolicies needs a table with at least one column");
164
163
  const tableName = getTableName(getColumnTable(firstColumn));
165
164
  return FUNCTION_POLICY_OPERATIONS.map((operation) => {
166
- const functionName = `${prefix}_${operation}_${tableName}`;
165
+ const functionName = functionPolicyName(prefix, operation, tableName);
167
166
  const callee = functionSchema ? sql`${sql.identifier(functionSchema)}.${sql.identifier(functionName)}` : sql`${sql.identifier(functionName)}`;
168
167
  const condition = sql`(select ${callee}(${sql.join(argumentsFor(argument, operation), sql`, `)}))`;
169
168
  const clause = { [POLICY_CLAUSE[operation]]: condition };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeno-lib/db",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -18,6 +18,21 @@
18
18
  "import": "./dist/config.mjs",
19
19
  "default": "./dist/config.mjs"
20
20
  },
21
+ "./errors": {
22
+ "types": "./dist/errors.d.mts",
23
+ "import": "./dist/errors.mjs",
24
+ "default": "./dist/errors.mjs"
25
+ },
26
+ "./next": {
27
+ "types": "./dist/next.d.mts",
28
+ "import": "./dist/next.mjs",
29
+ "default": "./dist/next.mjs"
30
+ },
31
+ "./query": {
32
+ "types": "./dist/query.d.mts",
33
+ "import": "./dist/query.mjs",
34
+ "default": "./dist/query.mjs"
35
+ },
21
36
  "./schema": {
22
37
  "types": "./dist/schema.d.mts",
23
38
  "import": "./dist/schema.mjs",
@@ -44,21 +59,30 @@
44
59
  "@supabase/supabase-js": ">=2",
45
60
  "drizzle-kit": "1.0.0-rc.3",
46
61
  "drizzle-orm": "1.0.0-rc.3",
47
- "postgres": ">=3.4"
62
+ "postgres": ">=3.4",
63
+ "react": ">=19"
64
+ },
65
+ "peerDependenciesMeta": {
66
+ "react": {
67
+ "optional": true
68
+ }
48
69
  },
49
70
  "devDependencies": {
50
- "@supabase/supabase-js": "2.106.1",
71
+ "@supabase/supabase-js": "2.116.0",
51
72
  "@types/node": "24.10.0",
73
+ "@types/react": "19.2.14",
52
74
  "dotenv": "17.4.2",
53
75
  "drizzle-kit": "1.0.0-rc.3",
54
76
  "drizzle-orm": "1.0.0-rc.3",
55
77
  "postgres": "3.4.7",
78
+ "react": "^19.2.5",
56
79
  "supabase": "2.84.1",
57
80
  "tsdown": "^0.22.14",
58
81
  "typescript": "7.0.2",
59
82
  "vite": "8.0.16",
60
- "vitest": "4.1.0",
61
- "@zeno-lib/test": "0.0.3",
83
+ "vitest": "4.1.11",
84
+ "zod": "^4.4.3",
85
+ "@zeno-lib/test": "0.1.0",
62
86
  "@zeno-lib/typescript": "^1.1.0"
63
87
  },
64
88
  "scripts": {