@atscript/db-postgres 0.1.104 → 0.1.106

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.cjs CHANGED
@@ -1,5 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_plugin = require("./plugin-D8KAYro7.cjs");
3
2
  let _atscript_db = require("@atscript/db");
4
3
  let _atscript_db_sql_tools = require("@atscript/db-sql-tools");
5
4
  //#region src/sql-builder.ts
@@ -66,11 +65,50 @@ const pgDialect = {
66
65
  params: [pattern]
67
66
  };
68
67
  },
68
+ geoWithin(quotedCol, circle) {
69
+ return {
70
+ sql: `ST_DWithin(${quotedCol}, ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography, ?)`,
71
+ params: [
72
+ circle.center[0],
73
+ circle.center[1],
74
+ circle.radius
75
+ ]
76
+ };
77
+ },
69
78
  createViewPrefix: "CREATE OR REPLACE VIEW",
70
79
  paramPlaceholder(index) {
71
80
  return `$${index}`;
72
81
  }
73
82
  };
83
+ /**
84
+ * Distance-in-meters expression from a geography column to a query point.
85
+ * Used as the `distExpr` of the shared geo search builder.
86
+ */
87
+ function pgGeoDistanceExpr(quotedCol, point) {
88
+ return {
89
+ sql: `ST_Distance(${quotedCol}, ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography)`,
90
+ params: [point[0], point[1]]
91
+ };
92
+ }
93
+ /** Formats a `[lng, lat]` tuple as EWKT — the text input format for geography params. */
94
+ function geoPointToEwkt(point) {
95
+ return `SRID=4326;POINT(${point[0]} ${point[1]})`;
96
+ }
97
+ /**
98
+ * Parses a hex-encoded (E)WKB point — the raw wire format the pg driver
99
+ * returns for geography columns — back to a `[lng, lat]` tuple.
100
+ * Returns `undefined` for anything that isn't a WKB point.
101
+ */
102
+ function parseEwkbPointHex(hex) {
103
+ if (hex.length < 42 || !/^[0-9A-Fa-f]+$/.test(hex)) return;
104
+ const buf = Buffer.from(hex, "hex");
105
+ const littleEndian = buf.readUInt8(0) === 1;
106
+ const type = littleEndian ? buf.readUInt32LE(1) : buf.readUInt32BE(1);
107
+ if ((type & 255) !== 1) return;
108
+ const offset = (type & 536870912) !== 0 ? 9 : 5;
109
+ if (buf.length < offset + 16) return;
110
+ return [littleEndian ? buf.readDoubleLE(offset) : buf.readDoubleBE(offset), littleEndian ? buf.readDoubleLE(offset + 8) : buf.readDoubleBE(offset + 8)];
111
+ }
74
112
  function buildInsert(table, data) {
75
113
  return (0, _atscript_db_sql_tools.buildInsert)(pgDialect, table, data);
76
114
  }
@@ -275,6 +313,8 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
275
313
  _nocaseColumns = /* @__PURE__ */ new Set();
276
314
  /** Whether citext extension has been provisioned (avoids redundant round-trips). */
277
315
  _citextProvisioned = false;
316
+ /** Whether the connected PostgreSQL instance has the PostGIS extension. */
317
+ _supportsGeo;
278
318
  /** Whether the connected PostgreSQL instance has the pgvector extension. */
279
319
  _supportsVector;
280
320
  /** Vector fields: physical field name → { dimensions, similarity, indexName }. */
@@ -372,11 +412,24 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
372
412
  * invalid for the pgvector `vector` type — it expects bracket-delimited `[1,2,3]`.
373
413
  */
374
414
  formatValue(field) {
375
- if (!this._vectorFields.has(field.path)) return;
376
- return {
415
+ if (this._vectorFields.has(field.path)) return {
377
416
  toStorage: (value) => Array.isArray(value) ? `[${value.join(",")}]` : value,
378
417
  fromStorage: (value) => typeof value === "string" ? JSON.parse(value) : value
379
418
  };
419
+ if (field.isGeoPoint && !field.encrypted) return {
420
+ toStorage: (value) => {
421
+ if (!this._supportsGeo) return value;
422
+ const point = (0, _atscript_db_sql_tools.normalizeGeoPointValue)(value);
423
+ return point ? geoPointToEwkt(point) : value;
424
+ },
425
+ fromStorage: (value) => {
426
+ if (typeof value === "string") {
427
+ const point = parseEwkbPointHex(value);
428
+ if (point) return point;
429
+ }
430
+ return value;
431
+ }
432
+ };
380
433
  }
381
434
  /**
382
435
  * Wraps an async write operation to catch PostgreSQL constraint errors
@@ -565,6 +618,14 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
565
618
  this._log(sql, params);
566
619
  return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
567
620
  }
621
+ async prepareTypeMapper() {
622
+ if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
623
+ if (this._supportsGeo === void 0 && this._hasGeoPointFields()) await this._detectGeoSupport();
624
+ }
625
+ /** Whether the table declares any `db.geoPoint` fields (unencrypted). */
626
+ _hasGeoPointFields() {
627
+ return this._table.fieldDescriptors.some((fd) => fd.isGeoPoint && !fd.encrypted);
628
+ }
568
629
  async ensureTable() {
569
630
  if (this._nocaseColumns.size > 0 && !this._citextProvisioned) try {
570
631
  await this._exec().exec("CREATE EXTENSION IF NOT EXISTS citext");
@@ -573,7 +634,8 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
573
634
  const msg = err instanceof Error ? err.message : String(err);
574
635
  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 });
575
636
  }
576
- if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
637
+ await this.prepareTypeMapper();
638
+ if (this._schema) await this._exec().exec(`CREATE SCHEMA IF NOT EXISTS ${qi(this._schema)}`);
577
639
  if (this._table instanceof _atscript_db.AtscriptDbView) return this._ensureView();
