@atscript/db 0.1.103 → 0.1.105

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/plugin.cjs CHANGED
@@ -302,6 +302,35 @@ const dbColumnAnnotations = {
302
302
  });
303
303
  return errors;
304
304
  }
305
+ }),
306
+ encrypted: new _atscript_core.AnnotationSpec({
307
+ description: "Encrypts this field at rest. Values are AES-256-GCM encrypted by the core layer before reaching the database and decrypted on read — transparent to application code.\n\nConstraints: the field cannot be filtered, sorted, indexed, used as PK/FK, patched with arithmetic ops, or referenced by search/vector/geo features.\n\n**Example:**\n```atscript\n@db.encrypted\napiToken?: string\n```\n",
308
+ nodeType: ["prop"],
309
+ multiple: false,
310
+ validate(token, _args, _doc) {
311
+ const errors = [];
312
+ const field = token.parentNode;
313
+ for (const [name, why] of [
314
+ ["meta.id", "the primary key must be addressable"],
315
+ ["db.rel.FK", "joins are impossible over ciphertext"],
316
+ ["db.index.plain", "indexes over ciphertext are meaningless"],
317
+ ["db.index.unique", "indexes over ciphertext are meaningless"],
318
+ ["db.index.fulltext", "indexes over ciphertext are meaningless"],
319
+ ["db.index.geo", "geo indexes over ciphertext are meaningless"],
320
+ ["db.search.vector", "vector search over ciphertext is impossible"],
321
+ ["db.search.filter", "search filters need cleartext equality"],
322
+ ["db.column.version", "the OCC filter needs cleartext equality"],
323
+ ["db.default.increment", "engine-side defaults bypass the encryption transform"],
324
+ ["db.default.now", "engine-side defaults bypass the encryption transform"],
325
+ ["db.mongo.search.text", "Atlas Search over ciphertext is impossible"],
326
+ ["db.mongo.search.autocomplete", "Atlas Search over ciphertext is impossible"]
327
+ ]) if (field.countAnnotations(name) > 0) errors.push({
328
+ message: `@db.encrypted cannot coexist with @${name} — ${why}`,
329
+ severity: 1,
330
+ range: token.range
331
+ });
332
+ return errors;
333
+ }
305
334
  })
306
335
  };
307
336
  function columnCapability(capability, verb) {
@@ -377,6 +406,17 @@ const dbIndexAnnotations = { index: {
377
406
  type: "number",
378
407
  description: "Field importance in search results (higher = more relevant). Defaults to `1`. Supported by databases with weighted fulltext (e.g., MongoDB, PostgreSQL)."
379
408
  }]
409
+ }),
410
+ geo: new _atscript_core.AnnotationSpec({
411
+ description: "Geospatial index on a `db.geoPoint` field. Enables `geoSearch()` (distance-ranked) and accelerates `$geoWithin` filters.\n\n**Example:**\n```atscript\n@db.index.geo\ngeo: db.geoPoint\n```\n",
412
+ nodeType: ["prop"],
413
+ multiple: false,
414
+ argument: {
415
+ optional: true,
416
+ name: "name",
417
+ type: "string",
418
+ description: "Index name. Defaults to the field name."
419
+ }
380
420
  })
381
421
  } };
382
422
  //#endregion
