@atscript/db-postgres 0.1.39 → 0.1.41

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/index.mjs CHANGED
@@ -1,31 +1,45 @@
1
+ import { t as PostgresPlugin } from "./plugin-C4tRUZGB.mjs";
1
2
  import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
2
3
  import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, finalizeParams, refActionToSql } from "@atscript/db-sql-tools";
3
- import { AnnotationSpec } from "@atscript/core";
4
-
5
- //#region packages/db-postgres/src/sql-builder.ts
4
+ //#region src/sql-builder.ts
5
+ /**
6
+ * PostgreSQL-aware default value for a given design type.
7
+ * PG uses native BOOLEAN (not TINYINT), so defaults must be `false`/`true` not `0`/`1`.
8
+ */
6
9
  function defaultValueForType$1(designType) {
7
10
  if (designType === "boolean") return "false";
8
11
  if (designType === "json" || designType === "object") return "'{}'";
9
12
  if (designType === "array") return "'[]'";
10
13
  return defaultValueForType(designType);
11
14
  }
15
+ /**
16
+ * PostgreSQL-aware default value literal.
17
+ * Converts boolean defaults to `true`/`false` instead of `1`/`0`.
18
+ */
12
19
  function defaultValueToSqlLiteral$1(designType, value) {
13
20
  if (designType === "boolean") return value === "true" || value === "1" ? "true" : "false";
14
21
  return defaultValueToSqlLiteral(designType, value);
15
22
  }
23
+ /** Escapes a PostgreSQL identifier by doubling double-quotes. */
16
24
  function esc(name) {
17
25
  return name.replace(/"/g, "\"\"");
18
26
  }
27
+ /** Double-quote-quotes a single identifier. */
19
28
  function qi(name) {
20
29
  return `"${esc(name)}"`;
21
30
  }
31
+ /**
32
+ * Double-quote-quotes a table name, handling `schema.table` format.
33
+ * Input is a raw name like `public.users` or just `users`.
34
+ */
22
35
  function quoteTableName(name) {
23
36
  const dot = name.indexOf(".");
24
37
  if (dot >= 0) return `${qi(name.slice(0, dot))}.${qi(name.slice(dot + 1))}`;
25
38
  return qi(name);
26
39
  }