578
640
  const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, {
579
641
  incrementFields: this._incrementFields,
@@ -654,7 +716,9 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
654
716
  for (const { field } of diff.typeChanged ?? []) {
655
717
  const sqlType = this.typeMapper(field);
656
718
  const col = qi(field.physicalName);
657
- const ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING ${col}::text::${sqlType}`;
719
+ let ddl;
720
+ if (field.isGeoPoint && sqlType.startsWith("geography")) ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING CASE WHEN ${col} IS NULL THEN NULL ELSE ST_SetSRID(ST_MakePoint((${col}->>0)::float8, (${col}->>1)::float8), 4326)::geography END`;
721
+ else ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING ${col}::text::${sqlType}`;
658
722
  this._log(ddl);
659
723
  await this._exec().exec(ddl);
660
724
  }
@@ -815,6 +879,27 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
815
879
  this._log(ddl);
816
880
  await this._exec().exec(ddl);
817
881
  }
882
+ async dropIndexesForColumns(columns) {
883
+ const tableName = this._table.tableName;
884
+ const schema = this._schema;
885
+ const rows = await this._exec().all(`SELECT DISTINCT i.relname AS name
886
+ FROM pg_index x
887
+ JOIN pg_class t ON t.oid = x.indrelid
888
+ JOIN pg_class i ON i.oid = x.indexrelid
889
+ JOIN pg_namespace n ON n.oid = t.relnamespace
890
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(x.indkey)
891
+ WHERE t.relname = $1 AND n.nspname = COALESCE($2, 'public')
892
+ AND i.relname LIKE 'atscript__%' AND a.attname = ANY($3)`, [
893
+ tableName,
894
+ schema,
895
+ columns
896
+ ]);
897
+ for (const row of rows) {
898
+ const sql = `DROP INDEX IF EXISTS ${schema ? `${qi(schema)}.` : ""}${qi(row.name)}`;
899
+ this._log(sql);
900
+ await this._exec().exec(sql);
901
+ }
902
+ }
818
903
  async dropTableByName(tableName) {
819
904
  const ddl = `DROP TABLE IF EXISTS ${quoteTableName(tableName)} CASCADE`;
820
905
  this._log(ddl);
@@ -837,14 +922,29 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
837
922
  const vec = this._vectorFields.get(field.path);
838
923
  return this._supportsVector ? `vector(${vec.dimensions})` : "JSONB";
839
924
  }
925
+ if (field.isGeoPoint) return this._supportsGeo ? "geography(Point,4326)" : pgTypeFromField(field);
840
926
  return pgTypeFromField(field);
841
927
  }
842
928
  async syncIndexes() {
843
929
  const tableName = this._table.tableName;
844
930
  const schema = this._schema;
931
+ await this.prepareTypeMapper();
845
932
  await this.syncIndexesWithDiff({
846
- listExisting: async () => this._exec().all(`SELECT indexname AS name FROM pg_indexes
847
- WHERE tablename = $1 AND schemaname = COALESCE($2, 'public')`, [tableName, schema]),
933
+ listExisting: async () => {
934
+ return (await this._exec().all(`SELECT i.relname AS name,
935
+ (SELECT array_agg(a.attname::text ORDER BY k.ord)
936
+ FROM unnest(x.indkey) WITH ORDINALITY AS k(attnum, ord)
937
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
938
+ WHERE k.attnum > 0) AS columns
939
+ FROM pg_index x
940
+ JOIN pg_class t ON t.oid = x.indrelid
941
+ JOIN pg_class i ON i.oid = x.indexrelid
942
+ JOIN pg_namespace n ON n.oid = t.relnamespace
943
+ WHERE t.relname = $1 AND n.nspname = COALESCE($2, 'public')`, [tableName, schema])).map((r) => ({
944
+ name: r.name,
945
+ columns: r.columns ?? void 0
946
+ }));
947
+ },
848
948
  createIndex: async (index) => {
849
949
  if (index.type === "fulltext") {
850
950
  const tsvectorExpr = this._buildTsvectorExpr(index.fields);
@@ -853,6 +953,16 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
853
953
  await this._exec().exec(sql);
854
954
  return;
855
955
  }
956
+ if (index.type === "geo") {
957
+ if (!this._supportsGeo) {
958
+ this.logger.warn(`[postgres] geo index "${index.name}" declared but PostGIS is not available — skipped`);
959
+ return;
960
+ }
961
+ const sql = `CREATE INDEX IF NOT EXISTS ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} USING gist (${qi(index.fields[0].name)})`;
962
+ this._log(sql);
963
+ await this._exec().exec(sql);
964
+ return;
965
+ }
856
966
  const unique = index.type === "unique" ? "UNIQUE " : "";
857
967
  const cols = index.fields.map((f) => `${qi(f.name)} ${f.sort === "desc" ? "DESC" : "ASC"}`).join(", ");
858
968
  const sql = `CREATE ${unique}INDEX IF NOT EXISTS ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} (${cols})`;
@@ -863,10 +973,6 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
863
973
  const sql = `DROP INDEX IF EXISTS ${schema ? `${qi(schema)}.` : ""}${qi(name)}`;
864
974
  this._log(sql);
865
975
  await this._exec().exec(sql);
866
- },
867
- warnUnsupportedTypes: {
868
- adapter: "postgres",
869
- types: ["geo"]
870
976
  }
871
977
  });
872
978
  if (this._supportsVector) for (const [field, vec] of this._vectorFields) {
@@ -1115,6 +1221,65 @@ var PostgresAdapter = class PostgresAdapter extends _atscript_db.BaseDbAdapter {
1115
1221
  if (queryThreshold !== void 0) return queryThreshold;
1116
1222
  return this._vectorThresholds.get(indexName);
1117
1223
  }
1224
+ /**
1225
+ * Detects PostGIS support by attempting to enable the extension.
1226
+ * Idempotent — safe to call multiple times.
1227
+ */
1228
+ async _detectGeoSupport() {
1229
+ if (this._supportsGeo !== void 0) return this._supportsGeo;
1230
+ try {
1231
+ await this._exec().exec("CREATE EXTENSION IF NOT EXISTS postgis");
1232
+ this._supportsGeo = true;
1233
+ } catch {
1234
+ this._supportsGeo = false;
1235
+ this.logger.warn("[postgres] PostGIS extension not available — db.geoPoint fields are stored as JSONB (no geo search).");
1236
+ }
1237
+ return this._supportsGeo;
1238
+ }
1239
+ isGeoSearchable() {
1240
+ return this._supportsGeo === true;
1241
+ }
1242
+ async geoSearch(point, query, indexName) {
1243
+ await this._detectGeoSupport();
1244
+ const { sql, params } = this._buildGeoSearchSelect(this._prepareGeoSearch(point, query, indexName));
1245
+ this._log(sql, params);
1246
+ return (await this._exec().all(sql, params)).map((row) => (0, _atscript_db_sql_tools.renameGeoDistance)(row));
1247
+ }
1248
+ async geoSearchWithCount(point, query, indexName) {
1249
+ await this._detectGeoSupport();
1250
+ const ctx = this._prepareGeoSearch(point, query, indexName);
1251
+ const { sql, params } = this._buildGeoSearchSelect(ctx);
1252
+ const countFrag = (0, _atscript_db_sql_tools.buildGeoSearchCount)(pgDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window);
1253
+ this._log(sql, params);
1254
+ this._log(countFrag.sql, countFrag.params);
1255
+ const [rows, countRow] = await Promise.all([this._exec().all(sql, params), this._exec().get(countFrag.sql, countFrag.params)]);
1256
+ return {
1257
+ data: rows.map((row) => (0, _atscript_db_sql_tools.renameGeoDistance)(row)),
1258
+ count: parseCount(countRow?.cnt)
1259
+ };
1260
+ }
1261
+ /** Resolves the shared parts of a geo search once (column, filter, distance, window). */
1262
+ _prepareGeoSearch(point, query, indexName) {
1263
+ if (!this._supportsGeo) throw new _atscript_db.DbError("GEO_NOT_SUPPORTED", [{
1264
+ path: "",
1265
+ message: "Geo search requires the PostGIS extension"
1266
+ }]);
1267
+ const column = this._resolveGeoColumn(indexName);
1268
+ const controls = query.controls ?? {};
1269
+ return {
1270
+ tableName: this.resolveTableName(),
1271
+ where: buildWhere(query.filter),
1272
+ dist: pgGeoDistanceExpr(qi(column), point),
1273
+ window: (0, _atscript_db_sql_tools.geoWindowFromControls)(controls),
1274
+ controls
1275
+ };
1276
+ }
1277
+ _buildGeoSearchSelect(ctx) {
1278
+ return (0, _atscript_db_sql_tools.buildGeoSearchSelect)(pgDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window, {
1279
+ $limit: ctx.controls.$limit,
1280
+ $skip: ctx.controls.$skip
1281
+ });
1282
+ }
1118
1283
  };
1119
1284
  /**
1120
1285
  * Normalizes PostgreSQL information_schema data_type values
@@ -1139,6 +1304,7 @@ function normalizePgType(dataType, maxLength, numericPrecision, numericScale, ud
1139
1304
  case "user-defined":
1140
1305
  if (udtName === "vector") return formattedType;
1141
1306
  if (udtName === "citext") return "CITEXT";
1307
+ if (udtName === "geography") return formattedType;
1142
1308
  return udtName?.toUpperCase() ?? "USER-DEFINED";
1143
1309
  default: return dataType.toUpperCase();
1144
1310
  }
@@ -1365,6 +1531,5 @@ function createAdapter(uri, options) {
1365
1531
  //#endregion
1366
1532
  exports.PgDriver = PgDriver;
1367
1533
  exports.PostgresAdapter = PostgresAdapter;
1368
- exports.PostgresPlugin = require_plugin.PostgresPlugin;
1369
1534
  exports.buildWhere = buildWhere;
1370
1535
  exports.createAdapter = createAdapter;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,3 @@
1
- import { t as PostgresPlugin } from "./index-Cky7JZNO.cjs";
2
1
  import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbUpdateResult, TExistingColumn, TFieldOps, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
3
2
  import { TMetadataMap } from "@atscript/typescript/utils";
4
3
  import { TSqlFragment } from "@atscript/db-sql-tools";
@@ -96,6 +95,8 @@ declare class PostgresAdapter extends BaseDbAdapter {
96
95
  private _nocaseColumns;
97
96
  /** Whether citext extension has been provisioned (avoids redundant round-trips). */
98
97
  private _citextProvisioned;
98
+ /** Whether the connected PostgreSQL instance has the PostGIS extension. */
99
+ private _supportsGeo;
99
100
  /** Whether the connected PostgreSQL instance has the pgvector extension. */
100
101
  private _supportsVector;
101
102
  /** Vector fields: physical field name → { dimensions, similarity, indexName }. */
@@ -152,6 +153,9 @@ declare class PostgresAdapter extends BaseDbAdapter {
152
153
  replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
153
154
  deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
154
155
  deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
156
+ prepareTypeMapper(): Promise<void>;
157
+ /** Whether the table declares any `db.geoPoint` fields (unencrypted). */
158
+ private _hasGeoPointFields;
155
159
  ensureTable(): Promise<void>;
156
160
  private _ensureView;
157
161
  getExistingColumns(): Promise<TExistingColumn[]>;
@@ -169,6 +173,7 @@ declare class PostgresAdapter extends BaseDbAdapter {
169
173
  tableExists(): Promise<boolean>;
170
174
  dropTable(): Promise<void>;
171
175
  dropColumns(columns: string[]): Promise<void>;
176
+ dropIndexesForColumns(columns: string[]): Promise<void>;
172
177
  dropTableByName(tableName: string): Promise<void>;
173
178
  dropViewByName(viewName: string): Promise<void>;
174
179
  renameTable(oldName: string): Promise<void>;
@@ -205,6 +210,20 @@ declare class PostgresAdapter extends BaseDbAdapter {
205
210
  private _buildVectorSearchCountQuery;
206
211
  /** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
207
212
  private _resolveVectorThreshold;
213
+ /**
214
+ * Detects PostGIS support by attempting to enable the extension.
215
+ * Idempotent — safe to call multiple times.
216
+ */
217
+ private _detectGeoSupport;
218
+ isGeoSearchable(): boolean;
219
+ geoSearch(point: [number, number], query: DbQuery, indexName?: string): Promise<Array<Record<string, unknown>>>;
220
+ geoSearchWithCount(point: [number, number], query: DbQuery, indexName?: string): Promise<{
221
+ data: Array<Record<string, unknown>>;
222
+ count: number;
223
+ }>;
224
+ /** Resolves the shared parts of a geo search once (column, filter, distance, window). */
225
+ private _prepareGeoSearch;
226
+ private _buildGeoSearchSelect;
208
227
  }
209
228
  //#endregion
210
229
  //#region src/pg-driver.d.ts
@@ -275,4 +294,4 @@ declare function buildWhere(filter: FilterExpr): TSqlFragment;
275
294
  */
276
295
  declare function createAdapter(uri: string, options?: Record<string, unknown>): DbSpace;
277
296
  //#endregion
278
- export { PgDriver, PostgresAdapter, PostgresPlugin, type TPgConnection, type TPgDriver, type TPgRunResult, type TSqlFragment, buildWhere, createAdapter };
297
+ export { PgDriver, PostgresAdapter, type TPgConnection, type TPgDriver, type TPgRunResult, type TSqlFragment, buildWhere, createAdapter };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,3 @@
1
- import { t as PostgresPlugin } from "./index-Cky7JZNO.mjs";
2
1
  import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbUpdateResult, TExistingColumn, TFieldOps, TSearchIndexInfo, TSyncColumnResult, TValueFormatterPair } from "@atscript/db";
3
2
  import { TSqlFragment } from "@atscript/db-sql-tools";
4
3
  import { TMetadataMap } from "@atscript/typescript/utils";
@@ -96,6 +95,8 @@ declare class PostgresAdapter extends BaseDbAdapter {
96
95
  private _nocaseColumns;
97
96
  /** Whether citext extension has been provisioned (avoids redundant round-trips). */
98
97
  private _citextProvisioned;
98
+ /** Whether the connected PostgreSQL instance has the PostGIS extension. */
99
+ private _supportsGeo;
99
100
  /** Whether the connected PostgreSQL instance has the pgvector extension. */
100
101
  private _supportsVector;
101
102
  /** Vector fields: physical field name → { dimensions, similarity, indexName }. */
@@ -152,6 +153,9 @@ declare class PostgresAdapter extends BaseDbAdapter {
152
153
  replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
153
154
  deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
154
155
  deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
156
+ prepareTypeMapper(): Promise<void>;
157
+ /** Whether the table declares any `db.geoPoint` fields (unencrypted). */
158
+ private _hasGeoPointFields;
155
159
  ensureTable(): Promise<void>;
156
160
  private _ensureView;
157
161
  getExistingColumns(): Promise<TExistingColumn[]>;
@@ -169,6 +173,7 @@ declare class PostgresAdapter extends BaseDbAdapter {
169
173
  tableExists(): Promise<boolean>;
170
174
  dropTable(): Promise<void>;
171
175
  dropColumns(columns: string[]): Promise<void>;
176
+ dropIndexesForColumns(columns: string[]): Promise<void>;
172
177
  dropTableByName(tableName: string): Promise<void>;
173
178
  dropViewByName(viewName: string): Promise<void>;
174
179
  renameTable(oldName: string): Promise<void>;
@@ -205,6 +210,20 @@ declare class PostgresAdapter extends BaseDbAdapter {
205
210
  private _buildVectorSearchCountQuery;
206
211
  /** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
207
212
  private _resolveVectorThreshold;
213
+ /**
214
+ * Detects PostGIS support by attempting to enable the extension.
215
+ * Idempotent — safe to call multiple times.
216
+ */
217
+ private _detectGeoSupport;
218
+ isGeoSearchable(): boolean;
219
+ geoSearch(point: [number, number], query: DbQuery, indexName?: string): Promise<Array<Record<string, unknown>>>;
220
+ geoSearchWithCount(point: [number, number], query: DbQuery, indexName?: string): Promise<{
221
+ data: Array<Record<string, unknown>>;
222
+ count: number;
223
+ }>;
224
+ /** Resolves the shared parts of a geo search once (column, filter, distance, window). */
225
+ private _prepareGeoSearch;
226
+ private _buildGeoSearchSelect;
208
227
  }
209
228
  //#endregion
210
229
  //#region src/pg-driver.d.ts
@@ -275,4 +294,4 @@ declare function buildWhere(filter: FilterExpr): TSqlFragment;
275
294
  */
276
295
  declare function createAdapter(uri: string, options?: Record<string, unknown>): DbSpace;
277
296
  //#endregion
278
- export { PgDriver, PostgresAdapter, PostgresPlugin, type TPgConnection, type TPgDriver, type TPgRunResult, type TSqlFragment, buildWhere, createAdapter };
297
+ export { PgDriver, PostgresAdapter, type TPgConnection, type TPgDriver, type TPgRunResult, type TSqlFragment, buildWhere, createAdapter };
package/dist/index.mjs CHANGED
@@ -1,6 +1,5 @@
1
- import { t as PostgresPlugin } from "./plugin-CAuSb5sS.mjs";
2
1
  import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
3
- import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, finalizeParams, parseRegexString, refActionToSql } from "@atscript/db-sql-tools";
2
+ import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, finalizeParams, geoWindowFromControls, normalizeGeoPointValue, parseRegexString, refActionToSql, renameGeoDistance } from "@atscript/db-sql-tools";
4
3
  //#region src/sql-builder.ts
5
4
  /**
6
5
  * PostgreSQL-aware default value for a given design type.
@@ -65,11 +64,50 @@ const pgDialect = {
65
64
  params: [pattern]
66
65
  };
67
66
  },
67
+ geoWithin(quotedCol, circle) {
68
+ return {
69
+ sql: `ST_DWithin(${quotedCol}, ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography, ?)`,
70
+ params: [
71
+ circle.center[0],
72
+ circle.center[1],
73
+ circle.radius
74
+ ]
75
+ };
76
+ },
68
77
  createViewPrefix: "CREATE OR REPLACE VIEW",
69
78
  paramPlaceholder(index) {
70
79
  return `$${index}`;
71
80
  }
72
81
  };
82
+ /**
83
+ * Distance-in-meters expression from a geography column to a query point.
84
+ * Used as the `distExpr` of the shared geo search builder.
85
+ */
86
+ function pgGeoDistanceExpr(quotedCol, point) {
87
+ return {
88
+ sql: `ST_Distance(${quotedCol}, ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography)`,
89
+ params: [point[0], point[1]]
90
+ };
91
+ }
92
+ /** Formats a `[lng, lat]` tuple as EWKT — the text input format for geography params. */
93
+ function geoPointToEwkt(point) {
94
+ return `SRID=4326;POINT(${point[0]} ${point[1]})`;
95
+ }
96
+ /**
97
+ * Parses a hex-encoded (E)WKB point — the raw wire format the pg driver
98
+ * returns for geography columns — back to a `[lng, lat]` tuple.
99
+ * Returns `undefined` for anything that isn't a WKB point.
100
+ */
101
+ function parseEwkbPointHex(hex) {
102
+ if (hex.length < 42 || !/^[0-9A-Fa-f]+$/.test(hex)) return;
103
+ const buf = Buffer.from(hex, "hex");
104
+ const littleEndian = buf.readUInt8(0) === 1;
105
+ const type = littleEndian ? buf.readUInt32LE(1) : buf.readUInt32BE(1);
106
+ if ((type & 255) !== 1) return;
107
+ const offset = (type & 536870912) !== 0 ? 9 : 5;
108
+ if (buf.length < offset + 16) return;
109
+ return [littleEndian ? buf.readDoubleLE(offset) : buf.readDoubleBE(offset), littleEndian ? buf.readDoubleLE(offset + 8) : buf.readDoubleBE(offset + 8)];
110
+ }
73
111
  function buildInsert$1(table, data) {
74
112
  return buildInsert(pgDialect, table, data);
75
113
  }
@@ -274,6 +312,8 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
274
312
  _nocaseColumns = /* @__PURE__ */ new Set();
275
313
  /** Whether citext extension has been provisioned (avoids redundant round-trips). */
276
314
  _citextProvisioned = false;
315
+ /** Whether the connected PostgreSQL instance has the PostGIS extension. */
316
+ _supportsGeo;
277
317
  /** Whether the connected PostgreSQL instance has the pgvector extension. */
278
318
  _supportsVector;
279
319
  /** Vector fields: physical field name → { dimensions, similarity, indexName }. */
@@ -371,11 +411,24 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
371
411
  * invalid for the pgvector `vector` type — it expects bracket-delimited `[1,2,3]`.
372
412
  */
373
413
  formatValue(field) {
374
- if (!this._vectorFields.has(field.path)) return;
375
- return {
414
+ if (this._vectorFields.has(field.path)) return {
376
415
  toStorage: (value) => Array.isArray(value) ? `[${value.join(",")}]` : value,
377
416
  fromStorage: (value) => typeof value === "string" ? JSON.parse(value) : value
378
417
  };
418
+ if (field.isGeoPoint && !field.encrypted) return {
419
+ toStorage: (value) => {
420
+ if (!this._supportsGeo) return value;
421
+ const point = normalizeGeoPointValue(value);
422
+ return point ? geoPointToEwkt(point) : value;
423
+ },
424
+ fromStorage: (value) => {
425
+ if (typeof value === "string") {
426
+ const point = parseEwkbPointHex(value);
427
+ if (point) return point;
428
+ }
429
+ return value;
430
+ }
431
+ };
379
432
  }
380
433
  /**
381
434
  * Wraps an async write operation to catch PostgreSQL constraint errors
@@ -564,6 +617,14 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
564
617
  this._log(sql, params);
565
618
  return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
566
619
  }
620
+ async prepareTypeMapper() {
621
+ if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
622
+ if (this._supportsGeo === void 0 && this._hasGeoPointFields()) await this._detectGeoSupport();
623
+ }
624
+ /** Whether the table declares any `db.geoPoint` fields (unencrypted). */
625
+ _hasGeoPointFields() {
626
+ return this._table.fieldDescriptors.some((fd) => fd.isGeoPoint && !fd.encrypted);
627
+ }
567
628
  async ensureTable() {
568
629
  if (this._nocaseColumns.size > 0 && !this._citextProvisioned) try {
569
630
  await this._exec().exec("CREATE EXTENSION IF NOT EXISTS citext");
@@ -572,7 +633,8 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
572
633
  const msg = err instanceof Error ? err.message : String(err);
573
634
  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 });
574
635
  }
575
- if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
636
+ await this.prepareTypeMapper();
637
+ if (this._schema) await this._exec().exec(`CREATE SCHEMA IF NOT EXISTS ${qi(this._schema)}`);
576
638
  if (this._table instanceof AtscriptDbView) return this._ensureView();
577
639
  const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, {
578
640
  incrementFields: this._incrementFields,
@@ -653,7 +715,9 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
653
715
  for (const { field } of diff.typeChanged ?? []) {
654
716
  const sqlType = this.typeMapper(field);
655
717
  const col = qi(field.physicalName);
656
- const ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING ${col}::text::${sqlType}`;
718
+ let ddl;
719
+ if (field.isGeoPoint && sqlType.startsWith("geography")) ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING CASE WHEN ${col} IS NULL THEN NULL ELSE ST_SetSRID(ST_MakePoint((${col}->>0)::float8, (${col}->>1)::float8), 4326)::geography END`;
720
+ else ddl = `ALTER TABLE ${quoteTableName(tableName)} ALTER COLUMN ${col} TYPE ${sqlType} USING ${col}::text::${sqlType}`;
657
721
  this._log(ddl);
658
722
  await this._exec().exec(ddl);
659
723
  }
@@ -814,6 +878,27 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
814
878
  this._log(ddl);
815
879
  await this._exec().exec(ddl);
816
880
  }
881
+ async dropIndexesForColumns(columns) {
882
+ const tableName = this._table.tableName;
883
+ const schema = this._schema;
884
+ const rows = await this._exec().all(`SELECT DISTINCT i.relname AS name
885
+ FROM pg_index x
886
+ JOIN pg_class t ON t.oid = x.indrelid
887
+ JOIN pg_class i ON i.oid = x.indexrelid
888
+ JOIN pg_namespace n ON n.oid = t.relnamespace
889
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(x.indkey)
890
+ WHERE t.relname = $1 AND n.nspname = COALESCE($2, 'public')
891
+ AND i.relname LIKE 'atscript__%' AND a.attname = ANY($3)`, [
892
+ tableName,
893
+ schema,
894
+ columns
895
+ ]);
896
+ for (const row of rows) {
897
+ const sql = `DROP INDEX IF EXISTS ${schema ? `${qi(schema)}.` : ""}${qi(row.name)}`;
898
+ this._log(sql);
899
+ await this._exec().exec(sql);
900
+ }
901
+ }
817
902
  async dropTableByName(tableName) {
818
903
  const ddl = `DROP TABLE IF EXISTS ${quoteTableName(tableName)} CASCADE`;
819
904
  this._log(ddl);
@@ -836,14 +921,29 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
836
921
  const vec = this._vectorFields.get(field.path);
837
922
  return this._supportsVector ? `vector(${vec.dimensions})` : "JSONB";
838
923
  }
924
+ if (field.isGeoPoint) return this._supportsGeo ? "geography(Point,4326)" : pgTypeFromField(field);
839
925
  return pgTypeFromField(field);
840
926
  }
841
927
  async syncIndexes() {
842
928
  const tableName = this._table.tableName;
843
929
  const schema = this._schema;
930
+ await this.prepareTypeMapper();
844
931
  await this.syncIndexesWithDiff({
845
- listExisting: async () => this._exec().all(`SELECT indexname AS name FROM pg_indexes
846
- WHERE tablename = $1 AND schemaname = COALESCE($2, 'public')`, [tableName, schema]),
932
+ listExisting: async () => {
933
+ return (await this._exec().all(`SELECT i.relname AS name,
934
+ (SELECT array_agg(a.attname::text ORDER BY k.ord)
935
+ FROM unnest(x.indkey) WITH ORDINALITY AS k(attnum, ord)
936
+ JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
937
+ WHERE k.attnum > 0) AS columns
938
+ FROM pg_index x
939
+ JOIN pg_class t ON t.oid = x.indrelid
940
+ JOIN pg_class i ON i.oid = x.indexrelid
941
+ JOIN pg_namespace n ON n.oid = t.relnamespace
942
+ WHERE t.relname = $1 AND n.nspname = COALESCE($2, 'public')`, [tableName, schema])).map((r) => ({
943
+ name: r.name,
944
+ columns: r.columns ?? void 0
945
+ }));
946
+ },
847
947
  createIndex: async (index) => {
848
948
  if (index.type === "fulltext") {
849
949
  const tsvectorExpr = this._buildTsvectorExpr(index.fields);
@@ -852,6 +952,16 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
852
952
  await this._exec().exec(sql);
853
953
  return;
854
954
  }
955
+ if (index.type === "geo") {
956
+ if (!this._supportsGeo) {
957
+ this.logger.warn(`[postgres] geo index "${index.name}" declared but PostGIS is not available — skipped`);
958
+ return;
959
+ }
960
+ const sql = `CREATE INDEX IF NOT EXISTS ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} USING gist (${qi(index.fields[0].name)})`;
961
+ this._log(sql);
962
+ await this._exec().exec(sql);
963
+ return;
964
+ }
855
965
  const unique = index.type === "unique" ? "UNIQUE " : "";
856
966
  const cols = index.fields.map((f) => `${qi(f.name)} ${f.sort === "desc" ? "DESC" : "ASC"}`).join(", ");
857
967
  const sql = `CREATE ${unique}INDEX IF NOT EXISTS ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} (${cols})`;
@@ -862,10 +972,6 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
862
972
  const sql = `DROP INDEX IF EXISTS ${schema ? `${qi(schema)}.` : ""}${qi(name)}`;
863
973
  this._log(sql);
864
974
  await this._exec().exec(sql);
865
- },
866
- warnUnsupportedTypes: {
867
- adapter: "postgres",
868
- types: ["geo"]
869
975
  }
870
976
  });
871
977
  if (this._supportsVector) for (const [field, vec] of this._vectorFields) {
@@ -1114,6 +1220,65 @@ var PostgresAdapter = class PostgresAdapter extends BaseDbAdapter {
1114
1220
  if (queryThreshold !== void 0) return queryThreshold;
1115
1221
  return this._vectorThresholds.get(indexName);
1116
1222
  }
1223
+ /**
1224
+ * Detects PostGIS support by attempting to enable the extension.
1225
+ * Idempotent — safe to call multiple times.
1226
+ */
1227
+ async _detectGeoSupport() {
1228
+ if (this._supportsGeo !== void 0) return this._supportsGeo;
1229
+ try {
1230
+ await this._exec().exec("CREATE EXTENSION IF NOT EXISTS postgis");
1231
+ this._supportsGeo = true;
1232
+ } catch {
1233
+ this._supportsGeo = false;
1234
+ this.logger.warn("[postgres] PostGIS extension not available — db.geoPoint fields are stored as JSONB (no geo search).");
1235
+ }
1236
+ return this._supportsGeo;
1237
+ }
1238
+ isGeoSearchable() {
1239
+ return this._supportsGeo === true;
1240
+ }
1241
+ async geoSearch(point, query, indexName) {
1242
+ await this._detectGeoSupport();
1243
+ const { sql, params } = this._buildGeoSearchSelect(this._prepareGeoSearch(point, query, indexName));
1244
+ this._log(sql, params);
1245
+ return (await this._exec().all(sql, params)).map((row) => renameGeoDistance(row));
1246
+ }
1247
+ async geoSearchWithCount(point, query, indexName) {
1248
+ await this._detectGeoSupport();
1249
+ const ctx = this._prepareGeoSearch(point, query, indexName);
1250
+ const { sql, params } = this._buildGeoSearchSelect(ctx);
1251
+ const countFrag = buildGeoSearchCount(pgDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window);
1252
+ this._log(sql, params);
1253
+ this._log(countFrag.sql, countFrag.params);
1254
+ const [rows, countRow] = await Promise.all([this._exec().all(sql, params), this._exec().get(countFrag.sql, countFrag.params)]);
1255
+ return {
1256
+ data: rows.map((row) => renameGeoDistance(row)),
1257
+ count: parseCount(countRow?.cnt)
1258
+ };
1259
+ }
1260
+ /** Resolves the shared parts of a geo search once (column, filter, distance, window). */
1261
+ _prepareGeoSearch(point, query, indexName) {
1262
+ if (!this._supportsGeo) throw new DbError("GEO_NOT_SUPPORTED", [{
1263
+ path: "",
1264
+ message: "Geo search requires the PostGIS extension"
1265
+ }]);
1266
+ const column = this._resolveGeoColumn(indexName);
1267
+ const controls = query.controls ?? {};
1268
+ return {
1269
+ tableName: this.resolveTableName(),
1270
+ where: buildWhere(query.filter),
1271
+ dist: pgGeoDistanceExpr(qi(column), point),
1272
+ window: geoWindowFromControls(controls),
1273
+ controls
1274
+ };
1275
+ }
1276
+ _buildGeoSearchSelect(ctx) {
1277
+ return buildGeoSearchSelect(pgDialect, ctx.tableName, ctx.where, ctx.dist, ctx.window, {
1278
+ $limit: ctx.controls.$limit,
1279
+ $skip: ctx.controls.$skip
1280
+ });
1281
+ }
1117
1282
  };
1118
1283
  /**
1119
1284
  * Normalizes PostgreSQL information_schema data_type values
@@ -1138,6 +1303,7 @@ function normalizePgType(dataType, maxLength, numericPrecision, numericScale, ud
1138
1303
  case "user-defined":
1139
1304
  if (udtName === "vector") return formattedType;
1140
1305
  if (udtName === "citext") return "CITEXT";
1306
+ if (udtName === "geography") return formattedType;
1141
1307
  return udtName?.toUpperCase() ?? "USER-DEFINED";
1142
1308
  default: return dataType.toUpperCase();
1143
1309
  }
@@ -1362,4 +1528,4 @@ function createAdapter(uri, options) {
1362
1528
  return new DbSpace(() => new PostgresAdapter(driver));
1363
1529
  }
1364
1530
  //#endregion
1365
- export { PgDriver, PostgresAdapter, PostgresPlugin, buildWhere, createAdapter };
1531
+ export { PgDriver, PostgresAdapter, buildWhere, createAdapter };
package/dist/plugin.cjs CHANGED
@@ -2,6 +2,57 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_plugin = require("./plugin-D8KAYro7.cjs");
6
- exports.PostgresPlugin = require_plugin.PostgresPlugin;
7
- exports.default = require_plugin.PostgresPlugin;
5
+ let _atscript_core = require("@atscript/core");
6
+ //#region src/plugin/annotations.ts
7
+ /**
8
+ * PostgreSQL-specific annotations.
9
+ *
10
+ * Merged into the global config under `{ db: { pg: ... } }` so they
11
+ * live alongside core's `@db.table`, `@db.index.*`, etc.
12
+ *
13
+ * These annotations opt-in to PostgreSQL-specific behavior. Files using only
14
+ * portable `@db.*` annotations remain adapter-agnostic.
15
+ */
16
+ const annotations = {
17
+ type: new _atscript_core.AnnotationSpec({
18
+ description: "Overrides the native PostgreSQL column type.\n\n```atscript\n@db.pg.type \"CITEXT\"\nname: string\n```",
19
+ nodeType: ["prop"],
20
+ multiple: false,
21
+ argument: {
22
+ name: "type",
23
+ type: "string",
24
+ description: "Native PostgreSQL column type (e.g., \"CITEXT\", \"INET\", \"MACADDR\")."
25
+ }
26
+ }),
27
+ schema: new _atscript_core.AnnotationSpec({
28
+ description: "Specifies the PostgreSQL schema for the table.\n\n**Default:** `\"public\"`\n\n```atscript\n@db.pg.schema \"analytics\"\nexport interface Events { ... }\n```",
29
+ nodeType: ["interface"],
30
+ multiple: false,
31
+ argument: {
32
+ name: "schema",
33
+ type: "string",
34
+ description: "PostgreSQL schema name (e.g., \"public\", \"analytics\")."
35
+ }
36
+ }),
37
+ collate: new _atscript_core.AnnotationSpec({
38
+ description: "Specifies a native PostgreSQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.pg.collate \"tr-x-icu\"\nname: string\n```",
39
+ nodeType: ["interface", "prop"],
40
+ multiple: false,
41
+ argument: {
42
+ name: "collation",
43
+ type: "string",
44
+ description: "Native PostgreSQL collation name (e.g., \"tr-x-icu\", \"C\", \"und-x-icu\")."
45
+ }
46
+ })
47
+ };
48
+ //#endregion
49
+ //#region src/plugin/index.ts
50
+ const PostgresPlugin = () => ({
51
+ name: "postgres",
52
+ config() {
53
+ return { annotations: { db: { pg: annotations } } };
54
+ }
55
+ });
56
+ //#endregion
57
+ exports.PostgresPlugin = PostgresPlugin;
58
+ exports.default = PostgresPlugin;
package/dist/plugin.d.cts CHANGED
@@ -1,2 +1,6 @@
1
- import { t as PostgresPlugin } from "./index-Cky7JZNO.cjs";
1
+ import { TAtscriptPlugin } from "@atscript/core";
2
+
3
+ //#region src/plugin/index.d.ts
4
+ declare const PostgresPlugin: () => TAtscriptPlugin;
5
+ //#endregion
2
6
  export { PostgresPlugin, PostgresPlugin as default };