@@ -1244,6 +1284,7 @@ const dbPlugin = () => ({
1244
1284
  default: dbColumnAnnotations.default,
1245
1285
  json: dbColumnAnnotations.json,
1246
1286
  ignore: dbColumnAnnotations.ignore,
1287
+ encrypted: dbColumnAnnotations.encrypted,
1247
1288
  http: dbTableAnnotations.http,
1248
1289
  sync: dbTableAnnotations.sync,
1249
1290
  depth: dbTableAnnotations.depth,
@@ -1262,6 +1303,13 @@ const dbPlugin = () => ({
1262
1303
  },
1263
1304
  documentation: "Represents a **vector embedding** (array of numbers) for **similarity search**.\n\n- Equivalent to `number[]` but explicitly marks the field as a vector embedding.\n- Each adapter maps this to its native vector type:\n - **MongoDB** → BSON array\n - **MySQL 9+** → `VECTOR(N)`\n - **PostgreSQL** → pgvector `vector(N)`\n - **SQLite** → JSON\n\n**Example:**\n```atscript\n@db.search.vector 1536, \"cosine\"\nembedding: db.vector\n```\n"
1264
1305
  },
1306
+ geoPoint: {
1307
+ type: {
1308
+ kind: "array",
1309
+ of: "number"
1310
+ },
1311
+ documentation: "Represents a **geographic point** as a `[longitude, latitude]` tuple (GeoJSON coordinate order).\n\n- Equivalent to `number[]` of length 2, but explicitly marks the field as a geo point.\n- **Coordinate order is GeoJSON order: longitude first.**\n- Each adapter maps this to its native storage:\n - **MongoDB** → GeoJSON `{ type: 'Point', coordinates: [lng, lat] }`\n - **PostgreSQL** → PostGIS `geography(Point,4326)` (Phase 2)\n - **MySQL** → `POINT` SRID 4326 (Phase 2)\n - **SQLite** → JSON `TEXT` (Phase 2)\n\n**Example:**\n```atscript\n@db.index.geo\ngeo: db.geoPoint\n```\n"
1312
+ },
1265
1313
  currencyCode: {
1266
1314
  type: "string",
1267
1315
  documentation: "Represents a **currency code** — typically ISO 4217 (`'USD'`, `'EUR'`, `'JPY'`) but accepts any uppercase alphanumeric code 2–10 chars long, so crypto and custom codes (`'BTC'`, `'USDC'`, `'POINTS'`) fit too.\n\nPair with `@db.amount.currency.ref 'fieldName'` on a `decimal` field to bind the amount to its row-level currency. The validator checks that the ref target resolves to this type (or a plain `string`).\n\n**Example:**\n```atscript\ncurrency: db.currencyCode\n@db.amount.currency.ref 'currency'\namount: decimal\n```\n",
package/dist/plugin.mjs CHANGED
@@ -298,6 +298,35 @@ const dbColumnAnnotations = {
298
298
  });
299
299
  return errors;
300
300
  }
301
+ }),
302
+ encrypted: new AnnotationSpec({
303
+ description: "Encrypts this field at rest. Values are AES-256-GCM encrypted by the core layer before reaching the database and decrypted on read — transparent to application code.\n\nConstraints: the field cannot be filtered, sorted, indexed, used as PK/FK, patched with arithmetic ops, or referenced by search/vector/geo features.\n\n**Example:**\n```atscript\n@db.encrypted\napiToken?: string\n```\n",
304
+ nodeType: ["prop"],
305
+ multiple: false,
306
+ validate(token, _args, _doc) {
307
+ const errors = [];
308
+ const field = token.parentNode;
309
+ for (const [name, why] of [
310
+ ["meta.id", "the primary key must be addressable"],
311
+ ["db.rel.FK", "joins are impossible over ciphertext"],
312
+ ["db.index.plain", "indexes over ciphertext are meaningless"],
313
+ ["db.index.unique", "indexes over ciphertext are meaningless"],
314
+ ["db.index.fulltext", "indexes over ciphertext are meaningless"],
315
+ ["db.index.geo", "geo indexes over ciphertext are meaningless"],
316
+ ["db.search.vector", "vector search over ciphertext is impossible"],
317
+ ["db.search.filter", "search filters need cleartext equality"],
318
+ ["db.column.version", "the OCC filter needs cleartext equality"],
319
+ ["db.default.increment", "engine-side defaults bypass the encryption transform"],
320
+ ["db.default.now", "engine-side defaults bypass the encryption transform"],
321
+ ["db.mongo.search.text", "Atlas Search over ciphertext is impossible"],
322
+ ["db.mongo.search.autocomplete", "Atlas Search over ciphertext is impossible"]
323
+ ]) if (field.countAnnotations(name) > 0) errors.push({
324
+ message: `@db.encrypted cannot coexist with @${name} — ${why}`,
325
+ severity: 1,
326
+ range: token.range
327
+ });
328
+ return errors;
329
+ }
301
330
  })
