@magnet-cms/adapter-db-drizzle 1.0.2 → 2.0.0

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
@@ -3,13 +3,13 @@
3
3
  require('reflect-metadata');
4
4
  var common = require('@magnet-cms/common');
5
5
  var common$1 = require('@nestjs/common');
6
+ var crypto = require('crypto');
6
7
  var utils = require('@magnet-cms/utils');
7
8
  var drizzleOrm = require('drizzle-orm');
8
9
  var pgCore = require('drizzle-orm/pg-core');
9
10
  var nodePostgres = require('drizzle-orm/node-postgres');
10
11
  var promises = require('fs/promises');
11
12
  var path = require('path');
12
- var crypto = require('crypto');
13
13
 
14
14
  var __create = Object.create;
15
15
  var __defProp = Object.defineProperty;
@@ -10997,8 +10997,11 @@ function generateSchema(schemaClass) {
10997
10997
  });
10998
10998
  const columns = {
10999
10999
  // Primary key - always UUID
11000
- // Type assertion needed due to drizzle-orm type system seeing SQL types as incompatible
11001
- id: pgCore.uuid("id").primaryKey().default(drizzleOrm.sql`gen_random_uuid()`)
11000
+ // $defaultFn generates UUIDs in JS (works on all dialects: PostgreSQL, MySQL, SQLite)
11001
+ // Note: SQL .default(gen_random_uuid()) is intentionally omitted because it conflicts
11002
+ // with $defaultFn when using pg-core types on non-PostgreSQL drivers.
11003
+ // The createTableFromConfig() method adds dialect-appropriate SQL DEFAULT for direct inserts.
11004
+ id: pgCore.uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID())
11002
11005
  };
11003
11006
  if (hasI18n || hasVersioning || hasIntlProps) {
11004
11007
  const docOptions = {
@@ -11014,8 +11017,8 @@ function generateSchema(schemaClass) {
11014
11017
  columns[columnName] = column;
11015
11018
  }
11016
11019
  });
