@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.
- package/dist/clients-BlUEH7QJ.d.mts +58 -0
- package/dist/clients-CdPr3mCZ.mjs +197 -0
- package/dist/errors.d.mts +70 -0
- package/dist/errors.mjs +78 -0
- package/dist/function-names-BDc-AuFm.mjs +10 -0
- package/dist/function-names-hv2mj_x1.d.mts +5 -0
- package/dist/index.d.mts +2 -42
- package/dist/index.mjs +2 -167
- package/dist/next.d.mts +87 -0
- package/dist/next.mjs +69 -0
- package/dist/query.d.mts +103 -0
- package/dist/query.mjs +115 -0
- package/dist/schema.d.mts +20 -11
- package/dist/schema.mjs +11 -12
- package/package.json +29 -5
- package/src/casing.test.ts +43 -20
- package/src/clients.ts +60 -5
- package/src/database-url.test.ts +88 -0
- package/src/define-action.ts +63 -0
- package/src/errors.test.ts +95 -0
- package/src/errors.ts +133 -0
- package/src/function-names.ts +21 -0
- package/src/index.ts +2 -0
- package/src/next.test-d.ts +32 -0
- package/src/next.test.ts +178 -0
- package/src/next.ts +112 -0
- package/src/query.test-d.ts +20 -0
- package/src/query.test.ts +261 -0
- package/src/query.ts +252 -0
- package/src/schema.test-d.ts +24 -0
- package/src/schema.ts +51 -21
package/src/query.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// Query-side helpers: the PostgREST conveniences Drizzle has no equivalent for
|
|
2
|
+
// (`.single()`, `.maybeSingle()`, a default upsert `set`), plus the read side
|
|
3
|
+
// of `functionPolicies`.
|
|
4
|
+
import { getColumns, is, type SQL, sql } from "drizzle-orm"
|
|
5
|
+
import { getTableConfig, PgTable } from "drizzle-orm/pg-core"
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_FUNCTION_PREFIX,
|
|
8
|
+
FUNCTION_POLICY_OPERATIONS,
|
|
9
|
+
type FunctionPolicyOperation,
|
|
10
|
+
functionPolicyName,
|
|
11
|
+
} from "./function-names.ts"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* PostgREST's `.single()`: exactly one row, or an error.
|
|
15
|
+
*
|
|
16
|
+
* `findFirst()` returns `undefined` for no rows and silently drops the rest
|
|
17
|
+
* when there are several, and `.returning()` hands back an array.
|
|
18
|
+
*/
|
|
19
|
+
export const one = <T>(rows: readonly T[]): T => {
|
|
20
|
+
if (rows.length !== 1) {
|
|
21
|
+
throw new Error(`Expected exactly one row, received ${rows.length}`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return rows[0] as T
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* PostgREST's `.maybeSingle()`: one row or none, erroring on more than one.
|
|
29
|
+
* Returns `undefined` rather than `null`, which is what `findFirst()` does.
|
|
30
|
+
*/
|
|
31
|
+
export const maybeOne = <T>(rows: readonly T[]): T | undefined => {
|
|
32
|
+
if (rows.length > 1) {
|
|
33
|
+
throw new Error(`Expected at most one row, received ${rows.length}`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return rows[0]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type ColumnKey<TTable extends PgTable> = Extract<
|
|
40
|
+
keyof TTable["_"]["columns"],
|
|
41
|
+
string
|
|
42
|
+
>
|
|
43
|
+
|
|
44
|
+
export type ExcludedSetOptions<TTable extends PgTable> = {
|
|
45
|
+
/**
|
|
46
|
+
* The conflict target's column keys, never written back from `excluded`.
|
|
47
|
+
* The first one that exists is also the empty-set fallback. Default `"id"`.
|
|
48
|
+
*/
|
|
49
|
+
target?: ColumnKey<TTable> | readonly ColumnKey<TTable>[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The `set` for `onConflictDoUpdate` that reproduces PostgREST's
|
|
54
|
+
* `.upsert(row, { onConflict })`: on conflict, every column the caller
|
|
55
|
+
* supplied is overwritten from the proposed row (`col = excluded.col`).
|
|
56
|
+
*
|
|
57
|
+
* - A key carrying `undefined` is skipped. Drizzle omits an undefined column
|
|
58
|
+
* from the INSERT, so `excluded.x` would be the column default, and on a
|
|
59
|
+
* conflict `x = excluded.x` would overwrite a stored value with it.
|
|
60
|
+
* - The conflict target (`id` unless `target` says otherwise) is excluded.
|
|
61
|
+
* - An empty result falls back to `<target> = excluded.<target>`. Drizzle
|
|
62
|
+
* throws `No values to set` while *building* the statement, so a payload
|
|
63
|
+
* holding nothing but the key would fail even on the plain insert path.
|
|
64
|
+
* Writing the key back to itself is a no-op that keeps `.returning()`
|
|
65
|
+
* yielding the row, which `onConflictDoNothing` does not.
|
|
66
|
+
*
|
|
67
|
+
* With no target column in the table (a junction keyed only by its foreign
|
|
68
|
+
* keys, with the default target) the fallback cannot apply and the result
|
|
69
|
+
* stays empty: such a table wants `onConflictDoNothing` anyway.
|
|
70
|
+
*
|
|
71
|
+
* Column names come from the schema, never from `values`.
|
|
72
|
+
*/
|
|
73
|
+
export const excludedSet = <TTable extends PgTable>(
|
|
74
|
+
table: TTable,
|
|
75
|
+
values: Record<string, unknown>,
|
|
76
|
+
{ target = "id" as ColumnKey<TTable> }: ExcludedSetOptions<TTable> = {}
|
|
77
|
+
): Record<string, SQL> => {
|
|
78
|
+
const columns: Record<string, { name: string }> = getColumns(table)
|
|
79
|
+
const targets: readonly string[] =
|
|
80
|
+
typeof target === "string" ? [target] : target
|
|
81
|
+
const set: Record<string, SQL> = {}
|
|
82
|
+
|
|
83
|
+
for (const [key, column] of Object.entries(columns)) {
|
|
84
|
+
if (key in values && values[key] !== undefined && !targets.includes(key)) {
|
|
85
|
+
set[key] = sql`excluded.${sql.identifier(column.name)}`
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (Object.keys(set).length > 0) {
|
|
90
|
+
return set
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const fallback = targets.find((key) => key in columns)
|
|
94
|
+
const fallbackColumn = fallback === undefined ? undefined : columns[fallback]
|
|
95
|
+
|
|
96
|
+
if (fallback !== undefined && fallbackColumn) {
|
|
97
|
+
set[fallback] = sql`excluded.${sql.identifier(fallbackColumn.name)}`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return set
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The same values with every `undefined` key dropped, so an insert and its
|
|
105
|
+
* `excludedSet` agree on which columns were supplied.
|
|
106
|
+
*/
|
|
107
|
+
export const definedValues = <T extends Record<string, unknown>>(
|
|
108
|
+
values: T
|
|
109
|
+
): T =>
|
|
110
|
+
Object.fromEntries(
|
|
111
|
+
Object.entries(values).filter(([, value]) => value !== undefined)
|
|
112
|
+
) as T
|
|
113
|
+
|
|
114
|
+
/** Table name to the schema its `<prefix>_<operation>_<table>` functions live in. */
|
|
115
|
+
export type FunctionPermissionTables = ReadonlyMap<string, string>
|
|
116
|
+
|
|
117
|
+
export type FunctionPermissionTablesOptions = {
|
|
118
|
+
/** Schemas whose tables are included. Default `["public"]`. */
|
|
119
|
+
schemas?: readonly string[]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const DEFAULT_SCHEMA = "public"
|
|
123
|
+
|
|
124
|
+
// A schema module exports tables directly; `defineRelations` wraps each in
|
|
125
|
+
// `{ table, name, relations }`. Both are accepted so either object works.
|
|
126
|
+
const tableOf = (value: unknown): PgTable | undefined => {
|
|
127
|
+
if (is(value, PgTable)) {
|
|
128
|
+
return value
|
|
129
|
+
}
|
|
130
|
+
if (typeof value === "object" && value !== null && "table" in value) {
|
|
131
|
+
const { table } = value
|
|
132
|
+
return is(table, PgTable) ? table : undefined
|
|
133
|
+
}
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The allowlist `selectFunctionPermissions` resolves a table name against,
|
|
139
|
+
* built from a Drizzle schema object (`import * as schema`) or the result of
|
|
140
|
+
* `defineRelations`. Build it once at module scope. Views and non-table
|
|
141
|
+
* exports are ignored; a table name that appears in two included schemas
|
|
142
|
+
* throws, since a bare name could not tell them apart.
|
|
143
|
+
*/
|
|
144
|
+
export const functionPermissionTables = (
|
|
145
|
+
schemaObject: Record<string, unknown>,
|
|
146
|
+
{ schemas = [DEFAULT_SCHEMA] }: FunctionPermissionTablesOptions = {}
|
|
147
|
+
): FunctionPermissionTables => {
|
|
148
|
+
const tables = new Map<string, string>()
|
|
149
|
+
|
|
150
|
+
for (const value of Object.values(schemaObject)) {
|
|
151
|
+
const table = tableOf(value)
|
|
152
|
+
if (!table) {
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
const { name, schema = DEFAULT_SCHEMA } = getTableConfig(table)
|
|
156
|
+
if (!schemas.includes(schema)) {
|
|
157
|
+
continue
|
|
158
|
+
}
|
|
159
|
+
const existing = tables.get(name)
|
|
160
|
+
if (existing !== undefined && existing !== schema) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Table "${name}" exists in both "${existing}" and "${schema}"; include only one of those schemas`
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
tables.set(name, schema)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return tables
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export type FunctionPermissions = Record<FunctionPolicyOperation, boolean>
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Anything with Drizzle's `execute` returning rows: a postgres-js client from
|
|
175
|
+
* `@zeno-lib/db`, a `PgAsyncDatabase`, or a transaction.
|
|
176
|
+
*/
|
|
177
|
+
export type QueryExecutor = {
|
|
178
|
+
execute: (query: SQL) => PromiseLike<readonly Record<string, unknown>[]>
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type SelectFunctionPermissionsOptions = {
|
|
182
|
+
/** From `functionPermissionTables`. */
|
|
183
|
+
tables: FunctionPermissionTables
|
|
184
|
+
/** The table asked about, checked against `tables`. */
|
|
185
|
+
table: string
|
|
186
|
+
/**
|
|
187
|
+
* The row, passed to every function but `insert` (there is no row yet) as
|
|
188
|
+
* a named argument. Omit to call all four with none.
|
|
189
|
+
*/
|
|
190
|
+
id?: number | string
|
|
191
|
+
/** Name of the functions' row parameter. Default `"id"`. */
|
|
192
|
+
argumentName?: string
|
|
193
|
+
/** Function prefix, as given to `functionPolicies`. Default `"can"`. */
|
|
194
|
+
prefix?: string
|
|
195
|
+
/** Schema the functions live in. Default: the table's own. */
|
|
196
|
+
functionSchema?: string
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The four `<prefix>_<operation>_<table>()` answers for one table, in one
|
|
201
|
+
* statement: the read side of `functionPolicies`, for showing or hiding UI.
|
|
202
|
+
*
|
|
203
|
+
* `table` is only ever a map key: every identifier that reaches the statement
|
|
204
|
+
* comes from `tables` and is quoted with `sql.identifier`. A function missing
|
|
205
|
+
* or with another signature raises `42883` (`SqlState.undefinedFunction`).
|
|
206
|
+
* A `null` answer, which a function gives for a row the caller cannot see,
|
|
207
|
+
* counts as `false`, as does anything else that is not `true`.
|
|
208
|
+
*/
|
|
209
|
+
export const selectFunctionPermissions = async (
|
|
210
|
+
db: QueryExecutor,
|
|
211
|
+
{
|
|
212
|
+
argumentName = "id",
|
|
213
|
+
functionSchema,
|
|
214
|
+
id,
|
|
215
|
+
prefix = DEFAULT_FUNCTION_PREFIX,
|
|
216
|
+
table,
|
|
217
|
+
tables,
|
|
218
|
+
}: SelectFunctionPermissionsOptions
|
|
219
|
+
): Promise<FunctionPermissions> => {
|
|
220
|
+
const tableSchema = tables.get(table)
|
|
221
|
+
|
|
222
|
+
if (tableSchema === undefined) {
|
|
223
|
+
throw new Error(`No permission functions for table "${table}"`)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const schema = functionSchema ?? tableSchema
|
|
227
|
+
|
|
228
|
+
const call = (operation: FunctionPolicyOperation) => {
|
|
229
|
+
const callee = sql`${sql.identifier(schema)}.${sql.identifier(functionPolicyName(prefix, operation, table))}`
|
|
230
|
+
return operation === "insert" || id === undefined
|
|
231
|
+
? sql`${callee}()`
|
|
232
|
+
: sql`${callee}(${sql.identifier(argumentName)} => ${id})`
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const row = one(
|
|
236
|
+
await db.execute(
|
|
237
|
+
sql`select ${sql.join(
|
|
238
|
+
FUNCTION_POLICY_OPERATIONS.map(
|
|
239
|
+
(operation) => sql`${call(operation)} as ${sql.identifier(operation)}`
|
|
240
|
+
),
|
|
241
|
+
sql`, `
|
|
242
|
+
)}`
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
delete: row.delete === true,
|
|
248
|
+
insert: row.insert === true,
|
|
249
|
+
select: row.select === true,
|
|
250
|
+
update: row.update === true,
|
|
251
|
+
}
|
|
252
|
+
}
|
package/src/schema.test-d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
primaryId,
|
|
8
8
|
sequentialPrimaryId,
|
|
9
9
|
table,
|
|
10
|
+
timestamps,
|
|
10
11
|
userId,
|
|
11
12
|
uuidPrimaryId,
|
|
12
13
|
} from "./schema.ts"
|
|
@@ -119,3 +120,26 @@ test("reference actions are rejected when there is no reference", () => {
|
|
|
119
120
|
// @ts-expect-error actions are meaningless without a foreign key
|
|
120
121
|
authUserId({ actions: { onDelete: "cascade" }, reference: null })
|
|
121
122
|
})
|
|
123
|
+
|
|
124
|
+
test("timestamps reads a Date by default and a string with mode: string", () => {
|
|
125
|
+
const dated = table("dated", { ...timestamps() })
|
|
126
|
+
const stringly = table("stringly", { ...timestamps({ mode: "string" }) })
|
|
127
|
+
const audited = table("audited_strings", {
|
|
128
|
+
...auditColumns({ mode: "string" }),
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
expectTypeOf<
|
|
132
|
+
(typeof dated)["$inferSelect"]["createdAt"]
|
|
133
|
+
>().toEqualTypeOf<Date>()
|
|
134
|
+
expectTypeOf<
|
|
135
|
+
(typeof stringly)["$inferSelect"]["updatedAt"]
|
|
136
|
+
>().toEqualTypeOf<string>()
|
|
137
|
+
expectTypeOf<
|
|
138
|
+
(typeof audited)["$inferSelect"]["createdAt"]
|
|
139
|
+
>().toEqualTypeOf<string>()
|
|
140
|
+
// Both still have a default, so neither is required on insert.
|
|
141
|
+
expectTypeOf<(typeof stringly)["$inferInsert"]>().toEqualTypeOf<{
|
|
142
|
+
createdAt?: string | undefined
|
|
143
|
+
updatedAt?: string | undefined
|
|
144
|
+
}>()
|
|
145
|
+
})
|
package/src/schema.ts
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
type PgBigInt64Builder,
|
|
11
11
|
type PgIntegerBuilder,
|
|
12
12
|
type PgPolicyConfig,
|
|
13
|
+
type PgTimestampBuilder,
|
|
14
|
+
type PgTimestampStringBuilder,
|
|
13
15
|
type PgUUIDBuilder,
|
|
14
16
|
type Precision,
|
|
15
17
|
pgPolicy,
|
|
@@ -24,6 +26,12 @@ import {
|
|
|
24
26
|
import { snakeCase } from "drizzle-orm/pg-core/casing"
|
|
25
27
|
import { authenticatedRole, authUid } from "drizzle-orm/supabase"
|
|
26
28
|
import { authUsers } from "./auth-schema.ts"
|
|
29
|
+
import {
|
|
30
|
+
DEFAULT_FUNCTION_PREFIX,
|
|
31
|
+
FUNCTION_POLICY_OPERATIONS,
|
|
32
|
+
type FunctionPolicyOperation,
|
|
33
|
+
functionPolicyName,
|
|
34
|
+
} from "./function-names.ts"
|
|
27
35
|
|
|
28
36
|
// pg-core primitives without the `pg` prefix they repeat at every call site.
|
|
29
37
|
// `table` is missing on purpose. Zeno's own is at the bottom of this file.
|
|
@@ -56,12 +64,34 @@ export {
|
|
|
56
64
|
supabaseAuthAdminRole,
|
|
57
65
|
} from "drizzle-orm/supabase"
|
|
58
66
|
|
|
59
|
-
|
|
67
|
+
/** How a timestamp column is read back: a `Date` (the default) or Postgres's own text. */
|
|
68
|
+
export type TimestampMode = "date" | "string"
|
|
69
|
+
|
|
70
|
+
export type TimestampsOptions<TMode extends TimestampMode = "date"> = {
|
|
71
|
+
/**
|
|
72
|
+
* `"date"` (the default) reads a `Date`; `"string"` keeps Postgres's text
|
|
73
|
+
* form, which also keeps the microseconds a `Date` truncates. A JavaScript
|
|
74
|
+
* mapping only: it emits no SQL.
|
|
75
|
+
*/
|
|
76
|
+
mode?: TMode
|
|
60
77
|
withTimezone?: boolean
|
|
61
78
|
/** Fractional-second digits. Postgres allows 0 to 6. */
|
|
62
79
|
precision?: Precision
|
|
63
80
|
}
|
|
64
81
|
|
|
82
|
+
// Mirrors drizzle's own `timestamp()` overload: only an exact `"string"` gives
|
|
83
|
+
// the string builder.
|
|
84
|
+
type TimestampColumn<TMode extends TimestampMode> = SetHasDefault<
|
|
85
|
+
SetNotNull<
|
|
86
|
+
[TMode] extends ["string"] ? PgTimestampStringBuilder : PgTimestampBuilder
|
|
87
|
+
>
|
|
88
|
+
>
|
|
89
|
+
|
|
90
|
+
export type TimestampColumns<TMode extends TimestampMode = "date"> = {
|
|
91
|
+
createdAt: TimestampColumn<TMode>
|
|
92
|
+
updatedAt: TimestampColumn<TMode>
|
|
93
|
+
}
|
|
94
|
+
|
|
65
95
|
// `created_at` is a column DEFAULT, so Postgres fills it for every writer.
|
|
66
96
|
// `updated_at` has no equivalent: SQL has no "on update" default, and Drizzle's
|
|
67
97
|
// `$onUpdateFn` is applied while Drizzle builds its own statement, so a write
|
|
@@ -74,16 +104,22 @@ type TimestampsOptions = {
|
|
|
74
104
|
// than a shared object, because Drizzle's builder methods mutate `this` and
|
|
75
105
|
// return it. One builder in two tables would leak `.notNull()`, `.references()`
|
|
76
106
|
// and its name from whichever table customised it first.
|
|
77
|
-
export const timestamps = ({
|
|
107
|
+
export const timestamps = <TMode extends TimestampMode = "date">({
|
|
108
|
+
mode,
|
|
78
109
|
precision,
|
|
79
110
|
withTimezone = true,
|
|
80
|
-
}: TimestampsOptions = {}) => {
|
|
81
|
-
const
|
|
111
|
+
}: TimestampsOptions<TMode> = {}): TimestampColumns<TMode> => {
|
|
112
|
+
const column = (name: string) =>
|
|
113
|
+
mode === "string"
|
|
114
|
+
? timestamp(name, { mode: "string", precision, withTimezone })
|
|
115
|
+
.notNull()
|
|
116
|
+
.defaultNow()
|
|
117
|
+
: timestamp(name, { precision, withTimezone }).notNull().defaultNow()
|
|
82
118
|
|
|
83
119
|
return {
|
|
84
|
-
createdAt:
|
|
85
|
-
updatedAt:
|
|
86
|
-
}
|
|
120
|
+
createdAt: column("created_at"),
|
|
121
|
+
updatedAt: column("updated_at"),
|
|
122
|
+
} as TimestampColumns<TMode>
|
|
87
123
|
}
|
|
88
124
|
|
|
89
125
|
type ReferenceActions = ReferenceConfig["config"]
|
|
@@ -208,10 +244,13 @@ export const authorship = <TNotNull extends boolean = false>(
|
|
|
208
244
|
})
|
|
209
245
|
|
|
210
246
|
// Takes both halves' options, since it builds both halves.
|
|
211
|
-
export const auditColumns = <
|
|
212
|
-
|
|
247
|
+
export const auditColumns = <
|
|
248
|
+
TNotNull extends boolean = false,
|
|
249
|
+
TMode extends TimestampMode = "date",
|
|
250
|
+
>(
|
|
251
|
+
options: AuthorshipOptions<TNotNull> & TimestampsOptions<TMode> = {}
|
|
213
252
|
) => ({
|
|
214
|
-
...timestamps(options),
|
|
253
|
+
...timestamps<TMode>(options),
|
|
215
254
|
...authorship<TNotNull>(options),
|
|
216
255
|
})
|
|
217
256
|
|
|
@@ -414,15 +453,6 @@ const POLICY_BUILDERS = {
|
|
|
414
453
|
update: updatePolicy,
|
|
415
454
|
} as const satisfies Record<PolicyOperation, typeof selectPolicy>
|
|
416
455
|
|
|
417
|
-
const FUNCTION_POLICY_OPERATIONS = [
|
|
418
|
-
"select",
|
|
419
|
-
"insert",
|
|
420
|
-
"update",
|
|
421
|
-
"delete",
|
|
422
|
-
] as const
|
|
423
|
-
|
|
424
|
-
type FunctionPolicyOperation = (typeof FUNCTION_POLICY_OPERATIONS)[number]
|
|
425
|
-
|
|
426
456
|
/** Columns one function is called with. `null` calls it with none. */
|
|
427
457
|
type FunctionArgument = AnyPgColumn | readonly AnyPgColumn[] | null
|
|
428
458
|
|
|
@@ -515,7 +545,7 @@ export const functionPolicies = (
|
|
|
515
545
|
{
|
|
516
546
|
argument,
|
|
517
547
|
name,
|
|
518
|
-
prefix =
|
|
548
|
+
prefix = DEFAULT_FUNCTION_PREFIX,
|
|
519
549
|
schema: functionSchema,
|
|
520
550
|
}: FunctionPoliciesOptions = {}
|
|
521
551
|
) => {
|
|
@@ -528,7 +558,7 @@ export const functionPolicies = (
|
|
|
528
558
|
const tableName = getTableName(getColumnTable(firstColumn))
|
|
529
559
|
|
|
530
560
|
return FUNCTION_POLICY_OPERATIONS.map((operation) => {
|
|
531
|
-
const functionName =
|
|
561
|
+
const functionName = functionPolicyName(prefix, operation, tableName)
|
|
532
562
|
// Unqualified by default, so the name resolves through `search_path` the
|
|
533
563
|
// way a hand-written policy would; `schema` pins it to the functions that
|
|
534
564
|
// live beside their table instead.
|