302
331
  };
303
332
  function columnCapability(capability, verb) {
@@ -373,6 +402,17 @@ const dbIndexAnnotations = { index: {
373
402
  type: "number",
374
403
  description: "Field importance in search results (higher = more relevant). Defaults to `1`. Supported by databases with weighted fulltext (e.g., MongoDB, PostgreSQL)."
375
404
  }]
405
+ }),
406
+ geo: new AnnotationSpec({
407
+ description: "Geospatial index on a `db.geoPoint` field. Enables `geoSearch()` (distance-ranked) and accelerates `$geoWithin` filters.\n\n**Example:**\n```atscript\n@db.index.geo\ngeo: db.geoPoint\n```\n",
408
+ nodeType: ["prop"],
409
+ multiple: false,
410
+ argument: {
411
+ optional: true,
412
+ name: "name",
413
+ type: "string",
414
+ description: "Index name. Defaults to the field name."
415
+ }
376
416
  })
377
417
  } };
378
418
  //#endregion
@@ -1240,6 +1280,7 @@ const dbPlugin = () => ({
1240
1280
  default: dbColumnAnnotations.default,
1241
1281
  json: dbColumnAnnotations.json,
1242
1282
  ignore: dbColumnAnnotations.ignore,
1283
+ encrypted: dbColumnAnnotations.encrypted,
1243
1284
  http: dbTableAnnotations.http,
1244
1285
  sync: dbTableAnnotations.sync,
1245
1286
  depth: dbTableAnnotations.depth,
@@ -1258,6 +1299,13 @@ const dbPlugin = () => ({
1258
1299
  },
1259
1300
  documentation: "Represents a **vector embedding** (array of numbers) for **similarity search**.\n\n- Equivalent to `number[]` but explicitly marks the field as a vector embedding.\n- Each adapter maps this to its native vector type:\n - **MongoDB** → BSON array\n - **MySQL 9+** → `VECTOR(N)`\n - **PostgreSQL** → pgvector `vector(N)`\n - **SQLite** → JSON\n\n**Example:**\n```atscript\n@db.search.vector 1536, \"cosine\"\nembedding: db.vector\n```\n"
1260
1301
  },
1302
+ geoPoint: {
1303
+ type: {
1304
+ kind: "array",
1305
+ of: "number"
1306
+ },
1307
+ documentation: "Represents a **geographic point** as a `[longitude, latitude]` tuple (GeoJSON coordinate order).\n\n- Equivalent to `number[]` of length 2, but explicitly marks the field as a geo point.\n- **Coordinate order is GeoJSON order: longitude first.**\n- Each adapter maps this to its native storage:\n - **MongoDB** → GeoJSON `{ type: 'Point', coordinates: [lng, lat] }`\n - **PostgreSQL** → PostGIS `geography(Point,4326)` (Phase 2)\n - **MySQL** → `POINT` SRID 4326 (Phase 2)\n - **SQLite** → JSON `TEXT` (Phase 2)\n\n**Example:**\n```atscript\n@db.index.geo\ngeo: db.geoPoint\n```\n"
1308
+ },
1261
1309
  currencyCode: {
1262
1310
  type: "string",
1263
1311
  documentation: "Represents a **currency code** — typically ISO 4217 (`'USD'`, `'EUR'`, `'JPY'`) but accepts any uppercase alphanumeric code 2–10 chars long, so crypto and custom codes (`'BTC'`, `'USDC'`, `'POINTS'`) fit too.\n\nPair with `@db.amount.currency.ref 'fieldName'` on a `decimal` field to bind the amount to its row-level currency. The validator checks that the ref target resolves to this type (or a plain `string`).\n\n**Example:**\n```atscript\ncurrency: db.currencyCode\n@db.amount.currency.ref 'currency'\namount: decimal\n```\n",
package/dist/rel.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { F as TDbForeignKey, H as TDbRelation, dt as TableMetadata, it as TTableResolver, o as BaseDbAdapter, ot as TWriteTableResolver, pt as TGenericLogger } from "./db-readable-mhPp-MPv.cjs";
1
+ import { L as TDbForeignKey, W as TDbRelation, _t as TGenericLogger, c as BaseDbAdapter, ct as TWriteTableResolver, ot as TTableResolver, pt as TableMetadata } from "./db-readable-BepVc21V.cjs";
2
2
  import { t as DbValidationContext } from "./db-validator-plugin-BWy60OvG.cjs";
3
3
  import { FilterExpr, WithRelation } from "@uniqu/core";
4
4
  import { Validator } from "@atscript/typescript/utils";
package/dist/rel.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { F as TDbForeignKey, H as TDbRelation, dt as TableMetadata, it as TTableResolver, o as BaseDbAdapter, ot as TWriteTableResolver, pt as TGenericLogger } from "./db-readable-CXdHBtF6.mjs";
1
+ import { L as TDbForeignKey, W as TDbRelation, _t as TGenericLogger, c as BaseDbAdapter, ct as TWriteTableResolver, ot as TTableResolver, pt as TableMetadata } from "./db-readable-Ds01ezjj.mjs";
2
2
  import { t as DbValidationContext } from "./db-validator-plugin-BWy60OvG.mjs";
3
3
  import { Validator } from "@atscript/typescript/utils";
4
4
  import { FilterExpr, WithRelation } from "@uniqu/core";
package/dist/sync.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_db_view = require("./db-view-GcbegVBD.cjs");
2
+ const require_db_view = require("./db-view-Bw_hlrWw.cjs");
3
3
  //#region src/schema/schema-hash.ts
4
4
  /** Extracts sorted field snapshots from a readable's field descriptors. */
5
5
  function extractFieldSnapshots(fields, typeMapper) {
@@ -13,6 +13,7 @@ function extractFieldSnapshots(fields, typeMapper) {
13
13
  };
14
14
  if (f.defaultValue) snap.defaultValue = f.defaultValue;
15
15
  if (typeMapper) snap.mappedType = typeMapper(f);
16
+ if (f.encrypted) snap.encrypted = true;
16
17
  return snap;
17
18
  }).toSorted((a, b) => a.physicalName.localeCompare(b.physicalName));
18
19
  }
package/dist/sync.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as TColumnDiff, F as TDbForeignKey, G as TExistingColumn, K as TExistingTableOption, M as TDbDefaultValue, P as TDbFieldMeta, U as TDbStorageType, pt as TGenericLogger, rt as TTableOptionDiff, t as AtscriptDbReadable } from "./db-readable-mhPp-MPv.cjs";
2
- import { r as AtscriptDbView, t as DbSpace } from "./db-space-BhOc9_OO.cjs";
1
+ import { G as TDbStorageType, I as TDbFieldMeta, J as TExistingTableOption, L as TDbForeignKey, P as TDbDefaultValue, T as TColumnDiff, _t as TGenericLogger, at as TTableOptionDiff, q as TExistingColumn, t as AtscriptDbReadable } from "./db-readable-BepVc21V.cjs";
2
+ import { i as AtscriptDbView, t as DbSpace } from "./db-space-CWhIEC7E.cjs";
3
3
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
4
4
 
