@digilogiclabs/platform-core 1.7.0 → 1.8.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.
@@ -1 +1 @@
1
- export { t as IMigrationDatabase, I as IMigrator, n as Migration, o as MigrationRecord, p as MigrationResult, q as MigrationStatus, M as Migrator, r as MigratorConfig, f as createDomainVerificationsTable, c as createMigration, b as createSsoOidcConfigsTable, m as createSsoSessionsTable, k as createTenantInvitationsTable, j as createTenantMembersTable, l as createTenantUsageTable, i as createTenantsTable, h as createVerifiedDomainsTable, d as defineMigration, e as enterpriseMigrations, a as getEnterpriseMigrations, s as sqlMigration } from '../index-CepDdu7h.mjs';
1
+ export { w as IMigrationDatabase, I as IMigrator, q as Migration, r as MigrationRecord, t as MigrationResult, u as MigrationStatus, M as Migrator, v as MigratorConfig, f as createDomainVerificationsTable, c as createMigration, b as createSsoOidcConfigsTable, m as createSsoSessionsTable, k as createTenantInvitationsTable, j as createTenantMembersTable, l as createTenantUsageTable, i as createTenantsTable, h as createVerifiedDomainsTable, d as defineMigration, e as enterpriseMigrations, a as getEnterpriseMigrations, s as sqlMigration } from '../index-DzQ0Js5Z.mjs';
@@ -1 +1 @@
1
- export { t as IMigrationDatabase, I as IMigrator, n as Migration, o as MigrationRecord, p as MigrationResult, q as MigrationStatus, M as Migrator, r as MigratorConfig, f as createDomainVerificationsTable, c as createMigration, b as createSsoOidcConfigsTable, m as createSsoSessionsTable, k as createTenantInvitationsTable, j as createTenantMembersTable, l as createTenantUsageTable, i as createTenantsTable, h as createVerifiedDomainsTable, d as defineMigration, e as enterpriseMigrations, a as getEnterpriseMigrations, s as sqlMigration } from '../index-CepDdu7h.js';
1
+ export { w as IMigrationDatabase, I as IMigrator, q as Migration, r as MigrationRecord, t as MigrationResult, u as MigrationStatus, M as Migrator, v as MigratorConfig, f as createDomainVerificationsTable, c as createMigration, b as createSsoOidcConfigsTable, m as createSsoSessionsTable, k as createTenantInvitationsTable, j as createTenantMembersTable, l as createTenantUsageTable, i as createTenantsTable, h as createVerifiedDomainsTable, d as defineMigration, e as enterpriseMigrations, a as getEnterpriseMigrations, s as sqlMigration } from '../index-DzQ0Js5Z.js';
@@ -712,6 +712,67 @@ var createSsoSessionsTable = {
712
712
  `,
713
713
  down: "DROP TABLE IF EXISTS sso_sessions CASCADE"
714
714
  };
715
+ var createBetaSettingsTable = {
716
+ version: "20241217_009",
717
+ name: "create_beta_settings_table",
718
+ up: `
719
+ CREATE TABLE IF NOT EXISTS beta_settings (
720
+ key VARCHAR(100) PRIMARY KEY,
721
+ value TEXT NOT NULL,
722
+ description TEXT,
723
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
724
+ updated_by VARCHAR(255)
725
+ );
726
+
727
+ INSERT INTO beta_settings (key, value, description) VALUES
728
+ ('beta_mode', 'true', 'Whether the app is in beta mode'),
729
+ ('require_invite_code', 'true', 'Whether an invite code is required to sign up'),
730
+ ('beta_message', 'We''re in beta! Thanks for being an early tester.', 'Message displayed during beta')
731
+ ON CONFLICT (key) DO NOTHING;
732
+ `,
733
+ down: "DROP TABLE IF EXISTS beta_settings CASCADE"
734
+ };
735
+ var createBetaInvitesTable = {
736
+ version: "20241217_010",
737
+ name: "create_beta_invites_table",
738
+ up: `
739
+ CREATE TABLE IF NOT EXISTS beta_invites (
740
+ id VARCHAR(255) PRIMARY KEY,
741
+ code VARCHAR(100) NOT NULL UNIQUE,
742
+ max_uses INTEGER NOT NULL DEFAULT 1,
743
+ current_uses INTEGER NOT NULL DEFAULT 0,
744
+ expires_at TIMESTAMP WITH TIME ZONE,
745
+ created_by VARCHAR(255) NOT NULL,
746
+ notes TEXT DEFAULT '',
747
+ is_active BOOLEAN NOT NULL DEFAULT true,
748
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
749
+ CONSTRAINT chk_beta_uses CHECK (current_uses <= max_uses)
750
+ );
751
+
752
+ CREATE INDEX IF NOT EXISTS idx_beta_invites_code_active
753
+ ON beta_invites(code) WHERE is_active = true;
754
+ CREATE INDEX IF NOT EXISTS idx_beta_invites_active
755
+ ON beta_invites(is_active, created_at DESC);
756
+ `,
757
+ down: "DROP TABLE IF EXISTS beta_invites CASCADE"
758
+ };
759
+ var createBetaTestersTable = {
760
+ version: "20241217_011",
761
+ name: "create_beta_testers_table",
762
+ up: `
763
+ CREATE TABLE IF NOT EXISTS beta_testers (
764
+ user_id VARCHAR(255) PRIMARY KEY,
765
+ invite_code VARCHAR(100) NOT NULL REFERENCES beta_invites(code) ON DELETE SET NULL,
766
+ is_beta_tester BOOLEAN NOT NULL DEFAULT true,
767
+ beta_joined_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
768
+ );
769
+
770
+ CREATE INDEX IF NOT EXISTS idx_beta_testers_active
771
+ ON beta_testers(is_beta_tester, beta_joined_at DESC)
772
+ WHERE is_beta_tester = true;
773
+ `,
774
+ down: "DROP TABLE IF EXISTS beta_testers CASCADE"
775
+ };
715
776
  var enterpriseMigrations = [
716
777
  createSsoOidcConfigsTable,
717
778
  createDomainVerificationsTable,
@@ -720,7 +781,10 @@ var enterpriseMigrations = [
720
781
  createTenantMembersTable,
721
782
  createTenantInvitationsTable,
722
783
  createTenantUsageTable,
723
- createSsoSessionsTable
784
+ createSsoSessionsTable,
785
+ createBetaSettingsTable,
786
+ createBetaInvitesTable,
787
+ createBetaTestersTable
724
788
  ];
725
789
  function getEnterpriseMigrations(features) {
726
790
  const migrations = [];
@@ -740,6 +804,13 @@ function getEnterpriseMigrations(features) {
740
804
  createTenantUsageTable
741
805
  );
742
806
  }
807
+ if (features.beta) {
808
+ migrations.push(
809
+ createBetaSettingsTable,
810
+ createBetaInvitesTable,
811
+ createBetaTestersTable
812
+ );
813
+ }
743
814
  return migrations;
744
815
  }
745
816
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/migrations/index.ts","../../src/migrations/Migrator.ts","../../src/migrations/helpers.ts","../../src/migrations/enterprise.ts"],"sourcesContent":["/**\n * Database Migrations\n *\n * Enterprise-grade migration system for managing database schema changes.\n *\n * @example\n * ```typescript\n * import { Migrator } from '@digilogiclabs/platform-core';\n * import { PostgresDatabase } from '@digilogiclabs/platform-core';\n *\n * // Create database connection\n * const db = await PostgresDatabase.fromEnv();\n *\n * // Create migrator\n * const migrator = new Migrator(db, {\n * tableName: '_migrations',\n * migrationsDir: './migrations'\n * });\n *\n * // Add migrations programmatically\n * migrator.addMigrations([\n * {\n * version: '20240101000000',\n * name: 'create_users_table',\n * up: `CREATE TABLE users (id UUID PRIMARY KEY, email VARCHAR(255) UNIQUE)`,\n * down: `DROP TABLE users`\n * }\n * ]);\n *\n * // Or load from directory\n * await migrator.loadMigrations('./migrations');\n *\n * // Check status\n * const status = await migrator.status();\n * console.log('Applied:', status.applied.length);\n * console.log('Pending:', status.pending.length);\n *\n * // Run pending migrations\n * const results = await migrator.up();\n * for (const result of results) {\n * if (result.success) {\n * console.log(`✓ ${result.migration.name} (${result.executionTime}ms)`);\n * } else {\n * console.error(`✗ ${result.migration.name}: ${result.error?.message}`);\n * }\n * }\n *\n * // Rollback last migration\n * await migrator.down();\n *\n * // Rollback last 3 migrations\n * await migrator.down({ steps: 3 });\n *\n * // Dry run (preview without applying)\n * await migrator.up({ dryRun: true });\n * ```\n */\n\nexport { Migrator } from \"./Migrator\";\nexport type {\n Migration,\n MigrationRecord,\n MigrationResult,\n MigrationStatus,\n MigratorConfig,\n IMigrator,\n IMigrationDatabase,\n} from \"../interfaces/IMigration\";\n\n// Re-export common migration patterns\nexport { createMigration, defineMigration, sqlMigration } from \"./helpers\";\n\n// Enterprise migrations for SSO and multi-tenancy\nexport {\n enterpriseMigrations,\n getEnterpriseMigrations,\n createSsoOidcConfigsTable,\n createDomainVerificationsTable,\n createVerifiedDomainsTable,\n createTenantsTable,\n createTenantMembersTable,\n createTenantInvitationsTable,\n createTenantUsageTable,\n createSsoSessionsTable,\n} from \"./enterprise\";\n","/**\n * Database Migrator\n *\n * Enterprise-grade database migration system supporting:\n * - PostgreSQL (primary)\n * - Memory database (for testing)\n * - Transaction-safe migrations\n * - Concurrent execution locking\n * - Checksum validation\n * - Dry-run mode\n */\n\nimport type {\n IMigrator,\n Migration,\n MigrationRecord,\n MigrationResult,\n MigrationStatus,\n MigratorConfig,\n IMigrationDatabase,\n} from \"../interfaces/IMigration\";\n\nconst DEFAULT_CONFIG: Required<Omit<MigratorConfig, \"migrationsDir\">> & {\n migrationsDir?: string;\n} = {\n tableName: \"_migrations\",\n schema: \"public\",\n lockTimeout: 60,\n validateChecksums: true,\n migrationsDir: undefined,\n};\n\n/**\n * Generate a checksum for migration content\n */\nfunction generateChecksum(content: string): string {\n // Simple hash for checksum - in production you might use crypto\n let hash = 0;\n for (let i = 0; i < content.length; i++) {\n const char = content.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, \"0\");\n}\n\n/**\n * Sort migrations by version\n */\nfunction sortMigrations(migrations: Migration[]): Migration[] {\n return [...migrations].sort((a, b) => a.version.localeCompare(b.version));\n}\n\nexport class Migrator implements IMigrator {\n private db: IMigrationDatabase;\n private config: Required<Omit<MigratorConfig, \"migrationsDir\">> & {\n migrationsDir?: string;\n };\n private migrations: Migration[] = [];\n private initialized = false;\n private locked = false;\n\n constructor(db: IMigrationDatabase, config: MigratorConfig = {}) {\n this.db = db;\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Get the fully qualified migration table name\n */\n private get tableName(): string {\n return `\"${this.config.schema}\".\"${this.config.tableName}\"`;\n }\n\n /**\n * Get the lock table name\n */\n private get lockTableName(): string {\n return `\"${this.config.schema}\".\"${this.config.tableName}_lock\"`;\n }\n\n /**\n * Initialize migration tracking tables\n */\n private async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Create schema if not exists\n await this.db.raw(`CREATE SCHEMA IF NOT EXISTS \"${this.config.schema}\"`);\n\n // Create migrations tracking table\n await this.db.raw(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n version VARCHAR(255) PRIMARY KEY,\n name VARCHAR(255) NOT NULL,\n applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n execution_time INTEGER NOT NULL,\n checksum VARCHAR(64)\n )\n `);\n\n // Create lock table for concurrent safety\n await this.db.raw(`\n CREATE TABLE IF NOT EXISTS ${this.lockTableName} (\n id INTEGER PRIMARY KEY DEFAULT 1,\n locked_at TIMESTAMP WITH TIME ZONE,\n locked_by VARCHAR(255),\n CONSTRAINT single_row CHECK (id = 1)\n )\n `);\n\n // Insert initial lock row if not exists\n await this.db.raw(`\n INSERT INTO ${this.lockTableName} (id, locked_at, locked_by)\n VALUES (1, NULL, NULL)\n ON CONFLICT (id) DO NOTHING\n `);\n\n this.initialized = true;\n }\n\n /**\n * Acquire migration lock\n */\n async lock(): Promise<boolean> {\n await this.initialize();\n\n const lockId = `${process.pid}-${Date.now()}`;\n const lockTimeout = new Date(Date.now() - this.config.lockTimeout * 1000);\n\n // Try to acquire lock (only if not locked or lock expired)\n const result = await this.db.raw<{ locked_at: Date | null }>(\n `\n UPDATE ${this.lockTableName}\n SET locked_at = CURRENT_TIMESTAMP, locked_by = $1\n WHERE id = 1 AND (locked_at IS NULL OR locked_at < $2)\n RETURNING locked_at\n `,\n [lockId, lockTimeout],\n );\n\n if (result.data.length > 0) {\n this.locked = true;\n return true;\n }\n\n return false;\n }\n\n /**\n * Release migration lock\n */\n async unlock(): Promise<void> {\n if (!this.locked) return;\n\n await this.db.raw(\n `\n UPDATE ${this.lockTableName}\n SET locked_at = NULL, locked_by = NULL\n WHERE id = 1\n `,\n );\n\n this.locked = false;\n }\n\n /**\n * Add migrations to the migrator\n */\n addMigrations(migrations: Migration[]): void {\n for (const migration of migrations) {\n // Generate checksum if not provided\n if (!migration.checksum) {\n const content =\n typeof migration.up === \"string\"\n ? migration.up\n : migration.up.toString();\n migration.checksum = generateChecksum(content);\n }\n\n // Avoid duplicates\n if (!this.migrations.find((m) => m.version === migration.version)) {\n this.migrations.push(migration);\n }\n }\n }\n\n /**\n * Load migrations from directory\n * Expects files named: {version}_{name}.ts or {version}_{name}.sql\n */\n async loadMigrations(dir: string): Promise<void> {\n // Dynamic import for Node.js file system\n const { readdir, readFile } = await import(\"fs/promises\");\n const { join } = await import(\"path\");\n\n const files = await readdir(dir);\n const migrationFiles = files.filter((f) =>\n f.match(/^\\d+_.*\\.(ts|js|sql)$/),\n );\n\n for (const file of migrationFiles) {\n const filePath = join(dir, file);\n const [version, ...nameParts] = file\n .replace(/\\.(ts|js|sql)$/, \"\")\n .split(\"_\");\n const name = nameParts.join(\"_\");\n\n if (file.endsWith(\".sql\")) {\n // SQL file - parse UP and DOWN sections\n const content = await readFile(filePath, \"utf-8\");\n const [up, down] = this.parseSqlMigration(content);\n\n this.addMigrations([\n {\n version: version!,\n name,\n up,\n down,\n },\n ]);\n } else {\n // TypeScript/JavaScript file - import the module\n const module = await import(filePath);\n this.addMigrations([\n {\n version: version!,\n name,\n up: module.up,\n down: module.down,\n transactional: module.transactional,\n },\n ]);\n }\n }\n }\n\n /**\n * Parse SQL migration file with -- UP and -- DOWN sections\n */\n private parseSqlMigration(content: string): [string, string] {\n const upMatch = content.match(/--\\s*UP\\s*\\n([\\s\\S]*?)(?=--\\s*DOWN|$)/i);\n const downMatch = content.match(/--\\s*DOWN\\s*\\n([\\s\\S]*?)$/i);\n\n const up = upMatch?.[1]?.trim() ?? content.trim();\n const down = downMatch?.[1]?.trim() ?? \"\";\n\n return [up, down];\n }\n\n /**\n * Get current migration status\n */\n async status(): Promise<MigrationStatus> {\n await this.initialize();\n\n // Get applied migrations\n const result = await this.db.raw<{\n version: string;\n name: string;\n applied_at: Date;\n execution_time: number;\n checksum: string | null;\n }>(\n `SELECT version, name, applied_at, execution_time, checksum\n FROM ${this.tableName}\n ORDER BY version ASC`,\n );\n\n const applied: MigrationRecord[] = result.data.map((row) => ({\n version: row.version,\n name: row.name,\n appliedAt: new Date(row.applied_at),\n executionTime: row.execution_time,\n checksum: row.checksum ?? undefined,\n }));\n\n const appliedVersions = new Set(applied.map((m) => m.version));\n const sortedMigrations = sortMigrations(this.migrations);\n\n const pending = sortedMigrations.filter(\n (m) => !appliedVersions.has(m.version),\n );\n\n return {\n applied,\n pending,\n currentVersion:\n applied.length > 0 ? applied[applied.length - 1]!.version : null,\n latestVersion:\n sortedMigrations.length > 0\n ? sortedMigrations[sortedMigrations.length - 1]!.version\n : null,\n };\n }\n\n /**\n * Run all pending migrations (or up to a specific version)\n */\n async up(\n options: { dryRun?: boolean; to?: string } = {},\n ): Promise<MigrationResult[]> {\n const { dryRun = false, to } = options;\n const results: MigrationResult[] = [];\n\n if (!dryRun) {\n const acquired = await this.lock();\n if (!acquired) {\n throw new Error(\n \"Could not acquire migration lock. Another migration may be in progress.\",\n );\n }\n }\n\n try {\n const status = await this.status();\n let pending = status.pending;\n\n // Filter to specific version if requested\n if (to) {\n const targetIndex = pending.findIndex((m) => m.version === to);\n if (targetIndex === -1) {\n throw new Error(\n `Target version ${to} not found in pending migrations`,\n );\n }\n pending = pending.slice(0, targetIndex + 1);\n }\n\n for (const migration of pending) {\n const result = await this.runMigration(migration, \"up\", dryRun);\n results.push(result);\n\n if (!result.success) {\n break; // Stop on first failure\n }\n }\n } finally {\n if (!dryRun) {\n await this.unlock();\n }\n }\n\n return results;\n }\n\n /**\n * Rollback migrations\n */\n async down(\n options: { dryRun?: boolean; steps?: number } = {},\n ): Promise<MigrationResult[]> {\n const { dryRun = false, steps = 1 } = options;\n const results: MigrationResult[] = [];\n\n if (!dryRun) {\n const acquired = await this.lock();\n if (!acquired) {\n throw new Error(\n \"Could not acquire migration lock. Another migration may be in progress.\",\n );\n }\n }\n\n try {\n const status = await this.status();\n\n // Get the last N applied migrations (in reverse order)\n const toRollback = status.applied.slice(-steps).reverse();\n\n for (const record of toRollback) {\n const migration = this.migrations.find(\n (m) => m.version === record.version,\n );\n if (!migration) {\n throw new Error(\n `Migration ${record.version} was applied but is not registered. Cannot rollback.`,\n );\n }\n\n const result = await this.runMigration(migration, \"down\", dryRun);\n results.push(result);\n\n if (!result.success) {\n break;\n }\n }\n } finally {\n if (!dryRun) {\n await this.unlock();\n }\n }\n\n return results;\n }\n\n /**\n * Rollback all migrations and re-run them\n */\n async reset(options: { dryRun?: boolean } = {}): Promise<MigrationResult[]> {\n const status = await this.status();\n const downResults = await this.down({\n dryRun: options.dryRun,\n steps: status.applied.length,\n });\n\n // Only proceed with up if all downs succeeded\n if (downResults.every((r) => r.success)) {\n const upResults = await this.up({ dryRun: options.dryRun });\n return [...downResults, ...upResults];\n }\n\n return downResults;\n }\n\n /**\n * Migrate to a specific version\n */\n async goto(\n version: string,\n options: { dryRun?: boolean } = {},\n ): Promise<MigrationResult[]> {\n const status = await this.status();\n const currentVersion = status.currentVersion;\n const results: MigrationResult[] = [];\n\n // If already at target, nothing to do\n if (currentVersion === version) {\n return results;\n }\n\n // Determine direction\n const isUp = !currentVersion || version > currentVersion;\n\n if (isUp) {\n return this.up({ ...options, to: version });\n } else {\n // Find how many steps down we need to go\n const currentIndex = status.applied.findIndex(\n (m) => m.version === currentVersion,\n );\n const targetIndex = status.applied.findIndex(\n (m) => m.version === version,\n );\n\n if (targetIndex === -1) {\n throw new Error(\n `Target version ${version} not found in applied migrations`,\n );\n }\n\n const steps = currentIndex - targetIndex;\n return this.down({ ...options, steps });\n }\n }\n\n /**\n * Run a single migration\n */\n private async runMigration(\n migration: Migration,\n direction: \"up\" | \"down\",\n dryRun: boolean,\n ): Promise<MigrationResult> {\n const startTime = Date.now();\n const action = direction === \"up\" ? migration.up : migration.down;\n\n try {\n if (dryRun) {\n // Just simulate - don't actually run\n return {\n migration,\n success: true,\n executionTime: 0,\n dryRun: true,\n };\n }\n\n const transactional = migration.transactional !== false;\n const runAction = async (db: IMigrationDatabase) => {\n if (typeof action === \"string\") {\n const result = await db.raw(action);\n if (result.error) {\n throw result.error;\n }\n } else {\n await action(db);\n }\n };\n\n if (transactional) {\n await this.db.transaction(async (tx) => {\n await runAction(tx);\n });\n } else {\n await runAction(this.db);\n }\n\n const executionTime = Date.now() - startTime;\n\n // Update tracking table\n if (direction === \"up\") {\n await this.db.raw(\n `INSERT INTO ${this.tableName} (version, name, execution_time, checksum)\n VALUES ($1, $2, $3, $4)`,\n [\n migration.version,\n migration.name,\n executionTime,\n migration.checksum,\n ],\n );\n } else {\n await this.db.raw(`DELETE FROM ${this.tableName} WHERE version = $1`, [\n migration.version,\n ]);\n }\n\n return {\n migration,\n success: true,\n executionTime,\n dryRun: false,\n };\n } catch (error) {\n return {\n migration,\n success: false,\n executionTime: Date.now() - startTime,\n error: error instanceof Error ? error : new Error(String(error)),\n dryRun: false,\n };\n }\n }\n\n /**\n * Create a new migration file\n */\n async create(name: string): Promise<string> {\n const { writeFile, mkdir } = await import(\"fs/promises\");\n const { join } = await import(\"path\");\n\n if (!this.config.migrationsDir) {\n throw new Error(\"migrationsDir must be configured to create migrations\");\n }\n\n // Ensure directory exists\n await mkdir(this.config.migrationsDir, { recursive: true });\n\n // Generate version timestamp\n const now = new Date();\n const version = [\n now.getFullYear(),\n String(now.getMonth() + 1).padStart(2, \"0\"),\n String(now.getDate()).padStart(2, \"0\"),\n String(now.getHours()).padStart(2, \"0\"),\n String(now.getMinutes()).padStart(2, \"0\"),\n String(now.getSeconds()).padStart(2, \"0\"),\n ].join(\"\");\n\n const safeName = name.replace(/[^a-zA-Z0-9_]/g, \"_\").toLowerCase();\n const fileName = `${version}_${safeName}.sql`;\n const filePath = join(this.config.migrationsDir, fileName);\n\n const template = `-- Migration: ${name}\n-- Version: ${version}\n-- Created: ${now.toISOString()}\n\n-- UP\n-- Add your forward migration SQL here\n\n\n-- DOWN\n-- Add your rollback migration SQL here\n\n`;\n\n await writeFile(filePath, template, \"utf-8\");\n\n return filePath;\n }\n\n /**\n * Validate migration integrity\n */\n async validate(): Promise<{ valid: boolean; errors: string[] }> {\n const errors: string[] = [];\n const status = await this.status();\n\n if (!this.config.validateChecksums) {\n return { valid: true, errors };\n }\n\n for (const applied of status.applied) {\n const migration = this.migrations.find(\n (m) => m.version === applied.version,\n );\n\n if (!migration) {\n errors.push(\n `Migration ${applied.version} (${applied.name}) was applied but is not registered`,\n );\n continue;\n }\n\n if (applied.checksum && migration.checksum !== applied.checksum) {\n errors.push(\n `Migration ${applied.version} (${applied.name}) has been modified since it was applied`,\n );\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n }\n}\n","/**\n * Migration Helper Functions\n *\n * Utilities for creating migrations in a type-safe and convenient way.\n */\n\nimport type { Migration, IMigrationDatabase } from \"../interfaces/IMigration\";\n\n/**\n * Create a SQL-based migration\n *\n * @example\n * ```typescript\n * export default sqlMigration({\n * version: '20240101000000',\n * name: 'create_users',\n * up: `\n * CREATE TABLE users (\n * id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n * email VARCHAR(255) UNIQUE NOT NULL,\n * created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n * )\n * `,\n * down: `DROP TABLE users`\n * });\n * ```\n */\nexport function sqlMigration(migration: {\n version: string;\n name: string;\n up: string;\n down: string;\n transactional?: boolean;\n}): Migration {\n return {\n version: migration.version,\n name: migration.name,\n up: migration.up,\n down: migration.down,\n transactional: migration.transactional,\n };\n}\n\n/**\n * Create a function-based migration for complex logic\n *\n * @example\n * ```typescript\n * export default createMigration({\n * version: '20240102000000',\n * name: 'seed_initial_data',\n * up: async (db) => {\n * await db.raw(`INSERT INTO users (email) VALUES ('admin@example.com')`);\n * await db.raw(`INSERT INTO settings (key, value) VALUES ('version', '1.0.0')`);\n * },\n * down: async (db) => {\n * await db.raw(`DELETE FROM settings WHERE key = 'version'`);\n * await db.raw(`DELETE FROM users WHERE email = 'admin@example.com'`);\n * }\n * });\n * ```\n */\nexport function createMigration(migration: {\n version: string;\n name: string;\n up: (db: IMigrationDatabase) => Promise<void>;\n down: (db: IMigrationDatabase) => Promise<void>;\n transactional?: boolean;\n}): Migration {\n return {\n version: migration.version,\n name: migration.name,\n up: migration.up,\n down: migration.down,\n transactional: migration.transactional,\n };\n}\n\n/**\n * Type-safe migration definition with inference\n * Useful for defining migrations in separate files\n *\n * @example\n * ```typescript\n * // migrations/20240101000000_create_users.ts\n * export const migration = defineMigration({\n * version: '20240101000000',\n * name: 'create_users',\n * up: 'CREATE TABLE users (...)',\n * down: 'DROP TABLE users'\n * });\n *\n * // Or with functions\n * export const migration = defineMigration({\n * version: '20240102000000',\n * name: 'complex_migration',\n * up: async (db) => { ... },\n * down: async (db) => { ... }\n * });\n * ```\n */\nexport function defineMigration<\n T extends {\n version: string;\n name: string;\n up: string | ((db: IMigrationDatabase) => Promise<void>);\n down: string | ((db: IMigrationDatabase) => Promise<void>);\n transactional?: boolean;\n },\n>(migration: T): Migration {\n return migration;\n}\n\n/**\n * Generate a version timestamp for new migrations\n *\n * @example\n * ```typescript\n * const version = generateVersion(); // '20240315143052'\n * ```\n */\nexport function generateVersion(): string {\n const now = new Date();\n return [\n now.getFullYear(),\n String(now.getMonth() + 1).padStart(2, \"0\"),\n String(now.getDate()).padStart(2, \"0\"),\n String(now.getHours()).padStart(2, \"0\"),\n String(now.getMinutes()).padStart(2, \"0\"),\n String(now.getSeconds()).padStart(2, \"0\"),\n ].join(\"\");\n}\n\n/**\n * Common SQL snippets for migrations\n */\nexport const SQL = {\n /**\n * Create updated_at trigger function (PostgreSQL)\n */\n createUpdatedAtFunction: `\n CREATE OR REPLACE FUNCTION update_updated_at_column()\n RETURNS TRIGGER AS $$\n BEGIN\n NEW.updated_at = CURRENT_TIMESTAMP;\n RETURN NEW;\n END;\n $$ language 'plpgsql';\n `,\n\n /**\n * Create an updated_at trigger for a table\n */\n createUpdatedAtTrigger: (tableName: string) => `\n CREATE TRIGGER update_${tableName}_updated_at\n BEFORE UPDATE ON \"${tableName}\"\n FOR EACH ROW\n EXECUTE FUNCTION update_updated_at_column();\n `,\n\n /**\n * Drop an updated_at trigger\n */\n dropUpdatedAtTrigger: (tableName: string) => `\n DROP TRIGGER IF EXISTS update_${tableName}_updated_at ON \"${tableName}\";\n `,\n\n /**\n * Standard timestamp columns\n */\n timestampColumns: `\n created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,\n updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL\n `,\n\n /**\n * UUID primary key with default\n */\n uuidPrimaryKey: `id UUID PRIMARY KEY DEFAULT gen_random_uuid()`,\n\n /**\n * Enable UUID extension\n */\n enableUuidExtension: `CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"`,\n\n /**\n * Enable pgcrypto for gen_random_uuid()\n */\n enablePgCrypto: `CREATE EXTENSION IF NOT EXISTS \"pgcrypto\"`,\n};\n","/**\n * Enterprise SSO and Multi-Tenancy Migrations\n *\n * These migrations set up the tables required for:\n * - OIDC/SSO configuration storage\n * - Domain verification\n * - Multi-tenant management\n * - Member management\n * - Usage tracking\n *\n * @example\n * ```typescript\n * import { Migrator, enterpriseMigrations } from '@digilogiclabs/platform-core';\n *\n * const migrator = new Migrator({\n * database: db,\n * migrations: [...enterpriseMigrations, ...yourMigrations],\n * });\n *\n * await migrator.up();\n * ```\n */\n\nimport type { Migration } from \"../interfaces/IMigration\";\n\n/**\n * SSO OIDC Configuration Table Migration\n */\nexport const createSsoOidcConfigsTable: Migration = {\n version: \"20241217_001\",\n name: \"create_sso_oidc_configs_table\",\n up: `\n CREATE TABLE IF NOT EXISTS sso_oidc_configs (\n tenant_id VARCHAR(255) PRIMARY KEY,\n issuer_url VARCHAR(500) NOT NULL,\n client_id VARCHAR(255) NOT NULL,\n client_secret VARCHAR(500) NOT NULL,\n authorization_endpoint VARCHAR(500),\n token_endpoint VARCHAR(500),\n userinfo_endpoint VARCHAR(500),\n jwks_uri VARCHAR(500),\n scopes JSONB DEFAULT '[\"openid\", \"profile\", \"email\"]',\n claim_mapping JSONB DEFAULT '{}',\n domains JSONB DEFAULT '[]',\n jit_provisioning_enabled BOOLEAN DEFAULT false,\n default_role VARCHAR(50) DEFAULT 'member',\n pkce_method VARCHAR(10) DEFAULT 'S256',\n metadata JSONB DEFAULT '{}',\n created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),\n updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n );\n\n CREATE INDEX IF NOT EXISTS idx_sso_oidc_domains ON sso_oidc_configs USING GIN (domains);\n `,\n down: \"DROP TABLE IF EXISTS sso_oidc_configs CASCADE\",\n};\n\n/**\n * Domain Verifications Table Migration\n */\nexport const createDomainVerificationsTable: Migration = {\n version: \"20241217_002\",\n name: \"create_domain_verifications_table\",\n up: `\n CREATE TABLE IF NOT EXISTS domain_verifications (\n id SERIAL PRIMARY KEY,\n tenant_id VARCHAR(255) NOT NULL,\n domain VARCHAR(255) NOT NULL,\n verification_token VARCHAR(255) NOT NULL,\n verification_method VARCHAR(50) DEFAULT 'dns_txt',\n status VARCHAR(50) DEFAULT 'pending',\n created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),\n expires_at TIMESTAMP WITH TIME ZONE,\n verified_at TIMESTAMP WITH TIME ZONE,\n UNIQUE(tenant_id, domain)\n );\n\n CREATE INDEX IF NOT EXISTS idx_domain_verifications_status ON domain_verifications(status);\n `,\n down: \"DROP TABLE IF EXISTS domain_verifications CASCADE\",\n};\n\n/**\n * Verified Domains Table Migration\n */\nexport const createVerifiedDomainsTable: Migration = {\n version: \"20241217_003\",\n name: \"create_verified_domains_table\",\n up: `\n CREATE TABLE IF NOT EXISTS verified_domains (\n id SERIAL PRIMARY KEY,\n tenant_id VARCHAR(255) NOT NULL,\n domain VARCHAR(255) NOT NULL,\n verification_token VARCHAR(255),\n verified_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),\n UNIQUE(tenant_id, domain)\n );\n\n CREATE INDEX IF NOT EXISTS idx_verified_domains_domain ON verified_domains(domain);\n `,\n down: \"DROP TABLE IF EXISTS verified_domains CASCADE\",\n};\n\n/**\n * Tenants Table Migration\n */\nexport const createTenantsTable: Migration = {\n version: \"20241217_004\",\n name: \"create_tenants_table\",\n up: `\n CREATE TABLE IF NOT EXISTS tenants (\n id VARCHAR(255) PRIMARY KEY,\n slug VARCHAR(255) UNIQUE NOT NULL,\n name VARCHAR(255) NOT NULL,\n status VARCHAR(50) NOT NULL DEFAULT 'active',\n plan VARCHAR(100) NOT NULL,\n custom_domain VARCHAR(255),\n settings JSONB NOT NULL DEFAULT '{}',\n features JSONB NOT NULL DEFAULT '{}',\n quotas JSONB NOT NULL DEFAULT '{}',\n metadata JSONB NOT NULL DEFAULT '{}',\n isolation_model VARCHAR(50) NOT NULL DEFAULT 'row_level',\n schema_name VARCHAR(255),\n created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n deleted_at TIMESTAMP WITH TIME ZONE\n );\n\n CREATE INDEX IF NOT EXISTS idx_tenants_slug ON tenants(slug);\n CREATE INDEX IF NOT EXISTS idx_tenants_custom_domain ON tenants(custom_domain);\n CREATE INDEX IF NOT EXISTS idx_tenants_status ON tenants(status);\n CREATE INDEX IF NOT EXISTS idx_tenants_plan ON tenants(plan);\n `,\n down: \"DROP TABLE IF EXISTS tenants CASCADE\",\n};\n\n/**\n * Tenant Members Table Migration\n */\nexport const createTenantMembersTable: Migration = {\n version: \"20241217_005\",\n name: \"create_tenant_members_table\",\n up: `\n CREATE TABLE IF NOT EXISTS tenant_members (\n id VARCHAR(255) PRIMARY KEY,\n tenant_id VARCHAR(255) NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,\n user_id VARCHAR(255) NOT NULL,\n role VARCHAR(50) NOT NULL DEFAULT 'member',\n permissions JSONB DEFAULT '[]',\n status VARCHAR(50) NOT NULL DEFAULT 'active',\n invited_by VARCHAR(255),\n joined_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n UNIQUE(tenant_id, user_id)\n );\n\n CREATE INDEX IF NOT EXISTS idx_tenant_members_user_id ON tenant_members(user_id);\n CREATE INDEX IF NOT EXISTS idx_tenant_members_status ON tenant_members(status);\n `,\n down: \"DROP TABLE IF EXISTS tenant_members CASCADE\",\n};\n\n/**\n * Tenant Invitations Table Migration\n */\nexport const createTenantInvitationsTable: Migration = {\n version: \"20241217_006\",\n name: \"create_tenant_invitations_table\",\n up: `\n CREATE TABLE IF NOT EXISTS tenant_invitations (\n id VARCHAR(255) PRIMARY KEY,\n tenant_id VARCHAR(255) NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,\n email VARCHAR(255) NOT NULL,\n role VARCHAR(50) NOT NULL DEFAULT 'member',\n permissions JSONB DEFAULT '[]',\n token VARCHAR(255) UNIQUE NOT NULL,\n invited_by VARCHAR(255) NOT NULL,\n status VARCHAR(50) NOT NULL DEFAULT 'pending',\n message TEXT,\n created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMP WITH TIME ZONE NOT NULL,\n accepted_at TIMESTAMP WITH TIME ZONE\n );\n\n CREATE INDEX IF NOT EXISTS idx_tenant_invitations_email ON tenant_invitations(email);\n CREATE INDEX IF NOT EXISTS idx_tenant_invitations_token ON tenant_invitations(token);\n CREATE INDEX IF NOT EXISTS idx_tenant_invitations_status ON tenant_invitations(status);\n `,\n down: \"DROP TABLE IF EXISTS tenant_invitations CASCADE\",\n};\n\n/**\n * Tenant Usage Table Migration\n */\nexport const createTenantUsageTable: Migration = {\n version: \"20241217_007\",\n name: \"create_tenant_usage_table\",\n up: `\n CREATE TABLE IF NOT EXISTS tenant_usage (\n tenant_id VARCHAR(255) PRIMARY KEY REFERENCES tenants(id) ON DELETE CASCADE,\n user_count INTEGER NOT NULL DEFAULT 0,\n storage_used_bytes BIGINT NOT NULL DEFAULT 0,\n api_requests_this_month BIGINT NOT NULL DEFAULT 0,\n project_count INTEGER NOT NULL DEFAULT 0,\n custom JSONB DEFAULT '{}',\n updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()\n );\n `,\n down: \"DROP TABLE IF EXISTS tenant_usage CASCADE\",\n};\n\n/**\n * SSO Sessions Table Migration\n */\nexport const createSsoSessionsTable: Migration = {\n version: \"20241217_008\",\n name: \"create_sso_sessions_table\",\n up: `\n CREATE TABLE IF NOT EXISTS sso_sessions (\n id VARCHAR(255) PRIMARY KEY,\n user_id VARCHAR(255) NOT NULL,\n tenant_id VARCHAR(255) NOT NULL,\n sso_provider VARCHAR(50) NOT NULL,\n idp_session_id VARCHAR(255),\n saml_session_index VARCHAR(255),\n groups JSONB DEFAULT '[]',\n access_token TEXT,\n refresh_token TEXT,\n id_token TEXT,\n expires_at TIMESTAMP WITH TIME ZONE NOT NULL,\n created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()\n );\n\n CREATE INDEX IF NOT EXISTS idx_sso_sessions_user_id ON sso_sessions(user_id);\n CREATE INDEX IF NOT EXISTS idx_sso_sessions_tenant_id ON sso_sessions(tenant_id);\n CREATE INDEX IF NOT EXISTS idx_sso_sessions_expires ON sso_sessions(expires_at);\n `,\n down: \"DROP TABLE IF EXISTS sso_sessions CASCADE\",\n};\n\n/**\n * All enterprise migrations in order\n */\nexport const enterpriseMigrations: Migration[] = [\n createSsoOidcConfigsTable,\n createDomainVerificationsTable,\n createVerifiedDomainsTable,\n createTenantsTable,\n createTenantMembersTable,\n createTenantInvitationsTable,\n createTenantUsageTable,\n createSsoSessionsTable,\n];\n\n/**\n * Get enterprise migrations for a specific feature\n */\nexport function getEnterpriseMigrations(features: {\n sso?: boolean;\n tenancy?: boolean;\n}): Migration[] {\n const migrations: Migration[] = [];\n\n if (features.sso) {\n migrations.push(\n createSsoOidcConfigsTable,\n createDomainVerificationsTable,\n createVerifiedDomainsTable,\n createSsoSessionsTable,\n );\n }\n\n if (features.tenancy) {\n migrations.push(\n createTenantsTable,\n createTenantMembersTable,\n createTenantInvitationsTable,\n createTenantUsageTable,\n );\n }\n\n return migrations;\n}\n"],"mappings":"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","names":["module"]}
1
+ {"version":3,"sources":["../../src/migrations/index.ts","../../src/migrations/Migrator.ts","../../src/migrations/helpers.ts","../../src/migrations/enterprise.ts"],"sourcesContent":["/**\n * Database Migrations\n *\n * Enterprise-grade migration system for managing database schema changes.\n *\n * @example\n * ```typescript\n * import { Migrator } from '@digilogiclabs/platform-core';\n * import { PostgresDatabase } from '@digilogiclabs/platform-core';\n *\n * // Create database connection\n * const db = await PostgresDatabase.fromEnv();\n *\n * // Create migrator\n * const migrator = new Migrator(db, {\n * tableName: '_migrations',\n * migrationsDir: './migrations'\n * });\n *\n * // Add migrations programmatically\n * migrator.addMigrations([\n * {\n * version: '20240101000000',\n * name: 'create_users_table',\n * up: `CREATE TABLE users (id UUID PRIMARY KEY, email VARCHAR(255) UNIQUE)`,\n * down: `DROP TABLE users`\n * }\n * ]);\n *\n * // Or load from directory\n * await migrator.loadMigrations('./migrations');\n *\n * // Check status\n * const status = await migrator.status();\n * console.log('Applied:', status.applied.length);\n * console.log('Pending:', status.pending.length);\n *\n * // Run pending migrations\n * const results = await migrator.up();\n * for (const result of results) {\n * if (result.success) {\n * console.log(`✓ ${result.migration.name} (${result.executionTime}ms)`);\n * } else {\n * console.error(`✗ ${result.migration.name}: ${result.error?.message}`);\n * }\n * }\n *\n * // Rollback last migration\n * await migrator.down();\n *\n * // Rollback last 3 migrations\n * await migrator.down({ steps: 3 });\n *\n * // Dry run (preview without applying)\n * await migrator.up({ dryRun: true });\n * ```\n */\n\nexport { Migrator } from \"./Migrator\";\nexport type {\n Migration,\n MigrationRecord,\n MigrationResult,\n MigrationStatus,\n MigratorConfig,\n IMigrator,\n IMigrationDatabase,\n} from \"../interfaces/IMigration\";\n\n// Re-export common migration patterns\nexport { createMigration, defineMigration, sqlMigration } from \"./helpers\";\n\n// Enterprise migrations for SSO and multi-tenancy\nexport {\n enterpriseMigrations,\n getEnterpriseMigrations,\n createSsoOidcConfigsTable,\n createDomainVerificationsTable,\n createVerifiedDomainsTable,\n createTenantsTable,\n createTenantMembersTable,\n createTenantInvitationsTable,\n createTenantUsageTable,\n createSsoSessionsTable,\n} from \"./enterprise\";\n","/**\n * Database Migrator\n *\n * Enterprise-grade database migration system supporting:\n * - PostgreSQL (primary)\n * - Memory database (for testing)\n * - Transaction-safe migrations\n * - Concurrent execution locking\n * - Checksum validation\n * - Dry-run mode\n */\n\nimport type {\n IMigrator,\n Migration,\n MigrationRecord,\n MigrationResult,\n MigrationStatus,\n MigratorConfig,\n IMigrationDatabase,\n} from \"../interfaces/IMigration\";\n\nconst DEFAULT_CONFIG: Required<Omit<MigratorConfig, \"migrationsDir\">> & {\n migrationsDir?: string;\n} = {\n tableName: \"_migrations\",\n schema: \"public\",\n lockTimeout: 60,\n validateChecksums: true,\n migrationsDir: undefined,\n};\n\n/**\n * Generate a checksum for migration content\n */\nfunction generateChecksum(content: string): string {\n // Simple hash for checksum - in production you might use crypto\n let hash = 0;\n for (let i = 0; i < content.length; i++) {\n const char = content.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash).toString(16).padStart(8, \"0\");\n}\n\n/**\n * Sort migrations by version\n */\nfunction sortMigrations(migrations: Migration[]): Migration[] {\n return [...migrations].sort((a, b) => a.version.localeCompare(b.version));\n}\n\nexport class Migrator implements IMigrator {\n private db: IMigrationDatabase;\n private config: Required<Omit<MigratorConfig, \"migrationsDir\">> & {\n migrationsDir?: string;\n };\n private migrations: Migration[] = [];\n private initialized = false;\n private locked = false;\n\n constructor(db: IMigrationDatabase, config: MigratorConfig = {}) {\n this.db = db;\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Get the fully qualified migration table name\n */\n private get tableName(): string {\n return `\"${this.config.schema}\".\"${this.config.tableName}\"`;\n }\n\n /**\n * Get the lock table name\n */\n private get lockTableName(): string {\n return `\"${this.config.schema}\".\"${this.config.tableName}_lock\"`;\n }\n\n /**\n * Initialize migration tracking tables\n */\n private async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Create schema if not exists\n await this.db.raw(`CREATE SCHEMA IF NOT EXISTS \"${this.config.schema}\"`);\n\n // Create migrations tracking table\n await this.db.raw(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n version VARCHAR(255) PRIMARY KEY,\n name VARCHAR(255) NOT NULL,\n applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\n execution_time INTEGER NOT NULL,\n checksum VARCHAR(64)\n )\n `);\n\n // Create lock table for concurrent safety\n await this.db.raw(`\n CREATE TABLE IF NOT EXISTS ${this.lockTableName} (\n id INTEGER PRIMARY KEY DEFAULT 1,\n locked_at TIMESTAMP WITH TIME ZONE,\n locked_by VARCHAR(255),\n CONSTRAINT single_row CHECK (id = 1)\n )\n `);\n\n // Insert initial lock row if not exists\n await this.db.raw(`\n INSERT INTO ${this.lockTableName} (id, locked_at, locked_by)\n VALUES (1, NULL, NULL)\n ON CONFLICT (id) DO NOTHING\n `);\n\n this.initialized = true;\n }\n\n /**\n * Acquire migration lock\n */\n async lock(): Promise<boolean> {\n await this.initialize();\n\n const lockId = `${process.pid}-${Date.now()}`;\n const lockTimeout = new Date(Date.now() - this.config.lockTimeout * 1000);\n\n // Try to acquire lock (only if not locked or lock expired)\n const result = await this.db.raw<{ locked_at: Date | null }>(\n `\n UPDATE ${this.lockTableName}\n SET locked_at = CURRENT_TIMESTAMP, locked_by = $1\n WHERE id = 1 AND (locked_at IS NULL OR locked_at < $2)\n RETURNING locked_at\n `,\n [lockId, lockTimeout],\n );\n\n if (result.data.length > 0) {\n this.locked = true;\n return true;\n }\n\n return false;\n }\n\n /**\n * Release migration lock\n */\n async unlock(): Promise<void> {\n if (!this.locked) return;\n\n await this.db.raw(\n `\n UPDATE ${this.lockTableName}\n SET locked_at = NULL, locked_by = NULL\n WHERE id = 1\n `,\n );\n\n this.locked = false;\n }\n\n /**\n * Add migrations to the migrator\n */\n addMigrations(migrations: Migration[]): void {\n for (const migration of migrations) {\n // Generate checksum if not provided\n if (!migration.checksum) {\n const content =\n typeof migration.up === \"string\"\n ? migration.up\n : migration.up.toString();\n migration.checksum = generateChecksum(content);\n }\n\n // Avoid duplicates\n if (!this.migrations.find((m) => m.version === migration.version)) {\n this.migrations.push(migration);\n }\n }\n }\n\n /**\n * Load migrations from directory\n * Expects files named: {version}_{name}.ts or {version}_{name}.sql\n */\n async loadMigrations(dir: string): Promise<void> {\n // Dynamic import for Node.js file system\n const { readdir, readFile } = await import(\"fs/promises\");\n const { join } = await import(\"path\");\n\n const files = await readdir(dir);\n const migrationFiles = files.filter((f) =>\n f.match(/^\\d+_.*\\.(ts|js|sql)$/),\n );\n\n for (const file of migrationFiles) {\n const filePath = join(dir, file);\n const [version, ...nameParts] = file\n .replace(/\\.(ts|js|sql)$/, \"\")\n .split(\"_\");\n const name = nameParts.join(\"_\");\n\n if (file.endsWith(\".sql\")) {\n // SQL file - parse UP and DOWN sections\n const content = await readFile(filePath, \"utf-8\");\n const [up, down] = this.parseSqlMigration(content);\n\n this.addMigrations([\n {\n version: version!,\n name,\n up,\n down,\n },\n ]);\n } else {\n // TypeScript/JavaScript file - import the module\n const module = await import(filePath);\n this.addMigrations([\n {\n version: version!,\n name,\n up: module.up,\n down: module.down,\n transactional: module.transactional,\n },\n ]);\n }\n }\n }\n\n /**\n * Parse SQL migration file with -- UP and -- DOWN sections\n */\n private parseSqlMigration(content: string): [string, string] {\n const upMatch = content.match(/--\\s*UP\\s*\\n([\\s\\S]*?)(?=--\\s*DOWN|$)/i);\n const downMatch = content.match(/--\\s*DOWN\\s*\\n([\\s\\S]*?)$/i);\n\n const up = upMatch?.[1]?.trim() ?? content.trim();\n const down = downMatch?.[1]?.trim() ?? \"\";\n\n return [up, down];\n }\n\n /**\n * Get current migration status\n */\n async status(): Promise<MigrationStatus> {\n await this.initialize();\n\n // Get applied migrations\n const result = await this.db.raw<{\n version: string;\n name: string;\n applied_at: Date;\n execution_time: number;\n checksum: string | null;\n }>(\n `SELECT version, name, applied_at, execution_time, checksum\n FROM ${this.tableName}\n ORDER BY version ASC`,\n );\n\n const applied: MigrationRecord[] = result.data.map((row) => ({\n version: row.version,\n name: row.name,\n appliedAt: new Date(row.applied_at),\n executionTime: row.execution_time,\n checksum: row.checksum ?? undefined,\n }));\n\n const appliedVersions = new Set(applied.map((m) => m.version));\n const sortedMigrations = sortMigrations(this.migrations);\n\n const pending = sortedMigrations.filter(\n (m) => !appliedVersions.has(m.version),\n );\n\n return {\n applied,\n pending,\n currentVersion:\n applied.length > 0 ? applied[applied.length - 1]!.version : null,\n latestVersion:\n sortedMigrations.length > 0\n ? sortedMigrations[sortedMigrations.length - 1]!.version\n : null,\n };\n }\n\n /**\n * Run all pending migrations (or up to a specific version)\n */\n async up(\n options: { dryRun?: boolean; to?: string } = {},\n ): Promise<MigrationResult[]> {\n const { dryRun = false, to } = options;\n const results: MigrationResult[] = [];\n\n if (!dryRun) {\n const acquired = await this.lock();\n if (!acquired) {\n throw new Error(\n \"Could not acquire migration lock. Another migration may be in progress.\",\n );\n }\n }\n\n try {\n const status = await this.status();\n let pending = status.pending;\n\n // Filter to specific version if requested\n if (to) {\n const targetIndex = pending.findIndex((m) => m.version === to);\n if (targetIndex === -1) {\n throw new Error(\n `Target version ${to} not found in pending migrations`,\n );\n }\n pending = pending.slice(0, targetIndex + 1);\n }\n\n for (const migration of pending) {\n const result = await this.runMigration(migration, \"up\", dryRun);\n results.push(result);\n\n if (!result.success) {\n break; // Stop on first failure\n }\n }\n } finally {\n if (!dryRun) {\n await this.unlock();\n }\n }\n\n return results;\n }\n\n /**\n * Rollback migrations\n */\n async down(\n options: { dryRun?: boolean; steps?: number } = {},\n ): Promise<MigrationResult[]> {\n const { dryRun = false, steps = 1 } = options;\n const results: MigrationResult[] = [];\n\n if (!dryRun) {\n const acquired = await this.lock();\n if (!acquired) {\n throw new Error(\n \"Could not acquire migration lock. Another migration may be in progress.\",\n );\n }\n }\n\n try {\n const status = await this.status();\n\n // Get the last N applied migrations (in reverse order)\n const toRollback = status.applied.slice(-steps).reverse();\n\n for (const record of toRollback) {\n const migration = this.migrations.find(\n (m) => m.version === record.version,\n );\n if (!migration) {\n throw new Error(\n `Migration ${record.version} was applied but is not registered. Cannot rollback.`,\n );\n }\n\n const result = await this.runMigration(migration, \"down\", dryRun);\n results.push(result);\n\n if (!result.success) {\n break;\n }\n }\n } finally {\n if (!dryRun) {\n await this.unlock();\n }\n }\n\n return results;\n }\n\n /**\n * Rollback all migrations and re-run them\n */\n async reset(options: { dryRun?: boolean } = {}): Promise<MigrationResult[]> {\n const status = await this.status();\n const downResults = await this.down({\n dryRun: options.dryRun,\n steps: status.applied.length,\n });\n\n // Only proceed with up if all downs succeeded\n if (downResults.every((r) => r.success)) {\n const upResults = await this.up({ dryRun: options.dryRun });\n return [...downResults, ...upResults];\n }\n\n return downResults;\n }\n\n /**\n * Migrate to a specific version\n */\n async goto(\n version: string,\n options: { dryRun?: boolean } = {},\n ): Promise<MigrationResult[]> {\n const status = await this.status();\n const currentVersion = status.currentVersion;\n const results: MigrationResult[] = [];\n\n // If already at target, nothing to do\n if (currentVersion === version) {\n return results;\n }\n\n // Determine direction\n const isUp = !currentVersion || version > currentVersion;\n\n if (isUp) {\n return this.up({ ...options, to: version });\n } else {\n // Find how many steps down we need to go\n const currentIndex = status.applied.findIndex(\n (m) => m.version === currentVersion,\n );\n const targetIndex = status.applied.findIndex(\n (m) => m.version === version,\n );\n\n if (targetIndex === -1) {\n throw new Error(\n `Target version ${version} not found in applied migrations`,\n );\n }\n\n const steps = currentIndex - targetIndex;\n return this.down({ ...options, steps });\n }\n }\n\n /**\n * Run a single migration\n */\n private async runMigration(\n migration: Migration,\n direction: \"up\" | \"down\",\n dryRun: boolean,\n ): Promise<MigrationResult> {\n const startTime = Date.now();\n const action = direction === \"up\" ? migration.up : migration.down;\n\n try {\n if (dryRun) {\n // Just simulate - don't actually run\n return {\n migration,\n success: true,\n executionTime: 0,\n dryRun: true,\n };\n }\n\n const transactional = migration.transactional !== false;\n const runAction = async (db: IMigrationDatabase) => {\n if (typeof action === \"string\") {\n const result = await db.raw(action);\n if (result.error) {\n throw result.error;\n }\n } else {\n await action(db);\n }\n };\n\n if (transactional) {\n await this.db.transaction(async (tx) => {\n await runAction(tx);\n });\n } else {\n await runAction(this.db);\n }\n\n const executionTime = Date.now() - startTime;\n\n // Update tracking table\n if (direction === \"up\") {\n await this.db.raw(\n `INSERT INTO ${this.tableName} (version, name, execution_time, checksum)\n VALUES ($1, $2, $3, $4)`,\n [\n migration.version,\n migration.name,\n executionTime,\n migration.checksum,\n ],\n );\n } else {\n await this.db.raw(`DELETE FROM ${this.tableName} WHERE version = $1`, [\n migration.version,\n ]);\n }\n\n return {\n migration,\n success: true,\n executionTime,\n dryRun: false,\n };\n } catch (error) {\n return {\n migration,\n success: false,\n executionTime: Date.now() - startTime,\n error: error instanceof Error ? error : new Error(String(error)),\n dryRun: false,\n };\n }\n }\n\n /**\n * Create a new migration file\n */\n async create(name: string): Promise<string> {\n const { writeFile, mkdir } = await import(\"fs/promises\");\n const { join } = await import(\"path\");\n\n if (!this.config.migrationsDir) {\n throw new Error(\"migrationsDir must be configured to create migrations\");\n }\n\n // Ensure directory exists\n await mkdir(this.config.migrationsDir, { recursive: true });\n\n // Generate version timestamp\n const now = new Date();\n const version = [\n now.getFullYear(),\n String(now.getMonth() + 1).padStart(2, \"0\"),\n String(now.getDate()).padStart(2, \"0\"),\n String(now.getHours()).padStart(2, \"0\"),\n String(now.getMinutes()).padStart(2, \"0\"),\n String(now.getSeconds()).padStart(2, \"0\"),\n ].join(\"\");\n\n const safeName = name.replace(/[^a-zA-Z0-9_]/g, \"_\").toLowerCase();\n const fileName = `${version}_${safeName}.sql`;\n const filePath = join(this.config.migrationsDir, fileName);\n\n const template = `-- Migration: ${name}\n-- Version: ${version}\n-- Created: ${now.toISOString()}\n\n-- UP\n-- Add your forward migration SQL here\n\n\n-- DOWN\n-- Add your rollback migration SQL here\n\n`;\n\n await writeFile(filePath, template, \"utf-8\");\n\n return filePath;\n }\n\n /**\n * Validate migration integrity\n */\n async validate(): Promise<{ valid: boolean; errors: string[] }> {\n const errors: string[] = [];\n const status = await this.status();\n\n if (!this.config.validateChecksums) {\n return { valid: true, errors };\n }\n\n for (const applied of status.applied) {\n const migration = this.migrations.find(\n (m) => m.version === applied.version,\n );\n\n if (!migration) {\n errors.push(\n `Migration ${applied.version} (${applied.name}) was applied but is not registered`,\n );\n continue;\n }\n\n if (applied.checksum && migration.checksum !== applied.checksum) {\n errors.push(\n `Migration ${applied.version} (${applied.name}) has been modified since it was applied`,\n );\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n }\n}\n","/**\n * Migration Helper Functions\n *\n * Utilities for creating migrations in a type-safe and convenient way.\n */\n\nimport type { Migration, IMigrationDatabase } from \"../interfaces/IMigration\";\n\n/**\n * Create a SQL-based migration\n *\n * @example\n * ```typescript\n * export default sqlMigration({\n * version: '20240101000000',\n * name: 'create_users',\n * up: `\n * CREATE TABLE users (\n * id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n * email VARCHAR(255) UNIQUE NOT NULL,\n * created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n * )\n * `,\n * down: `DROP TABLE users`\n * });\n * ```\n */\nexport function sqlMigration(migration: {\n version: string;\n name: string;\n up: string;\n down: string;\n transactional?: boolean;\n}): Migration {\n return {\n version: migration.version,\n name: migration.name,\n up: migration.up,\n down: migration.down,\n transactional: migration.transactional,\n };\n}\n\n/**\n * Create a function-based migration for complex logic\n *\n * @example\n * ```typescript\n * export default createMigration({\n * version: '20240102000000',\n * name: 'seed_initial_data',\n * up: async (db) => {\n * await db.raw(`INSERT INTO users (email) VALUES ('admin@example.com')`);\n * await db.raw(`INSERT INTO settings (key, value) VALUES ('version', '1.0.0')`);\n * },\n * down: async (db) => {\n * await db.raw(`DELETE FROM settings WHERE key = 'version'`);\n * await db.raw(`DELETE FROM users WHERE email = 'admin@example.com'`);\n * }\n * });\n * ```\n */\nexport function createMigration(migration: {\n version: string;\n name: string;\n up: (db: IMigrationDatabase) => Promise<void>;\n down: (db: IMigrationDatabase) => Promise<void>;\n transactional?: boolean;\n}): Migration {\n return {\n version: migration.version,\n name: migration.name,\n up: migration.up,\n down: migration.down,\n transactional: migration.transactional,\n };\n}\n\n/**\n * Type-safe migration definition with inference\n * Useful for defining migrations in separate files\n *\n * @example\n * ```typescript\n * // migrations/20240101000000_create_users.ts\n * export const migration = defineMigration({\n * version: '20240101000000',\n * name: 'create_users',\n * up: 'CREATE TABLE users (...)',\n * down: 'DROP TABLE users'\n * });\n *\n * // Or with functions\n * export const migration = defineMigration({\n * version: '20240102000000',\n * name: 'complex_migration',\n * up: async (db) => { ... },\n * down: async (db) => { ... }\n * });\n * ```\n */\nexport function defineMigration<\n T extends {\n version: string;\n name: string;\n up: string | ((db: IMigrationDatabase) => Promise<void>);\n down: string | ((db: IMigrationDatabase) => Promise<void>);\n transactional?: boolean;\n },\n>(migration: T): Migration {\n return migration;\n}\n\n/**\n * Generate a version timestamp for new migrations\n *\n * @example\n * ```typescript\n * const version = generateVersion(); // '20240315143052'\n * ```\n */\nexport function generateVersion(): string {\n const now = new Date();\n return [\n now.getFullYear(),\n String(now.getMonth() + 1).padStart(2, \"0\"),\n String(now.getDate()).padStart(2, \"0\"),\n String(now.getHours()).padStart(2, \"0\"),\n String(now.getMinutes()).padStart(2, \"0\"),\n String(now.getSeconds()).padStart(2, \"0\"),\n ].join(\"\");\n}\n\n/**\n * Common SQL snippets for migrations\n */\nexport const SQL = {\n /**\n * Create updated_at trigger function (PostgreSQL)\n */\n createUpdatedAtFunction: `\n CREATE OR REPLACE FUNCTION update_updated_at_column()\n RETURNS TRIGGER AS $$\n BEGIN\n NEW.updated_at = CURRENT_TIMESTAMP;\n RETURN NEW;\n END;\n $$ language 'plpgsql';\n `,\n\n /**\n * Create an updated_at trigger for a table\n */\n createUpdatedAtTrigger: (tableName: string) => `\n CREATE TRIGGER update_${tableName}_updated_at\n BEFORE UPDATE ON \"${tableName}\"\n FOR EACH ROW\n EXECUTE FUNCTION update_updated_at_column();\n `,\n\n /**\n * Drop an updated_at trigger\n */\n dropUpdatedAtTrigger: (tableName: string) => `\n DROP TRIGGER IF EXISTS update_${tableName}_updated_at ON \"${tableName}\";\n `,\n\n /**\n * Standard timestamp columns\n */\n timestampColumns: `\n created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,\n updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL\n `,\n\n /**\n * UUID primary key with default\n */\n uuidPrimaryKey: `id UUID PRIMARY KEY DEFAULT gen_random_uuid()`,\n\n /**\n * Enable UUID extension\n */\n enableUuidExtension: `CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"`,\n\n /**\n * Enable pgcrypto for gen_random_uuid()\n */\n enablePgCrypto: `CREATE EXTENSION IF NOT EXISTS \"pgcrypto\"`,\n};\n","/**\n * Enterprise SSO and Multi-Tenancy Migrations\n *\n * These migrations set up the tables required for:\n * - OIDC/SSO configuration storage\n * - Domain verification\n * - Multi-tenant management\n * - Member management\n * - Usage tracking\n *\n * @example\n * ```typescript\n * import { Migrator, enterpriseMigrations } from '@digilogiclabs/platform-core';\n *\n * const migrator = new Migrator({\n * database: db,\n * migrations: [...enterpriseMigrations, ...yourMigrations],\n * });\n *\n * await migrator.up();\n * ```\n */\n\nimport type { Migration } from \"../interfaces/IMigration\";\n\n/**\n * SSO OIDC Configuration Table Migration\n */\nexport const createSsoOidcConfigsTable: Migration = {\n version: \"20241217_001\",\n name: \"create_sso_oidc_configs_table\",\n up: `\n CREATE TABLE IF NOT EXISTS sso_oidc_configs (\n tenant_id VARCHAR(255) PRIMARY KEY,\n issuer_url VARCHAR(500) NOT NULL,\n client_id VARCHAR(255) NOT NULL,\n client_secret VARCHAR(500) NOT NULL,\n authorization_endpoint VARCHAR(500),\n token_endpoint VARCHAR(500),\n userinfo_endpoint VARCHAR(500),\n jwks_uri VARCHAR(500),\n scopes JSONB DEFAULT '[\"openid\", \"profile\", \"email\"]',\n claim_mapping JSONB DEFAULT '{}',\n domains JSONB DEFAULT '[]',\n jit_provisioning_enabled BOOLEAN DEFAULT false,\n default_role VARCHAR(50) DEFAULT 'member',\n pkce_method VARCHAR(10) DEFAULT 'S256',\n metadata JSONB DEFAULT '{}',\n created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),\n updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\n );\n\n CREATE INDEX IF NOT EXISTS idx_sso_oidc_domains ON sso_oidc_configs USING GIN (domains);\n `,\n down: \"DROP TABLE IF EXISTS sso_oidc_configs CASCADE\",\n};\n\n/**\n * Domain Verifications Table Migration\n */\nexport const createDomainVerificationsTable: Migration = {\n version: \"20241217_002\",\n name: \"create_domain_verifications_table\",\n up: `\n CREATE TABLE IF NOT EXISTS domain_verifications (\n id SERIAL PRIMARY KEY,\n tenant_id VARCHAR(255) NOT NULL,\n domain VARCHAR(255) NOT NULL,\n verification_token VARCHAR(255) NOT NULL,\n verification_method VARCHAR(50) DEFAULT 'dns_txt',\n status VARCHAR(50) DEFAULT 'pending',\n created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),\n expires_at TIMESTAMP WITH TIME ZONE,\n verified_at TIMESTAMP WITH TIME ZONE,\n UNIQUE(tenant_id, domain)\n );\n\n CREATE INDEX IF NOT EXISTS idx_domain_verifications_status ON domain_verifications(status);\n `,\n down: \"DROP TABLE IF EXISTS domain_verifications CASCADE\",\n};\n\n/**\n * Verified Domains Table Migration\n */\nexport const createVerifiedDomainsTable: Migration = {\n version: \"20241217_003\",\n name: \"create_verified_domains_table\",\n up: `\n CREATE TABLE IF NOT EXISTS verified_domains (\n id SERIAL PRIMARY KEY,\n tenant_id VARCHAR(255) NOT NULL,\n domain VARCHAR(255) NOT NULL,\n verification_token VARCHAR(255),\n verified_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),\n UNIQUE(tenant_id, domain)\n );\n\n CREATE INDEX IF NOT EXISTS idx_verified_domains_domain ON verified_domains(domain);\n `,\n down: \"DROP TABLE IF EXISTS verified_domains CASCADE\",\n};\n\n/**\n * Tenants Table Migration\n */\nexport const createTenantsTable: Migration = {\n version: \"20241217_004\",\n name: \"create_tenants_table\",\n up: `\n CREATE TABLE IF NOT EXISTS tenants (\n id VARCHAR(255) PRIMARY KEY,\n slug VARCHAR(255) UNIQUE NOT NULL,\n name VARCHAR(255) NOT NULL,\n status VARCHAR(50) NOT NULL DEFAULT 'active',\n plan VARCHAR(100) NOT NULL,\n custom_domain VARCHAR(255),\n settings JSONB NOT NULL DEFAULT '{}',\n features JSONB NOT NULL DEFAULT '{}',\n quotas JSONB NOT NULL DEFAULT '{}',\n metadata JSONB NOT NULL DEFAULT '{}',\n isolation_model VARCHAR(50) NOT NULL DEFAULT 'row_level',\n schema_name VARCHAR(255),\n created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n deleted_at TIMESTAMP WITH TIME ZONE\n );\n\n CREATE INDEX IF NOT EXISTS idx_tenants_slug ON tenants(slug);\n CREATE INDEX IF NOT EXISTS idx_tenants_custom_domain ON tenants(custom_domain);\n CREATE INDEX IF NOT EXISTS idx_tenants_status ON tenants(status);\n CREATE INDEX IF NOT EXISTS idx_tenants_plan ON tenants(plan);\n `,\n down: \"DROP TABLE IF EXISTS tenants CASCADE\",\n};\n\n/**\n * Tenant Members Table Migration\n */\nexport const createTenantMembersTable: Migration = {\n version: \"20241217_005\",\n name: \"create_tenant_members_table\",\n up: `\n CREATE TABLE IF NOT EXISTS tenant_members (\n id VARCHAR(255) PRIMARY KEY,\n tenant_id VARCHAR(255) NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,\n user_id VARCHAR(255) NOT NULL,\n role VARCHAR(50) NOT NULL DEFAULT 'member',\n permissions JSONB DEFAULT '[]',\n status VARCHAR(50) NOT NULL DEFAULT 'active',\n invited_by VARCHAR(255),\n joined_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n UNIQUE(tenant_id, user_id)\n );\n\n CREATE INDEX IF NOT EXISTS idx_tenant_members_user_id ON tenant_members(user_id);\n CREATE INDEX IF NOT EXISTS idx_tenant_members_status ON tenant_members(status);\n `,\n down: \"DROP TABLE IF EXISTS tenant_members CASCADE\",\n};\n\n/**\n * Tenant Invitations Table Migration\n */\nexport const createTenantInvitationsTable: Migration = {\n version: \"20241217_006\",\n name: \"create_tenant_invitations_table\",\n up: `\n CREATE TABLE IF NOT EXISTS tenant_invitations (\n id VARCHAR(255) PRIMARY KEY,\n tenant_id VARCHAR(255) NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,\n email VARCHAR(255) NOT NULL,\n role VARCHAR(50) NOT NULL DEFAULT 'member',\n permissions JSONB DEFAULT '[]',\n token VARCHAR(255) UNIQUE NOT NULL,\n invited_by VARCHAR(255) NOT NULL,\n status VARCHAR(50) NOT NULL DEFAULT 'pending',\n message TEXT,\n created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMP WITH TIME ZONE NOT NULL,\n accepted_at TIMESTAMP WITH TIME ZONE\n );\n\n CREATE INDEX IF NOT EXISTS idx_tenant_invitations_email ON tenant_invitations(email);\n CREATE INDEX IF NOT EXISTS idx_tenant_invitations_token ON tenant_invitations(token);\n CREATE INDEX IF NOT EXISTS idx_tenant_invitations_status ON tenant_invitations(status);\n `,\n down: \"DROP TABLE IF EXISTS tenant_invitations CASCADE\",\n};\n\n/**\n * Tenant Usage Table Migration\n */\nexport const createTenantUsageTable: Migration = {\n version: \"20241217_007\",\n name: \"create_tenant_usage_table\",\n up: `\n CREATE TABLE IF NOT EXISTS tenant_usage (\n tenant_id VARCHAR(255) PRIMARY KEY REFERENCES tenants(id) ON DELETE CASCADE,\n user_count INTEGER NOT NULL DEFAULT 0,\n storage_used_bytes BIGINT NOT NULL DEFAULT 0,\n api_requests_this_month BIGINT NOT NULL DEFAULT 0,\n project_count INTEGER NOT NULL DEFAULT 0,\n custom JSONB DEFAULT '{}',\n updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()\n );\n `,\n down: \"DROP TABLE IF EXISTS tenant_usage CASCADE\",\n};\n\n/**\n * SSO Sessions Table Migration\n */\nexport const createSsoSessionsTable: Migration = {\n version: \"20241217_008\",\n name: \"create_sso_sessions_table\",\n up: `\n CREATE TABLE IF NOT EXISTS sso_sessions (\n id VARCHAR(255) PRIMARY KEY,\n user_id VARCHAR(255) NOT NULL,\n tenant_id VARCHAR(255) NOT NULL,\n sso_provider VARCHAR(50) NOT NULL,\n idp_session_id VARCHAR(255),\n saml_session_index VARCHAR(255),\n groups JSONB DEFAULT '[]',\n access_token TEXT,\n refresh_token TEXT,\n id_token TEXT,\n expires_at TIMESTAMP WITH TIME ZONE NOT NULL,\n created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()\n );\n\n CREATE INDEX IF NOT EXISTS idx_sso_sessions_user_id ON sso_sessions(user_id);\n CREATE INDEX IF NOT EXISTS idx_sso_sessions_tenant_id ON sso_sessions(tenant_id);\n CREATE INDEX IF NOT EXISTS idx_sso_sessions_expires ON sso_sessions(expires_at);\n `,\n down: \"DROP TABLE IF EXISTS sso_sessions CASCADE\",\n};\n\n/**\n * Beta Access Management Tables Migration\n *\n * Creates the tables needed for beta invite code management:\n * - beta_settings: Feature flags for beta mode (live toggleable)\n * - beta_invites: Invite codes with usage tracking and expiration\n * - beta_testers: User beta participation records\n */\nexport const createBetaSettingsTable: Migration = {\n version: \"20241217_009\",\n name: \"create_beta_settings_table\",\n up: `\n CREATE TABLE IF NOT EXISTS beta_settings (\n key VARCHAR(100) PRIMARY KEY,\n value TEXT NOT NULL,\n description TEXT,\n updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),\n updated_by VARCHAR(255)\n );\n\n INSERT INTO beta_settings (key, value, description) VALUES\n ('beta_mode', 'true', 'Whether the app is in beta mode'),\n ('require_invite_code', 'true', 'Whether an invite code is required to sign up'),\n ('beta_message', 'We''re in beta! Thanks for being an early tester.', 'Message displayed during beta')\n ON CONFLICT (key) DO NOTHING;\n `,\n down: \"DROP TABLE IF EXISTS beta_settings CASCADE\",\n};\n\nexport const createBetaInvitesTable: Migration = {\n version: \"20241217_010\",\n name: \"create_beta_invites_table\",\n up: `\n CREATE TABLE IF NOT EXISTS beta_invites (\n id VARCHAR(255) PRIMARY KEY,\n code VARCHAR(100) NOT NULL UNIQUE,\n max_uses INTEGER NOT NULL DEFAULT 1,\n current_uses INTEGER NOT NULL DEFAULT 0,\n expires_at TIMESTAMP WITH TIME ZONE,\n created_by VARCHAR(255) NOT NULL,\n notes TEXT DEFAULT '',\n is_active BOOLEAN NOT NULL DEFAULT true,\n created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n CONSTRAINT chk_beta_uses CHECK (current_uses <= max_uses)\n );\n\n CREATE INDEX IF NOT EXISTS idx_beta_invites_code_active\n ON beta_invites(code) WHERE is_active = true;\n CREATE INDEX IF NOT EXISTS idx_beta_invites_active\n ON beta_invites(is_active, created_at DESC);\n `,\n down: \"DROP TABLE IF EXISTS beta_invites CASCADE\",\n};\n\nexport const createBetaTestersTable: Migration = {\n version: \"20241217_011\",\n name: \"create_beta_testers_table\",\n up: `\n CREATE TABLE IF NOT EXISTS beta_testers (\n user_id VARCHAR(255) PRIMARY KEY,\n invite_code VARCHAR(100) NOT NULL REFERENCES beta_invites(code) ON DELETE SET NULL,\n is_beta_tester BOOLEAN NOT NULL DEFAULT true,\n beta_joined_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()\n );\n\n CREATE INDEX IF NOT EXISTS idx_beta_testers_active\n ON beta_testers(is_beta_tester, beta_joined_at DESC)\n WHERE is_beta_tester = true;\n `,\n down: \"DROP TABLE IF EXISTS beta_testers CASCADE\",\n};\n\n/**\n * All enterprise migrations in order\n */\nexport const enterpriseMigrations: Migration[] = [\n createSsoOidcConfigsTable,\n createDomainVerificationsTable,\n createVerifiedDomainsTable,\n createTenantsTable,\n createTenantMembersTable,\n createTenantInvitationsTable,\n createTenantUsageTable,\n createSsoSessionsTable,\n createBetaSettingsTable,\n createBetaInvitesTable,\n createBetaTestersTable,\n];\n\n/**\n * Get enterprise migrations for a specific feature\n */\nexport function getEnterpriseMigrations(features: {\n sso?: boolean;\n tenancy?: boolean;\n beta?: boolean;\n}): Migration[] {\n const migrations: Migration[] = [];\n\n if (features.sso) {\n migrations.push(\n createSsoOidcConfigsTable,\n createDomainVerificationsTable,\n createVerifiedDomainsTable,\n createSsoSessionsTable,\n );\n }\n\n if (features.tenancy) {\n migrations.push(\n createTenantsTable,\n createTenantMembersTable,\n createTenantInvitationsTable,\n createTenantUsageTable,\n );\n }\n\n if (features.beta) {\n migrations.push(\n createBetaSettingsTable,\n createBetaInvitesTable,\n createBetaTestersTable,\n );\n }\n\n return migrations;\n}\n"],"mappings":"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","names":["module"]}
@@ -663,6 +663,67 @@ var createSsoSessionsTable = {
663
663
  `,
664
664
  down: "DROP TABLE IF EXISTS sso_sessions CASCADE"
665
665
  };
666
+ var createBetaSettingsTable = {
667
+ version: "20241217_009",
668
+ name: "create_beta_settings_table",
669
+ up: `
670
+ CREATE TABLE IF NOT EXISTS beta_settings (
671
+ key VARCHAR(100) PRIMARY KEY,
672
+ value TEXT NOT NULL,
673
+ description TEXT,
674
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
675
+ updated_by VARCHAR(255)
676
+ );
677
+
678
+ INSERT INTO beta_settings (key, value, description) VALUES
679
+ ('beta_mode', 'true', 'Whether the app is in beta mode'),
680
+ ('require_invite_code', 'true', 'Whether an invite code is required to sign up'),
681
+ ('beta_message', 'We''re in beta! Thanks for being an early tester.', 'Message displayed during beta')
682
+ ON CONFLICT (key) DO NOTHING;
683
+ `,
684
+ down: "DROP TABLE IF EXISTS beta_settings CASCADE"
685
+ };
686
+ var createBetaInvitesTable = {
687
+ version: "20241217_010",
688
+ name: "create_beta_invites_table",
689
+ up: `
690
+ CREATE TABLE IF NOT EXISTS beta_invites (
691
+ id VARCHAR(255) PRIMARY KEY,
692
+ code VARCHAR(100) NOT NULL UNIQUE,
693
+ max_uses INTEGER NOT NULL DEFAULT 1,
694
+ current_uses INTEGER NOT NULL DEFAULT 0,
695
+ expires_at TIMESTAMP WITH TIME ZONE,
696
+ created_by VARCHAR(255) NOT NULL,
697
+ notes TEXT DEFAULT '',
698
+ is_active BOOLEAN NOT NULL DEFAULT true,
699
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
700
+ CONSTRAINT chk_beta_uses CHECK (current_uses <= max_uses)
701
+ );
702
+
703
+ CREATE INDEX IF NOT EXISTS idx_beta_invites_code_active
704
+ ON beta_invites(code) WHERE is_active = true;
705
+ CREATE INDEX IF NOT EXISTS idx_beta_invites_active
706
+ ON beta_invites(is_active, created_at DESC);
707
+ `,
708
+ down: "DROP TABLE IF EXISTS beta_invites CASCADE"
709
+ };
710
+ var createBetaTestersTable = {
711
+ version: "20241217_011",
712
+ name: "create_beta_testers_table",
713
+ up: `
714
+ CREATE TABLE IF NOT EXISTS beta_testers (
715
+ user_id VARCHAR(255) PRIMARY KEY,
716
+ invite_code VARCHAR(100) NOT NULL REFERENCES beta_invites(code) ON DELETE SET NULL,
717
+ is_beta_tester BOOLEAN NOT NULL DEFAULT true,
718
+ beta_joined_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
719
+ );
720
+
721
+ CREATE INDEX IF NOT EXISTS idx_beta_testers_active
722
+ ON beta_testers(is_beta_tester, beta_joined_at DESC)
723
+ WHERE is_beta_tester = true;
724
+ `,
725
+ down: "DROP TABLE IF EXISTS beta_testers CASCADE"
726
+ };
666
727
  var enterpriseMigrations = [
667
728
  createSsoOidcConfigsTable,
668
729
  createDomainVerificationsTable,
@@ -671,7 +732,10 @@ var enterpriseMigrations = [
671
732
  createTenantMembersTable,
672
733
  createTenantInvitationsTable,
673
734
  createTenantUsageTable,
674
- createSsoSessionsTable
735
+ createSsoSessionsTable,
736
+ createBetaSettingsTable,
737
+ createBetaInvitesTable,
738
+ createBetaTestersTable
675
739
  ];
676
740
  function getEnterpriseMigrations(features) {
677
741
  const migrations = [];
@@ -691,6 +755,13 @@ function getEnterpriseMigrations(features) {
691
755
  createTenantUsageTable
692
756
  );
693
757
  }
758
+ if (features.beta) {
759
+ migrations.push(
760
+ createBetaSettingsTable,
761
+ createBetaInvitesTable,
762
+ createBetaTestersTable
763
+ );
764
+ }
694
765
  return migrations;
695
766
  }
696
767
  export {