27
- /** Converts JS values to SQL-bindable params, keeping booleans native. */ function toPgValue(value) {
28
- if (value === undefined) return null;
40
+ /** Converts JS values to SQL-bindable params, keeping booleans native. */
41
+ function toPgValue(value) {
42
+ if (value === void 0) return null;
29
43
  if (value === null) return null;
30
44
  if (value instanceof Date) return value.toISOString();
31
45
  if (typeof value === "object") return JSON.stringify(value);
@@ -41,7 +55,7 @@ const pgDialect = {
41
55
  unlimitedLimit: "ALL",
42
56
  toValue: toPgValue,
43
57
  toParam(value) {
44
- if (value === undefined) return null;
58
+ if (value === void 0) return null;
45
59
  return value;
46
60
  },
47
61
  regex(quotedCol, value) {
@@ -62,8 +76,8 @@ function buildInsert$1(table, data) {
62
76
  function buildSelect$1(table, where, controls) {
63
77
  return buildSelect(pgDialect, table, where, controls);
64
78
  }
65
- function buildUpdate$1(table, data, where, limit) {
66
- return buildUpdate(pgDialect, table, data, where, limit);
79
+ function buildUpdate$1(table, data, where, limit, ops) {
80
+ return buildUpdate(pgDialect, table, data, where, limit, ops);
67
81
  }
68
82
  function buildDelete$1(table, where, limit) {
69
83
  return buildDelete(pgDialect, table, where, limit);
@@ -77,6 +91,13 @@ function buildAggregateSelect$1(table, where, controls) {
77
91
  function buildAggregateCount$1(table, where, controls) {
78
92
  return buildAggregateCount(pgDialect, table, where, controls);
79
93
  }
94
+ /**
95
+ * Maps portable collation values to PostgreSQL collation names.
96
+ *
97
+ * Returns `null` for `'nocase'` — case-insensitive columns use CITEXT type instead
98
+ * (provisioned via `CREATE EXTENSION IF NOT EXISTS citext` in `ensureTable`).
99
+ * Users can also opt into ICU collation explicitly via `@db.pg.collate "und-u-ks-level2"`.
100
+ */
80
101
  function collationToPg(collation) {
81
102
  switch (collation) {
82
103
  case "binary": return "\"C\"";
@@ -90,7 +111,8 @@ function collationToPg(collation) {
90
111
  * PostgreSQL only has signed integer types:
91
112
  * uint16 (0-65535) → INTEGER (not SMALLINT which caps at 32767)
92
113
  * uint32 (0-4.3B) → BIGINT (not INTEGER which caps at ~2.1B)
93
- */ function intTypeFromTags(tags) {
114
+ */
115
+ function intTypeFromTags(tags) {
94
116
  if (tags?.has("int8") || tags?.has("byte")) return "SMALLINT";
95
117
  if (tags?.has("uint8")) return "SMALLINT";
96
118
  if (tags?.has("int16") || tags?.has("port")) return "SMALLINT";
@@ -100,6 +122,12 @@ function collationToPg(collation) {
100
122
  if (tags?.has("int64") || tags?.has("uint64")) return "BIGINT";
101
123
  return "INTEGER";
102
124
  }
125
+ /**
126
+ * Maps an Atscript field descriptor to a PostgreSQL column type.
127
+ *
128
+ * For FK fields, delegates to the target PK's type via `field.fkTargetField`
129
+ * so the FK column type always matches the referenced column.
130
+ */
103
131
  function pgTypeFromField(field) {
104
132
  if (field.fkTargetField) return pgTypeFromField(field.fkTargetField);
105
133
  const tags = field.type?.type?.tags;
@@ -108,36 +136,36 @@ function pgTypeFromField(field) {
108
136
  if (pgTypeOverride) return pgTypeOverride;
109
137
  const precision = metadata?.get("db.column.precision");
110
138
  switch (field.designType) {
111
- case "number": {
139
+ case "number":
112
140
  if (precision) return `NUMERIC(${precision.precision},${precision.scale})`;
113
141
  if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment") return "BIGINT";
114
142
  if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "now") return "BIGINT";
115
143
  if (tags?.has("int")) return intTypeFromTags(tags);
116
144
  return "DOUBLE PRECISION";
117
- }
118
145
  case "integer": return intTypeFromTags(tags);
119
- case "decimal": {
146
+ case "decimal":
120
147
  if (precision) return `NUMERIC(${precision.precision},${precision.scale})`;
121
148
  return "NUMERIC(10,2)";
122
- }
123
149
  case "boolean": return "BOOLEAN";
124
150
  case "string": {
125
151
  if (field.collate === "nocase" && !metadata?.get("db.pg.collate")) return "CITEXT";
126
152
  if (tags?.has("char")) return "CHAR(1)";
127
- const maxLen = metadata?.get("expect.maxLength")?.length;
128
- if (maxLen !== undefined) return `VARCHAR(${maxLen})`;
153
+ const maxLen = (metadata?.get("expect.maxLength"))?.length;
154
+ if (maxLen !== void 0) return `VARCHAR(${maxLen})`;
129
155
  if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
130
156
  return "TEXT";
131
157
  }
132
158
  case "json":
133
159
  case "object":
134
160
  case "array": return "JSONB";
135
- default: {
161
+ default:
136
162
  if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
137
163
  return "TEXT";
138
- }
139
164
  }
140
165
  }
166
+ /**
167
+ * Builds a CREATE TABLE IF NOT EXISTS statement with PostgreSQL syntax.
168
+ */
141
169
  function buildCreateTable(table, fields, foreignKeys, options) {
142
170
  const colDefs = [];
143
171
  const primaryKeys = fields.filter((f) => f.isPrimaryKey);
@@ -147,17 +175,17 @@ function buildCreateTable(table, fields, foreignKeys, options) {
147
175
  let def = `${qi(field.physicalName)} ${sqlType}`;
148
176
  if (options?.incrementFields?.has(field.physicalName)) {
149
177
  const start = options.autoIncrementStart;
150
- def += start !== undefined ? ` GENERATED BY DEFAULT AS IDENTITY (START WITH ${start})` : " GENERATED BY DEFAULT AS IDENTITY";
178
+ def += start !== void 0 ? ` GENERATED BY DEFAULT AS IDENTITY (START WITH ${start})` : " GENERATED BY DEFAULT AS IDENTITY";
151
179
  }
152
180
  if (!field.optional && !field.isPrimaryKey && !options?.incrementFields?.has(field.physicalName)) def += " NOT NULL";
153
181
  if (field.defaultValue?.kind === "value") def += ` DEFAULT ${defaultValueToSqlLiteral$1(field.designType, field.defaultValue.value)}`;
154
- else if (field.defaultValue?.kind === "fn") {
182
+ else if (field.defaultValue?.kind === "fn") {
155
183
  if (field.defaultValue.fn === "uuid") def += " DEFAULT gen_random_uuid()";
156
- else if (field.defaultValue.fn === "now") def += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
184
+ else if (field.defaultValue.fn === "now") def += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
157
185
  }
158
186
  const nativeCollate = field.type?.metadata?.get("db.pg.collate");
159
187
  if (nativeCollate) def += ` COLLATE "${nativeCollate}"`;
160
- else if (field.collate) {
188
+ else if (field.collate) {
161
189
  const pgCollate = collationToPg(field.collate);
162
190
  if (pgCollate) def += ` COLLATE ${pgCollate}`;
163
191
  }
@@ -183,41 +211,82 @@ else if (field.collate) {
183
211
  }
184
212
  return `CREATE TABLE IF NOT EXISTS ${quoteTableName(table)} (${colDefs.join(", ")})`;
185
213
  }
214
+ /**
215
+ * Offsets numbered placeholders ($1, $2, ...) in a SQL fragment by a given amount.
216
+ * Used when WHERE params follow SET params in UPDATE statements.
217
+ */
186
218
  function offsetPlaceholders(fragment, offset) {
187
219
  if (offset === 0) return fragment;
188
- const sql = fragment.sql.replace(/\$(\d+)/g, (_, n) => `$${Number(n) + offset}`);
189
220
  return {
190
- sql,
221
+ sql: fragment.sql.replace(/\$(\d+)/g, (_, n) => `$${Number(n) + offset}`),
191
222
  params: fragment.params
192
223
  };
193
224
  }
194
-
195
225
  //#endregion
196
- //#region packages/db-postgres/src/filter-builder.ts
226
+ //#region src/filter-builder.ts
227
+ /**
228
+ * Translates a filter expression into a parameterized PostgreSQL WHERE clause.
229
+ *
230
+ * Note: The returned fragment uses `?` placeholders (not `$N`).
231
+ * Finalization to `$N` happens when the fragment is consumed by a DML builder
232
+ * (buildSelect, buildUpdate, etc.) via the dialect's `paramPlaceholder`.
233
+ *
234
+ * Case-insensitive columns (`@db.collate 'nocase'`) are handled by CITEXT
235
+ * column type at the storage level — no query-side wrapping needed.
236
+ */
197
237
  function buildWhere(filter) {
198
238
  return buildWhere$1(pgDialect, filter);
199
239
  }
200
-
201
240
  //#endregion
202
- //#region packages/db-postgres/src/postgres-adapter.ts
203
- function _define_property$1(obj, key, value) {
204
- if (key in obj) Object.defineProperty(obj, key, {
205
- value,
206
- enumerable: true,
207
- configurable: true,
208
- writable: true
209
- });
210
- else obj[key] = value;
211
- return obj;
212
- }
213
- /** PostgreSQL COUNT() may return string (bigint) — parse to number. */ function parseCount(value) {
241
+ //#region src/postgres-adapter.ts
242
+ /** PostgreSQL COUNT() may return string (bigint) — parse to number. */
243
+ function parseCount(value) {
214
244
  if (typeof value === "string") return Number.parseInt(value, 10);
215
245
  return value ?? 0;
216
246
  }
247
+ /**
248
+ * PostgreSQL adapter for {@link AtscriptDbTable}.
249
+ *
250
+ * Accepts any {@link TPgDriver} implementation — the actual PostgreSQL driver
251
+ * is fully swappable (pg Pool, custom implementations, etc.).
252
+ *
253
+ * Usage:
254
+ * ```typescript
255
+ * import { PgDriver, PostgresAdapter } from '@atscript/db-postgres'
256
+ * import { DbSpace } from '@atscript/db'
257
+ *
258
+ * const driver = new PgDriver('postgresql://user@localhost:5432/mydb')
259
+ * const space = new DbSpace(() => new PostgresAdapter(driver))
260
+ * const users = space.getTable(UsersType)
261
+ * ```
262
+ */
217
263
  var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
218
- /** Schema name for queries (null falls through to 'public'). */ get _schema() {
264
+ supportsColumnModify = true;
265
+ static NATIVE_DEFAULT_FNS = new Set([
266
+ "now",
267
+ "uuid",
268
+ "increment"
269
+ ]);
270
+ _incrementFields = /* @__PURE__ */ new Set();
271
+ _autoIncrementStart;
272
+ /** Physical column names with @db.collate 'nocase'. Used to trigger CITEXT extension. */
273
+ _nocaseColumns = /* @__PURE__ */ new Set();
274
+ /** Whether citext extension has been provisioned (avoids redundant round-trips). */
275
+ _citextProvisioned = false;
276
+ /** Whether the connected PostgreSQL instance has the pgvector extension. */
277
+ _supportsVector;
278
+ /** Vector fields: physical field name → { dimensions, similarity, indexName }. */
279
+ _vectorFields = /* @__PURE__ */ new Map();
280
+ /** Default similarity thresholds per vector field (from @db.search.vector.threshold). */
281
+ _vectorThresholds = /* @__PURE__ */ new Map();
282
+ /** Schema name for queries (null falls through to 'public'). */
283
+ get _schema() {
219
284
  return this._table.schema ?? null;
220
285
  }
286
+ constructor(driver) {
287
+ super();
288
+ this.driver = driver;
289
+ }
221
290
  async _beginTransaction() {
222
291
  const conn = await this.driver.getConnection();
223
292
  try {
@@ -250,11 +319,12 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
250
319
  /**
251
320
  * Returns the active executor: dedicated connection if inside a transaction,
252
321
  * otherwise the pool-based driver.
253
- */ _exec() {
254
- const txState = this._getTransactionState();
255
- return txState ?? this.driver;
322
+ */
323
+ _exec() {
324
+ return this._getTransactionState() ?? this.driver;
256
325
  }
257
- /** PostgreSQL enforces FK constraints natively. */ supportsNativeForeignKeys() {
326
+ /** PostgreSQL enforces FK constraints natively. */
327
+ supportsNativeForeignKeys() {
258
328
  return true;
259
329
  }
260
330
  prepareId(id, _fieldType) {
@@ -285,7 +355,7 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
285
355
  indexName
286
356
  });
287
357
  const threshold = metadata.get("db.search.vector.threshold");
288
- if (threshold !== undefined) this._vectorThresholds.set(indexName, threshold);
358
+ if (threshold !== void 0) this._vectorThresholds.set(indexName, threshold);
289
359
  }
290
360
  }
291
361
  getDesiredTableOptions() {
@@ -298,8 +368,9 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
298
368
  * Converts vector fields between JavaScript `number[]` and pgvector text format `[1,2,3]`.
299
369
  * The pg driver serializes JS arrays as PostgreSQL array literals `{1,2,3}` which is
300
370
  * invalid for the pgvector `vector` type — it expects bracket-delimited `[1,2,3]`.
301
- */ formatValue(field) {
302
- if (!this._vectorFields.has(field.path)) return undefined;
371
+ */
372
+ formatValue(field) {
373
+ if (!this._vectorFields.has(field.path)) return;
303
374
  return {
304
375
  toStorage: (value) => Array.isArray(value) ? `[${value.join(",")}]` : value,
305
376
  fromStorage: (value) => typeof value === "string" ? JSON.parse(value) : value
@@ -312,29 +383,24 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
312
383
  * PostgreSQL uses SQLSTATE codes:
313
384
  * - 23505 = unique_violation
314
385
  * - 23503 = foreign_key_violation
315
- */ async _wrapConstraintError(fn) {
386
+ */
387
+ async _wrapConstraintError(fn) {
316
388
  try {
317
389
  return await fn();
318
390
  } catch (error) {
319
391
  if (error && typeof error === "object" && "code" in error) {
320
392
  const err = error;
321
- if (err.code === "23505") {
322
- const field = this._extractFieldFromConstraint(err.constraint) ?? "";
323
- throw new DbError("CONFLICT", [{
324
- path: field,
325
- message: err.detail ?? err.message
326
- }]);
327
- }
328
- if (err.code === "23503") {
329
- const errors = this._mapFkError(err.detail ?? err.message, err.constraint);
330
- throw new DbError("FK_VIOLATION", errors);
331
- }
393
+ if (err.code === "23505") throw new DbError("CONFLICT", [{
394
+ path: this._extractFieldFromConstraint(err.constraint) ?? "",
395
+ message: err.detail ?? err.message
396
+ }]);
397
+ if (err.code === "23503") throw new DbError("FK_VIOLATION", this._mapFkError(err.detail ?? err.message, err.constraint));
332
398
  }
333
399
  throw error;
334
400
  }
335
401
  }
336
402
  _extractFieldFromConstraint(constraint) {
337
- if (!constraint) return undefined;
403
+ if (!constraint) return;
338
404
  const tableName = this._table.tableName;
339
405
  if (constraint.startsWith(`${tableName}_`) && constraint.endsWith("_key")) {
340
406
  const fieldPart = constraint.slice(tableName.length + 1, -4);
@@ -347,9 +413,8 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
347
413
  const fkMatch = detail.match(/Key \(([^)]+)\)/);
348
414
  if (fkMatch) {
349
415
  const physicalCol = fkMatch[1].split(",")[0].trim();
350
- const field = this._table.fieldDescriptors.find((f) => f.physicalName === physicalCol);
351
416
  return [{
352
- path: field?.path ?? physicalCol,
417
+ path: this._table.fieldDescriptors.find((f) => f.physicalName === physicalCol)?.path ?? physicalCol,
353
418
  message: detail
354
419
  }];
355
420
  }
@@ -363,9 +428,8 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
363
428
  const pkCols = this._table.primaryKeys.map((pk) => qi(pk));
364
429
  if (pkCols.length > 0) sql += ` RETURNING ${pkCols.join(", ")}`;
365
430
  this._log(sql, params);
366
- const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
367
- const returned = result.rows?.[0];
368
- return { insertedId: this._resolveInsertedId(data, returned ? Object.values(returned)[0] : undefined) };
431
+ const returned = (await this._wrapConstraintError(() => this._exec().run(sql, params))).rows?.[0];
432
+ return { insertedId: this._resolveInsertedId(data, returned ? Object.values(returned)[0] : void 0) };
369
433
  }
370
434
  async insertMany(data) {
371
435
  if (data.length === 0) return {
@@ -400,7 +464,7 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
400
464
  const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
401
465
  for (let i = 0; i < batchSize; i++) {
402
466
  const returned = result.rows?.[i];
403
- allIds.push(this._resolveInsertedId(data[offset + i], returned ? Object.values(returned)[0] : undefined));
467
+ allIds.push(this._resolveInsertedId(data[offset + i], returned ? Object.values(returned)[0] : void 0));
404
468
  }
405
469
  }
406
470
  return {
@@ -427,40 +491,32 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
427
491
  }
428
492
  async count(query) {
429
493
  const where = buildWhere(query.filter);
430
- const tableName = this.resolveTableName();
431
- const raw = {
432
- sql: `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${where.sql}`,
494
+ const { sql, params } = finalizeParams(pgDialect, {
495
+ sql: `SELECT COUNT(*) as cnt FROM ${quoteTableName(this.resolveTableName())} WHERE ${where.sql}`,
433
496
  params: where.params
434
- };
435
- const { sql, params } = finalizeParams(pgDialect, raw);
497
+ });
436
498
  this._log(sql, params);
437
- const row = await this._exec().get(sql, params);
438
- return parseCount(row?.cnt);
499
+ return parseCount((await this._exec().get(sql, params))?.cnt);
439
500
  }
440
501
  async aggregate(query) {
441
502
  const where = buildWhere(query.filter);
442
503
  const tableName = this.resolveTableName();
443
504
  if (query.controls.$count) {
444
- const { sql: sql$1, params: params$1 } = buildAggregateCount$1(tableName, where, query.controls);
445
- this._log(sql$1, params$1);
446
- const row = await this._exec().get(sql$1, params$1);
447
- const count = parseCount(row?.count);
448
- return [{ count }];
505
+ const { sql, params } = buildAggregateCount$1(tableName, where, query.controls);
506
+ this._log(sql, params);
507
+ return [{ count: parseCount((await this._exec().get(sql, params))?.count) }];
449
508
  }
450
509
  const { sql, params } = buildAggregateSelect$1(tableName, where, query.controls);
451
510
  this._log(sql, params);
452
511
  return this._exec().all(sql, params);
453
512
  }
454
- async updateOne(filter, data) {
513
+ async updateOne(filter, data, ops) {
455
514
  const where = buildWhere(filter);
456
515
  const tableName = this.resolveTableName();
457
- const keys = Object.keys(data);
458
- const setClauses = keys.map((k, i) => `${qi(k)} = $${i + 1}`);
459
- const setParams = keys.map((k) => pgDialect.toValue(data[k]));
460
- const finalizedWhere = finalizeParams(pgDialect, where);
461
- const offsetWhere = offsetPlaceholders(finalizedWhere, keys.length);
462
- const sql = `UPDATE ${quoteTableName(tableName)} SET ${setClauses.join(", ")} WHERE ctid = (SELECT ctid FROM ${quoteTableName(tableName)} WHERE ${offsetWhere.sql} LIMIT 1)`;
463
- const params = [...setParams, ...where.params];
516
+ const { sql, params } = buildUpdate$1(tableName, data, {
517
+ sql: `ctid = (SELECT ctid FROM ${quoteTableName(tableName)} WHERE ${where.sql} LIMIT 1)`,
518
+ params: where.params
519
+ }, void 0, ops);
464
520
  this._log(sql, params);
465
521
  const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
466
522
  return {
@@ -468,9 +524,9 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
468
524
  modifiedCount: result.affectedRows
469
525
  };
470
526
  }
471
- async updateMany(filter, data) {
527
+ async updateMany(filter, data, ops) {
472
528
  const where = buildWhere(filter);
473
- const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where);
529
+ const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, void 0, ops);
474
530
  this._log(sql, params);
475
531
  const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
476
532
  return {
@@ -487,21 +543,18 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
487
543
  async deleteOne(filter) {
488
544
  const where = buildWhere(filter);
489
545
  const tableName = this.resolveTableName();
490
- const raw = {
546
+ const { sql, params } = finalizeParams(pgDialect, {
491
547
  sql: `DELETE FROM ${quoteTableName(tableName)} WHERE ctid = (SELECT ctid FROM ${quoteTableName(tableName)} WHERE ${where.sql} LIMIT 1)`,
492
548
  params: where.params
493
- };
494
- const { sql, params } = finalizeParams(pgDialect, raw);
549
+ });
495
550
  this._log(sql, params);
496
- const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
497
- return { deletedCount: result.affectedRows };
551
+ return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
498
552
  }
499
553
  async deleteMany(filter) {
500
554
  const where = buildWhere(filter);
501
555
  const { sql, params } = buildDelete$1(this.resolveTableName(), where);
502
556
  this._log(sql, params);
503
- const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
504
- return { deletedCount: result.affectedRows };
557
+ return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
505
558
  }
506
559
  async ensureTable() {
507
560
  if (this._nocaseColumns.size > 0 && !this._citextProvisioned) try {
@@ -509,9 +562,9 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
509
562
  this._citextProvisioned = true;
510
563
  } catch (err) {
511
564
  const msg = err instanceof Error ? err.message : String(err);
512
- throw new Error(`Failed to create citext extension for @db.collate 'nocase' columns: ${msg}. ` + `Either run 'CREATE EXTENSION citext' as a superuser, or use @db.pg.type "CITEXT" after provisioning the extension manually.`);
565
+ throw new Error(`Failed to create citext extension for @db.collate 'nocase' columns: ${msg}. Either run 'CREATE EXTENSION citext' as a superuser, or use @db.pg.type "CITEXT" after provisioning the extension manually.`, { cause: err });
513
566
  }
514
- if (this._supportsVector === undefined && this._vectorFields.size > 0) await this._detectVectorSupport();
567
+ if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
515
568
  if (this._table instanceof AtscriptDbView) return this._ensureView();
516
569
  const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, {
517
570
  incrementFields: this._incrementFields,
@@ -569,18 +622,18 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
569
622
  const sqlType = this.typeMapper(field);
570
623
  let ddl = `ALTER TABLE ${quoteTableName(tableName)} ADD COLUMN ${qi(field.physicalName)} ${sqlType}`;
571
624
  if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment") ddl += " GENERATED BY DEFAULT AS IDENTITY";
572
- else {
625
+ else {
573
626
  if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
574
627
  if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral$1(field.designType, field.defaultValue.value)}`;
575
- else if (field.defaultValue?.kind === "fn") {
628
+ else if (field.defaultValue?.kind === "fn") {
576
629
  if (field.defaultValue.fn === "uuid") ddl += " DEFAULT gen_random_uuid()";
577
- else if (field.defaultValue.fn === "now") ddl += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
630
+ else if (field.defaultValue.fn === "now") ddl += " DEFAULT (extract(epoch from now()) * 1000)::bigint";
578
631
  } else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType$1(field.designType)}`;
579
632
  }
580
633
  if (field.collate) {
581
634
  const nativeCollate = field.type?.metadata?.get("db.pg.collate");
582
635
  if (nativeCollate) ddl += ` COLLATE "${nativeCollate}"`;
583
- else {
636
+ else {
584
637
  const pgCollate = collationToPg(field.collate);
585
638
  if (pgCollate) ddl += ` COLLATE ${pgCollate}`;
586
639
  }
@@ -611,7 +664,7 @@ else {
611
664
  for (const { field } of diff.defaultChanged ?? []) {
612
665
  let ddl;
613
666
  if (field.defaultValue?.kind === "value") ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${qi(field.physicalName)} SET DEFAULT ${defaultValueToSqlLiteral$1(field.designType, field.defaultValue.value)}`;
614
- else if (field.defaultValue?.kind === "fn") {
667
+ else if (field.defaultValue?.kind === "fn") {
615
668
  const fnExpr = field.defaultValue.fn === "now" ? "(extract(epoch from now()) * 1000)::bigint" : field.defaultValue.fn === "uuid" ? "gen_random_uuid()" : `${field.defaultValue.fn}()`;
616
669
  ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${qi(field.physicalName)} SET DEFAULT ${fnExpr}`;
617
670
  } else ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${qi(field.physicalName)} DROP DEFAULT`;
@@ -647,7 +700,7 @@ else if (field.defaultValue?.kind === "fn") {
647
700
  SELECT constraint_name FROM information_schema.table_constraints
648
701
  WHERE table_name = $2 AND table_schema = COALESCE($1, 'public') AND constraint_type = 'PRIMARY KEY'
649
702
  )`, [schema, this._table.tableName]);
650
- const fkByName = new Map();
703
+ const fkByName = /* @__PURE__ */ new Map();
651
704
  for (const fk of fkRefs) {
652
705
  let entry = fkByName.get(fk.constraint_name);
653
706
  if (!entry) {
@@ -726,7 +779,8 @@ else if (field.defaultValue?.kind === "fn") {
726
779
  * value doesn't conflict with existing data. PostgreSQL's GENERATED BY DEFAULT
727
780
  * AS IDENTITY does not advance the sequence when rows are inserted with explicit
728
781
  * values, so this is needed after data seeding, bulk imports, or recreateTable().
729
- */ async _resetIdentitySequences() {
782
+ */
783
+ async _resetIdentitySequences() {
730
784
  if (this._incrementFields.size === 0) return;
731
785
  const tableName = this.resolveTableName();
732
786
  const emptyFallback = this._autoIncrementStart ?? 1;
@@ -738,8 +792,7 @@ else if (field.defaultValue?.kind === "fn") {
738
792
  }
739
793
  }
740
794
  async tableExists() {
741
- const row = await this._exec().get(`SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1 AND table_schema = COALESCE($2, 'public')) AS "exists"`, [this._table.tableName, this._schema]);
742
- return row?.exists ?? false;
795
+ return (await this._exec().get(`SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1 AND table_schema = COALESCE($2, 'public')) AS "exists"`, [this._table.tableName, this._schema]))?.exists ?? false;
743
796
  }
744
797
  async dropTable() {
745
798
  const ddl = `DROP TABLE IF EXISTS ${quoteTableName(this.resolveTableName())} CASCADE`;
@@ -785,9 +838,9 @@ else if (field.defaultValue?.kind === "fn") {
785
838
  createIndex: async (index) => {
786
839
  if (index.type === "fulltext") {
787
840
  const tsvectorExpr = this._buildTsvectorExpr(index.fields);
788
- const sql$1 = `CREATE INDEX IF NOT EXISTS ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} USING gin(to_tsvector('english', ${tsvectorExpr}))`;
789
- this._log(sql$1);
790
- await this._exec().exec(sql$1);
841
+ const sql = `CREATE INDEX IF NOT EXISTS ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} USING gin(to_tsvector('english', ${tsvectorExpr}))`;
842
+ this._log(sql);
843
+ await this._exec().exec(sql);
791
844
  return;
792
845
  }
793
846
  const unique = index.type === "unique" ? "UNIQUE " : "";
@@ -797,8 +850,7 @@ else if (field.defaultValue?.kind === "fn") {
797
850
  await this._exec().exec(sql);
798
851
  },
799
852
  dropIndex: async (name) => {
800
- const schemaPrefix = schema ? `${qi(schema)}.` : "";
801
- const sql = `DROP INDEX IF EXISTS ${schemaPrefix}${qi(name)}`;
853
+ const sql = `DROP INDEX IF EXISTS ${schema ? `${qi(schema)}.` : ""}${qi(name)}`;
802
854
  this._log(sql);
803
855
  await this._exec().exec(sql);
804
856
  }
@@ -813,19 +865,19 @@ else if (field.defaultValue?.kind === "fn") {
813
865
  }
814
866
  async syncForeignKeys() {
815
867
  const existingByName = await this._getExistingFkConstraints();
816
- const desiredFkKeys = new Set();
817
- for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].sort().join(","));
868
+ const desiredFkKeys = /* @__PURE__ */ new Set();
869
+ for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].toSorted().join(","));
818
870
  for (const [constraintName, columns] of existingByName) {
819
- const key = [...columns].sort().join(",");
871
+ const key = [...columns].toSorted().join(",");
820
872
  if (!desiredFkKeys.has(key)) {
821
873
  const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP CONSTRAINT ${qi(constraintName)}`;
822
874
  this._log(ddl);
823
875
  await this._exec().exec(ddl);
824
876
  }
825
877
  }
826
- const existingKeys = new Set([...existingByName.values()].map((cols) => cols.sort().join(",")));
878
+ const existingKeys = new Set([...existingByName.values()].map((cols) => cols.toSorted().join(",")));
827
879
  for (const fk of this._table.foreignKeys.values()) {
828
- const key = [...fk.fields].sort().join(",");
880
+ const key = [...fk.fields].toSorted().join(",");
829
881
  if (!existingKeys.has(key)) {
830
882
  const localCols = fk.fields.map((f) => qi(f)).join(", ");
831
883
  const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
@@ -842,7 +894,7 @@ else if (field.defaultValue?.kind === "fn") {
842
894
  const keySet = new Set(fkFieldKeys);
843
895
  const existingByName = await this._getExistingFkConstraints();
844
896
  for (const [constraintName, cols] of existingByName) {
845
- const key = cols.sort().join(",");
897
+ const key = cols.toSorted().join(",");
846
898
  if (keySet.has(key)) {
847
899
  const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP CONSTRAINT ${qi(constraintName)}`;
848
900
  this._log(ddl);
@@ -850,14 +902,15 @@ else if (field.defaultValue?.kind === "fn") {
850
902
  }
851
903
  }
852
904
  }
853
- /** Queries information_schema for existing FK constraints. */ async _getExistingFkConstraints() {
905
+ /** Queries information_schema for existing FK constraints. */
906
+ async _getExistingFkConstraints() {
854
907
  const rows = await this._exec().all(`SELECT kcu.constraint_name, kcu.column_name
855
908
  FROM information_schema.table_constraints tc
856
909
  JOIN information_schema.key_column_usage kcu
857
910
  ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
858
911
  WHERE tc.table_name = $1 AND tc.table_schema = COALESCE($2, 'public')
859
912
  AND tc.constraint_type = 'FOREIGN KEY'`, [this._table.tableName, this._schema]);
860
- const byName = new Map();
913
+ const byName = /* @__PURE__ */ new Map();
861
914
  for (const row of rows) {
862
915
  let cols = byName.get(row.constraint_name);
863
916
  if (!cols) {
@@ -902,14 +955,12 @@ else if (field.defaultValue?.kind === "fn") {
902
955
  return this._exec().all(sql, params);
903
956
  })();
904
957
  const countPromise = (async () => {
905
- const raw = {
958
+ const { sql, params } = finalizeParams(pgDialect, {
906
959
  sql: `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${combinedWhere.sql}`,
907
960
  params: combinedWhere.params
908
- };
909
- const { sql, params } = finalizeParams(pgDialect, raw);
961
+ });
910
962
  this._log(sql, params);
911
- const row = await this._exec().get(sql, params);
912
- return parseCount(row?.cnt);
963
+ return parseCount((await this._exec().get(sql, params))?.cnt);
913
964
  })();
914
965
  const [data, count] = await Promise.all([selectPromise, countPromise]);
915
966
  return {
@@ -928,20 +979,21 @@ else if (field.defaultValue?.kind === "fn") {
928
979
  params: [...where.params, text]
929
980
  };
930
981
  }
931
- /** Builds the tsvector SQL expression for a fulltext index's fields. Must match between index DDL and queries. */ _buildTsvectorExpr(fields) {
982
+ /** Builds the tsvector SQL expression for a fulltext index's fields. Must match between index DDL and queries. */
983
+ _buildTsvectorExpr(fields) {
932
984
  return fields.map((f) => `coalesce(${qi(f.name)}, '')`).join(" || ' ' || ");
933
985
  }
934
986
  _getFulltextIndex(indexName) {
935
987
  for (const index of this._table.indexes.values()) if (index.type === "fulltext") {
936
988
  if (!indexName || index.key === indexName) return index;
937
989
  }
938
- return undefined;
939
990
  }
940
991
  /**
941
992
  * Detects pgvector support by attempting to enable the extension.
942
993
  * Idempotent — safe to call multiple times.
943
- */ async _detectVectorSupport() {
944
- if (this._supportsVector !== undefined) return this._supportsVector;
994
+ */
995
+ async _detectVectorSupport() {
996
+ if (this._supportsVector !== void 0) return this._supportsVector;
945
997
  try {
946
998
  await this._exec().exec("CREATE EXTENSION IF NOT EXISTS vector");
947
999
  this._supportsVector = true;
@@ -968,13 +1020,13 @@ else if (field.defaultValue?.kind === "fn") {
968
1020
  this._log(sql, params);
969
1021
  this._log(countSql, countParams);
970
1022
  const [data, countRow] = await Promise.all([this._exec().all(sql, params), this._exec().get(countSql, countParams)]);
971
- const count = parseCount(countRow?.cnt);
972
1023
  return {
973
1024
  data,
974
- count
1025
+ count: parseCount(countRow?.cnt)
975
1026
  };
976
1027
  }
977
- /** Resolves vector field and computes shared context for vector search SQL builders. */ _prepareVectorSearch(vector, query, indexName) {
1028
+ /** Resolves vector field and computes shared context for vector search SQL builders. */
1029
+ _prepareVectorSearch(vector, query, indexName) {
978
1030
  let field;
979
1031
  let vec;
980
1032
  if (indexName) {
@@ -1012,7 +1064,7 @@ else if (field.defaultValue?.kind === "fn") {
1012
1064
  let inner = `SELECT *, (${qi(ctx.field)} ${ctx.distanceOp} $1::vector) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${offsetPlaceholders(finalizeParams(pgDialect, ctx.where), 1).sql}`;
1013
1065
  const params = [ctx.vectorStr, ...ctx.where.params];
1014
1066
  let sql = `SELECT * FROM (${inner}) _v`;
1015
- if (ctx.threshold !== undefined) {
1067
+ if (ctx.threshold !== void 0) {
1016
1068
  sql += ` WHERE _distance <= $${params.length + 1}`;
1017
1069
  params.push(thresholdToDistance(ctx.threshold, ctx.vec.similarity));
1018
1070
  }
@@ -1034,7 +1086,7 @@ else if (field.defaultValue?.kind === "fn") {
1034
1086
  let inner = `SELECT (${qi(ctx.field)} ${ctx.distanceOp} $1::vector) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${offsetPlaceholders(finalizeParams(pgDialect, ctx.where), 1).sql}`;
1035
1087
  const params = [ctx.vectorStr, ...ctx.where.params];
1036
1088
  let sql = `SELECT COUNT(*) AS cnt FROM (${inner}) _v`;
1037
- if (ctx.threshold !== undefined) {
1089
+ if (ctx.threshold !== void 0) {
1038
1090
  sql += ` WHERE _distance <= $${params.length + 1}`;
1039
1091
  params.push(thresholdToDistance(ctx.threshold, ctx.vec.similarity));
1040
1092
  }
@@ -1043,26 +1095,19 @@ else if (field.defaultValue?.kind === "fn") {
1043
1095
  params
1044
1096
  };
1045
1097
  }
1046
- /** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */ _resolveVectorThreshold(controls, indexName) {
1098
+ /** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
1099
+ _resolveVectorThreshold(controls, indexName) {
1047
1100
  const queryThreshold = controls.$threshold;
1048
- if (queryThreshold !== undefined) return queryThreshold;
1101
+ if (queryThreshold !== void 0) return queryThreshold;
1049
1102
  return this._vectorThresholds.get(indexName);
1050
1103
  }
1051
- constructor(driver) {
1052
- super(), _define_property$1(this, "driver", void 0), _define_property$1(this, "supportsColumnModify", void 0), _define_property$1(this, "_incrementFields", void 0), _define_property$1(this, "_autoIncrementStart", void 0), _define_property$1(this, "_nocaseColumns", void 0), _define_property$1(this, "_citextProvisioned", void 0), _define_property$1(this, "_supportsVector", void 0), _define_property$1(this, "_vectorFields", void 0), _define_property$1(this, "_vectorThresholds", void 0), this.driver = driver, this.supportsColumnModify = true, this._incrementFields = new Set(), this._nocaseColumns = new Set(), this._citextProvisioned = false, this._vectorFields = new Map(), this._vectorThresholds = new Map();
1053
- }
1054
1104
  };
1055
- _define_property$1(PostgresAdapter, "NATIVE_DEFAULT_FNS", new Set([
1056
- "now",
1057
- "uuid",
1058
- "increment"
1059
- ]));
1060
1105
  /**
1061
1106
  * Normalizes PostgreSQL information_schema data_type values
1062
1107
  * to match the format produced by `pgTypeFromField()`.
1063
- */ function normalizePgType(dataType, maxLength, numericPrecision, numericScale, udtName, formattedType) {
1064
- const dt = dataType.toLowerCase();
1065
- switch (dt) {
1108
+ */
1109
+ function normalizePgType(dataType, maxLength, numericPrecision, numericScale, udtName, formattedType) {
1110
+ switch (dataType.toLowerCase()) {
1066
1111
  case "character varying": return maxLength ? `VARCHAR(${maxLength})` : "VARCHAR(255)";
1067
1112
  case "character": return maxLength ? `CHAR(${maxLength})` : "CHAR(1)";
1068
1113
  case "integer": return "INTEGER";
@@ -1077,20 +1122,20 @@ _define_property$1(PostgresAdapter, "NATIVE_DEFAULT_FNS", new Set([
1077
1122
  case "timestamp with time zone": return "TIMESTAMPTZ";
1078
1123
  case "timestamp without time zone": return "TIMESTAMP";
1079
1124
  case "uuid": return "UUID";
1080
- case "user-defined": {
1125
+ case "user-defined":
1081
1126
  if (udtName === "vector") return formattedType;
1082
1127
  if (udtName === "citext") return "CITEXT";
1083
1128
  return udtName?.toUpperCase() ?? "USER-DEFINED";
1084
- }
1085
1129
  default: return dataType.toUpperCase();
1086
1130
  }
1087
1131
  }
1088
1132
  /**
1089
1133
  * Normalizes PostgreSQL column_default values to match the format
1090
1134
  * produced by `serializeDefaultValue()`.
1091
- */ function normalizePgDefault(value, isIdentity) {
1135
+ */
1136
+ function normalizePgDefault(value, isIdentity) {
1092
1137
  if (isIdentity === "YES") return "fn:increment";
1093
- if (value === null) return undefined;
1138
+ if (value === null) return;
1094
1139
  let cleaned = value;
1095
1140
  while (cleaned.startsWith("(") && cleaned.endsWith(")")) cleaned = cleaned.slice(1, -1);
1096
1141
  const lower = cleaned.toLowerCase();
@@ -1113,60 +1158,58 @@ _define_property$1(PostgresAdapter, "NATIVE_DEFAULT_FNS", new Set([
1113
1158
  * pgvector cosine distance = 1 - cosine_similarity, range [0, 2]
1114
1159
  *
1115
1160
  * Conversion: distance = 2 * (1 - score)
1116
- */ function thresholdToDistance(threshold, similarity) {
1161
+ */
1162
+ function thresholdToDistance(threshold, similarity) {
1117
1163
  switch (similarity) {
1118
1164
  case "euclidean": return threshold;
1119
1165
  case "dotProduct": return -threshold;
1120
1166
  default: return 2 * (1 - threshold);
1121
1167
  }
1122
1168
  }
1123
- /** Maps generic similarity metric to PostgreSQL distance operator. */ function similarityToPgOp(similarity) {
1169
+ /** Maps generic similarity metric to PostgreSQL distance operator. */
1170
+ function similarityToPgOp(similarity) {
1124
1171
  switch (similarity) {
1125
1172
  case "euclidean": return "<->";
1126
1173
  case "dotProduct": return "<#>";
1127
1174
  default: return "<=>";
1128
1175
  }
1129
1176
  }
1130
- /** Maps generic similarity metric to pgvector index ops class. */ function similarityToPgOps(similarity) {
1177
+ /** Maps generic similarity metric to pgvector index ops class. */
1178
+ function similarityToPgOps(similarity) {
1131
1179
  switch (similarity) {
1132
1180
  case "euclidean": return "vector_l2_ops";
1133
1181
  case "dotProduct": return "vector_ip_ops";
1134
1182
  default: return "vector_cosine_ops";
1135
1183
  }
1136
1184
  }
1137
- /** Formats a number[] vector as pgvector input: '[1.0, 2.0, ...]'. */ function vectorToString(vector) {
1185
+ /** Formats a number[] vector as pgvector input: '[1.0, 2.0, ...]'. */
1186
+ function vectorToString(vector) {
1138
1187
  return `[${vector.join(",")}]`;
1139
1188
  }
1140
-
1141
1189
  //#endregion
1142
- //#region packages/db-postgres/src/pg-driver.ts
1143
- function _define_property(obj, key, value) {
1144
- if (key in obj) Object.defineProperty(obj, key, {
1145
- value,
1146
- enumerable: true,
1147
- configurable: true,
1148
- writable: true
1149
- });
1150
- else obj[key] = value;
1151
- return obj;
1152
- }
1153
- /** pg rejects `undefined` in bind arrays — coerce to `null`. */ function sanitizeParams(params) {
1190
+ //#region src/pg-driver.ts
1191
+ /** pg rejects `undefined` in bind arrays — coerce to `null`. */
1192
+ function sanitizeParams(params) {
1154
1193
  if (!params) return [];
1155
- return params.map((v) => v === undefined ? null : v);
1194
+ return params.map((v) => v === void 0 ? null : v);
1156
1195
  }
1157
- /** Parses TIMESTAMP/TIMESTAMPTZ to epoch milliseconds. */ function parseTimestamp(val) {
1196
+ /** Parses TIMESTAMP/TIMESTAMPTZ to epoch milliseconds. */
1197
+ function parseTimestamp(val) {
1158
1198
  const ms = new Date(val).getTime();
1159
1199
  return Number.isNaN(ms) ? val : ms;
1160
1200
  }
1161
- /** Parses NUMERIC to number. */ function parseNumeric(val) {
1201
+ /** Parses NUMERIC to number. */
1202
+ function parseNumeric(val) {
1162
1203
  const n = Number.parseFloat(val);
1163
1204
  return Number.isNaN(n) ? val : n;
1164
1205
  }
1165
- /** Parses INT8/BIGINT to number. Returns string if value exceeds safe integer range. */ function parseBigInt(val) {
1206
+ /** Parses INT8/BIGINT to number. Returns string if value exceeds safe integer range. */
1207
+ function parseBigInt(val) {
1166
1208
  const n = Number.parseInt(val, 10);
1167
1209
  return Number.isNaN(n) || !Number.isSafeInteger(n) ? val : n;
1168
1210
  }
1169
- /** OIDs for types we override. */ const TIMESTAMP_OID = 1114;
1211
+ /** OIDs for types we override. */
1212
+ const TIMESTAMP_OID = 1114;
1170
1213
  const TIMESTAMPTZ_OID = 1184;
1171
1214
  const NUMERIC_OID = 1700;
1172
1215
  const INT8_OID = 20;
@@ -1177,7 +1220,8 @@ const INT8_OID = 20;
1177
1220
  * - TIMESTAMP/TIMESTAMPTZ → epoch milliseconds (number)
1178
1221
  * - NUMERIC → number (not string)
1179
1222
  * - INT8/BIGINT → number (for JS-safe range)
1180
- */ function createCustomTypes(pgTypes) {
1223
+ */
1224
+ function createCustomTypes(pgTypes) {
1181
1225
  const overrides = new Map([
1182
1226
  [TIMESTAMP_OID, parseTimestamp],
1183
1227
  [TIMESTAMPTZ_OID, parseTimestamp],
@@ -1190,35 +1234,78 @@ const INT8_OID = 20;
1190
1234
  return pgTypes.getTypeParser(oid, format);
1191
1235
  } };
1192
1236
  }
1237
+ /**
1238
+ * {@link TPgDriver} implementation backed by `pg` (node-postgres).
1239
+ *
1240
+ * Accepts a connection URI string, a `pg.PoolConfig` object, or a pre-created
1241
+ * `pg.Pool` instance.
1242
+ *
1243
+ * ```typescript
1244
+ * import { PgDriver } from '@atscript/db-postgres'
1245
+ *
1246
+ * // Connection URI
1247
+ * const driver = new PgDriver('postgresql://user:pass@localhost:5432/mydb')
1248
+ *
1249
+ * // Pool options
1250
+ * const driver = new PgDriver({
1251
+ * host: 'localhost',
1252
+ * user: 'postgres',
1253
+ * database: 'mydb',
1254
+ * max: 10,
1255
+ * })
1256
+ *
1257
+ * // Pre-created pool
1258
+ * import pg from 'pg'
1259
+ * const pool = new pg.Pool({ connectionString: '...' })
1260
+ * const driver = new PgDriver(pool)
1261
+ * ```
1262
+ *
1263
+ * Requires `pg` to be installed:
1264
+ * ```bash
1265
+ * pnpm add pg
1266
+ * ```
1267
+ */
1193
1268
  var PgDriver = class {
1269
+ pool;
1270
+ poolInit;
1271
+ constructor(poolOrConfig) {
1272
+ if (typeof poolOrConfig === "object" && typeof poolOrConfig.query === "function") this.pool = poolOrConfig;
1273
+ else this.poolInit = import("pg").then((pg) => {
1274
+ const Pool = pg.default?.Pool ?? pg.Pool;
1275
+ const types = pg.default?.types ?? pg.types;
1276
+ const customTypes = types ? createCustomTypes(types) : void 0;
1277
+ if (typeof poolOrConfig === "string") this.pool = new Pool({
1278
+ connectionString: poolOrConfig,
1279
+ types: customTypes
1280
+ });
1281
+ else this.pool = new Pool({
1282
+ ...poolOrConfig,
1283
+ types: customTypes
1284
+ });
1285
+ return this.pool;
1286
+ });
1287
+ }
1194
1288
  getPool() {
1195
1289
  return this.pool || this.poolInit;
1196
1290
  }
1197
1291
  async run(sql, params) {
1198
- const pool = await this.getPool();
1199
- const result = await pool.query(sql, sanitizeParams(params));
1292
+ const result = await (await this.getPool()).query(sql, sanitizeParams(params));
1200
1293
  return {
1201
1294
  affectedRows: result.rowCount ?? 0,
1202
1295
  rows: result.rows ?? []
1203
1296
  };
1204
1297
  }
1205
1298
  async all(sql, params) {
1206
- const pool = await this.getPool();
1207
- const result = await pool.query(sql, sanitizeParams(params));
1208
- return result.rows;
1299
+ return (await (await this.getPool()).query(sql, sanitizeParams(params))).rows;
1209
1300
  }
1210
1301
  async get(sql, params) {
1211
- const pool = await this.getPool();
1212
- const result = await pool.query(sql, sanitizeParams(params));
1213
- return result.rows[0] ?? null;
1302
+ return (await (await this.getPool()).query(sql, sanitizeParams(params))).rows[0] ?? null;
1214
1303
  }
1215
1304
  async exec(sql) {
1216
- const pool = await this.getPool();
1217
- await pool.query(sql);
1305
+ await (await this.getPool()).query(sql);
1218
1306
  }
1219
1307
  async getConnection() {
1220
- const pool = await this.getPool();
1221
- const client = await pool.connect();
1308
+ const client = await (await this.getPool()).connect();
1222
1309
  return {
1223
1310
  async run(sql, params) {
1224
1311
  const result = await client.query(sql, sanitizeParams(params));
@@ -1228,12 +1315,10 @@ var PgDriver = class {
1228
1315
  };
1229
1316
  },
1230
1317
  async all(sql, params) {
1231
- const result = await client.query(sql, sanitizeParams(params));
1232
- return result.rows;
1318
+ return (await client.query(sql, sanitizeParams(params))).rows;
1233
1319
  },
1234
1320
  async get(sql, params) {
1235
- const result = await client.query(sql, sanitizeParams(params));
1236
- return result.rows[0] ?? null;
1321
+ return (await client.query(sql, sanitizeParams(params))).rows[0] ?? null;
1237
1322
  },
1238
1323
  async exec(sql) {
1239
1324
  await client.query(sql);
@@ -1244,76 +1329,18 @@ var PgDriver = class {
1244
1329
  };
1245
1330
  }
1246
1331
  async close() {
1247
- const pool = await this.getPool();
1248
- await pool.end();
1249
- }
1250
- constructor(poolOrConfig) {
1251
- _define_property(this, "pool", void 0);
1252
- _define_property(this, "poolInit", void 0);
1253
- if (typeof poolOrConfig === "object" && typeof poolOrConfig.query === "function") this.pool = poolOrConfig;
1254
- else this.poolInit = import("pg").then((pg) => {
1255
- const Pool = pg.default?.Pool ?? pg.Pool;
1256
- const types = pg.default?.types ?? pg.types;
1257
- const customTypes = types ? createCustomTypes(types) : undefined;
1258
- if (typeof poolOrConfig === "string") this.pool = new Pool({
1259
- connectionString: poolOrConfig,
1260
- types: customTypes
1261
- });
1262
- else this.pool = new Pool({
1263
- ...poolOrConfig,
1264
- types: customTypes
1265
- });
1266
- return this.pool;
1267
- });
1332
+ await (await this.getPool()).end();
1268
1333
  }
1269
1334
  };
1270
-
1271
1335
  //#endregion
1272
- //#region packages/db-postgres/src/plugin/annotations.ts
1273
- const annotations = {
1274
- type: new AnnotationSpec({
1275
- description: "Overrides the native PostgreSQL column type.\n\n```atscript\n@db.pg.type \"CITEXT\"\nname: string\n```",
1276
- nodeType: ["prop"],
1277
- multiple: false,
1278
- argument: {
1279
- name: "type",
1280
- type: "string",
1281
- description: "Native PostgreSQL column type (e.g., \"CITEXT\", \"INET\", \"MACADDR\")."
1282
- }
1283
- }),
1284
- schema: new AnnotationSpec({
1285
- description: "Specifies the PostgreSQL schema for the table.\n\n**Default:** `\"public\"`\n\n```atscript\n@db.pg.schema \"analytics\"\nexport interface Events { ... }\n```",
1286
- nodeType: ["interface"],
1287
- multiple: false,
1288
- argument: {
1289
- name: "schema",
1290
- type: "string",
1291
- description: "PostgreSQL schema name (e.g., \"public\", \"analytics\")."
1292
- }
1293
- }),
1294
- collate: new AnnotationSpec({
1295
- description: "Specifies a native PostgreSQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.pg.collate \"tr-x-icu\"\nname: string\n```",
1296
- nodeType: ["interface", "prop"],
1297
- multiple: false,
1298
- argument: {
1299
- name: "collation",
1300
- type: "string",
1301
- description: "Native PostgreSQL collation name (e.g., \"tr-x-icu\", \"C\", \"und-x-icu\")."
1302
- }
1303
- })
1304
- };
1305
-
1306
- //#endregion
1307
- //#region packages/db-postgres/src/plugin/index.ts
1308
- const PostgresPlugin = () => ({
1309
- name: "postgres",
1310
- config() {
1311
- return { annotations: { db: { pg: annotations } } };
1312
- }
1313
- });
1314
-
1315
- //#endregion
1316
- //#region packages/db-postgres/src/index.ts
1336
+ //#region src/index.ts
1337
+ /**
1338
+ * Creates a {@link DbSpace} backed by a PostgreSQL connection pool.
1339
+ *
1340
+ * @param uri - PostgreSQL connection URI (e.g., `postgresql://user@localhost:5432/mydb`)
1341
+ * @param options - Additional pool options passed to pg.
1342
+ * @returns A `DbSpace` that creates `PostgresAdapter` instances per table.
1343
+ */
1317
1344
  function createAdapter(uri, options) {
1318
1345
  const driver = new PgDriver({
1319
1346
  connectionString: uri,
@@ -1321,6 +1348,5 @@ function createAdapter(uri, options) {
1321
1348
  });
1322
1349
  return new DbSpace(() => new PostgresAdapter(driver));
1323
1350
  }
1324
-
1325
1351
  //#endregion
1326
- export { PgDriver, PostgresAdapter, PostgresPlugin, buildWhere, createAdapter };
1352
+ export { PgDriver, PostgresAdapter, PostgresPlugin, buildWhere, createAdapter };