5
5
  //#region src/schema/sync-entry.d.ts
@@ -132,6 +132,8 @@ interface TFieldSnapshot {
132
132
  defaultValue?: TDbDefaultValue;
133
133
  /** Adapter-specific mapped type (e.g., "VARCHAR(255)", "INTEGER"). */
134
134
  mappedType?: string;
135
+ /** `@db.encrypted` — toggling encryption changes the snapshot hash on every adapter. */
136
+ encrypted?: boolean;
135
137
  }
136
138
  interface TIndexSnapshot {
137
139
  key: string;
package/dist/sync.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as TColumnDiff, F as TDbForeignKey, G as TExistingColumn, K as TExistingTableOption, M as TDbDefaultValue, P as TDbFieldMeta, U as TDbStorageType, pt as TGenericLogger, rt as TTableOptionDiff, t as AtscriptDbReadable } from "./db-readable-CXdHBtF6.mjs";
2
- import { r as AtscriptDbView, t as DbSpace } from "./db-space-BYyVZnL1.mjs";
1
+ import { G as TDbStorageType, I as TDbFieldMeta, J as TExistingTableOption, L as TDbForeignKey, P as TDbDefaultValue, T as TColumnDiff, _t as TGenericLogger, at as TTableOptionDiff, q as TExistingColumn, t as AtscriptDbReadable } from "./db-readable-Ds01ezjj.mjs";
2
+ import { i as AtscriptDbView, t as DbSpace } from "./db-space-0U_4PiNS.mjs";
3
3
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
4
4
 
