@prisma-next/adapter-postgres 0.3.0-dev.4 → 0.3.0-dev.6

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.
Files changed (47) hide show
  1. package/dist/{exports/chunk-B5SU5BVC.js → chunk-HD5YISNQ.js} +1 -1
  2. package/dist/chunk-HD5YISNQ.js.map +1 -0
  3. package/dist/{exports/chunk-CPAKRHXM.js → chunk-J3XSOAM2.js} +1 -1
  4. package/dist/chunk-J3XSOAM2.js.map +1 -0
  5. package/dist/{exports/chunk-ZHJOVBWT.js → chunk-T6S3A6VT.js} +2 -2
  6. package/dist/chunk-T6S3A6VT.js.map +1 -0
  7. package/dist/core/adapter.d.ts +19 -0
  8. package/dist/core/adapter.d.ts.map +1 -0
  9. package/dist/core/codecs.d.ts +110 -0
  10. package/dist/core/codecs.d.ts.map +1 -0
  11. package/dist/core/control-adapter.d.ts +33 -0
  12. package/dist/core/control-adapter.d.ts.map +1 -0
  13. package/dist/core/descriptor-meta.d.ts +72 -0
  14. package/dist/core/descriptor-meta.d.ts.map +1 -0
  15. package/dist/core/types.d.ts +16 -0
  16. package/dist/core/types.d.ts.map +1 -0
  17. package/dist/exports/adapter.d.ts +2 -21
  18. package/dist/exports/adapter.d.ts.map +1 -0
  19. package/dist/exports/adapter.js +2 -2
  20. package/dist/exports/codec-types.d.ts +7 -34
  21. package/dist/exports/codec-types.d.ts.map +1 -0
  22. package/dist/exports/codec-types.js +1 -1
  23. package/dist/exports/column-types.d.ts +11 -14
  24. package/dist/exports/column-types.d.ts.map +1 -0
  25. package/dist/exports/control.d.ts +4 -5
  26. package/dist/exports/control.d.ts.map +1 -0
  27. package/dist/exports/control.js +1 -1
  28. package/dist/exports/runtime.d.ts +6 -8
  29. package/dist/exports/runtime.d.ts.map +1 -0
  30. package/dist/exports/runtime.js +3 -3
  31. package/dist/exports/types.d.ts +2 -19
  32. package/dist/exports/types.d.ts.map +1 -0
  33. package/package.json +15 -14
  34. package/src/core/adapter.ts +429 -0
  35. package/src/core/codecs.ts +194 -0
  36. package/src/core/control-adapter.ts +375 -0
  37. package/src/core/descriptor-meta.ts +41 -0
  38. package/src/core/types.ts +53 -0
  39. package/src/exports/adapter.ts +1 -0
  40. package/src/exports/codec-types.ts +11 -0
  41. package/src/exports/column-types.ts +53 -0
  42. package/src/exports/control.ts +20 -0
  43. package/src/exports/runtime.ts +32 -0
  44. package/src/exports/types.ts +14 -0
  45. package/dist/exports/chunk-B5SU5BVC.js.map +0 -1
  46. package/dist/exports/chunk-CPAKRHXM.js.map +0 -1
  47. package/dist/exports/chunk-ZHJOVBWT.js.map +0 -1