package/dist/plugin.d.mts CHANGED
@@ -1,2 +1,6 @@
1
- import { t as PostgresPlugin } from "./index-Cky7JZNO.mjs";
1
+ import { TAtscriptPlugin } from "@atscript/core";
2
+
3
+ //#region src/plugin/index.d.ts
4
+ declare const PostgresPlugin: () => TAtscriptPlugin;
5
+ //#endregion
2
6
  export { PostgresPlugin, PostgresPlugin as default };
package/dist/plugin.mjs CHANGED
@@ -1,2 +1,53 @@
1
- import { t as PostgresPlugin } from "./plugin-CAuSb5sS.mjs";
1
+ import { AnnotationSpec } from "@atscript/core";
2
+ //#region src/plugin/annotations.ts
3
+ /**
4
+ * PostgreSQL-specific annotations.
5
+ *
6
+ * Merged into the global config under `{ db: { pg: ... } }` so they
7
+ * live alongside core's `@db.table`, `@db.index.*`, etc.
8
+ *
9
+ * These annotations opt-in to PostgreSQL-specific behavior. Files using only
10
+ * portable `@db.*` annotations remain adapter-agnostic.
11
+ */
12
+ const annotations = {
13
+ type: new AnnotationSpec({
14
+ description: "Overrides the native PostgreSQL column type.\n\n```atscript\n@db.pg.type \"CITEXT\"\nname: string\n```",
15
+ nodeType: ["prop"],
16
+ multiple: false,
17
+ argument: {
18
+ name: "type",
19
+ type: "string",
20
+ description: "Native PostgreSQL column type (e.g., \"CITEXT\", \"INET\", \"MACADDR\")."
21
+ }
22
+ }),
23
+ schema: new AnnotationSpec({
24
+ description: "Specifies the PostgreSQL schema for the table.\n\n**Default:** `\"public\"`\n\n```atscript\n@db.pg.schema \"analytics\"\nexport interface Events { ... }\n```",
25
+ nodeType: ["interface"],
26
+ multiple: false,
27
+ argument: {
28
+ name: "schema",
29
+ type: "string",
30
+ description: "PostgreSQL schema name (e.g., \"public\", \"analytics\")."
31
+ }
32
+ }),
33
+ collate: new AnnotationSpec({
34
+ description: "Specifies a native PostgreSQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.pg.collate \"tr-x-icu\"\nname: string\n```",
35
+ nodeType: ["interface", "prop"],
36
+ multiple: false,
37
+ argument: {
38
+ name: "collation",
39
+ type: "string",
40
+ description: "Native PostgreSQL collation name (e.g., \"tr-x-icu\", \"C\", \"und-x-icu\")."
41
+ }
42
+ })
43
+ };
44
+ //#endregion
45
+ //#region src/plugin/index.ts
46
+ const PostgresPlugin = () => ({
47
+ name: "postgres",
48
+ config() {
49
+ return { annotations: { db: { pg: annotations } } };
50
+ }
51
+ });
52
+ //#endregion
2
53
  export { PostgresPlugin, PostgresPlugin as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db-postgres",
3
- "version": "0.1.104",
3
+ "version": "0.1.106",
4
4
  "description": "PostgreSQL adapter for @atscript/db with pg (node-postgres) driver support.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -43,20 +43,20 @@
43
43
  "access": "public"
44
44
  },