5
5
  //#region src/schema/sync-entry.d.ts
@@ -132,6 +132,8 @@ interface TFieldSnapshot {
132
132
  defaultValue?: TDbDefaultValue;
133
133
  /** Adapter-specific mapped type (e.g., "VARCHAR(255)", "INTEGER"). */
134
134
  mappedType?: string;
135
+ /** `@db.encrypted` — toggling encryption changes the snapshot hash on every adapter. */
136
+ encrypted?: boolean;
135
137
  }
136
138
  interface TIndexSnapshot {
137
139
  key: string;
package/dist/sync.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { g as NoopLogger } from "./db-view-Sq4wCceR.mjs";
1
+ import { S as NoopLogger } from "./db-view-TyzB5VFb.mjs";
2
2
  //#region src/schema/schema-hash.ts
3
3
  /** Extracts sorted field snapshots from a readable's field descriptors. */
4
4
  function extractFieldSnapshots(fields, typeMapper) {
@@ -12,6 +12,7 @@ function extractFieldSnapshots(fields, typeMapper) {
12
12
  };
13
13
  if (f.defaultValue) snap.defaultValue = f.defaultValue;
14
14
  if (typeMapper) snap.mappedType = typeMapper(f);
15
+ if (f.encrypted) snap.encrypted = true;
15
16
  return snap;
16
17
  }).toSorted((a, b) => a.physicalName.localeCompare(b.physicalName));
17
18
  }