@@ -1,19 +1,2 @@
1
- import { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
2
- export { StorageColumn, StorageTable } from '@prisma-next/sql-contract/types';
3
- import { LoweredStatement, ColumnRef, ParamRef, Direction } from '@prisma-next/sql-relational-core/ast';
4
- export { BinaryExpr, ColumnRef, Direction, ParamRef, SelectAst } from '@prisma-next/sql-relational-core/ast';
5
-
6
- interface PostgresAdapterOptions {
7
- readonly profileId?: string;
8
- }
9
- type PostgresContract = SqlContract<SqlStorage> & {
10
- readonly target: 'postgres';
11
- };
12
- type Expr = ColumnRef | ParamRef;
13
- interface OrderClause {
14
- readonly expr: ColumnRef;
15
- readonly dir: Direction;
16
- }
17
- type PostgresLoweredStatement = LoweredStatement;
18
-
19
- export type { Expr, OrderClause, PostgresAdapterOptions, PostgresContract, PostgresLoweredStatement };
1
+ export type { BinaryExpr, ColumnRef, Direction, Expr, OrderClause, ParamRef, PostgresAdapterOptions, PostgresContract, PostgresLoweredStatement, SelectAst, StorageColumn, StorageTable, } from '../core/types';
2
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/exports/types.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,IAAI,EACJ,WAAW,EACX,QAAQ,EACR,sBAAsB,EACtB,gBAAgB,EAChB,wBAAwB,EACxB,SAAS,EACT,aAAa,EACb,YAAY,GACb,MAAM,eAAe,CAAC"}
package/package.json CHANGED
@@ -1,24 +1,25 @@
1
1
  {
2
2
  "name": "@prisma-next/adapter-postgres",
3
- "version": "0.3.0-dev.4",
3
+ "version": "0.3.0-dev.6",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "files": [
7
- "dist"
7
+ "dist",
8
+ "src"
8
9
  ],
9
10
  "dependencies": {
10
11
  "arktype": "^2.0.0",
11
- "@prisma-next/cli": "0.3.0-dev.4",
12
- "@prisma-next/contract": "0.3.0-dev.4",
13
- "@prisma-next/contract-authoring": "0.3.0-dev.4",
14
- "@prisma-next/core-control-plane": "0.3.0-dev.4",
15
- "@prisma-next/core-execution-plane": "0.3.0-dev.4",
16
- "@prisma-next/family-sql": "0.3.0-dev.4",
17
- "@prisma-next/sql-contract-ts": "0.3.0-dev.4",
18
- "@prisma-next/sql-contract": "0.3.0-dev.4",
19
- "@prisma-next/sql-operations": "0.3.0-dev.4",
20
- "@prisma-next/sql-relational-core": "0.3.0-dev.4",
21
- "@prisma-next/sql-schema-ir": "0.3.0-dev.4"
12
+ "@prisma-next/contract": "0.3.0-dev.6",
13
+ "@prisma-next/contract-authoring": "0.3.0-dev.6",
14
+ "@prisma-next/cli": "0.3.0-dev.6",
15
+ "@prisma-next/core-control-plane": "0.3.0-dev.6",
16
+ "@prisma-next/family-sql": "0.3.0-dev.6",
17
+ "@prisma-next/sql-contract-ts": "0.3.0-dev.6",
18
+ "@prisma-next/sql-contract": "0.3.0-dev.6",
19
+ "@prisma-next/sql-operations": "0.3.0-dev.6",
20
+ "@prisma-next/sql-relational-core": "0.3.0-dev.6",
21
+ "@prisma-next/sql-schema-ir": "0.3.0-dev.6",
22
+ "@prisma-next/core-execution-plane": "0.3.0-dev.6"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@vitest/coverage-v8": "4.0.16",
@@ -55,7 +56,7 @@
55
56
  }
56
57
  },
57
58
  "scripts": {
58
- "build": "tsup --config tsup.config.ts",
59
+ "build": "tsup --config tsup.config.ts && tsc --project tsconfig.build.json",
59
60
  "test": "vitest run",
60
61
  "test:coverage": "vitest run --coverage",
61
62
  "typecheck": "tsc --project tsconfig.json --noEmit",
@@ -0,0 +1,429 @@
1
+ import type {
2
+ Adapter,
3
+ AdapterProfile,
4
+ BinaryExpr,
5
+ ColumnRef,
6
+ DeleteAst,
7
+ ExistsExpr,
8
+ IncludeRef,
9
+ InsertAst,
10
+ JoinAst,
11
+ LiteralExpr,
12
+ LowererContext,
13
+ OperationExpr,
14
+ ParamRef,
15
+ QueryAst,
16
+ SelectAst,
17
+ UpdateAst,
18
+ } from '@prisma-next/sql-relational-core/ast';
19
+ import { createCodecRegistry, isOperationExpr } from '@prisma-next/sql-relational-core/ast';
20
+ import { codecDefinitions } from './codecs';
21
+ import type { PostgresAdapterOptions, PostgresContract, PostgresLoweredStatement } from './types';
22
+
23
+ const VECTOR_CODEC_ID = 'pg/vector@1' as const;
24
+
25
+ const defaultCapabilities = Object.freeze({
26
+ postgres: {
27
+ orderBy: true,
28
+ limit: true,
29
+ lateral: true,
30
+ jsonAgg: true,
31
+ returning: true,
32
+ },
33
+ });
34
+
35
+ class PostgresAdapterImpl implements Adapter<QueryAst, PostgresContract, PostgresLoweredStatement> {
36
+ // These fields make the adapter instance structurally compatible with
37
+ // RuntimeAdapterInstance<'sql', 'postgres'> without introducing a runtime-plane dependency.
38
+ readonly familyId = 'sql' as const;
39
+ readonly targetId = 'postgres' as const;
40
+
41
+ readonly profile: AdapterProfile<'postgres'>;
42
+ private readonly codecRegistry = (() => {
43
+ const registry = createCodecRegistry();
44
+ for (const definition of Object.values(codecDefinitions)) {
45
+ registry.register(definition.codec);
46
+ }
47
+ return registry;
48
+ })();
49
+
50
+ constructor(options?: PostgresAdapterOptions) {
51
+ this.profile = Object.freeze({
52
+ id: options?.profileId ?? 'postgres/default@1',
53
+ target: 'postgres',
54
+ capabilities: defaultCapabilities,
55
+ codecs: () => this.codecRegistry,
56
+ });
57
+ }
58
+
59
+ lower(ast: QueryAst, context: LowererContext<PostgresContract>) {
60
+ let sql: string;
61
+ const params = context.params ? [...context.params] : [];
62
+
63
+ if (ast.kind === 'select') {
64
+ sql = renderSelect(ast, context.contract);
65
+ } else if (ast.kind === 'insert') {
66
+ sql = renderInsert(ast, context.contract);
67
+ } else if (ast.kind === 'update') {
68
+ sql = renderUpdate(ast, context.contract);
69
+ } else if (ast.kind === 'delete') {
70
+ sql = renderDelete(ast, context.contract);
71
+ } else {
72
+ throw new Error(`Unsupported AST kind: ${(ast as { kind: string }).kind}`);
73
+ }
74
+
75
+ return Object.freeze({
76
+ profileId: this.profile.id,
77
+ body: Object.freeze({ sql, params }),
78
+ });
79
+ }
80
+ }
81
+
82
+ function renderSelect(ast: SelectAst, contract?: PostgresContract): string {
83
+ const selectClause = `SELECT ${renderProjection(ast, contract)}`;
84
+ const fromClause = `FROM ${quoteIdentifier(ast.from.name)}`;
85
+
86
+ const joinsClause = ast.joins?.length
87
+ ? ast.joins.map((join) => renderJoin(join, contract)).join(' ')
88
+ : '';
89
+ const includesClause = ast.includes?.length
90
+ ? ast.includes.map((include) => renderInclude(include, contract)).join(' ')
91
+ : '';
92
+
93
+ const whereClause = ast.where ? ` WHERE ${renderWhere(ast.where, contract)}` : '';
94
+ const orderClause = ast.orderBy?.length
95
+ ? ` ORDER BY ${ast.orderBy
96
+ .map((order) => {
97
+ const expr = renderExpr(order.expr as ColumnRef | OperationExpr, contract);
98
+ return `${expr} ${order.dir.toUpperCase()}`;
99
+ })
100
+ .join(', ')}`
101
+ : '';
102
+ const limitClause = typeof ast.limit === 'number' ? ` LIMIT ${ast.limit}` : '';
103
+
104
+ const clauses = [joinsClause, includesClause].filter(Boolean).join(' ');
105
+ return `${selectClause} ${fromClause}${clauses ? ` ${clauses}` : ''}${whereClause}${orderClause}${limitClause}`.trim();
106
+ }
107
+
108
+ function renderProjection(ast: SelectAst, contract?: PostgresContract): string {
109
+ return ast.project
110
+ .map((item) => {
111
+ const expr = item.expr as ColumnRef | IncludeRef | OperationExpr | LiteralExpr;
112
+ if (expr.kind === 'includeRef') {
113
+ // For include references, select the column from the LATERAL join alias
114
+ // The LATERAL subquery returns a single column (the JSON array) with the alias
115
+ // The table is aliased as {alias}_lateral, and the column inside is aliased as the include alias
116
+ // We select it using table_alias.column_alias
117
+ const tableAlias = `${expr.alias}_lateral`;
118
+ return `${quoteIdentifier(tableAlias)}.${quoteIdentifier(expr.alias)} AS ${quoteIdentifier(item.alias)}`;
119
+ }
120
+ if (expr.kind === 'operation') {
121
+ const operation = renderOperation(expr, contract);
122
+ const alias = quoteIdentifier(item.alias);
123
+ return `${operation} AS ${alias}`;
124
+ }
125
+ if (expr.kind === 'literal') {
126
+ const literal = renderLiteral(expr);
127
+ const alias = quoteIdentifier(item.alias);
128
+ return `${literal} AS ${alias}`;
129
+ }
130
+ const column = renderColumn(expr as ColumnRef);
131
+ const alias = quoteIdentifier(item.alias);
132
+ return `${column} AS ${alias}`;
133
+ })
134
+ .join(', ');
135
+ }
136
+
137
+ function renderWhere(expr: BinaryExpr | ExistsExpr, contract?: PostgresContract): string {
138
+ if (expr.kind === 'exists') {
139
+ const notKeyword = expr.not ? 'NOT ' : '';
140
+ const subquery = renderSelect(expr.subquery, contract);
141
+ return `${notKeyword}EXISTS (${subquery})`;
142
+ }
143
+ return renderBinary(expr, contract);
144
+ }
145
+
146
+ function renderBinary(expr: BinaryExpr, contract?: PostgresContract): string {
147
+ const leftExpr = expr.left as ColumnRef | OperationExpr;
148
+ const left = renderExpr(leftExpr, contract);
149
+ // Handle both ParamRef and ColumnRef on the right side
150
+ // (ColumnRef can appear in EXISTS subqueries for correlation)
151
+ const rightExpr = expr.right as ParamRef | ColumnRef;
152
+ const right =
153
+ rightExpr.kind === 'col'
154
+ ? renderColumn(rightExpr)
155
+ : renderParam(rightExpr as ParamRef, contract);
156
+ // Only wrap in parentheses if it's an operation expression
157
+ const leftRendered = isOperationExpr(leftExpr) ? `(${left})` : left;
158
+
159
+ // Map operators to SQL symbols
160
+ const operatorMap: Record<BinaryExpr['op'], string> = {
161
+ eq: '=',
162
+ neq: '!=',
163
+ gt: '>',
164
+ lt: '<',
165
+ gte: '>=',
166
+ lte: '<=',
167
+ };
168
+
169
+ return `${leftRendered} ${operatorMap[expr.op]} ${right}`;
170
+ }
171
+
172
+ function renderColumn(ref: ColumnRef): string {
173
+ return `${quoteIdentifier(ref.table)}.${quoteIdentifier(ref.column)}`;
174
+ }
175
+
176
+ function renderExpr(expr: ColumnRef | OperationExpr, contract?: PostgresContract): string {
177
+ if (isOperationExpr(expr)) {
178
+ return renderOperation(expr, contract);
179
+ }
180
+ return renderColumn(expr);
181
+ }
182
+
183
+ function renderParam(
184
+ ref: ParamRef,
185
+ contract?: PostgresContract,
186
+ tableName?: string,
187
+ columnName?: string,
188
+ ): string {
189
+ // Cast vector parameters to vector type for PostgreSQL
190
+ if (contract && tableName && columnName) {
191
+ const tableMeta = contract.storage.tables[tableName];
192
+ const columnMeta = tableMeta?.columns[columnName];
193
+ if (columnMeta?.codecId === VECTOR_CODEC_ID) {
194
+ return `$${ref.index}::vector`;
195
+ }
196
+ }
197
+ return `$${ref.index}`;
198
+ }
199
+
200
+ function renderLiteral(expr: LiteralExpr): string {
201
+ if (typeof expr.value === 'string') {
202
+ return `'${expr.value.replace(/'/g, "''")}'`;
203
+ }
204
+ if (typeof expr.value === 'number' || typeof expr.value === 'boolean') {
205
+ return String(expr.value);
206
+ }
207
+ if (expr.value === null) {
208
+ return 'NULL';
209
+ }
210
+ if (Array.isArray(expr.value)) {
211
+ return `ARRAY[${expr.value.map((v: unknown) => renderLiteral({ kind: 'literal', value: v })).join(', ')}]`;
212
+ }
213
+ return JSON.stringify(expr.value);
214
+ }
215
+
216
+ function renderOperation(expr: OperationExpr, contract?: PostgresContract): string {
217
+ const self = renderExpr(expr.self, contract);
218
+ // For vector operations, cast param arguments to vector type
219
+ const isVectorOperation = expr.forTypeId === VECTOR_CODEC_ID;
220
+ const args = expr.args.map((arg: ColumnRef | ParamRef | LiteralExpr | OperationExpr) => {
221
+ if (arg.kind === 'col') {
222
+ return renderColumn(arg);
223
+ }
224
+ if (arg.kind === 'param') {
225
+ // Cast vector operation parameters to vector type
226
+ return isVectorOperation ? `$${arg.index}::vector` : renderParam(arg, contract);
227
+ }
228
+ if (arg.kind === 'literal') {
229
+ return renderLiteral(arg);
230
+ }
231
+ if (arg.kind === 'operation') {
232
+ return renderOperation(arg, contract);
233
+ }
234
+ const _exhaustive: never = arg;
235
+ throw new Error(`Unsupported argument kind: ${(_exhaustive as { kind: string }).kind}`);
236
+ });
237
+
238
+ let result = expr.lowering.template;
239
+ result = result.replace(/\$\{self\}/g, self);
240
+ for (let i = 0; i < args.length; i++) {
241
+ result = result.replace(new RegExp(`\\$\\{arg${i}\\}`, 'g'), args[i] ?? '');
242
+ }
243
+
244
+ if (expr.lowering.strategy === 'function') {
245
+ return result;
246
+ }
247
+
248
+ return result;
249
+ }
250
+
251
+ function renderJoin(join: JoinAst, _contract?: PostgresContract): string {
252
+ const joinType = join.joinType.toUpperCase();
253
+ const table = quoteIdentifier(join.table.name);
254
+ const onClause = renderJoinOn(join.on);
255
+ return `${joinType} JOIN ${table} ON ${onClause}`;
256
+ }
257
+
258
+ function renderJoinOn(on: JoinAst['on']): string {
259
+ if (on.kind === 'eqCol') {
260
+ const left = renderColumn(on.left);
261
+ const right = renderColumn(on.right);
262
+ return `${left} = ${right}`;
263
+ }
264
+ throw new Error(`Unsupported join ON expression kind: ${on.kind}`);
265
+ }
266
+
267
+ function renderInclude(
268
+ include: NonNullable<SelectAst['includes']>[number],
269
+ contract?: PostgresContract,
270
+ ): string {
271
+ const alias = include.alias;
272
+
273
+ // Build the lateral subquery
274
+ const childProjection = include.child.project
275
+ .map((item: { alias: string; expr: ColumnRef | OperationExpr }) => {
276
+ const expr = renderExpr(item.expr, contract);
277
+ return `'${item.alias}', ${expr}`;
278
+ })
279
+ .join(', ');
280
+
281
+ const jsonBuildObject = `json_build_object(${childProjection})`;
282
+
283
+ // Build the ON condition from the include's ON clause - this goes in the WHERE clause
284
+ const onCondition = renderJoinOn(include.child.on);
285
+
286
+ // Build WHERE clause: combine ON condition with any additional WHERE clauses
287
+ let whereClause = ` WHERE ${onCondition}`;
288
+ if (include.child.where) {
289
+ whereClause += ` AND ${renderWhere(include.child.where, contract)}`;
290
+ }
291
+
292
+ // Add ORDER BY if present - it goes inside json_agg() call
293
+ const childOrderBy = include.child.orderBy?.length
294
+ ? ` ORDER BY ${include.child.orderBy
295
+ .map(
296
+ (order: { expr: ColumnRef | OperationExpr; dir: string }) =>
297
+ `${renderExpr(order.expr, contract)} ${order.dir.toUpperCase()}`,
298
+ )
299
+ .join(', ')}`
300
+ : '';
301
+
302
+ // Add LIMIT if present
303
+ const childLimit = typeof include.child.limit === 'number' ? ` LIMIT ${include.child.limit}` : '';
304
+
305
+ // Build the lateral subquery
306
+ // When ORDER BY is present without LIMIT, it goes inside json_agg() call: json_agg(expr ORDER BY ...)
307
+ // When LIMIT is present (with or without ORDER BY), we need to wrap in a subquery
308
+ const childTable = quoteIdentifier(include.child.table.name);
309
+ let subquery: string;
310
+ if (typeof include.child.limit === 'number') {
311
+ // With LIMIT, we need to wrap in a subquery
312
+ // Select individual columns in inner query, then aggregate
313
+ // Create a map of column references to their aliases for ORDER BY
314
+ // Only ColumnRef can be mapped (OperationExpr doesn't have table/column properties)
315
+ const columnAliasMap = new Map<string, string>();
316
+ for (const item of include.child.project) {
317
+ if (item.expr.kind === 'col') {
318
+ const columnKey = `${item.expr.table}.${item.expr.column}`;
319
+ columnAliasMap.set(columnKey, item.alias);
320
+ }
321
+ }
322
+
323
+ const innerColumns = include.child.project
324
+ .map((item: { alias: string; expr: ColumnRef | OperationExpr }) => {
325
+ const expr = renderExpr(item.expr, contract);
326
+ return `${expr} AS ${quoteIdentifier(item.alias)}`;
327
+ })
328
+ .join(', ');
329
+
330
+ // For ORDER BY, use column aliases if the column is in the SELECT list
331
+ const childOrderByWithAliases = include.child.orderBy?.length
332
+ ? ` ORDER BY ${include.child.orderBy
333
+ .map((order: { expr: ColumnRef | OperationExpr; dir: string }) => {
334
+ if (order.expr.kind === 'col') {
335
+ const columnKey = `${order.expr.table}.${order.expr.column}`;
336
+ const alias = columnAliasMap.get(columnKey);
337
+ if (alias) {
338
+ return `${quoteIdentifier(alias)} ${order.dir.toUpperCase()}`;
339
+ }
340
+ }
341
+ return `${renderExpr(order.expr, contract)} ${order.dir.toUpperCase()}`;
342
+ })
343
+ .join(', ')}`
344
+ : '';
345
+
346
+ const innerSelect = `SELECT ${innerColumns} FROM ${childTable}${whereClause}${childOrderByWithAliases}${childLimit}`;
347
+ subquery = `(SELECT json_agg(row_to_json(sub.*)) AS ${quoteIdentifier(alias)} FROM (${innerSelect}) sub)`;
348
+ } else if (childOrderBy) {
349
+ // With ORDER BY but no LIMIT, ORDER BY goes inside json_agg()
350
+ subquery = `(SELECT json_agg(${jsonBuildObject}${childOrderBy}) AS ${quoteIdentifier(alias)} FROM ${childTable}${whereClause})`;
351
+ } else {
352
+ // No ORDER BY or LIMIT
353
+ subquery = `(SELECT json_agg(${jsonBuildObject}) AS ${quoteIdentifier(alias)} FROM ${childTable}${whereClause})`;
354
+ }
355
+
356
+ // Return the LATERAL join with ON true (the condition is in the WHERE clause)
357
+ // The subquery returns a single column (the JSON array) with the alias
358
+ // We use a different alias for the table to avoid ambiguity when selecting the column
359
+ const tableAlias = `${alias}_lateral`;
360
+ return `LEFT JOIN LATERAL ${subquery} AS ${quoteIdentifier(tableAlias)} ON true`;
361
+ }
362
+
363
+ function quoteIdentifier(identifier: string): string {
364
+ return `"${identifier.replace(/"/g, '""')}"`;
365
+ }
366
+
367
+ function renderInsert(ast: InsertAst, contract: PostgresContract): string {
368
+ const table = quoteIdentifier(ast.table.name);
369
+ const columns = Object.keys(ast.values).map((col) => quoteIdentifier(col));
370
+ const tableMeta = contract.storage.tables[ast.table.name];
371
+ const values = Object.entries(ast.values).map(([colName, val]) => {
372
+ if (val.kind === 'param') {
373
+ const columnMeta = tableMeta?.columns[colName];
374
+ const isVector = columnMeta?.codecId === VECTOR_CODEC_ID;
375
+ return isVector ? `$${val.index}::vector` : `$${val.index}`;
376
+ }
377
+ if (val.kind === 'col') {
378
+ return `${quoteIdentifier(val.table)}.${quoteIdentifier(val.column)}`;
379
+ }
380
+ throw new Error(`Unsupported value kind in INSERT: ${(val as { kind: string }).kind}`);
381
+ });
382
+
383
+ const insertClause = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${values.join(', ')})`;
384
+ const returningClause = ast.returning?.length
385
+ ? ` RETURNING ${ast.returning.map((col) => `${quoteIdentifier(col.table)}.${quoteIdentifier(col.column)}`).join(', ')}`
386
+ : '';
387
+
388
+ return `${insertClause}${returningClause}`;
389
+ }
390
+
391
+ function renderUpdate(ast: UpdateAst, contract: PostgresContract): string {
392
+ const table = quoteIdentifier(ast.table.name);
393
+ const tableMeta = contract.storage.tables[ast.table.name];
394
+ const setClauses = Object.entries(ast.set).map(([col, val]) => {
395
+ const column = quoteIdentifier(col);
396
+ let value: string;
397
+ if (val.kind === 'param') {
398
+ const columnMeta = tableMeta?.columns[col];
399
+ const isVector = columnMeta?.codecId === VECTOR_CODEC_ID;
400
+ value = isVector ? `$${val.index}::vector` : `$${val.index}`;
401
+ } else if (val.kind === 'col') {
402
+ value = `${quoteIdentifier(val.table)}.${quoteIdentifier(val.column)}`;
403
+ } else {
404
+ throw new Error(`Unsupported value kind in UPDATE: ${(val as { kind: string }).kind}`);
405
+ }
406
+ return `${column} = ${value}`;
407
+ });
408
+
409
+ const whereClause = ` WHERE ${renderBinary(ast.where, contract)}`;
410
+ const returningClause = ast.returning?.length
411
+ ? ` RETURNING ${ast.returning.map((col) => `${quoteIdentifier(col.table)}.${quoteIdentifier(col.column)}`).join(', ')}`
412
+ : '';
413
+
414
+ return `UPDATE ${table} SET ${setClauses.join(', ')}${whereClause}${returningClause}`;
415
+ }
416
+
417
+ function renderDelete(ast: DeleteAst, contract?: PostgresContract): string {
418
+ const table = quoteIdentifier(ast.table.name);
419
+ const whereClause = ` WHERE ${renderBinary(ast.where, contract)}`;
420
+ const returningClause = ast.returning?.length
421
+ ? ` RETURNING ${ast.returning.map((col) => `${quoteIdentifier(col.table)}.${quoteIdentifier(col.column)}`).join(', ')}`
422
+ : '';
423
+
424
+ return `DELETE FROM ${table}${whereClause}${returningClause}`;
425
+ }
426
+
427
+ export function createPostgresAdapter(options?: PostgresAdapterOptions) {
428
+ return Object.freeze(new PostgresAdapterImpl(options));
429
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Unified codec definitions for Postgres adapter.
3
+ *
4
+ * This file contains a single source of truth for all codec information:
5
+ * - Scalar names
6
+ * - Type IDs
7
+ * - Codec implementations (runtime)
8
+ * - Type information (compile-time)
9
+ *
10
+ * This structure is used both at runtime (to populate the registry) and
11
+ * at compile time (to derive CodecTypes).
12
+ */
13
+
14
+ import { codec, defineCodecs } from '@prisma-next/sql-relational-core/ast';
15
+
16
+ // Create individual codec instances
17
+ const pgTextCodec = codec({
18
+ typeId: 'pg/text@1',
19
+ targetTypes: ['text'],
20
+ encode: (value: string): string => value,
21
+ decode: (wire: string): string => wire,
22
+ meta: {
23
+ db: {
24
+ sql: {
25
+ postgres: {
26
+ nativeType: 'text',
27
+ },
28
+ },
29
+ },
30
+ },
31
+ });
32
+
33
+ const pgInt4Codec = codec<'pg/int4@1', number, number>({
34
+ typeId: 'pg/int4@1',
35
+ targetTypes: ['int4'],
36
+ encode: (value) => value,
37
+ decode: (wire) => wire,
38
+ meta: {
39
+ db: {
40
+ sql: {
41
+ postgres: {
42
+ nativeType: 'integer',
43
+ },
44
+ },
45
+ },
46
+ },
47
+ });
48
+
49
+ const pgInt2Codec = codec<'pg/int2@1', number, number>({
50
+ typeId: 'pg/int2@1',
51
+ targetTypes: ['int2'],
52
+ encode: (value) => value,
53
+ decode: (wire) => wire,
54
+ meta: {
55
+ db: {
56
+ sql: {
57
+ postgres: {
58
+ nativeType: 'smallint',
59
+ },
60
+ },
61
+ },
62
+ },
63
+ });
64
+
65
+ const pgInt8Codec = codec<'pg/int8@1', number, number>({
66
+ typeId: 'pg/int8@1',
67
+ targetTypes: ['int8'],
68
+ encode: (value) => value,
69
+ decode: (wire) => wire,
70
+ meta: {
71
+ db: {
72
+ sql: {
73
+ postgres: {
74
+ nativeType: 'bigint',
75
+ },
76
+ },
77
+ },
78
+ },
79
+ });
80
+
81
+ const pgFloat4Codec = codec<'pg/float4@1', number, number>({
82
+ typeId: 'pg/float4@1',
83
+ targetTypes: ['float4'],
84
+ encode: (value) => value,
85
+ decode: (wire) => wire,
86
+ meta: {
87
+ db: {
88
+ sql: {
89
+ postgres: {
90
+ nativeType: 'real',
91
+ },
92
+ },
93
+ },
94
+ },
95
+ });
96
+
97
+ const pgFloat8Codec = codec<'pg/float8@1', number, number>({
98
+ typeId: 'pg/float8@1',
99
+ targetTypes: ['float8'],
100
+ encode: (value) => value,
101
+ decode: (wire) => wire,
102
+ meta: {
103
+ db: {
104
+ sql: {
105
+ postgres: {
106
+ nativeType: 'double precision',
107
+ },
108
+ },
109
+ },
110
+ },
111
+ });
112
+
113
+ const pgTimestampCodec = codec<'pg/timestamp@1', string | Date, string>({
114
+ typeId: 'pg/timestamp@1',
115
+ targetTypes: ['timestamp'],
116
+ encode: (value: string | Date): string => {
117
+ if (value instanceof Date) return value.toISOString();
118
+ if (typeof value === 'string') return value;
119
+ return String(value);
120
+ },
121
+ decode: (wire: string | Date): string => {
122
+ if (typeof wire === 'string') return wire;
123
+ if (wire instanceof Date) return wire.toISOString();
124
+ return String(wire);
125
+ },
126
+ meta: {
127
+ db: {
128
+ sql: {
129
+ postgres: {
130
+ nativeType: 'timestamp without time zone',
131
+ },
132
+ },
133
+ },
134
+ },
135
+ });
136
+
137
+ const pgTimestamptzCodec = codec<'pg/timestamptz@1', string | Date, string>({
138
+ typeId: 'pg/timestamptz@1',
139
+ targetTypes: ['timestamptz'],
140
+ encode: (value: string | Date): string => {
141
+ if (value instanceof Date) return value.toISOString();
142
+ if (typeof value === 'string') return value;
143
+ return String(value);
144
+ },
145
+ decode: (wire: string | Date): string => {
146
+ if (typeof wire === 'string') return wire;
147
+ if (wire instanceof Date) return wire.toISOString();
148
+ return String(wire);
149
+ },
150
+ meta: {
151
+ db: {
152
+ sql: {
153
+ postgres: {
154
+ nativeType: 'timestamp with time zone',
155
+ },
156
+ },
157
+ },
158
+ },
159
+ });
160
+
161
+ const pgBoolCodec = codec<'pg/bool@1', boolean, boolean>({
162
+ typeId: 'pg/bool@1',
163
+ targetTypes: ['bool'],
164
+ encode: (value) => value,
165
+ decode: (wire) => wire,
166
+ meta: {
167
+ db: {
168
+ sql: {
169
+ postgres: {
170
+ nativeType: 'boolean',
171
+ },
172
+ },
173
+ },
174
+ },
175
+ });
176
+
177
+ // Build codec definitions using the builder DSL
178
+ const codecs = defineCodecs()
179
+ .add('text', pgTextCodec)
180
+ .add('int4', pgInt4Codec)
181
+ .add('int2', pgInt2Codec)
182
+ .add('int8', pgInt8Codec)
183
+ .add('float4', pgFloat4Codec)
184
+ .add('float8', pgFloat8Codec)
185
+ .add('timestamp', pgTimestampCodec)
186
+ .add('timestamptz', pgTimestamptzCodec)
187
+ .add('bool', pgBoolCodec);
188
+
189
+ // Export derived structures directly from codecs builder
190
+ export const codecDefinitions = codecs.codecDefinitions;
191
+ export const dataTypes = codecs.dataTypes;
192
+
193
+ // Export types derived from codecs builder
194
+ export type CodecTypes = typeof codecs.CodecTypes;