11017
- columns.createdAt = pgCore.timestamp("created_at").defaultNow().notNull();
11018
- columns.updatedAt = pgCore.timestamp("updated_at").defaultNow().notNull();
11020
+ columns.createdAt = pgCore.timestamp("created_at").notNull();
11021
+ columns.updatedAt = pgCore.timestamp("updated_at").notNull();
11019
11022
  const table = pgCore.pgTable(tableName, columns, (table2) => {
11020
11023
  const tableIndexes = {};
11021
11024
  const t = table2;
@@ -11065,7 +11068,9 @@ function generateColumn(columnName, options) {
11065
11068
  column = pgCore.text(snakeCaseName);
11066
11069
  }
11067
11070
  if (options.default !== void 0) {
11068
- column = column.default(options.default);
11071
+ if (Array.isArray(options.default) && options.default.length === 0) ; else {
11072
+ column = column.default(options.default);
11073
+ }
11069
11074
  }
11070
11075
  return column;
11071
11076
  }
@@ -11089,8 +11094,8 @@ async function createNeonConnection(config) {
11089
11094
  } catch {
11090
11095
  throw new Error("@neondatabase/serverless is required for Neon driver. Install it with: bun add @neondatabase/serverless");
11091
11096
  }
11092
- const sql5 = neon.neon(config.connectionString);
11093
- const db = drizzleNeon.drizzle(sql5, {
11097
+ const sql4 = neon.neon(config.connectionString);
11098
+ const db = drizzleNeon.drizzle(sql4, {
11094
11099
  logger: config.debug
11095
11100
  });
11096
11101
  return {
@@ -11558,6 +11563,11 @@ var DrizzleQueryBuilder = class extends common.QueryBuilder {
11558
11563
  };
11559
11564
 
11560
11565
  // src/drizzle.model.ts
11566
+ function isSQLiteDriver(db) {
11567
+ const d = db;
11568
+ return typeof d.run === "function" && typeof d.execute !== "function";
11569
+ }
11570
+ __name(isSQLiteDriver, "isSQLiteDriver");
11561
11571
  function createModel(db, table, schemaClass) {
11562
11572
  return class DrizzleModelAdapter extends common.Model {
11563
11573
  static {
@@ -11621,8 +11631,9 @@ function createModel(db, table, schemaClass) {
11621
11631
  const insertData = {
11622
11632
  ...this._prepareData(data, true)
11623
11633
  };
11634
+ this._applyDeclaredEmptyArrayDefaults(insertData);
11624
11635
  if ("documentId" in dynamicTable) {
11625
- insertData.documentId = insertData.documentId || drizzleOrm.sql`gen_random_uuid()`;
11636
+ insertData.documentId = insertData.documentId || crypto.randomUUID();
11626
11637
  insertData.locale = insertData.locale || this.currentLocale || DEFAULT_LOCALE;
11627
11638
  insertData.status = insertData.status || DOCUMENT_STATUS.DRAFT;
11628
11639
  }
@@ -12009,14 +12020,29 @@ function createModel(db, table, schemaClass) {
12009
12020
  * For JSONB columns (arrays/objects), ensure values are properly formatted
12010
12021
  * @internal
12011
12022
  */
12023
+ /**
12024
+ * Fields declared with `default: []` omit a DB default (cross-dialect migration/runtime).
12025
+ * Fill missing keys so inserts match Mongoose-style expectations.
12026
+ */
12027
+ _applyDeclaredEmptyArrayDefaults(row) {
12028
+ const props = getDrizzlePropsMetadata(this._schemaClass);
12029
+ for (const [propertyKey, options] of props) {
12030
+ const key = String(propertyKey);
12031
+ if (options.default !== void 0 && Array.isArray(options.default) && options.default.length === 0 && row[key] === void 0) {
12032
+ row[key] = isSQLiteDriver(this._db) ? JSON.stringify([]) : [];
12033
+ }
12034
+ }
12035
+ }
12012
12036
  _prepareData(data, isCreate = false) {
12013
12037
  const result = {};
12014
- if (isCreate && !data.createdAt) {
12015
- result.createdAt = /* @__PURE__ */ new Date();
12038
+ const isSQLite = isSQLiteDriver(this._db);
12039
+ if (isCreate) {
12040
+ result.createdAt = data.createdAt instanceof Date ? data.createdAt : /* @__PURE__ */ new Date();
12016
12041
  }
12042
+ result.updatedAt = data.updatedAt instanceof Date ? data.updatedAt : /* @__PURE__ */ new Date();
12017
12043
  for (const [key, value] of Object.entries(data)) {
12018
12044
  if (key === "id") continue;
12019
- if (key === "createdAt") continue;
12045
+ if (key === "createdAt" || key === "updatedAt") continue;
12020
12046
  if (value === void 0) {
12021
12047
  continue;
12022
12048
  }
@@ -12024,6 +12050,10 @@ function createModel(db, table, schemaClass) {
12024
12050
  result[key] = null;
12025
12051
  continue;
12026
12052
  }
12053
+ if (typeof value === "boolean" && isSQLite) {
12054
+ result[key] = value ? 1 : 0;
12055
+ continue;
12056
+ }
12027
12057
  if (value instanceof Date) {
12028
12058
  result[key] = value;
12029
12059
  continue;
@@ -12042,7 +12072,7 @@ function createModel(db, table, schemaClass) {
12042
12072
  result[key] = value;
12043
12073
  }
12044
12074
  } else if (Array.isArray(value)) {
12045
- result[key] = value;
12075
+ result[key] = isSQLite ? JSON.stringify(value) : value;
12046
12076
  } else if (value !== null && typeof value === "object") {
12047
12077
  result[key] = value;
12048
12078
  } else {
@@ -12102,13 +12132,13 @@ var AutoMigration = class {
12102
12132
  dangerous: diffResult.dangerous,
12103
12133
  warnings: diffResult.warnings,
12104
12134
  async up(db) {
12105
- for (const sql5 of upSQL) {
12135
+ for (const sql4 of upSQL) {
12106
12136
  try {
12107
- await db.execute(sql5);
12137
+ await db.execute(sql4);
12108
12138
  } catch (err) {
12109
12139
  const msg = err instanceof Error ? err.message : String(err);
12110
12140
  const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
12111
- if (code === "42P07" || msg.includes("already exists") || msg.includes("ER_TABLE_EXISTS") || msg.includes("duplicate table name")) {
12141
+ if (code === "42P07" || code === "42710" || msg.includes("already exists") || msg.includes("ER_TABLE_EXISTS") || msg.includes("duplicate table name") || msg.includes("duplicate key value")) {
12112
12142
  logger.warn(`Skipping (object already exists): ${msg.slice(0, 80)}`);
12113
12143
  continue;
12114
12144
  }
@@ -12136,8 +12166,8 @@ var MigrationGenerator = class {
12136
12166
  generate(name, upSQL, downSQL, options = {}) {
12137
12167
  const timestamp3 = Date.now();
12138
12168
  const id = `${timestamp3}_${name}`;
12139
- const upStatements = upSQL.map((sql5) => ` await db.execute(\`${sql5}\`)`).join("\n");
12140
- const downStatements = downSQL.length > 0 ? downSQL.map((sql5) => ` await db.execute(\`${sql5}\`)`).join("\n") : " // TODO: implement down migration";
12169
+ const upStatements = upSQL.map((sql4) => ` await db.execute(\`${sql4}\`)`).join("\n");
12170
+ const downStatements = downSQL.length > 0 ? downSQL.map((sql4) => ` await db.execute(\`${sql4}\`)`).join("\n") : " // TODO: implement down migration";
12141
12171
  const dangerousField = options.dangerous ? "\n dangerous: true," : "";
12142
12172
  const warningsField = options.warnings && options.warnings.length > 0 ? `
12143
12173
  warnings: [
@@ -12484,6 +12514,12 @@ var MigrationRunner = class {
12484
12514
  };
12485
12515
  }
12486
12516
  };
12517
+ function sanitizeExecutableMigrationSql(dialect, statement) {
12518
+ if (dialect !== "postgresql") return statement;
12519
+ if (!statement.includes("$")) return statement;
12520
+ return statement.replace(/"locale" = \$1/g, `"locale" = 'en'`).replace(/"status" = \$2/g, `"status" = 'draft'`);
12521
+ }
12522
+ __name(sanitizeExecutableMigrationSql, "sanitizeExecutableMigrationSql");
12487
12523
  var SNAPSHOT_FILENAME = ".schema_snapshot.json";
12488
12524
  var SchemaBridge = class {
12489
12525
  static {
@@ -12523,7 +12559,8 @@ var SchemaBridge = class {
12523
12559
  */
12524
12560
  async generateSQL(prev, cur, generateSQLFn, dialect = "postgresql") {
12525
12561
  const fn = generateSQLFn ?? await this.resolveGenerateSQLFn(dialect);
12526
- return fn(prev, cur);
12562
+ const statements = await fn(prev, cur);
12563
+ return statements.map((s) => sanitizeExecutableMigrationSql(dialect, s));
12527
12564
  }
12528
12565
  /**
12529
12566
  * Load a previously saved snapshot from disk.
@@ -12652,9 +12689,9 @@ var SchemaDiff = class {
12652
12689
  */
12653
12690
  detectDangerousOperations(sqlStatements) {
12654
12691
  const warnings = [];
12655
- for (const sql5 of sqlStatements) {
12692
+ for (const sql4 of sqlStatements) {
12656
12693
  for (const { pattern, message } of DANGEROUS_PATTERNS) {
12657
- const match = sql5.match(pattern);
12694
+ const match = sql4.match(pattern);
12658
12695
  if (match) {
12659
12696
  warnings.push(message(match[1] ?? match[0]));
12660
12697
  }
@@ -12708,14 +12745,27 @@ exports.DrizzleDatabaseAdapter = class _DrizzleDatabaseAdapter extends common.Da
12708
12745
  options = null;
12709
12746
  schemaRegistry = /* @__PURE__ */ new Map();
12710
12747
  tablesInitialized = false;
12748
+ tablesInitPromise = null;
12711
12749
  /**
12712
12750
  * Automatically create tables for all registered schemas.
12713
12751
  * Called lazily when first model is accessed, or can be called explicitly.
12752
+ * Uses a lock (Promise) to prevent concurrent execution from multiple forFeature() calls.
12714
12753
  */
12715
12754
  async ensureTablesCreated() {
12716
12755
  if (this.tablesInitialized || !this.db || this.schemaRegistry.size === 0) {
12717
12756
  return;
12718
12757
  }
12758
+ if (this.tablesInitPromise) {
12759
+ return this.tablesInitPromise;
12760
+ }
12761
+ this.tablesInitPromise = this._doEnsureTablesCreated();
12762
+ try {
12763
+ await this.tablesInitPromise;
12764
+ } finally {
12765
+ this.tablesInitPromise = null;
12766
+ }
12767
+ }
12768
+ async _doEnsureTablesCreated() {
12719
12769
  await new Promise((resolve) => setTimeout(resolve, 200));
12720
12770
  const drizzleConfig = this.options;
12721
12771
  if (drizzleConfig?.migrations) {
@@ -12734,7 +12784,7 @@ exports.DrizzleDatabaseAdapter = class _DrizzleDatabaseAdapter extends common.Da
12734
12784
  ...config.migrations
12735
12785
  };
12736
12786
  const migrationDb = {
12737
- execute: /* @__PURE__ */ __name(async (sql5) => this.db.execute(sql5), "execute")
12787
+ execute: /* @__PURE__ */ __name(async (sql4) => this.db.execute(sql4), "execute")
12738
12788
  };
12739
12789
  const bridge = new SchemaBridge();
12740
12790
  const diff = new SchemaDiff(bridge);
@@ -12764,13 +12814,20 @@ ${stack}` : ""}`);
12764
12814
  }
12765
12815
  const drizzleConfig = this.options;
12766
12816
  const dialect = drizzleConfig?.dialect ?? "postgresql";
12767
- try {
12768
- for (const [, { table }] of this.schemaRegistry.entries()) {
12817
+ const logger2 = new common$1.Logger("DrizzleAdapter");
12818
+ let hasErrors = false;
12819
+ for (const [name, { table }] of this.schemaRegistry.entries()) {
12820
+ try {
12769
12821
  const config = pgCore.getTableConfig(table);
12770
12822
  await this.createTableFromConfig(config, dialect);
12823
+ } catch (error) {
12824
+ hasErrors = true;
12825
+ const msg = error instanceof Error ? error.message : String(error);
12826
+ logger2.warn(`Error creating table for "${name}" automatically: ${msg}`);
12771
12827
  }
12772
- } catch (error) {
12773
- new common$1.Logger("DrizzleAdapter").warn("Error creating tables automatically:", JSON.stringify(error));
12828
+ }
12829
+ if (hasErrors) {
12830
+ logger2.warn("Some tables could not be created. The application may not work correctly.");
12774
12831
  }
12775
12832
  }
12776
12833
  /**
@@ -12827,7 +12884,7 @@ ${stack}` : ""}`);
12827
12884
  ${columns.join(",\n ")}
12828
12885
  )
12829
12886
  `);
12830
- await this.db.execute(createTableSQL);
12887
+ await this.execRawSQL(createTableSQL);
12831
12888
  const indexes = config.indexes;
12832
12889
  if (indexes) {
12833
12890
  for (const index2 of Object.values(indexes)) {
@@ -12846,13 +12903,27 @@ ${stack}` : ""}`);
12846
12903
  CREATE ${uniqueKeyword} INDEX IF NOT EXISTS ${q(indexName)}
12847
12904
  ON ${q(tableName)} (${columnsStr})
12848
12905
  `);
12849
- await this.db.execute(createIndexSQL).catch(() => {
12906
+ await this.execRawSQL(createIndexSQL).catch(() => {
12850
12907
  });
12851
12908
  }
12852
12909
  }
12853
12910
  }
12854
12911
  }
12855
12912
  /**
12913
+ * Execute raw SQL across all supported dialects.
12914
+ * PostgreSQL/MySQL use db.execute(), SQLite uses db.run().
12915
+ */
12916
+ async execRawSQL(rawSQL) {
12917
+ const db = this.db;
12918
+ if (typeof db.execute === "function") {
12919
+ await db.execute(rawSQL);
12920
+ } else if (typeof db.run === "function") {
12921
+ db.run(rawSQL);
12922
+ } else {
12923
+ throw new Error("Database instance has no execute() or run() method");
12924
+ }
12925
+ }
12926
+ /**
12856
12927
  * Map PostgreSQL SQL types to dialect-specific types.
12857
12928
  */
12858
12929
  mapSQLTypeForDialect(pgType, dialect) {
@@ -13562,4 +13633,5 @@ exports.getOrGenerateSchema = getOrGenerateSchema;
13562
13633
  exports.getRegisteredSchemas = getRegisteredSchemas;
13563
13634
  exports.isDraft = isDraft;
13564
13635
  exports.isPublished = isPublished;
13636
+ exports.sanitizeExecutableMigrationSql = sanitizeExecutableMigrationSql;
13565
13637
  exports.sqlGenerators = sql_generators_exports;
package/dist/index.d.cts CHANGED
@@ -433,11 +433,14 @@ declare class DrizzleDatabaseAdapter extends DatabaseAdapter {
433
433
  private options;
434
434
  private schemaRegistry;
435
435
  private tablesInitialized;
436
+ private tablesInitPromise;
436
437
  /**
437
438
  * Automatically create tables for all registered schemas.
438
439
  * Called lazily when first model is accessed, or can be called explicitly.
440
+ * Uses a lock (Promise) to prevent concurrent execution from multiple forFeature() calls.
439
441
  */
440
442
  ensureTablesCreated(): Promise<void>;
443
+ private _doEnsureTablesCreated;
441
444
  /**
442
445
  * Run the auto-migration system when `config.migrations` is configured.
443
446
  */
@@ -455,6 +458,11 @@ declare class DrizzleDatabaseAdapter extends DatabaseAdapter {
455
458
  * Generate and execute CREATE TABLE IF NOT EXISTS statement from table config.
456
459
  */
457
460
  private createTableFromConfig;
461
+ /**
462
+ * Execute raw SQL across all supported dialects.
463
+ * PostgreSQL/MySQL use db.execute(), SQLite uses db.run().
464
+ */
465
+ private execRawSQL;
458
466
  /**
459
467
  * Map PostgreSQL SQL types to dialect-specific types.
460
468
  */
@@ -763,6 +771,11 @@ declare class MigrationRunner {
763
771
  }>;
764
772
  }
765
773
 
774
+ /**
775
+ * drizzle-kit emits partial unique-index WHERE clauses with `$1`/`$2` placeholders,
776
+ * but Magnet runs DDL as plain strings without bound parameters.
777
+ */
778
+ declare function sanitizeExecutableMigrationSql(dialect: MigrationDialect, statement: string): string;
766
779
  /**
767
780
  * Default filename for persisted schema snapshot (used by auto-migration).
768
781
  */
@@ -1056,4 +1069,4 @@ declare class AutoMigration {
1056
1069
  run(dialect: MigrationDialect, config: MigrationConfig, directory: string): Promise<void>;
1057
1070
  }
1058
1071
 
1059
- export { Adapter, AutoMigration, type ColumnChange, DEFAULT_LOCALE, DEFAULT_MIGRATION_CONFIG, DOCUMENT_STATUS, DRIZZLE_CONFIG, DRIZZLE_DB, type DiffResult, type DocumentColumnOptions, type DocumentStatus, type DrizzleConfig, type DrizzleDB, DrizzleDatabaseAdapter, type DrizzleModuleOptions, DrizzleQueryBuilder, type GenerateJsonFn, type GenerateSQLFn, type GeneratedSchema, type IndexChange, InjectModel, LazyQueryBuilder, type Migration, MigrationChecksumError, type MigrationConfig, type MigrationDb, type MigrationDialect, MigrationError, type MigrationGenerateOptions, MigrationGenerator, MigrationHistory, type MigrationHistoryRecord, MigrationLock, MigrationLockError, type MigrationMode, type MigrationResult, MigrationRunner, Prop, type PropMetadata, SNAPSHOT_FILENAME, Schema, SchemaBridge, SchemaDiff, type SchemaDiffResult, type SchemaMetadata, type SnapshotJSON, type TableDefinition, applyDocumentColumns, clearSchemaRegistry, createNeonConnection, createNeonWebSocketConnection, generateSchema, getDrizzleModelToken, getDrizzlePropsMetadata, getDrizzleSchemaMetadata, getOrGenerateSchema, getRegisteredSchemas, isDraft, isPublished, index as sqlGenerators };
1072
+ export { Adapter, AutoMigration, type ColumnChange, DEFAULT_LOCALE, DEFAULT_MIGRATION_CONFIG, DOCUMENT_STATUS, DRIZZLE_CONFIG, DRIZZLE_DB, type DiffResult, type DocumentColumnOptions, type DocumentStatus, type DrizzleConfig, type DrizzleDB, DrizzleDatabaseAdapter, type DrizzleModuleOptions, DrizzleQueryBuilder, type GenerateJsonFn, type GenerateSQLFn, type GeneratedSchema, type IndexChange, InjectModel, LazyQueryBuilder, type Migration, MigrationChecksumError, type MigrationConfig, type MigrationDb, type MigrationDialect, MigrationError, type MigrationGenerateOptions, MigrationGenerator, MigrationHistory, type MigrationHistoryRecord, MigrationLock, MigrationLockError, type MigrationMode, type MigrationResult, MigrationRunner, Prop, type PropMetadata, SNAPSHOT_FILENAME, Schema, SchemaBridge, SchemaDiff, type SchemaDiffResult, type SchemaMetadata, type SnapshotJSON, type TableDefinition, applyDocumentColumns, clearSchemaRegistry, createNeonConnection, createNeonWebSocketConnection, generateSchema, getDrizzleModelToken, getDrizzlePropsMetadata, getDrizzleSchemaMetadata, getOrGenerateSchema, getRegisteredSchemas, isDraft, isPublished, sanitizeExecutableMigrationSql, index as sqlGenerators };
package/dist/index.d.ts CHANGED
@@ -433,11 +433,14 @@ declare class DrizzleDatabaseAdapter extends DatabaseAdapter {
433
433
  private options;
434
434
  private schemaRegistry;
435
435
  private tablesInitialized;
436
+ private tablesInitPromise;
436
437
  /**
437
438
  * Automatically create tables for all registered schemas.
438
439
  * Called lazily when first model is accessed, or can be called explicitly.
440
+ * Uses a lock (Promise) to prevent concurrent execution from multiple forFeature() calls.
439
441
  */
440
442
  ensureTablesCreated(): Promise<void>;
443
+ private _doEnsureTablesCreated;
441
444
  /**
442
445
  * Run the auto-migration system when `config.migrations` is configured.
443
446
  */
@@ -455,6 +458,11 @@ declare class DrizzleDatabaseAdapter extends DatabaseAdapter {
455
458
  * Generate and execute CREATE TABLE IF NOT EXISTS statement from table config.
456
459
  */
457
460
  private createTableFromConfig;
461
+ /**
462
+ * Execute raw SQL across all supported dialects.
463
+ * PostgreSQL/MySQL use db.execute(), SQLite uses db.run().
464
+ */
465
+ private execRawSQL;
458
466
  /**
459
467
  * Map PostgreSQL SQL types to dialect-specific types.
460
468
  */
@@ -763,6 +771,11 @@ declare class MigrationRunner {
763
771
  }>;
764
772
  }
765
773
 
774
+ /**
775
+ * drizzle-kit emits partial unique-index WHERE clauses with `$1`/`$2` placeholders,
776
+ * but Magnet runs DDL as plain strings without bound parameters.
777
+ */
778
+ declare function sanitizeExecutableMigrationSql(dialect: MigrationDialect, statement: string): string;
766
779
  /**
767
780
  * Default filename for persisted schema snapshot (used by auto-migration).
768
781
  */
@@ -1056,4 +1069,4 @@ declare class AutoMigration {
1056
1069
  run(dialect: MigrationDialect, config: MigrationConfig, directory: string): Promise<void>;
1057
1070
  }
1058
1071
 
1059
- export { Adapter, AutoMigration, type ColumnChange, DEFAULT_LOCALE, DEFAULT_MIGRATION_CONFIG, DOCUMENT_STATUS, DRIZZLE_CONFIG, DRIZZLE_DB, type DiffResult, type DocumentColumnOptions, type DocumentStatus, type DrizzleConfig, type DrizzleDB, DrizzleDatabaseAdapter, type DrizzleModuleOptions, DrizzleQueryBuilder, type GenerateJsonFn, type GenerateSQLFn, type GeneratedSchema, type IndexChange, InjectModel, LazyQueryBuilder, type Migration, MigrationChecksumError, type MigrationConfig, type MigrationDb, type MigrationDialect, MigrationError, type MigrationGenerateOptions, MigrationGenerator, MigrationHistory, type MigrationHistoryRecord, MigrationLock, MigrationLockError, type MigrationMode, type MigrationResult, MigrationRunner, Prop, type PropMetadata, SNAPSHOT_FILENAME, Schema, SchemaBridge, SchemaDiff, type SchemaDiffResult, type SchemaMetadata, type SnapshotJSON, type TableDefinition, applyDocumentColumns, clearSchemaRegistry, createNeonConnection, createNeonWebSocketConnection, generateSchema, getDrizzleModelToken, getDrizzlePropsMetadata, getDrizzleSchemaMetadata, getOrGenerateSchema, getRegisteredSchemas, isDraft, isPublished, index as sqlGenerators };
1072
+ export { Adapter, AutoMigration, type ColumnChange, DEFAULT_LOCALE, DEFAULT_MIGRATION_CONFIG, DOCUMENT_STATUS, DRIZZLE_CONFIG, DRIZZLE_DB, type DiffResult, type DocumentColumnOptions, type DocumentStatus, type DrizzleConfig, type DrizzleDB, DrizzleDatabaseAdapter, type DrizzleModuleOptions, DrizzleQueryBuilder, type GenerateJsonFn, type GenerateSQLFn, type GeneratedSchema, type IndexChange, InjectModel, LazyQueryBuilder, type Migration, MigrationChecksumError, type MigrationConfig, type MigrationDb, type MigrationDialect, MigrationError, type MigrationGenerateOptions, MigrationGenerator, MigrationHistory, type MigrationHistoryRecord, MigrationLock, MigrationLockError, type MigrationMode, type MigrationResult, MigrationRunner, Prop, type PropMetadata, SNAPSHOT_FILENAME, Schema, SchemaBridge, SchemaDiff, type SchemaDiffResult, type SchemaMetadata, type SnapshotJSON, type TableDefinition, applyDocumentColumns, clearSchemaRegistry, createNeonConnection, createNeonWebSocketConnection, generateSchema, getDrizzleModelToken, getDrizzlePropsMetadata, getDrizzleSchemaMetadata, getOrGenerateSchema, getRegisteredSchemas, isDraft, isPublished, sanitizeExecutableMigrationSql, index as sqlGenerators };
package/dist/index.js CHANGED
@@ -2,13 +2,13 @@ import { __commonJS, __name, __require, __toESM, __export } from './chunk-AAIPPT
2
2
  import 'reflect-metadata';
3
3
  import { QueryBuilder, DatabaseAdapter, getModelToken, setDatabaseAdapter, getSchemaOptions, Model, DatabaseError, fromDrizzleError, DocumentNotFoundError } from '@magnet-cms/common';
4
4
  import { Logger, Module, Injectable, Inject } from '@nestjs/common';
5
+ import { createHash, randomUUID } from 'node:crypto';
5
6
  import { toSnakeCase, toCamelCase, pluralize } from '@magnet-cms/utils';
6
7
  import { and, or, eq, desc, asc, sql, isNotNull, isNull, ilike, like, notInArray, inArray, lte, lt, gte, gt, ne } from 'drizzle-orm';
7
8
  import { getTableConfig, varchar, timestamp, uuid, pgTable, uniqueIndex, index, text, doublePrecision, boolean, jsonb } from 'drizzle-orm/pg-core';
8
9
  import { drizzle } from 'drizzle-orm/node-postgres';
9
10
  import { readdir, mkdir, writeFile, readFile } from 'node:fs/promises';
10
11
  import { join } from 'node:path';
11
- import { createHash } from 'node:crypto';
12
12
 
13
13
  // ../../../node_modules/postgres-array/index.js
14
14
  var require_postgres_array = __commonJS({
@@ -5346,8 +5346,11 @@ function generateSchema(schemaClass) {
5346
5346
  });
5347
5347
  const columns = {
5348
5348
  // Primary key - always UUID
5349
- // Type assertion needed due to drizzle-orm type system seeing SQL types as incompatible
5350
- id: uuid("id").primaryKey().default(sql`gen_random_uuid()`)
5349
+ // $defaultFn generates UUIDs in JS (works on all dialects: PostgreSQL, MySQL, SQLite)
5350
+ // Note: SQL .default(gen_random_uuid()) is intentionally omitted because it conflicts
5351
+ // with $defaultFn when using pg-core types on non-PostgreSQL drivers.
5352
+ // The createTableFromConfig() method adds dialect-appropriate SQL DEFAULT for direct inserts.
5353
+ id: uuid("id").primaryKey().$defaultFn(() => randomUUID())
5351
5354
  };
5352
5355
  if (hasI18n || hasVersioning || hasIntlProps) {
5353
5356
  const docOptions = {
@@ -5363,8 +5366,8 @@ function generateSchema(schemaClass) {
5363
5366
  columns[columnName] = column;
5364
5367
  }
5365
5368
  });
5366
- columns.createdAt = timestamp("created_at").defaultNow().notNull();
5367
- columns.updatedAt = timestamp("updated_at").defaultNow().notNull();
5369
+ columns.createdAt = timestamp("created_at").notNull();
5370
+ columns.updatedAt = timestamp("updated_at").notNull();
5368
5371
  const table = pgTable(tableName, columns, (table2) => {
5369
5372
  const tableIndexes = {};
5370
5373
  const t = table2;
@@ -5414,7 +5417,9 @@ function generateColumn(columnName, options) {
5414
5417
  column = text(snakeCaseName);
5415
5418
  }
5416
5419
  if (options.default !== void 0) {
5417
- column = column.default(options.default);
5420
+ if (Array.isArray(options.default) && options.default.length === 0) ; else {
5421
+ column = column.default(options.default);
5422
+ }
5418
5423
  }
5419
5424
  return column;
5420
5425
  }
@@ -5438,8 +5443,8 @@ async function createNeonConnection(config) {
5438
5443
  } catch {
5439
5444
  throw new Error("@neondatabase/serverless is required for Neon driver. Install it with: bun add @neondatabase/serverless");
5440
5445
  }
5441
- const sql5 = neon.neon(config.connectionString);
5442
- const db = drizzleNeon.drizzle(sql5, {
5446
+ const sql4 = neon.neon(config.connectionString);
5447
+ const db = drizzleNeon.drizzle(sql4, {
5443
5448
  logger: config.debug
5444
5449
  });
5445
5450
  return {
@@ -5907,6 +5912,11 @@ var DrizzleQueryBuilder = class extends QueryBuilder {
5907
5912
  };
5908
5913
 
5909
5914
  // src/drizzle.model.ts
5915
+ function isSQLiteDriver(db) {
5916
+ const d = db;
5917
+ return typeof d.run === "function" && typeof d.execute !== "function";
5918
+ }
5919
+ __name(isSQLiteDriver, "isSQLiteDriver");
5910
5920
  function createModel(db, table, schemaClass) {
5911
5921
  return class DrizzleModelAdapter extends Model {
5912
5922
  static {
@@ -5970,8 +5980,9 @@ function createModel(db, table, schemaClass) {
5970
5980
  const insertData = {
5971
5981
  ...this._prepareData(data, true)
5972
5982
  };
5983
+ this._applyDeclaredEmptyArrayDefaults(insertData);
5973
5984
  if ("documentId" in dynamicTable) {
5974
- insertData.documentId = insertData.documentId || sql`gen_random_uuid()`;
5985
+ insertData.documentId = insertData.documentId || randomUUID();
5975
5986
  insertData.locale = insertData.locale || this.currentLocale || DEFAULT_LOCALE;
5976
5987
  insertData.status = insertData.status || DOCUMENT_STATUS.DRAFT;
5977
5988
  }
@@ -6358,14 +6369,29 @@ function createModel(db, table, schemaClass) {
6358
6369
  * For JSONB columns (arrays/objects), ensure values are properly formatted
6359
6370
  * @internal
6360
6371
  */
6372
+ /**
6373
+ * Fields declared with `default: []` omit a DB default (cross-dialect migration/runtime).
6374
+ * Fill missing keys so inserts match Mongoose-style expectations.
6375
+ */
6376
+ _applyDeclaredEmptyArrayDefaults(row) {
6377
+ const props = getDrizzlePropsMetadata(this._schemaClass);
6378
+ for (const [propertyKey, options] of props) {
6379
+ const key = String(propertyKey);
6380
+ if (options.default !== void 0 && Array.isArray(options.default) && options.default.length === 0 && row[key] === void 0) {
6381
+ row[key] = isSQLiteDriver(this._db) ? JSON.stringify([]) : [];
6382
+ }
6383
+ }
6384
+ }
6361
6385
  _prepareData(data, isCreate = false) {
6362
6386
  const result = {};
6363
- if (isCreate && !data.createdAt) {
6364
- result.createdAt = /* @__PURE__ */ new Date();
6387
+ const isSQLite = isSQLiteDriver(this._db);
6388
+ if (isCreate) {
6389
+ result.createdAt = data.createdAt instanceof Date ? data.createdAt : /* @__PURE__ */ new Date();
6365
6390
  }
6391
+ result.updatedAt = data.updatedAt instanceof Date ? data.updatedAt : /* @__PURE__ */ new Date();
6366
6392
  for (const [key, value] of Object.entries(data)) {
6367
6393
  if (key === "id") continue;
6368
- if (key === "createdAt") continue;
6394
+ if (key === "createdAt" || key === "updatedAt") continue;
6369
6395
  if (value === void 0) {
6370
6396
  continue;
6371
6397
  }
@@ -6373,6 +6399,10 @@ function createModel(db, table, schemaClass) {
6373
6399
  result[key] = null;
6374
6400
  continue;
6375
6401
  }
6402
+ if (typeof value === "boolean" && isSQLite) {
6403
+ result[key] = value ? 1 : 0;
6404
+ continue;
6405
+ }
6376
6406
  if (value instanceof Date) {
6377
6407
  result[key] = value;
6378
6408
  continue;
@@ -6391,7 +6421,7 @@ function createModel(db, table, schemaClass) {
6391
6421
  result[key] = value;
6392
6422
  }
6393
6423
  } else if (Array.isArray(value)) {
6394
- result[key] = value;
6424
+ result[key] = isSQLite ? JSON.stringify(value) : value;
6395
6425
  } else if (value !== null && typeof value === "object") {
6396
6426
  result[key] = value;
6397
6427
  } else {
@@ -6451,13 +6481,13 @@ var AutoMigration = class {
6451
6481
  dangerous: diffResult.dangerous,
6452
6482
  warnings: diffResult.warnings,
6453
6483
  async up(db) {
6454
- for (const sql5 of upSQL) {
6484
+ for (const sql4 of upSQL) {
6455
6485
  try {
6456
- await db.execute(sql5);
6486
+ await db.execute(sql4);
6457
6487
  } catch (err) {
6458
6488
  const msg = err instanceof Error ? err.message : String(err);
6459
6489
  const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
6460
- if (code === "42P07" || msg.includes("already exists") || msg.includes("ER_TABLE_EXISTS") || msg.includes("duplicate table name")) {
6490
+ if (code === "42P07" || code === "42710" || msg.includes("already exists") || msg.includes("ER_TABLE_EXISTS") || msg.includes("duplicate table name") || msg.includes("duplicate key value")) {
6461
6491
  logger.warn(`Skipping (object already exists): ${msg.slice(0, 80)}`);
6462
6492
  continue;
6463
6493
  }
@@ -6485,8 +6515,8 @@ var MigrationGenerator = class {
6485
6515
  generate(name, upSQL, downSQL, options = {}) {
6486
6516
  const timestamp3 = Date.now();
6487
6517
  const id = `${timestamp3}_${name}`;
6488
- const upStatements = upSQL.map((sql5) => ` await db.execute(\`${sql5}\`)`).join("\n");
6489
- const downStatements = downSQL.length > 0 ? downSQL.map((sql5) => ` await db.execute(\`${sql5}\`)`).join("\n") : " // TODO: implement down migration";
6518
+ const upStatements = upSQL.map((sql4) => ` await db.execute(\`${sql4}\`)`).join("\n");
6519
+ const downStatements = downSQL.length > 0 ? downSQL.map((sql4) => ` await db.execute(\`${sql4}\`)`).join("\n") : " // TODO: implement down migration";
6490
6520
  const dangerousField = options.dangerous ? "\n dangerous: true," : "";
6491
6521
  const warningsField = options.warnings && options.warnings.length > 0 ? `
6492
6522
  warnings: [
@@ -6833,6 +6863,12 @@ var MigrationRunner = class {
6833
6863
  };
6834
6864
  }
6835
6865
  };
6866
+ function sanitizeExecutableMigrationSql(dialect, statement) {
6867
+ if (dialect !== "postgresql") return statement;
6868
+ if (!statement.includes("$")) return statement;
6869
+ return statement.replace(/"locale" = \$1/g, `"locale" = 'en'`).replace(/"status" = \$2/g, `"status" = 'draft'`);
6870
+ }
6871
+ __name(sanitizeExecutableMigrationSql, "sanitizeExecutableMigrationSql");
6836
6872
  var SNAPSHOT_FILENAME = ".schema_snapshot.json";
6837
6873
  var SchemaBridge = class {
6838
6874
  static {
@@ -6872,7 +6908,8 @@ var SchemaBridge = class {
6872
6908
  */
6873
6909
  async generateSQL(prev, cur, generateSQLFn, dialect = "postgresql") {
6874
6910
  const fn = generateSQLFn ?? await this.resolveGenerateSQLFn(dialect);
6875
- return fn(prev, cur);
6911
+ const statements = await fn(prev, cur);
6912
+ return statements.map((s) => sanitizeExecutableMigrationSql(dialect, s));
6876
6913
  }
6877
6914
  /**
6878
6915
  * Load a previously saved snapshot from disk.
@@ -7001,9 +7038,9 @@ var SchemaDiff = class {
7001
7038
  */
7002
7039
  detectDangerousOperations(sqlStatements) {
7003
7040
  const warnings = [];
7004
- for (const sql5 of sqlStatements) {
7041
+ for (const sql4 of sqlStatements) {
7005
7042
  for (const { pattern, message } of DANGEROUS_PATTERNS) {
7006
- const match = sql5.match(pattern);
7043
+ const match = sql4.match(pattern);
7007
7044
  if (match) {
7008
7045
  warnings.push(message(match[1] ?? match[0]));
7009
7046
  }
@@ -7057,14 +7094,27 @@ var DrizzleDatabaseAdapter = class _DrizzleDatabaseAdapter extends DatabaseAdapt
7057
7094
  options = null;
7058
7095
  schemaRegistry = /* @__PURE__ */ new Map();
7059
7096
  tablesInitialized = false;
7097
+ tablesInitPromise = null;
7060
7098
  /**
7061
7099
  * Automatically create tables for all registered schemas.
7062
7100
  * Called lazily when first model is accessed, or can be called explicitly.
7101
+ * Uses a lock (Promise) to prevent concurrent execution from multiple forFeature() calls.
7063
7102
  */
7064
7103
  async ensureTablesCreated() {
7065
7104
  if (this.tablesInitialized || !this.db || this.schemaRegistry.size === 0) {
7066
7105
  return;
7067
7106
  }
7107
+ if (this.tablesInitPromise) {
7108
+ return this.tablesInitPromise;
7109
+ }
7110
+ this.tablesInitPromise = this._doEnsureTablesCreated();
7111
+ try {
7112
+ await this.tablesInitPromise;
7113
+ } finally {
7114
+ this.tablesInitPromise = null;
7115
+ }
7116
+ }
7117
+ async _doEnsureTablesCreated() {
7068
7118
  await new Promise((resolve) => setTimeout(resolve, 200));
7069
7119
  const drizzleConfig = this.options;
7070
7120
  if (drizzleConfig?.migrations) {
@@ -7083,7 +7133,7 @@ var DrizzleDatabaseAdapter = class _DrizzleDatabaseAdapter extends DatabaseAdapt
7083
7133
  ...config.migrations
7084
7134
  };
7085
7135
  const migrationDb = {
7086
- execute: /* @__PURE__ */ __name(async (sql5) => this.db.execute(sql5), "execute")
7136
+ execute: /* @__PURE__ */ __name(async (sql4) => this.db.execute(sql4), "execute")
7087
7137
  };
7088
7138
  const bridge = new SchemaBridge();
7089
7139
  const diff = new SchemaDiff(bridge);
@@ -7113,13 +7163,20 @@ ${stack}` : ""}`);
7113
7163
  }
7114
7164
  const drizzleConfig = this.options;
7115
7165
  const dialect = drizzleConfig?.dialect ?? "postgresql";
7116
- try {
7117
- for (const [, { table }] of this.schemaRegistry.entries()) {
7166
+ const logger2 = new Logger("DrizzleAdapter");
7167
+ let hasErrors = false;
7168
+ for (const [name, { table }] of this.schemaRegistry.entries()) {
7169
+ try {
7118
7170
  const config = getTableConfig(table);
7119
7171
  await this.createTableFromConfig(config, dialect);
7172
+ } catch (error) {
7173
+ hasErrors = true;
7174
+ const msg = error instanceof Error ? error.message : String(error);
7175
+ logger2.warn(`Error creating table for "${name}" automatically: ${msg}`);
7120
7176
  }
7121
- } catch (error) {
7122
- new Logger("DrizzleAdapter").warn("Error creating tables automatically:", JSON.stringify(error));
7177
+ }
7178
+ if (hasErrors) {
7179
+ logger2.warn("Some tables could not be created. The application may not work correctly.");
7123
7180
  }
7124
7181
  }
7125
7182
  /**
@@ -7176,7 +7233,7 @@ ${stack}` : ""}`);
7176
7233
  ${columns.join(",\n ")}
7177
7234
  )
7178
7235
  `);
7179
- await this.db.execute(createTableSQL);
7236
+ await this.execRawSQL(createTableSQL);
7180
7237
  const indexes = config.indexes;
7181
7238
  if (indexes) {
7182
7239
  for (const index2 of Object.values(indexes)) {
@@ -7195,13 +7252,27 @@ ${stack}` : ""}`);
7195
7252
  CREATE ${uniqueKeyword} INDEX IF NOT EXISTS ${q(indexName)}
7196
7253
  ON ${q(tableName)} (${columnsStr})
7197
7254
  `);
7198
- await this.db.execute(createIndexSQL).catch(() => {
7255
+ await this.execRawSQL(createIndexSQL).catch(() => {
7199
7256
  });
7200
7257
  }
7201
7258
  }
7202
7259
  }
7203
7260
  }
7204
7261
  /**
7262
+ * Execute raw SQL across all supported dialects.
7263
+ * PostgreSQL/MySQL use db.execute(), SQLite uses db.run().
7264
+ */
7265
+ async execRawSQL(rawSQL) {
7266
+ const db = this.db;
7267
+ if (typeof db.execute === "function") {
7268
+ await db.execute(rawSQL);
7269
+ } else if (typeof db.run === "function") {
7270
+ db.run(rawSQL);
7271
+ } else {
7272
+ throw new Error("Database instance has no execute() or run() method");
7273
+ }
7274
+ }
7275
+ /**
7205
7276
  * Map PostgreSQL SQL types to dialect-specific types.
7206
7277
  */
7207
7278
  mapSQLTypeForDialect(pgType, dialect) {
@@ -7860,4 +7931,4 @@ __name(dropIndex3, "dropIndex");
7860
7931
  // src/index.ts
7861
7932
  setDatabaseAdapter("drizzle");
7862
7933
 
7863
- export { Adapter, AutoMigration, DEFAULT_LOCALE, DEFAULT_MIGRATION_CONFIG, DOCUMENT_STATUS, DRIZZLE_CONFIG, DRIZZLE_DB, DrizzleDatabaseAdapter, DrizzleQueryBuilder, InjectModel, LazyQueryBuilder, MigrationChecksumError, MigrationError, MigrationGenerator, MigrationHistory, MigrationLock, MigrationLockError, MigrationRunner, Prop, SNAPSHOT_FILENAME, Schema, SchemaBridge, SchemaDiff, applyDocumentColumns, clearSchemaRegistry, createNeonConnection, createNeonWebSocketConnection, generateSchema, getDrizzleModelToken, getDrizzlePropsMetadata, getDrizzleSchemaMetadata, getOrGenerateSchema, getRegisteredSchemas, isDraft, isPublished, sql_generators_exports as sqlGenerators };
7934
+ export { Adapter, AutoMigration, DEFAULT_LOCALE, DEFAULT_MIGRATION_CONFIG, DOCUMENT_STATUS, DRIZZLE_CONFIG, DRIZZLE_DB, DrizzleDatabaseAdapter, DrizzleQueryBuilder, InjectModel, LazyQueryBuilder, MigrationChecksumError, MigrationError, MigrationGenerator, MigrationHistory, MigrationLock, MigrationLockError, MigrationRunner, Prop, SNAPSHOT_FILENAME, Schema, SchemaBridge, SchemaDiff, applyDocumentColumns, clearSchemaRegistry, createNeonConnection, createNeonWebSocketConnection, generateSchema, getDrizzleModelToken, getDrizzlePropsMetadata, getDrizzleSchemaMetadata, getOrGenerateSchema, getRegisteredSchemas, isDraft, isPublished, sanitizeExecutableMigrationSql, sql_generators_exports as sqlGenerators };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magnet-cms/adapter-db-drizzle",
3
- "version": "1.0.2",
3
+ "version": "2.0.0",
4
4
  "description": "Drizzle ORM database adapter for Magnet CMS - supports PostgreSQL, MySQL, and SQLite",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -42,8 +42,8 @@
42
42
  "reflect-metadata": "0.2.2"
43
43
  },
44
44
  "peerDependencies": {
45
- "@magnet-cms/common": "^0.1.0",
46
- "@magnet-cms/utils": "^0.1.0",
45
+ "@magnet-cms/common": "^0.2.0",
46
+ "@magnet-cms/utils": "^0.1.1",
47
47
  "@nestjs/common": "^11.1.12",
48
48
  "drizzle-orm": "^0.38.0",
49
49
  "reflect-metadata": "0.2.2"