@@ -54,6 +54,21 @@ function createDbValidatorPlugin() {
54
54
  return (ctx, def, value) => {
55
55
  const dbCtx = ctx.context;
56
56
  if (!dbCtx) return;
57
+ if (def.type.tags?.has("geoPoint") && value !== void 0 && value !== null) {
58
+ if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "number" || typeof value[1] !== "number" || !Number.isFinite(value[0]) || !Number.isFinite(value[1])) {
59
+ ctx.error("Expected a [lng, lat] tuple of two finite numbers (GeoJSON order)");
60
+ return false;
61
+ }
62
+ if (value[0] < -180 || value[0] > 180) {
63
+ ctx.error(`Longitude ${value[0]} out of range [-180, 180] (geo points are [lng, lat])`);
64
+ return false;
65
+ }
66
+ if (value[1] < -90 || value[1] > 90) {
67
+ ctx.error(`Latitude ${value[1]} out of range [-90, 90] (geo points are [lng, lat])`);
68
+ return false;
69
+ }
70
+ return true;
71
+ }
57
72
  const isTo = def.metadata.has("db.rel.to");
58
73
  const isFrom = def.metadata.has("db.rel.from");
59
74
  const isVia = def.metadata.has("db.rel.via");
@@ -54,6 +54,21 @@ function createDbValidatorPlugin() {
54
54
  return (ctx, def, value) => {
55
55
  const dbCtx = ctx.context;
56
56
  if (!dbCtx) return;
57
+ if (def.type.tags?.has("geoPoint") && value !== void 0 && value !== null) {
58
+ if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "number" || typeof value[1] !== "number" || !Number.isFinite(value[0]) || !Number.isFinite(value[1])) {
59
+ ctx.error("Expected a [lng, lat] tuple of two finite numbers (GeoJSON order)");
60
+ return false;
61
+ }
62
+ if (value[0] < -180 || value[0] > 180) {
63
+ ctx.error(`Longitude ${value[0]} out of range [-180, 180] (geo points are [lng, lat])`);
64
+ return false;
65
+ }
66
+ if (value[1] < -90 || value[1] > 90) {
67
+ ctx.error(`Latitude ${value[1]} out of range [-90, 90] (geo points are [lng, lat])`);
68
+ return false;
69
+ }
70
+ return true;
71
+ }
57
72
  const isTo = def.metadata.has("db.rel.to");
58
73
  const isFrom = def.metadata.has("db.rel.from");
59
74
  const isVia = def.metadata.has("db.rel.via");
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_ops = require("./ops.cjs");
3
- const require_validator = require("./validator-f43UcYiY.cjs");
3
+ const require_validator = require("./validator-BSk8lPH1.cjs");
4
4
  exports.$cas = require_ops.$cas;
5
5
  exports.$dec = require_ops.$dec;
6
6
  exports.$inc = require_ops.$inc;
@@ -1,3 +1,3 @@
1
1
  import { $cas, $dec, $inc, $insert, $mul, $remove, $replace, $update, $upsert, isDbFieldOp } from "./ops.mjs";
2
- import { a as isNavRelation, i as forceNavNonOptional, n as buildValidationContext, o as createDbValidatorPlugin, r as dbPlugin, s as getKeyProps, t as buildDbValidator } from "./validator-bLsSgi0N.mjs";
2
+ import { a as isNavRelation, i as forceNavNonOptional, n as buildValidationContext, o as createDbValidatorPlugin, r as dbPlugin, s as getKeyProps, t as buildDbValidator } from "./validator-C7plt6Kf.mjs";
3
3
  export { $cas, $dec, $inc, $insert, $mul, $remove, $replace, $update, $upsert, buildDbValidator, buildValidationContext, createDbValidatorPlugin, dbPlugin, forceNavNonOptional, getKeyProps, isDbFieldOp, isNavRelation };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db",
3
- "version": "0.1.103",
3
+ "version": "0.1.105",
4
4
  "description": "Database adapter utilities for atscript.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -71,14 +71,14 @@
71
71
  "access": "public"
72
72
  },
73
73
  "devDependencies": {
74
- "@atscript/core": "^0.1.75",
75
- "@atscript/typescript": "^0.1.75",
74
+ "@atscript/core": "^0.1.76",
75
+ "@atscript/typescript": "^0.1.76",
76
76
  "@uniqu/core": "^0.1.6",
77
- "unplugin-atscript": "^0.1.75"
77
+ "unplugin-atscript": "^0.1.76"
78
78
  },
79
79
  "peerDependencies": {
80
- "@atscript/core": "^0.1.75",
81
- "@atscript/typescript": "^0.1.75",
80
+ "@atscript/core": "^0.1.76",
81
+ "@atscript/typescript": "^0.1.76",
82
82
  "@uniqu/core": "^0.1.6"
83
83
  },
84
84
  "scripts": {