@atscript/db-sqlite 0.1.35 → 0.1.36

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
@@ -129,6 +129,7 @@ function buildCreateTable(table, fields, foreignKeys) {
129
129
  let def = `"${esc(field.physicalName)}" ${sqlType}`;
130
130
  if (field.isPrimaryKey && primaryKeys.length === 1) def += " PRIMARY KEY";
131
131
  if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
132
+ if (field.defaultValue?.kind === "value") def += ` DEFAULT ${sqlStringLiteral(field.defaultValue.value)}`;
132
133
  colDefs.push(def);
133
134
  }
134
135
  if (primaryKeys.length > 1) {
@@ -176,6 +177,9 @@ function buildProjection(select) {
176
177
  function esc(name) {
177
178
  return name.replace(/"/g, "\"\"");
178
179
  }
180
+ function sqlStringLiteral(value) {
181
+ return `'${value.replace(/'/g, "''")}'`;
182
+ }
179
183
  function toSqliteValue(value) {
180
184
  if (value === undefined) return null;
181
185
  if (value === null) return null;
@@ -395,13 +399,41 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
395
399
  /** SQLite does not use schemas — override to always exclude schema. */ resolveTableName() {
396
400
  return super.resolveTableName(false);
397
401
  }
402
+ /** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */ supportsNativeForeignKeys() {
403
+ return true;
404
+ }
398
405
  prepareId(id, _fieldType) {
399
406
  return id;
400
407
  }
408
+ /**
409
+ * Wraps a write operation to catch native SQLite constraint errors
410
+ * and rethrow as structured `DbError`.
411
+ */ _wrapConstraintError(fn) {
412
+ try {
413
+ return fn();
414
+ } catch (e) {
415
+ if (e instanceof Error) {
416
+ if (e.message.includes("FOREIGN KEY constraint failed")) throw new __atscript_utils_db.DbError("FK_VIOLATION", [{
417
+ path: "",
418
+ message: e.message
419
+ }]);
420
+ const uniqueMatch = e.message.match(/UNIQUE constraint failed:\s*\S+\.(\S+)/);
421
+ if (uniqueMatch) throw new __atscript_utils_db.DbError("CONFLICT", [{
422
+ path: uniqueMatch[1],
423
+ message: e.message
424
+ }]);
425
+ if (e.message.includes("UNIQUE constraint failed")) throw new __atscript_utils_db.DbError("CONFLICT", [{
426
+ path: "",
427
+ message: e.message
428
+ }]);
429
+ }
430
+ throw e;
431
+ }
432
+ }
401
433
  async insertOne(data) {
402
434
  const { sql, params } = buildInsert(this.resolveTableName(), data);
403
435
  this._log(sql, params);
404
- const result = this.driver.run(sql, params);
436
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
405
437
  return { insertedId: result.lastInsertRowid };
406
438
  }
407
439
  async insertMany(data) {
@@ -410,7 +442,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
410
442
  for (const row of data) {
411
443
  const { sql, params } = buildInsert(this.resolveTableName(), row);
412
444
  this._log(sql, params);
413
- const result = this.driver.run(sql, params);
445
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
414
446
  ids.push(result.lastInsertRowid);
415
447
  }
416
448
  return {
@@ -455,7 +487,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
455
487
  const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
456
488
  const allParams = [...setParams, ...where.params];
457
489
  this._log(sql, allParams);
458
- const result = this.driver.run(sql, allParams);
490
+ const result = this._wrapConstraintError(() => this.driver.run(sql, allParams));
459
491
  return {
460
492
  matchedCount: result.changes,
461
493
  modifiedCount: result.changes
@@ -465,7 +497,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
465
497
  const where = buildWhere(filter);
466
498
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
467
499
  this._log(sql, params);
468
- const result = this.driver.run(sql, params);
500
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
469
501
  return {
470
502
  matchedCount: result.changes,
471
503
  modifiedCount: result.changes
@@ -480,7 +512,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
480
512
  };
481
513
  const { sql, params } = buildUpdate(tableName, data, limitedWhere);
482
514
  this._log(sql, params);
483
- const result = this.driver.run(sql, params);
515
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
484
516
  return {
485
517
  matchedCount: result.changes,
486
518
  modifiedCount: result.changes
@@ -490,7 +522,7 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
490
522
  const where = buildWhere(filter);
491
523
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
492
524
  this._log(sql, params);
493
- const result = this.driver.run(sql, params);
525
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
494
526
  return {
495
527
  matchedCount: result.changes,
496
528
  modifiedCount: result.changes
@@ -501,14 +533,14 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
501
533
  const tableName = this.resolveTableName();
502
534
  const sql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
503
535
  this._log(sql, where.params);
504
- const result = this.driver.run(sql, where.params);
536
+ const result = this._wrapConstraintError(() => this.driver.run(sql, where.params));
505
537
  return { deletedCount: result.changes };
506
538
  }
507
539
  async deleteMany(filter) {
508
540
  const where = buildWhere(filter);
509
541
  const { sql, params } = buildDelete(this.resolveTableName(), where);
510
542
  this._log(sql, params);
511
- const result = this.driver.run(sql, params);
543
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
512
544
  return { deletedCount: result.changes };
513
545
  }
514
546
  async ensureTable() {
@@ -537,9 +569,11 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
537
569
  renamed.push(field.physicalName);
538
570
  }
539
571
  for (const field of diff.added) {
540
- const sqlType = field.isPrimaryKey && (field.designType === "number" || field.designType === "integer") ? "INTEGER" : sqliteTypeFromDesignType(field.designType);
572
+ const sqlType = this.typeMapper(field);
541
573
  let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
542
- if (!field.optional && !field.isPrimaryKey) ddl += ` NOT NULL DEFAULT ${defaultValueForType(field.designType)}`;
574
+ if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
575
+ if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${sqlStringLiteral(field.defaultValue.value)}`;
576
+ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
543
577
  this._log(ddl);
544
578
  this.driver.exec(ddl);
545
579
  added.push(field.physicalName);
@@ -552,21 +586,39 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
552
586
  async recreateTable() {
553
587
  const tableName = this.resolveTableName();
554
588
  const tempName = `${tableName}__tmp_${Date.now()}`;
555
- const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys);
556
- this._log(createSql);
557
- this.driver.exec(createSql);
558
- const oldCols = (await this.getExistingColumns()).map((c) => c.name);
559
- const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
560
- const oldColSet = new Set(oldCols);
561
- const commonCols = newCols.filter((c) => oldColSet.has(c));
562
- if (commonCols.length > 0) {
563
- const cols = commonCols.map((c) => `"${esc(c)}"`).join(", ");
564
- const copySql = `INSERT INTO "${esc(tempName)}" (${cols}) SELECT ${cols} FROM "${esc(tableName)}"`;
565
- this._log(copySql);
566
- this.driver.exec(copySql);
589
+ this.driver.exec("PRAGMA foreign_keys = OFF");
590
+ this.driver.exec("PRAGMA legacy_alter_table = ON");
591
+ try {
592
+ const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys);
593
+ this._log(createSql);
594
+ this.driver.exec(createSql);
595
+ const oldCols = (await this.getExistingColumns()).map((c) => c.name);
596
+ const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
597
+ const oldColSet = new Set(oldCols);
598
+ const commonCols = newCols.filter((c) => oldColSet.has(c));
599
+ if (commonCols.length > 0) {
600
+ const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
601
+ const colNames = commonCols.map((c) => `"${esc(c)}"`).join(", ");
602
+ const selectExprs = commonCols.map((c) => {
603
+ const field = fieldsByName.get(c);
604
+ if (field && !field.optional && !field.isPrimaryKey) {
605
+ const fallback = field.defaultValue?.kind === "value" ? sqlStringLiteral(field.defaultValue.value) : defaultValueForType(field.designType);
606
+ return `COALESCE("${esc(c)}", ${fallback}) AS "${esc(c)}"`;
607
+ }
608
+ return `"${esc(c)}"`;
609
+ }).join(", ");
610
+ const copySql = `INSERT INTO "${esc(tempName)}" (${colNames}) SELECT ${selectExprs} FROM "${esc(tableName)}"`;
611
+ this._log(copySql);
612
+ this.driver.exec(copySql);
613
+ }
614
+ const oldName = `${tableName}__old_${Date.now()}`;
615
+ this.driver.exec(`ALTER TABLE "${esc(tableName)}" RENAME TO "${esc(oldName)}"`);
616
+ this.driver.exec(`ALTER TABLE "${esc(tempName)}" RENAME TO "${esc(tableName)}"`);
617
+ this.driver.exec(`DROP TABLE IF EXISTS "${esc(oldName)}"`);
618
+ } finally {
619
+ this.driver.exec("PRAGMA legacy_alter_table = OFF");
620
+ this.driver.exec("PRAGMA foreign_keys = ON");
567
621
  }
568
- this.driver.exec(`DROP TABLE "${esc(tableName)}"`);
569
- this.driver.exec(`ALTER TABLE "${esc(tempName)}" RENAME TO "${esc(tableName)}"`);
570
622
  }
571
623
  async dropTable() {
572
624
  const tableName = this.resolveTableName();
@@ -600,13 +652,18 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
600
652
  this._log(ddl);
601
653
  this.driver.exec(ddl);
602
654
  }
655
+ typeMapper(field) {
656
+ if (field.isPrimaryKey && (field.designType === "number" || field.designType === "integer")) return "INTEGER";
657
+ return sqliteTypeFromDesignType(field.designType);
658
+ }
603
659
  async getExistingColumnsForTable(tableName) {
604
660
  const rows = this.driver.all(`PRAGMA table_info("${esc(tableName)}")`);
605
661
  return rows.map((r) => ({
606
662
  name: r.name,
607
663
  type: r.type,
608
664
  notnull: r.notnull === 1,
609
- pk: r.pk > 0
665
+ pk: r.pk > 0,
666
+ dflt_value: normalizeSqliteDefault(r.dflt_value)
610
667
  }));
611
668
  }
612
669
  async syncIndexes() {
@@ -641,6 +698,12 @@ var SqliteAdapter = class extends __atscript_utils_db.BaseDbAdapter {
641
698
  default: return "''";
642
699
  }
643
700
  }
701
+ /** Normalizes SQLite PRAGMA dflt_value to match serialized format.
702
+ * PRAGMA returns `'active'` (SQL-quoted), we store `active` (raw). */ function normalizeSqliteDefault(value) {
703
+ if (value == null) return undefined;
704
+ if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
705
+ return value;
706
+ }
644
707
 
645
708
  //#endregion
646
709
  //#region packages/db-sqlite/src/index.ts
package/dist/index.d.ts CHANGED
@@ -71,7 +71,14 @@ declare class SqliteAdapter extends BaseDbAdapter {
71
71
  protected _rollbackTransaction(): Promise<void>;
72
72
  /** SQLite does not use schemas — override to always exclude schema. */
73
73
  resolveTableName(): string;
74
+ /** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */
75
+ supportsNativeForeignKeys(): boolean;
74
76
  prepareId(id: unknown, _fieldType: TAtscriptAnnotatedType): unknown;
77
+ /**
78
+ * Wraps a write operation to catch native SQLite constraint errors
79
+ * and rethrow as structured `DbError`.
80
+ */
81
+ private _wrapConstraintError;
75
82
  insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
76
83
  insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
77
84
  findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
@@ -93,6 +100,10 @@ declare class SqliteAdapter extends BaseDbAdapter {
93
100
  dropTableByName(tableName: string): Promise<void>;
94
101
  dropViewByName(viewName: string): Promise<void>;
95
102
  renameTable(oldName: string): Promise<void>;
103
+ typeMapper(field: {
104
+ designType: string;
105
+ isPrimaryKey: boolean;
106
+ }): string;
96
107
  getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
97
108
  syncIndexes(): Promise<void>;
98
109
  }
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { AtscriptDbView, BaseDbAdapter, DbSpace } from "@atscript/utils-db";
1
+ import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/utils-db";
2
2
  import { walkFilter } from "@uniqu/core";
3
3
 
4
4
  //#region rolldown:runtime
@@ -112,6 +112,7 @@ function buildCreateTable(table, fields, foreignKeys) {
112
112
  let def = `"${esc(field.physicalName)}" ${sqlType}`;
113
113
  if (field.isPrimaryKey && primaryKeys.length === 1) def += " PRIMARY KEY";
114
114
  if (!field.optional && !field.isPrimaryKey) def += " NOT NULL";
115
+ if (field.defaultValue?.kind === "value") def += ` DEFAULT ${sqlStringLiteral(field.defaultValue.value)}`;
115
116
  colDefs.push(def);
116
117
  }
117
118
  if (primaryKeys.length > 1) {
@@ -159,6 +160,9 @@ function buildProjection(select) {
159
160
  function esc(name) {
160
161
  return name.replace(/"/g, "\"\"");
161
162
  }
163
+ function sqlStringLiteral(value) {
164
+ return `'${value.replace(/'/g, "''")}'`;
165
+ }
162
166
  function toSqliteValue(value) {
163
167
  if (value === undefined) return null;
164
168
  if (value === null) return null;
@@ -378,13 +382,41 @@ var SqliteAdapter = class extends BaseDbAdapter {
378
382
  /** SQLite does not use schemas — override to always exclude schema. */ resolveTableName() {
379
383
  return super.resolveTableName(false);
380
384
  }
385
+ /** SQLite enforces FK constraints natively via PRAGMA foreign_keys. */ supportsNativeForeignKeys() {
386
+ return true;
387
+ }
381
388
  prepareId(id, _fieldType) {
382
389
  return id;
383
390
  }
391
+ /**
392
+ * Wraps a write operation to catch native SQLite constraint errors
393
+ * and rethrow as structured `DbError`.
394
+ */ _wrapConstraintError(fn) {
395
+ try {
396
+ return fn();
397
+ } catch (e) {
398
+ if (e instanceof Error) {
399
+ if (e.message.includes("FOREIGN KEY constraint failed")) throw new DbError("FK_VIOLATION", [{
400
+ path: "",
401
+ message: e.message
402
+ }]);
403
+ const uniqueMatch = e.message.match(/UNIQUE constraint failed:\s*\S+\.(\S+)/);
404
+ if (uniqueMatch) throw new DbError("CONFLICT", [{
405
+ path: uniqueMatch[1],
406
+ message: e.message
407
+ }]);
408
+ if (e.message.includes("UNIQUE constraint failed")) throw new DbError("CONFLICT", [{
409
+ path: "",
410
+ message: e.message
411
+ }]);
412
+ }
413
+ throw e;
414
+ }
415
+ }
384
416
  async insertOne(data) {
385
417
  const { sql, params } = buildInsert(this.resolveTableName(), data);
386
418
  this._log(sql, params);
387
- const result = this.driver.run(sql, params);
419
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
388
420
  return { insertedId: result.lastInsertRowid };
389
421
  }
390
422
  async insertMany(data) {
@@ -393,7 +425,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
393
425
  for (const row of data) {
394
426
  const { sql, params } = buildInsert(this.resolveTableName(), row);
395
427
  this._log(sql, params);
396
- const result = this.driver.run(sql, params);
428
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
397
429
  ids.push(result.lastInsertRowid);
398
430
  }
399
431
  return {
@@ -438,7 +470,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
438
470
  const sql = `UPDATE "${esc(tableName)}" SET ${setClauses.join(", ")} WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
439
471
  const allParams = [...setParams, ...where.params];
440
472
  this._log(sql, allParams);
441
- const result = this.driver.run(sql, allParams);
473
+ const result = this._wrapConstraintError(() => this.driver.run(sql, allParams));
442
474
  return {
443
475
  matchedCount: result.changes,
444
476
  modifiedCount: result.changes
@@ -448,7 +480,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
448
480
  const where = buildWhere(filter);
449
481
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
450
482
  this._log(sql, params);
451
- const result = this.driver.run(sql, params);
483
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
452
484
  return {
453
485
  matchedCount: result.changes,
454
486
  modifiedCount: result.changes
@@ -463,7 +495,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
463
495
  };
464
496
  const { sql, params } = buildUpdate(tableName, data, limitedWhere);
465
497
  this._log(sql, params);
466
- const result = this.driver.run(sql, params);
498
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
467
499
  return {
468
500
  matchedCount: result.changes,
469
501
  modifiedCount: result.changes
@@ -473,7 +505,7 @@ var SqliteAdapter = class extends BaseDbAdapter {
473
505
  const where = buildWhere(filter);
474
506
  const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
475
507
  this._log(sql, params);
476
- const result = this.driver.run(sql, params);
508
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
477
509
  return {
478
510
  matchedCount: result.changes,
479
511
  modifiedCount: result.changes
@@ -484,14 +516,14 @@ var SqliteAdapter = class extends BaseDbAdapter {
484
516
  const tableName = this.resolveTableName();
485
517
  const sql = `DELETE FROM "${esc(tableName)}" WHERE rowid = (SELECT rowid FROM "${esc(tableName)}" WHERE ${where.sql} LIMIT 1)`;
486
518
  this._log(sql, where.params);
487
- const result = this.driver.run(sql, where.params);
519
+ const result = this._wrapConstraintError(() => this.driver.run(sql, where.params));
488
520
  return { deletedCount: result.changes };
489
521
  }
490
522
  async deleteMany(filter) {
491
523
  const where = buildWhere(filter);
492
524
  const { sql, params } = buildDelete(this.resolveTableName(), where);
493
525
  this._log(sql, params);
494
- const result = this.driver.run(sql, params);
526
+ const result = this._wrapConstraintError(() => this.driver.run(sql, params));
495
527
  return { deletedCount: result.changes };
496
528
  }
497
529
  async ensureTable() {
@@ -520,9 +552,11 @@ var SqliteAdapter = class extends BaseDbAdapter {
520
552
  renamed.push(field.physicalName);
521
553
  }
522
554
  for (const field of diff.added) {
523
- const sqlType = field.isPrimaryKey && (field.designType === "number" || field.designType === "integer") ? "INTEGER" : sqliteTypeFromDesignType(field.designType);
555
+ const sqlType = this.typeMapper(field);
524
556
  let ddl = `ALTER TABLE "${esc(tableName)}" ADD COLUMN "${esc(field.physicalName)}" ${sqlType}`;
525
- if (!field.optional && !field.isPrimaryKey) ddl += ` NOT NULL DEFAULT ${defaultValueForType(field.designType)}`;
557
+ if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
558
+ if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${sqlStringLiteral(field.defaultValue.value)}`;
559
+ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
526
560
  this._log(ddl);
527
561
  this.driver.exec(ddl);
528
562
  added.push(field.physicalName);
@@ -535,21 +569,39 @@ var SqliteAdapter = class extends BaseDbAdapter {
535
569
  async recreateTable() {
536
570
  const tableName = this.resolveTableName();
537
571
  const tempName = `${tableName}__tmp_${Date.now()}`;
538
- const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys);
539
- this._log(createSql);
540
- this.driver.exec(createSql);
541
- const oldCols = (await this.getExistingColumns()).map((c) => c.name);
542
- const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
543
- const oldColSet = new Set(oldCols);
544
- const commonCols = newCols.filter((c) => oldColSet.has(c));
545
- if (commonCols.length > 0) {
546
- const cols = commonCols.map((c) => `"${esc(c)}"`).join(", ");
547
- const copySql = `INSERT INTO "${esc(tempName)}" (${cols}) SELECT ${cols} FROM "${esc(tableName)}"`;
548
- this._log(copySql);
549
- this.driver.exec(copySql);
572
+ this.driver.exec("PRAGMA foreign_keys = OFF");
573
+ this.driver.exec("PRAGMA legacy_alter_table = ON");
574
+ try {
575
+ const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys);
576
+ this._log(createSql);
577
+ this.driver.exec(createSql);
578
+ const oldCols = (await this.getExistingColumns()).map((c) => c.name);
579
+ const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
580
+ const oldColSet = new Set(oldCols);
581
+ const commonCols = newCols.filter((c) => oldColSet.has(c));
582
+ if (commonCols.length > 0) {
583
+ const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
584
+ const colNames = commonCols.map((c) => `"${esc(c)}"`).join(", ");
585
+ const selectExprs = commonCols.map((c) => {
586
+ const field = fieldsByName.get(c);
587
+ if (field && !field.optional && !field.isPrimaryKey) {
588
+ const fallback = field.defaultValue?.kind === "value" ? sqlStringLiteral(field.defaultValue.value) : defaultValueForType(field.designType);
589
+ return `COALESCE("${esc(c)}", ${fallback}) AS "${esc(c)}"`;
590
+ }
591
+ return `"${esc(c)}"`;
592
+ }).join(", ");
593
+ const copySql = `INSERT INTO "${esc(tempName)}" (${colNames}) SELECT ${selectExprs} FROM "${esc(tableName)}"`;
594
+ this._log(copySql);
595
+ this.driver.exec(copySql);
596
+ }
597
+ const oldName = `${tableName}__old_${Date.now()}`;
598
+ this.driver.exec(`ALTER TABLE "${esc(tableName)}" RENAME TO "${esc(oldName)}"`);
599
+ this.driver.exec(`ALTER TABLE "${esc(tempName)}" RENAME TO "${esc(tableName)}"`);
600
+ this.driver.exec(`DROP TABLE IF EXISTS "${esc(oldName)}"`);
601
+ } finally {
602
+ this.driver.exec("PRAGMA legacy_alter_table = OFF");
603
+ this.driver.exec("PRAGMA foreign_keys = ON");
550
604
  }
551
- this.driver.exec(`DROP TABLE "${esc(tableName)}"`);
552
- this.driver.exec(`ALTER TABLE "${esc(tempName)}" RENAME TO "${esc(tableName)}"`);
553
605
  }
554
606
  async dropTable() {
555
607
  const tableName = this.resolveTableName();
@@ -583,13 +635,18 @@ var SqliteAdapter = class extends BaseDbAdapter {
583
635
  this._log(ddl);
584
636
  this.driver.exec(ddl);
585
637
  }
638
+ typeMapper(field) {
639
+ if (field.isPrimaryKey && (field.designType === "number" || field.designType === "integer")) return "INTEGER";
640
+ return sqliteTypeFromDesignType(field.designType);
641
+ }
586
642
  async getExistingColumnsForTable(tableName) {
587
643
  const rows = this.driver.all(`PRAGMA table_info("${esc(tableName)}")`);
588
644
  return rows.map((r) => ({
589
645
  name: r.name,
590
646
  type: r.type,
591
647
  notnull: r.notnull === 1,
592
- pk: r.pk > 0
648
+ pk: r.pk > 0,
649
+ dflt_value: normalizeSqliteDefault(r.dflt_value)
593
650
  }));
594
651
  }
595
652
  async syncIndexes() {
@@ -624,6 +681,12 @@ var SqliteAdapter = class extends BaseDbAdapter {
624
681
  default: return "''";
625
682
  }
626
683
  }
684
+ /** Normalizes SQLite PRAGMA dflt_value to match serialized format.
685
+ * PRAGMA returns `'active'` (SQL-quoted), we store `active` (raw). */ function normalizeSqliteDefault(value) {
686
+ if (value == null) return undefined;
687
+ if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
688
+ return value;
689
+ }
627
690
 
628
691
  //#endregion
629
692
  //#region packages/db-sqlite/src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/db-sqlite",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "description": "SQLite adapter for @atscript/utils-db with swappable driver support.",
5
5
  "keywords": [
6
6
  "atscript",
@@ -38,11 +38,11 @@
38
38
  "vitest": "3.2.4"
39
39
  },
40
40
  "peerDependencies": {
41
- "@uniqu/core": "^0.0.5",
41
+ "@uniqu/core": "^0.0.6",
42
42
  "better-sqlite3": ">=11.0.0",
43
- "@atscript/core": "^0.1.35",
44
- "@atscript/typescript": "^0.1.35",
45
- "@atscript/utils-db": "^0.1.35"
43
+ "@atscript/core": "^0.1.36",
44
+ "@atscript/typescript": "^0.1.36",
45
+ "@atscript/utils-db": "^0.1.36"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "better-sqlite3": {