45
45
  "devDependencies": {
46
- "@atscript/core": "^0.1.75",
47
- "@atscript/typescript": "^0.1.75",
46
+ "@atscript/core": "^0.1.76",
47
+ "@atscript/typescript": "^0.1.76",
48
48
  "@types/pg": "^8.11.0",
49
49
  "@uniqu/core": "^0.1.6",
50
50
  "pg": "^8.13.0",
51
- "unplugin-atscript": "^0.1.75"
51
+ "unplugin-atscript": "^0.1.76"
52
52
  },
53
53
  "peerDependencies": {
54
- "@atscript/core": "^0.1.75",
55
- "@atscript/typescript": "^0.1.75",
54
+ "@atscript/core": "^0.1.76",
55
+ "@atscript/typescript": "^0.1.76",
56
56
  "@uniqu/core": "^0.1.6",
57
57
  "pg": ">=8.0.0",
58
- "@atscript/db": "^0.1.104",
59
- "@atscript/db-sql-tools": "^0.1.104"
58
+ "@atscript/db": "^0.1.106",
59
+ "@atscript/db-sql-tools": "^0.1.106"
60
60
  },
61
61
  "peerDependenciesMeta": {
62
62
  "pg": {
@@ -1,6 +0,0 @@
1
- import { TAtscriptPlugin } from "@atscript/core";
2
-
3
- //#region src/plugin/index.d.ts
4
- declare const PostgresPlugin: () => TAtscriptPlugin;
5
- //#endregion
6
- export { PostgresPlugin as t };
@@ -1,6 +0,0 @@
1
- import { TAtscriptPlugin } from "@atscript/core";
2
-
3
- //#region src/plugin/index.d.ts
4
- declare const PostgresPlugin: () => TAtscriptPlugin;
5
- //#endregion
6
- export { PostgresPlugin as t };
@@ -1,53 +0,0 @@
1
- import { AnnotationSpec } from "@atscript/core";
2
- //#region src/plugin/annotations.ts
3
- /**
4
- * PostgreSQL-specific annotations.
5
- *
6
- * Merged into the global config under `{ db: { pg: ... } }` so they
7
- * live alongside core's `@db.table`, `@db.index.*`, etc.
8
- *
9
- * These annotations opt-in to PostgreSQL-specific behavior. Files using only
10
- * portable `@db.*` annotations remain adapter-agnostic.
11
- */
12
- const annotations = {
13
- type: new AnnotationSpec({
14
- description: "Overrides the native PostgreSQL column type.\n\n```atscript\n@db.pg.type \"CITEXT\"\nname: string\n```",
15
- nodeType: ["prop"],
16
- multiple: false,
17
- argument: {
18
- name: "type",
19
- type: "string",
20
- description: "Native PostgreSQL column type (e.g., \"CITEXT\", \"INET\", \"MACADDR\")."
21
- }
22
- }),
23
- schema: new AnnotationSpec({
24
- description: "Specifies the PostgreSQL schema for the table.\n\n**Default:** `\"public\"`\n\n```atscript\n@db.pg.schema \"analytics\"\nexport interface Events { ... }\n```",
25
- nodeType: ["interface"],
26
- multiple: false,
27
- argument: {
28
- name: "schema",
29
- type: "string",
30
- description: "PostgreSQL schema name (e.g., \"public\", \"analytics\")."
31
- }
32
- }),
33
- collate: new AnnotationSpec({
34
- description: "Specifies a native PostgreSQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.pg.collate \"tr-x-icu\"\nname: string\n```",
35
- nodeType: ["interface", "prop"],
36
- multiple: false,
37
- argument: {
38
- name: "collation",
39
- type: "string",
40
- description: "Native PostgreSQL collation name (e.g., \"tr-x-icu\", \"C\", \"und-x-icu\")."
41
- }
42
- })
43
- };
44
- //#endregion
45
- //#region src/plugin/index.ts
46
- const PostgresPlugin = () => ({
47
- name: "postgres",
48
- config() {
49
- return { annotations: { db: { pg: annotations } } };
50
- }
51
- });
52
- //#endregion
53
- export { PostgresPlugin as t };
@@ -1,58 +0,0 @@
1
- let _atscript_core = require("@atscript/core");
2
- //#region src/plugin/annotations.ts
3
- /**
4
- * PostgreSQL-specific annotations.
5
- *
6
- * Merged into the global config under `{ db: { pg: ... } }` so they
7
- * live alongside core's `@db.table`, `@db.index.*`, etc.
8
- *
9
- * These annotations opt-in to PostgreSQL-specific behavior. Files using only
10
- * portable `@db.*` annotations remain adapter-agnostic.
11
- */
12
- const annotations = {
13
- type: new _atscript_core.AnnotationSpec({
14
- description: "Overrides the native PostgreSQL column type.\n\n```atscript\n@db.pg.type \"CITEXT\"\nname: string\n```",
15
- nodeType: ["prop"],
16
- multiple: false,
17
- argument: {
18
- name: "type",
19
- type: "string",
20
- description: "Native PostgreSQL column type (e.g., \"CITEXT\", \"INET\", \"MACADDR\")."
21
- }
22
- }),
23
- schema: new _atscript_core.AnnotationSpec({
24
- description: "Specifies the PostgreSQL schema for the table.\n\n**Default:** `\"public\"`\n\n```atscript\n@db.pg.schema \"analytics\"\nexport interface Events { ... }\n```",
25
- nodeType: ["interface"],
26
- multiple: false,
27
- argument: {
28
- name: "schema",
29
- type: "string",
30
- description: "PostgreSQL schema name (e.g., \"public\", \"analytics\")."
31
- }
32
- }),
33
- collate: new _atscript_core.AnnotationSpec({
34
- description: "Specifies a native PostgreSQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.pg.collate \"tr-x-icu\"\nname: string\n```",
35
- nodeType: ["interface", "prop"],
36
- multiple: false,
37
- argument: {
38
- name: "collation",
39
- type: "string",
40
- description: "Native PostgreSQL collation name (e.g., \"tr-x-icu\", \"C\", \"und-x-icu\")."
41
- }
42
- })
43
- };
44
- //#endregion
45
- //#region src/plugin/index.ts
46
- const PostgresPlugin = () => ({
47
- name: "postgres",
48
- config() {
49
- return { annotations: { db: { pg: annotations } } };
50
- }
51
- });
52
- //#endregion
53
- Object.defineProperty(exports, "PostgresPlugin", {
54
- enumerable: true,
55
- get: function() {
56
- return PostgresPlugin;
57
- }
58
- });