@atscript/db-sqlite 0.1.140 → 0.1.142

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
@@ -163,6 +163,19 @@ function sqliteCalendarBucket(quotedCol, b) {
163
163
  }
164
164
  //#endregion
165
165
  //#region src/sql-builder.ts
166
+ /**
167
+ * The generated-column clause of a `@db.column.derived` field (since 0.1.141):
168
+ * `GENERATED ALWAYS AS (<json extraction>) VIRTUAL` — computed per read,
169
+ * never stored, nullable (a missing or off-type leaf is NULL), no DEFAULT;
170
+ * `COLLATE` may follow it. Shared by CREATE TABLE and ADD COLUMN.
171
+ */
172
+ function sqliteDerivedClause(field) {
173
+ return ` GENERATED ALWAYS AS (${(0, _atscript_db_sql_tools.derivedColumnExpr)(sqliteDialect, field)}) VIRTUAL${sqliteCollateClause(field)}`;
174
+ }
175
+ /** The `COLLATE` clause of a `@db.column.collate` field (SQLite's built-in names) — `""` without one. */
176
+ function sqliteCollateClause(field) {
177
+ return field.collate ? ` COLLATE ${field.collate.toUpperCase()}` : "";
178
+ }
166
179
  function esc(name) {
167
180
  return name.replace(/"/g, "\"\"");
168
181
  }
@@ -407,13 +420,17 @@ function buildCreateTable(table, fields, foreignKeys, options) {
407
420
  if (field.ignored) continue;
408
421
  const sqlType = options?.typeMapper?.(field) ?? (field.isPrimaryKey && (field.designType === "number" || field.designType === "integer") ? "INTEGER" : sqliteTypeFromDesignType(field.designType));
409
422
  let def = `"${esc(field.physicalName)}" ${sqlType}`;
423
+ if (field.derived) {
424
+ colDefs.push(def + sqliteDerivedClause(field));
425
+ continue;
426
+ }
410
427
  if (field.isPrimaryKey && primaryKeys.length === 1) {
411
428
  def += " PRIMARY KEY";
412
429
  if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment" && (field.designType === "number" || field.designType === "integer")) def += " AUTOINCREMENT";
413
430
  }
414
431
  if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
415
432
  if (field.defaultValue?.kind === "value") def += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
416
- if (field.collate) def += ` COLLATE ${field.collate.toUpperCase()}`;
433
+ def += sqliteCollateClause(field);
417
434
  colDefs.push(def);
418
435
  }
419
436
  if (primaryKeys.length > 1) {
@@ -940,7 +957,7 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
940
957
  async updateOne(filter, data, ops, expectedVersion) {
941
958
  const where = buildWhere(filter);
942
959
  const tableName = this.resolveTableName();
943
- const versionColumn = this._table.versionColumn;
960
+ const versionColumn = this._table.versionColumnPhysical;
944
961
  const { sql, params } = buildUpdate(tableName, data, {
945
962
  sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
946
963
  params: where.params
@@ -954,7 +971,7 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
954
971
  }
955
972
  async updateMany(filter, data, ops) {
956
973
  const where = buildWhere(filter);
957
- const versionColumn = this._table.versionColumn;
974
+ const versionColumn = this._table.versionColumnPhysical;
958
975
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where, ops, versionColumn);
959
976
  this._log(sql, params);
960
977
  const result = await this._stmt(() => this._wrapConstraintError(() => this.driver.run(sql, params)));
@@ -966,7 +983,7 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
966
983
  async replaceOne(filter, data, expectedVersion) {
967
984
  const where = buildWhere(filter);
968
985
  const tableName = this.resolveTableName();
969
- const versionColumn = this._table.versionColumn;
986
+ const versionColumn = this._table.versionColumnPhysical;
970
987
  const { sql, params } = buildUpdate(tableName, (0, _atscript_db_sql_tools.fillReplacePayload)(data, (0, _atscript_db_sql_tools.replaceColumnsFor)(this._table.fieldDescriptors, this.nativeDefaultFns()), versionColumn), {
971
988
  sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
972
989
  params: where.params
@@ -980,7 +997,7 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
980
997
  }
981
998
  async replaceMany(filter, data) {
982
999
  const where = buildWhere(filter);
983
- const versionColumn = this._table.versionColumn;
1000
+ const versionColumn = this._table.versionColumnPhysical;
984
1001
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where, void 0, versionColumn);
985
1002
  this._log(sql, params);
986
1003
  const result = await this._stmt(() => this._wrapConstraintError(() => this.driver.run(sql, params)));
@@ -1057,10 +1074,13 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
1057
1074
  for (const field of diff.added) {
1058
1075
  const sqlType = this.typeMapper(field);
1059
1076
  let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
1060
- if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
1061
- if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
1062
- else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueForType)(field.designType)}`;
1063
- if (field.collate) ddl += ` COLLATE ${field.collate.toUpperCase()}`;
1077
+ if (field.derived) ddl += sqliteDerivedClause(field);
1078
+ else {
1079
+ if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
1080
+ if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
1081
+ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueForType)(field.designType)}`;
1082
+ ddl += sqliteCollateClause(field);
1083
+ }
1064
1084
  this._log(ddl);
1065
1085
  this.driver.exec(ddl);
1066
1086
  added.push(field.physicalName);
@@ -1085,7 +1105,7 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
1085
1105
  this._log(createSql);
1086
1106
  this.driver.exec(createSql);
1087
1107
  const oldCols = (await this.getExistingColumns()).map((c) => c.name);
1088
- const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
1108
+ const newCols = this._table.storedDescriptors.map((f) => f.physicalName);
1089
1109
  const oldColSet = new Set(oldCols);
1090
1110
  const commonCols = newCols.filter((c) => oldColSet.has(c));
1091
1111
  if (commonCols.length > 0) {
@@ -1247,15 +1267,25 @@ var SqliteAdapter = class SqliteAdapter extends _atscript_db.BaseDbAdapter {
1247
1267
  if (field.isPrimaryKey && (field.designType === "number" || field.designType === "integer")) return "INTEGER";
1248
1268
  return sqliteTypeFromDesignType(field.designType);
1249
1269
  }
1270
+ /**
1271
+ * `PRAGMA table_xinfo` (since 0.1.141; `table_info` before): it lists
1272
+ * generated columns too (`hidden` 2 = VIRTUAL, 3 = STORED — `table_info`
1273
+ * omits them, so a `@db.column.derived` column would be re-added on every
1274
+ * sync). A virtual table's hidden columns (`hidden` 1) are skipped.
1275
+ */
1250
1276
  async getExistingColumnsForTable(tableName) {
1251
1277
  return this._stmt(() => {
1252
- return this.driver.all(`PRAGMA table_info("${esc(tableName)}")`).map((r) => ({
1253
- name: r.name,
1254
- type: r.type,
1255
- notnull: r.notnull === 1,
1256
- pk: r.pk > 0,
1257
- dflt_value: normalizeSqliteDefault(r.dflt_value)
1258
- }));
1278
+ return this.driver.all(`PRAGMA table_xinfo("${esc(tableName)}")`).filter((r) => r.hidden !== 1).map((r) => {
1279
+ const column = {
1280
+ name: r.name,
1281
+ type: r.type,
1282
+ notnull: r.notnull === 1,
1283
+ pk: r.pk > 0,
1284
+ dflt_value: normalizeSqliteDefault(r.dflt_value)
1285
+ };
1286
+ if (r.hidden === 2 || r.hidden === 3) column.generated = true;
1287
+ return column;
1288
+ });
1259
1289
  });
1260
1290
  }
1261
1291
  async syncIndexes() {
package/dist/index.d.cts CHANGED
@@ -380,6 +380,12 @@ declare class SqliteAdapter extends BaseDbAdapter {
380
380
  getObjectKind(name: string): Promise<TDbObjectKind | undefined>;
381
381
  renameTable(oldName: string): Promise<void>;
382
382
  typeMapper(field: TDbFieldMeta): string;
383
+ /**
384
+ * `PRAGMA table_xinfo` (since 0.1.141; `table_info` before): it lists
385
+ * generated columns too (`hidden` 2 = VIRTUAL, 3 = STORED — `table_info`
386
+ * omits them, so a `@db.column.derived` column would be re-added on every
387
+ * sync). A virtual table's hidden columns (`hidden` 1) are skipped.
388
+ */
383
389
  getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
384
390
  syncIndexes(): Promise<void>;
385
391
  getSearchIndexes(): TSearchIndexInfo[];
package/dist/index.d.mts CHANGED
@@ -380,6 +380,12 @@ declare class SqliteAdapter extends BaseDbAdapter {
380
380
  getObjectKind(name: string): Promise<TDbObjectKind | undefined>;
381
381
  renameTable(oldName: string): Promise<void>;
382
382
  typeMapper(field: TDbFieldMeta): string;
383
+ /**
384
+ * `PRAGMA table_xinfo` (since 0.1.141; `table_info` before): it lists
385
+ * generated columns too (`hidden` 2 = VIRTUAL, 3 = STORED — `table_info`
386
+ * omits them, so a `@db.column.derived` column would be re-added on every
387
+ * sync). A virtual table's hidden columns (`hidden` 1) are skipped.
388
+ */
383
389
  getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
384
390
  syncIndexes(): Promise<void>;
385
391
  getSearchIndexes(): TSearchIndexInfo[];
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
3
3
  import { resolveAggregateSearch } from "@atscript/db/agg";
4
- import { EMPTY_AND, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, geoWindowFromControls, jsonDollarPath, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlTimeZoneLiteral, toSqlValue } from "@atscript/db-sql-tools";
4
+ import { EMPTY_AND, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, geoWindowFromControls, jsonDollarPath, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlTimeZoneLiteral, toSqlValue } from "@atscript/db-sql-tools";
5
5
  import { bucketer } from "@uniqu/core";
6
6
  import { AsyncLocalStorage } from "node:async_hooks";
7
7
  //#region src/better-sqlite3-driver.ts
@@ -162,6 +162,19 @@ function sqliteCalendarBucket(quotedCol, b) {
162
162
  }
163
163
  //#endregion
164
164
  //#region src/sql-builder.ts
165
+ /**
166
+ * The generated-column clause of a `@db.column.derived` field (since 0.1.141):
167
+ * `GENERATED ALWAYS AS (<json extraction>) VIRTUAL` — computed per read,
168
+ * never stored, nullable (a missing or off-type leaf is NULL), no DEFAULT;
169
+ * `COLLATE` may follow it. Shared by CREATE TABLE and ADD COLUMN.
170
+ */
171
+ function sqliteDerivedClause(field) {
172
+ return ` GENERATED ALWAYS AS (${derivedColumnExpr(sqliteDialect, field)}) VIRTUAL${sqliteCollateClause(field)}`;
173
+ }
174
+ /** The `COLLATE` clause of a `@db.column.collate` field (SQLite's built-in names) — `""` without one. */
175
+ function sqliteCollateClause(field) {
176
+ return field.collate ? ` COLLATE ${field.collate.toUpperCase()}` : "";
177
+ }
165
178
  function esc(name) {
166
179
  return name.replace(/"/g, "\"\"");
167
180
  }
@@ -406,13 +419,17 @@ function buildCreateTable(table, fields, foreignKeys, options) {
406
419
  if (field.ignored) continue;
407
420
  const sqlType = options?.typeMapper?.(field) ?? (field.isPrimaryKey && (field.designType === "number" || field.designType === "integer") ? "INTEGER" : sqliteTypeFromDesignType(field.designType));
408
421
  let def = `"${esc(field.physicalName)}" ${sqlType}`;
422
+ if (field.derived) {
423
+ colDefs.push(def + sqliteDerivedClause(field));
424
+ continue;
425
+ }
409
426
  if (field.isPrimaryKey && primaryKeys.length === 1) {
410
427
  def += " PRIMARY KEY";
411
428
  if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment" && (field.designType === "number" || field.designType === "integer")) def += " AUTOINCREMENT";
412
429
  }
413
430
  if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
414
431
  if (field.defaultValue?.kind === "value") def += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
415
- if (field.collate) def += ` COLLATE ${field.collate.toUpperCase()}`;
432
+ def += sqliteCollateClause(field);
416
433
  colDefs.push(def);
417
434
  }
418
435
  if (primaryKeys.length > 1) {
@@ -939,7 +956,7 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
939
956
  async updateOne(filter, data, ops, expectedVersion) {
940
957
  const where = buildWhere(filter);
941
958
  const tableName = this.resolveTableName();
942
- const versionColumn = this._table.versionColumn;
959
+ const versionColumn = this._table.versionColumnPhysical;
943
960
  const { sql, params } = buildUpdate$1(tableName, data, {
944
961
  sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
945
962
  params: where.params
@@ -953,7 +970,7 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
953
970
  }
954
971
  async updateMany(filter, data, ops) {
955
972
  const where = buildWhere(filter);
956
- const versionColumn = this._table.versionColumn;
973
+ const versionColumn = this._table.versionColumnPhysical;
957
974
  const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, ops, versionColumn);
958
975
  this._log(sql, params);
959
976
  const result = await this._stmt(() => this._wrapConstraintError(() => this.driver.run(sql, params)));
@@ -965,7 +982,7 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
965
982
  async replaceOne(filter, data, expectedVersion) {
966
983
  const where = buildWhere(filter);
967
984
  const tableName = this.resolveTableName();
968
- const versionColumn = this._table.versionColumn;
985
+ const versionColumn = this._table.versionColumnPhysical;
969
986
  const { sql, params } = buildUpdate$1(tableName, fillReplacePayload(data, replaceColumnsFor(this._table.fieldDescriptors, this.nativeDefaultFns()), versionColumn), {
970
987
  sql: `rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`,
971
988
  params: where.params
@@ -979,7 +996,7 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
979
996
  }
980
997
  async replaceMany(filter, data) {
981
998
  const where = buildWhere(filter);
982
- const versionColumn = this._table.versionColumn;
999
+ const versionColumn = this._table.versionColumnPhysical;
983
1000
  const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, void 0, versionColumn);
984
1001
  this._log(sql, params);
985
1002
  const result = await this._stmt(() => this._wrapConstraintError(() => this.driver.run(sql, params)));
@@ -1056,10 +1073,13 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
1056
1073
  for (const field of diff.added) {
1057
1074
  const sqlType = this.typeMapper(field);
1058
1075
  let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
1059
- if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
1060
- if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
1061
- else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
1062
- if (field.collate) ddl += ` COLLATE ${field.collate.toUpperCase()}`;
1076
+ if (field.derived) ddl += sqliteDerivedClause(field);
1077
+ else {
1078
+ if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
1079
+ if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
1080
+ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
1081
+ ddl += sqliteCollateClause(field);
1082
+ }
1063
1083
  this._log(ddl);
1064
1084
  this.driver.exec(ddl);
1065
1085
  added.push(field.physicalName);
@@ -1084,7 +1104,7 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
1084
1104
  this._log(createSql);
1085
1105
  this.driver.exec(createSql);
1086
1106
  const oldCols = (await this.getExistingColumns()).map((c) => c.name);
1087
- const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
1107
+ const newCols = this._table.storedDescriptors.map((f) => f.physicalName);
1088
1108
  const oldColSet = new Set(oldCols);
1089
1109
  const commonCols = newCols.filter((c) => oldColSet.has(c));
1090
1110
  if (commonCols.length > 0) {
@@ -1246,15 +1266,25 @@ var SqliteAdapter = class SqliteAdapter extends BaseDbAdapter {
1246
1266
  if (field.isPrimaryKey && (field.designType === "number" || field.designType === "integer")) return "INTEGER";
1247
1267
  return sqliteTypeFromDesignType(field.designType);
1248
1268
  }
1269
+ /**
1270
+ * `PRAGMA table_xinfo` (since 0.1.141; `table_info` before): it lists
1271
+ * generated columns too (`hidden` 2 = VIRTUAL, 3 = STORED — `table_info`
1272
+ * omits them, so a `@db.column.derived` column would be re-added on every
1273
+ * sync). A virtual table's hidden columns (`hidden` 1) are skipped.
1274
+ */
1249
1275
  async getExistingColumnsForTable(tableName) {
1250
1276
  return this._stmt(() => {
1251
- return this.driver.all(`PRAGMA table_info("${esc(tableName)}")`).map((r) => ({
1252
- name: r.name,
1253
- type: r.type,
1254
- notnull: r.notnull === 1,
1255
- pk: r.pk > 0,
1256
- dflt_value: normalizeSqliteDefault(r.dflt_value)
1257
- }));
1277
+ return this.driver.all(`PRAGMA table_xinfo("${esc(tableName)}")`).filter((r) => r.hidden !== 1).map((r) => {
1278
+ const column = {
1279
+ name: r.name,
1280
+ type: r.type,
1281
+ notnull: r.notnull === 1,
1282
+ pk: r.pk > 0,
1283
+ dflt_value: normalizeSqliteDefault(r.dflt_value)
1284
+ };
1285
+ if (r.hidden === 2 || r.hidden === 3) column.generated = true;
1286
+ return column;
1287
+ });
1258
1288
  });
1259
1289
  }
1260
1290
  async syncIndexes() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db-sqlite",
3
- "version": "0.1.140",
3
+ "version": "0.1.142",
4
4
  "description": "SQLite adapter for @atscript/db with swappable driver support.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -36,21 +36,21 @@
36
36
  "access": "public"
37
37
  },
38
38
  "devDependencies": {
39
- "@atscript/core": "^0.1.92",
40
- "@atscript/typescript": "^0.1.92",
39
+ "@atscript/core": "^0.1.95",
40
+ "@atscript/typescript": "^0.1.95",
41
41
  "@types/better-sqlite3": "^7.6.13",
42
42
  "@uniqu/core": "^0.1.11",
43
43
  "better-sqlite3": "^12.6.2",
44
44
  "sqlite-vec": "^0.1.9",
45
- "unplugin-atscript": "^0.1.92"
45
+ "unplugin-atscript": "^0.1.95"
46
46
  },
47
47
  "peerDependencies": {
48
- "@atscript/core": "^0.1.92",
49
- "@atscript/typescript": "^0.1.92",
48
+ "@atscript/core": "^0.1.95",
49
+ "@atscript/typescript": "^0.1.95",
50
50
  "@uniqu/core": "^0.1.11",
51
51
  "better-sqlite3": ">=11.0.0",
52
- "@atscript/db": "^0.1.140",
53
- "@atscript/db-sql-tools": "^0.1.140"
52
+ "@atscript/db": "^0.1.142",
53
+ "@atscript/db-sql-tools": "^0.1.142"
54
54
  },
55
55
  "peerDependenciesMeta": {
56
56
  "better-sqlite3": {