@tenora/multi-tenant 0.1.1
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/README.md +176 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1013 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +118 -0
- package/dist/index.js +468 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/knexFactory.ts","../src/tenantRegistry.ts","../src/password.ts","../src/configLoader.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport fs from \"fs\";\nimport knex from \"knex\";\nimport path from \"path\";\nimport { createTenoraFactory } from \"./knexFactory\";\nimport { loadConfigModuleAsync, resolveConfigPath, unwrapConfig } from \"./configLoader\";\nimport { ensureRegistryMigration, listTenantsFromRegistry, resolveDecrypt } from \"./tenantRegistry\";\nimport type { CliConfig, TenantRecord } from \"./types\";\n\nconst program = new Command();\n\nconst ensureRegistryMigrationIfNeeded = (cfg: CliConfig): boolean => {\n const result = ensureRegistryMigration(cfg);\n if (result.created) {\n console.log(\n `Tenora: created tenant registry migration at ${result.filePath}. Review it (rename if desired), then run 'tenora migrate' again.`\n );\n return true;\n }\n return false;\n};\n\nconst findNearestPackageJson = (startDir: string): string | undefined => {\n let current = startDir;\n while (true) {\n const candidate = path.join(current, \"package.json\");\n if (fs.existsSync(candidate)) return candidate;\n const parent = path.dirname(current);\n if (parent === current) return undefined;\n current = parent;\n }\n};\n\nconst detectModuleType = (): \"esm\" | \"cjs\" => {\n const pkgPath = findNearestPackageJson(process.cwd());\n if (!pkgPath) return \"cjs\";\n try {\n const raw = fs.readFileSync(pkgPath, \"utf8\");\n const json = JSON.parse(raw);\n return json.type === \"module\" ? \"esm\" : \"cjs\";\n } catch {\n return \"cjs\";\n }\n};\n\nconst resolveTemplateModuleType = (opts: { esm?: boolean; cjs?: boolean }): \"esm\" | \"cjs\" => {\n if (opts.esm && opts.cjs) {\n throw new Error(\"Tenora: choose only one of --esm or --cjs.\");\n }\n if (opts.esm) return \"esm\";\n if (opts.cjs) return \"cjs\";\n return detectModuleType();\n};\n\nconst resolveClient = (value?: string): string => value ?? \"pg\";\n\nconst isPostgresClient = (client: string): boolean =>\n client === \"pg\" || client === \"postgres\" || client === \"postgresql\";\n\nconst isMysqlClient = (client: string): boolean =>\n client === \"mysql\" || client === \"mysql2\" || client === \"mariadb\";\n\nconst isSqliteClient = (client: string): boolean =>\n client === \"sqlite3\" || client === \"better-sqlite3\" || client === \"sqlite\";\n\nconst isMssqlClient = (client: string): boolean =>\n client === \"mssql\" || client === \"sqlserver\";\n\nconst normalizePassword = (value: unknown): string | undefined => {\n if (value === undefined || value === null) return undefined;\n if (typeof value === \"string\") return value;\n return String(value);\n};\n\nconst escapePgIdent = (value: string) => value.replace(/\"/g, \"\\\"\\\"\");\nconst escapeMysqlIdent = (value: string) => value.replace(/`/g, \"``\");\nconst escapeMssqlIdent = (value: string) => value.replace(/]/g, \"]]\");\nconst escapeSqlString = (value: string) => value.replace(/'/g, \"''\");\n\nconst resolveBaseDatabaseName = (cfg: CliConfig): string => {\n const client = resolveClient(cfg.base.client);\n if (cfg.base.connection) {\n const conn = cfg.base.connection as unknown as Record<string, unknown>;\n if (isSqliteClient(client)) {\n const filename = (conn.filename as string | undefined) ?? cfg.base.database;\n if (!filename) {\n throw new Error(\"Tenora: base.database or base.connection.filename is required for SQLite.\");\n }\n return filename;\n }\n const dbName = (conn.database as string | undefined) ?? cfg.base.database;\n if (!dbName) throw new Error(\"Tenora: base.database is required.\");\n return dbName;\n }\n\n if (!cfg.base.database) {\n throw new Error(\"Tenora: base.database is required.\");\n }\n return cfg.base.database;\n};\n\nconst applyConnectionDefaults = (cfg: CliConfig, conn: Record<string, unknown>) => {\n const client = resolveClient(cfg.base.client);\n if (conn.user === undefined && cfg.base.user !== undefined) conn.user = cfg.base.user;\n if (conn.port === undefined && cfg.base.port !== undefined) conn.port = cfg.base.port;\n if (isMssqlClient(client)) {\n if (conn.server === undefined && cfg.base.host !== undefined) conn.server = cfg.base.host;\n } else if (conn.host === undefined && cfg.base.host !== undefined) {\n conn.host = cfg.base.host;\n }\n if (conn.ssl === undefined && cfg.base.ssl !== undefined) conn.ssl = cfg.base.ssl;\n const normalized = normalizePassword(conn.password ?? cfg.base.password);\n if (normalized !== undefined) {\n conn.password = normalized;\n } else {\n delete conn.password;\n }\n};\n\nconst buildBaseConnection = (cfg: CliConfig, databaseOverride?: string) => {\n const client = resolveClient(cfg.base.client);\n if (cfg.base.connection) {\n const conn = { ...(cfg.base.connection as unknown as Record<string, unknown>) };\n applyConnectionDefaults(cfg, conn);\n if (databaseOverride !== undefined) {\n if (isSqliteClient(client)) {\n conn.filename = databaseOverride;\n delete conn.database;\n } else {\n conn.database = databaseOverride;\n }\n } else if (isSqliteClient(client)) {\n if (conn.filename === undefined && cfg.base.database) conn.filename = cfg.base.database;\n } else if (conn.database === undefined && cfg.base.database) {\n conn.database = cfg.base.database;\n }\n return conn;\n }\n\n if (isSqliteClient(client)) {\n const filename = databaseOverride ?? cfg.base.database;\n if (!filename) {\n throw new Error(\"Tenora: base.database is required for SQLite.\");\n }\n return { filename };\n }\n\n if (!cfg.base.host || !cfg.base.user || !cfg.base.database) {\n throw new Error(\"Tenora: base connection is incomplete. Provide base.connection or host/user/database.\");\n }\n\n const conn: Record<string, unknown> = {\n ...(isMssqlClient(client) ? { server: cfg.base.host } : { host: cfg.base.host }),\n port: cfg.base.port,\n user: cfg.base.user,\n database: databaseOverride ?? cfg.base.database,\n ssl: cfg.base.ssl ?? false,\n };\n const basePassword = normalizePassword(cfg.base.password);\n if (basePassword !== undefined) conn.password = basePassword;\n return conn;\n};\n\nconst normalizeCreatedPath = (created: string | string[]): string =>\n Array.isArray(created) ? created[0] : created;\n\nconst tokenizeName = (name: string): string[] =>\n name\n .replace(/([a-z0-9])([A-Z])/g, \"$1_$2\")\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter(Boolean);\n\nconst joinTokens = (tokens: string[]): string => tokens.join(\"_\");\n\nconst splitColumns = (tokens: string[]): string[] => {\n const columns: string[] = [];\n let current: string[] = [];\n for (const token of tokens) {\n if (token === \"and\") {\n if (current.length) {\n columns.push(joinTokens(current));\n current = [];\n }\n continue;\n }\n current.push(token);\n }\n if (current.length) columns.push(joinTokens(current));\n return columns.filter(Boolean);\n};\n\nconst inferCreateTable = (tokens: string[]): string | undefined => {\n const tableIdx = tokens.indexOf(\"table\");\n if (tableIdx > 0) return tokens[tableIdx - 1];\n const createIdx = tokens.indexOf(\"create\");\n if (createIdx >= 0 && tokens[createIdx + 1]) return tokens[createIdx + 1];\n return undefined;\n};\n\nconst inferAlterAdd = (tokens: string[]) => {\n const addIdx = tokens.indexOf(\"add\");\n const toIdx = tokens.indexOf(\"to\");\n if (addIdx === -1 || toIdx === -1 || toIdx <= addIdx + 1) return undefined;\n const cols = splitColumns(tokens.slice(addIdx + 1, toIdx));\n const tableTokens = tokens.slice(toIdx + 1);\n if (!cols.length || !tableTokens.length) return undefined;\n const table = tableTokens[tableTokens.length - 1] === \"table\"\n ? joinTokens(tableTokens.slice(0, -1))\n : joinTokens(tableTokens);\n if (!table) return undefined;\n return { table, columns: cols };\n};\n\nconst inferAlterRemove = (tokens: string[]) => {\n const removeIdx = tokens.indexOf(\"remove\");\n const dropIdx = tokens.indexOf(\"drop\");\n const fromIdx = tokens.indexOf(\"from\");\n const startIdx = removeIdx !== -1 ? removeIdx : dropIdx;\n if (startIdx === -1 || fromIdx === -1 || fromIdx <= startIdx + 1) return undefined;\n const cols = splitColumns(tokens.slice(startIdx + 1, fromIdx));\n const tableTokens = tokens.slice(fromIdx + 1);\n if (!cols.length || !tableTokens.length) return undefined;\n const table = tableTokens[tableTokens.length - 1] === \"table\"\n ? joinTokens(tableTokens.slice(0, -1))\n : joinTokens(tableTokens);\n if (!table) return undefined;\n return { table, columns: cols };\n};\n\nconst buildCreateTableTemplate = (table: string, moduleType: \"esm\" | \"cjs\") =>\n moduleType === \"esm\"\n ? `export const up = (knex) =>\\n knex.schema.createTable(\\\"${table}\\\", (t) => {\\n t.increments(\\\"id\\\").primary();\\n t.timestamps(true, true);\\n });\\n\\nexport const down = (knex) => knex.schema.dropTableIfExists(\\\"${table}\\\");\\n`\n : `exports.up = (knex) =>\\n knex.schema.createTable(\\\"${table}\\\", (t) => {\\n t.increments(\\\"id\\\").primary();\\n t.timestamps(true, true);\\n });\\n\\nexports.down = (knex) => knex.schema.dropTableIfExists(\\\"${table}\\\");\\n`;\n\nconst buildAlterAddTemplate = (table: string, columns: string[], moduleType: \"esm\" | \"cjs\") => {\n const addLines = columns.map((c) => ` t.string(\\\"${c}\\\");`).join(\"\\n\");\n const dropLines = columns.map((c) => ` t.dropColumn(\\\"${c}\\\");`).join(\"\\n\");\n return moduleType === \"esm\"\n ? `export const up = (knex) =>\\n knex.schema.alterTable(\\\"${table}\\\", (t) => {\\n${addLines}\\n });\\n\\nexport const down = (knex) =>\\n knex.schema.alterTable(\\\"${table}\\\", (t) => {\\n${dropLines}\\n });\\n`\n : `exports.up = (knex) =>\\n knex.schema.alterTable(\\\"${table}\\\", (t) => {\\n${addLines}\\n });\\n\\nexports.down = (knex) =>\\n knex.schema.alterTable(\\\"${table}\\\", (t) => {\\n${dropLines}\\n });\\n`;\n};\n\nconst buildAlterRemoveTemplate = (table: string, columns: string[], moduleType: \"esm\" | \"cjs\") => {\n const dropLines = columns.map((c) => ` t.dropColumn(\\\"${c}\\\");`).join(\"\\n\");\n const addLines = columns.map((c) => ` t.string(\\\"${c}\\\");`).join(\"\\n\");\n return moduleType === \"esm\"\n ? `export const up = (knex) =>\\n knex.schema.alterTable(\\\"${table}\\\", (t) => {\\n${dropLines}\\n });\\n\\nexport const down = (knex) =>\\n knex.schema.alterTable(\\\"${table}\\\", (t) => {\\n${addLines}\\n });\\n`\n : `exports.up = (knex) =>\\n knex.schema.alterTable(\\\"${table}\\\", (t) => {\\n${dropLines}\\n });\\n\\nexports.down = (knex) =>\\n knex.schema.alterTable(\\\"${table}\\\", (t) => {\\n${addLines}\\n });\\n`;\n};\n\nconst buildDefaultMigrationTemplate = (moduleType: \"esm\" | \"cjs\") =>\n moduleType === \"esm\"\n ? `export const up = (knex) => {\\n // TODO\\n};\\n\\nexport const down = (knex) => {\\n // TODO\\n};\\n`\n : `exports.up = (knex) => {\\n // TODO\\n};\\n\\nexports.down = (knex) => {\\n // TODO\\n};\\n`;\n\nconst writeMigrationTemplate = (filePath: string, moduleType: \"esm\" | \"cjs\", name: string) => {\n const tokens = tokenizeName(name);\n const createTable = inferCreateTable(tokens);\n const addPlan = inferAlterAdd(tokens);\n const removePlan = inferAlterRemove(tokens);\n const body = createTable\n ? buildCreateTableTemplate(createTable, moduleType)\n : addPlan\n ? buildAlterAddTemplate(addPlan.table, addPlan.columns, moduleType)\n : removePlan\n ? buildAlterRemoveTemplate(removePlan.table, removePlan.columns, moduleType)\n : buildDefaultMigrationTemplate(moduleType);\n fs.writeFileSync(filePath, body);\n};\n\nconst writeSeedTemplate = (filePath: string, moduleType: \"esm\" | \"cjs\") => {\n const body =\n moduleType === \"esm\"\n ? `export const seed = async (knex) => {\\n // TODO\\n};\\n`\n : `exports.seed = async (knex) => {\\n // TODO\\n};\\n`;\n fs.writeFileSync(filePath, body);\n};\n\nconst makeKnexForDirs = (cfg: CliConfig, migrationsDir?: string, seedsDir?: string) =>\n knex({\n client: resolveClient(cfg.base.client),\n useNullAsDefault: true,\n connection: buildBaseConnection(cfg),\n migrations: migrationsDir ? { directory: migrationsDir } : undefined,\n seeds: seedsDir ? { directory: seedsDir } : undefined,\n });\n\nconst ensureBaseDatabase = async (cfg: CliConfig) => {\n const client = resolveClient(cfg.base.client);\n const baseDb = resolveBaseDatabaseName(cfg);\n\n if (isSqliteClient(client)) {\n if (baseDb === \":memory:\") return;\n fs.mkdirSync(path.dirname(baseDb), { recursive: true });\n if (!fs.existsSync(baseDb)) {\n fs.closeSync(fs.openSync(baseDb, \"a\"));\n console.log(`Tenora: created base database \"${baseDb}\"`);\n }\n return;\n }\n\n const adminDatabase =\n cfg.base.adminDatabase ??\n (isPostgresClient(client)\n ? \"postgres\"\n : isMysqlClient(client)\n ? \"mysql\"\n : isMssqlClient(client)\n ? \"master\"\n : baseDb);\n const admin = knex({\n client,\n useNullAsDefault: true,\n connection: buildBaseConnection(cfg, adminDatabase),\n });\n\n try {\n if (isPostgresClient(client)) {\n const result = await admin.raw(`SELECT 1 FROM pg_database WHERE datname = ?`, [baseDb]);\n if (result?.rows?.length) return;\n\n const safeName = escapePgIdent(baseDb);\n await admin.raw(`CREATE DATABASE \"${safeName}\"`);\n } else if (isMysqlClient(client)) {\n const result = await admin.raw(\n `SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?`,\n [baseDb]\n );\n if (result?.[0]?.length) return;\n\n const safeName = escapeMysqlIdent(baseDb);\n await admin.raw(`CREATE DATABASE \\`${safeName}\\``);\n } else if (isMssqlClient(client)) {\n const result = await admin.raw(`SELECT name FROM sys.databases WHERE name = ?`, [baseDb]);\n if (result?.[0]?.length) return;\n\n const safeName = escapeMssqlIdent(baseDb);\n await admin.raw(`CREATE DATABASE [${safeName}]`);\n } else {\n throw new Error(\n `Tenora: --create-base is only supported for Postgres, MySQL/MariaDB, SQLite, and SQL Server clients (got \"${client}\").`\n );\n }\n console.log(`Tenora: created base database \"${baseDb}\"`);\n } finally {\n await admin.destroy();\n }\n};\n\nconst loadConfig = async (configPath: string): Promise<CliConfig> => {\n const isDefault = configPath === \"tenora.config.js\";\n let fullPath = path.isAbsolute(configPath)\n ? configPath\n : path.join(process.cwd(), configPath);\n if (isDefault && !fs.existsSync(fullPath)) {\n fullPath = resolveConfigPath();\n }\n const module = await loadConfigModuleAsync(fullPath);\n const cfg = unwrapConfig(module);\n if (!cfg) {\n throw new Error(`No config exported from ${fullPath}`);\n }\n return cfg as CliConfig;\n};\n\nconst getTenantPassword = (\n tenant: TenantRecord,\n decryptPassword?: (encrypted: string) => string,\n cfg?: CliConfig\n) => {\n if (tenant.password) return tenant.password;\n if (tenant.encryptedPassword) {\n const resolver = decryptPassword ?? (cfg ? resolveDecrypt(cfg) : undefined);\n if (resolver) return resolver(tenant.encryptedPassword);\n }\n return undefined;\n};\n\nconst addBaseCommands = () => {\n program\n .command(\"migrate:base\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--create-base\", \"create base database if missing\")\n .description(\"Run base database migrations\")\n .action(async (opts) => {\n const cfg = await loadConfig(opts.config);\n if (opts.createBase) {\n await ensureBaseDatabase(cfg);\n }\n if (ensureRegistryMigrationIfNeeded(cfg)) return;\n const manager = createTenoraFactory(cfg);\n try {\n const base = manager.getBase();\n const [, files] = await base.migrate.latest();\n console.log(files.length ? files.join(\"\\n\") : \"Base up to date\");\n } finally {\n await manager.destroyAll();\n }\n });\n\n // Short alias: `tenora migrate` == migrate base\n program\n .command(\"migrate\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--create-base\", \"create base database if missing\")\n .description(\"Run base database migrations (alias of migrate:base)\")\n .action(async (opts) => {\n const cfg = await loadConfig(opts.config);\n if (opts.createBase) {\n await ensureBaseDatabase(cfg);\n }\n if (ensureRegistryMigrationIfNeeded(cfg)) return;\n const manager = createTenoraFactory(cfg);\n try {\n const base = manager.getBase();\n const [, files] = await base.migrate.latest();\n console.log(files.length ? files.join(\"\\n\") : \"Base up to date\");\n } finally {\n await manager.destroyAll();\n }\n });\n\n program\n .command(\"rollback:base\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .description(\"Rollback last base migration batch\")\n .action(async (opts) => {\n const cfg = await loadConfig(opts.config);\n const manager = createTenoraFactory(cfg);\n try {\n const base = manager.getBase();\n const [, files] = await base.migrate.rollback();\n console.log(files.length ? files.join(\"\\n\") : \"Nothing to rollback\");\n } finally {\n await manager.destroyAll();\n }\n });\n\n // Short alias: `tenora rollback` == rollback base\n program\n .command(\"rollback\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .description(\"Rollback last base migration batch (alias of rollback:base)\")\n .action(async (opts) => {\n const cfg = await loadConfig(opts.config);\n const manager = createTenoraFactory(cfg);\n try {\n const base = manager.getBase();\n const [, files] = await base.migrate.rollback();\n console.log(files.length ? files.join(\"\\n\") : \"Nothing to rollback\");\n } finally {\n await manager.destroyAll();\n }\n });\n};\n\nconst addTenantCommands = () => {\n program\n .command(\"migrate:tenants\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--create-base\", \"create base database if missing\")\n .description(\"Run tenant database migrations for all tenants\")\n .action(async (opts) => {\n const cfg = await loadConfig(opts.config);\n if (opts.createBase) {\n await ensureBaseDatabase(cfg);\n }\n if (ensureRegistryMigrationIfNeeded(cfg)) return;\n const manager = createTenoraFactory(cfg);\n try {\n const tenants = await listTenantsFromRegistry(manager.getBase(), cfg);\n for (const tenant of tenants) {\n const pwd = getTenantPassword(tenant, cfg.decryptPassword, cfg);\n const knex = manager.getTenant(tenant.id, pwd);\n const [, files] = await knex.migrate.latest();\n console.log(`Tenant ${tenant.id}: ${files.length ? files.join(\", \") : \"up to date\"}`);\n await knex.destroy();\n }\n } finally {\n await manager.destroyAll();\n }\n });\n\n program\n .command(\"rollback:tenants\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .description(\"Rollback last migration batch for all tenants\")\n .action(async (opts) => {\n const cfg = await loadConfig(opts.config);\n const manager = createTenoraFactory(cfg);\n try {\n const tenants = await listTenantsFromRegistry(manager.getBase(), cfg);\n for (const tenant of tenants) {\n const pwd = getTenantPassword(tenant, cfg.decryptPassword, cfg);\n const knex = manager.getTenant(tenant.id, pwd);\n const [, files] = await knex.migrate.rollback();\n console.log(`Tenant ${tenant.id}: ${files.length ? files.join(\", \") : \"Nothing to rollback\"}`);\n await knex.destroy();\n }\n } finally {\n await manager.destroyAll();\n }\n });\n};\n\naddBaseCommands();\naddTenantCommands();\n\nconst runMakeMigration = async (\n scope: \"base\" | \"tenants\",\n name: string,\n opts: { config: string; esm?: boolean; cjs?: boolean }\n) => {\n const cfg = await loadConfig(opts.config);\n const dir = scope === \"base\" ? cfg.base.migrationsDir : cfg.tenant?.migrationsDir;\n if (!dir) {\n throw new Error(\n `Tenora: ${scope === \"base\" ? \"base.migrationsDir\" : \"tenant.migrationsDir\"} is required to create migrations.`\n );\n }\n const moduleType = resolveTemplateModuleType(opts);\n const client = makeKnexForDirs(cfg, dir);\n try {\n const created = await client.migrate.make(name);\n const file = normalizeCreatedPath(created);\n writeMigrationTemplate(file, moduleType, name);\n console.log(file);\n } finally {\n await client.destroy();\n }\n};\n\nconst runMakeSeed = async (\n scope: \"base\" | \"tenants\",\n name: string,\n opts: { config: string; esm?: boolean; cjs?: boolean }\n) => {\n const cfg = await loadConfig(opts.config);\n const dir = scope === \"base\" ? cfg.base.seedsDir : cfg.tenant?.seedsDir;\n if (!dir) {\n throw new Error(\n `Tenora: ${scope === \"base\" ? \"base.seedsDir\" : \"tenant.seedsDir\"} is required to create seeds.`\n );\n }\n const moduleType = resolveTemplateModuleType(opts);\n const client = makeKnexForDirs(cfg, undefined, dir);\n try {\n const created = await client.seed.make(name);\n const file = normalizeCreatedPath(created);\n writeSeedTemplate(file, moduleType);\n console.log(file);\n } finally {\n await client.destroy();\n }\n};\n\nprogram\n .command(\"make:migration <name>\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--esm\", \"force ESM template output\")\n .option(\"--cjs\", \"force CommonJS template output\")\n .description(\"Create a base migration file (alias of make:migration:base)\")\n .action((name, opts) => runMakeMigration(\"base\", name, opts));\n\nprogram\n .command(\"make:migration:base <name>\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--esm\", \"force ESM template output\")\n .option(\"--cjs\", \"force CommonJS template output\")\n .description(\"Create a base migration file\")\n .action((name, opts) => runMakeMigration(\"base\", name, opts));\n\nprogram\n .command(\"make:migration:tenants <name>\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--esm\", \"force ESM template output\")\n .option(\"--cjs\", \"force CommonJS template output\")\n .description(\"Create a tenant migration file\")\n .action((name, opts) => runMakeMigration(\"tenants\", name, opts));\n\nprogram\n .command(\"make:seed <name>\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--esm\", \"force ESM template output\")\n .option(\"--cjs\", \"force CommonJS template output\")\n .description(\"Create a base seed file (alias of make:seed:base)\")\n .action((name, opts) => runMakeSeed(\"base\", name, opts));\n\nprogram\n .command(\"make:seed:base <name>\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--esm\", \"force ESM template output\")\n .option(\"--cjs\", \"force CommonJS template output\")\n .description(\"Create a base seed file\")\n .action((name, opts) => runMakeSeed(\"base\", name, opts));\n\nprogram\n .command(\"make:seed:tenants <name>\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--esm\", \"force ESM template output\")\n .option(\"--cjs\", \"force CommonJS template output\")\n .description(\"Create a tenant seed file\")\n .action((name, opts) => runMakeSeed(\"tenants\", name, opts));\n\nprogram\n .command(\"seed:run:base\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--create-base\", \"create base database if missing\")\n .description(\"Run base database seeds\")\n .action(async (opts) => {\n const cfg = await loadConfig(opts.config);\n if (opts.createBase) {\n await ensureBaseDatabase(cfg);\n }\n const manager = createTenoraFactory(cfg);\n try {\n const base = manager.getBase();\n const result = await base.seed.run();\n const files = Array.isArray(result) ? result[0] : [];\n console.log(files.length ? files.join(\"\\n\") : \"No seeds executed\");\n } finally {\n await manager.destroyAll();\n }\n });\n\nprogram\n .command(\"seed:run\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--create-base\", \"create base database if missing\")\n .description(\"Run base database seeds (alias of seed:run:base)\")\n .action(async (opts) => {\n const cfg = await loadConfig(opts.config);\n if (opts.createBase) {\n await ensureBaseDatabase(cfg);\n }\n const manager = createTenoraFactory(cfg);\n try {\n const base = manager.getBase();\n const result = await base.seed.run();\n const files = Array.isArray(result) ? result[0] : [];\n console.log(files.length ? files.join(\"\\n\") : \"No seeds executed\");\n } finally {\n await manager.destroyAll();\n }\n });\n\nprogram\n .command(\"seed:run:tenants\")\n .option(\"-c, --config <path>\", \"config file\", \"tenora.config.js\")\n .option(\"--create-base\", \"create base database if missing\")\n .description(\"Run tenant database seeds for all tenants\")\n .action(async (opts) => {\n const cfg = await loadConfig(opts.config);\n if (opts.createBase) {\n await ensureBaseDatabase(cfg);\n }\n if (ensureRegistryMigrationIfNeeded(cfg)) return;\n const manager = createTenoraFactory(cfg);\n try {\n const tenants = await listTenantsFromRegistry(manager.getBase(), cfg);\n for (const tenant of tenants) {\n const pwd = getTenantPassword(tenant, cfg.decryptPassword, cfg);\n const knex = manager.getTenant(tenant.id, pwd);\n const result = await knex.seed.run();\n const files = Array.isArray(result) ? result[0] : [];\n console.log(`Tenant ${tenant.id}: ${files.length ? files.join(\", \") : \"No seeds executed\"}`);\n await knex.destroy();\n }\n } finally {\n await manager.destroyAll();\n }\n });\n\nprogram\n .command(\"list\")\n .description(\"List available commands\")\n .action(() => program.outputHelp());\n\nprogram.parse(process.argv);\n","import knex, { Knex } from \"knex\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport type { MultiTenantOptions, TenantManager, TenoraClient } from \"./types\";\nimport { ensureRegistryTable, upsertTenantInRegistry } from \"./tenantRegistry\";\nimport { loadConfigModuleAsync, loadConfigModuleSync, resolveConfigPath, unwrapConfig } from \"./configLoader\";\n\nconst resolveClient = (value?: TenoraClient): TenoraClient => value ?? \"pg\";\n\nconst isPostgresClient = (client: TenoraClient): boolean =>\n client === \"pg\" || client === \"postgres\" || client === \"postgresql\";\n\nconst isMysqlClient = (client: TenoraClient): boolean =>\n client === \"mysql\" || client === \"mysql2\" || client === \"mariadb\";\n\nconst isSqliteClient = (client: TenoraClient): boolean =>\n client === \"sqlite3\" || client === \"better-sqlite3\" || client === \"sqlite\";\n\nconst isMssqlClient = (client: TenoraClient): boolean =>\n client === \"mssql\" || client === \"sqlserver\";\n\nconst normalizePassword = (value: unknown): string | undefined => {\n if (value === undefined || value === null) return undefined;\n if (typeof value === \"string\") return value;\n return String(value);\n};\n\nconst escapePgIdent = (value: string) => value.replace(/\"/g, \"\\\"\\\"\");\nconst escapeMysqlIdent = (value: string) => value.replace(/`/g, \"``\");\nconst escapeMssqlIdent = (value: string) => value.replace(/]/g, \"]]\");\nconst escapeSqlString = (value: string) => value.replace(/'/g, \"''\");\n\nconst loadDefaultConfig = (): MultiTenantOptions => {\n const explicit = process.env.TENORA_CONFIG;\n const fullPath = resolveConfigPath(explicit ?? undefined);\n const module = loadConfigModuleSync(fullPath);\n const cfg = unwrapConfig(module);\n if (!cfg) {\n throw new Error(\n `Tenora: config file ${path.basename(fullPath)} did not export a config object.`\n );\n }\n return cfg as MultiTenantOptions;\n};\n\nexport const loadDefaultConfigAsync = async (): Promise<MultiTenantOptions> => {\n const explicit = process.env.TENORA_CONFIG;\n const fullPath = resolveConfigPath(explicit ?? undefined);\n const module = await loadConfigModuleAsync(fullPath);\n const cfg = unwrapConfig(module);\n if (!cfg) {\n throw new Error(\n `Tenora: config file ${path.basename(fullPath)} did not export a config object.`\n );\n }\n return cfg as MultiTenantOptions;\n};\n\nconst defaultPool: Knex.PoolConfig = { min: 2, max: 10, acquireTimeoutMillis: 60_000, idleTimeoutMillis: 60_000 };\n\nconst buildTenoraFactory = (resolved: MultiTenantOptions): TenantManager => {\n const { base, tenant = {}, knexOptions = {} } = resolved;\n const cache = new Map<string, Knex>();\n const basePassword = normalizePassword(base.password);\n const client = resolveClient(base.client);\n\n const resolveBaseDatabaseName = (): string => {\n if (base.connection) {\n const conn = base.connection as unknown as Record<string, unknown>;\n if (isSqliteClient(client)) {\n const filename = (conn.filename as string | undefined) ?? base.database;\n if (!filename) {\n throw new Error(\"Tenora: base.database or base.connection.filename is required for SQLite.\");\n }\n return filename;\n }\n const dbName = (conn.database as string | undefined) ?? base.database;\n if (!dbName) {\n throw new Error(\"Tenora: base.database is required.\");\n }\n return dbName;\n }\n\n if (!base.database) {\n throw new Error(\"Tenora: base.database is required.\");\n }\n return base.database;\n };\n\n const applyConnectionDefaults = (conn: Record<string, unknown>) => {\n if (conn.user === undefined && base.user !== undefined) conn.user = base.user;\n if (conn.port === undefined && base.port !== undefined) conn.port = base.port;\n if (isMssqlClient(client)) {\n if (conn.server === undefined && base.host !== undefined) conn.server = base.host;\n } else if (conn.host === undefined && base.host !== undefined) {\n conn.host = base.host;\n }\n if (conn.ssl === undefined && base.ssl !== undefined) conn.ssl = base.ssl;\n const normalized = normalizePassword(conn.password ?? basePassword);\n if (normalized !== undefined) {\n conn.password = normalized;\n } else {\n delete conn.password;\n }\n };\n\n const buildBaseConnection = (databaseOverride?: string): Knex.StaticConnectionConfig => {\n if (base.connection) {\n const conn = { ...(base.connection as unknown as Record<string, unknown>) };\n applyConnectionDefaults(conn);\n if (databaseOverride !== undefined) {\n if (isSqliteClient(client)) {\n conn.filename = databaseOverride;\n delete conn.database;\n } else {\n conn.database = databaseOverride;\n }\n } else if (isSqliteClient(client)) {\n if (conn.filename === undefined && base.database) conn.filename = base.database;\n } else if (conn.database === undefined && base.database) {\n conn.database = base.database;\n }\n return conn as Knex.StaticConnectionConfig;\n }\n\n if (isSqliteClient(client)) {\n const filename = databaseOverride ?? base.database;\n if (!filename) {\n throw new Error(\"Tenora: base.database is required for SQLite.\");\n }\n return { filename } as Knex.StaticConnectionConfig;\n }\n\n if (!base.host || !base.user || !base.database) {\n throw new Error(\"Tenora: base connection is incomplete. Provide base.connection or host/user/database.\");\n }\n\n const conn: Record<string, unknown> = {\n ...(isMssqlClient(client) ? { server: base.host } : { host: base.host }),\n port: base.port,\n user: base.user,\n database: databaseOverride ?? base.database,\n ssl: base.ssl ?? false,\n };\n if (basePassword !== undefined) conn.password = basePassword;\n return conn as Knex.StaticConnectionConfig;\n };\n\n const baseKnexConfig: Knex.Config = {\n client,\n useNullAsDefault: true,\n connection: buildBaseConnection(resolveBaseDatabaseName()),\n pool: base.pool ?? defaultPool,\n migrations: base.migrationsDir ? { directory: base.migrationsDir } : undefined,\n seeds: base.seedsDir ? { directory: base.seedsDir } : undefined,\n ...knexOptions,\n } as Knex.Config;\n\n const baseClient = knex(baseKnexConfig);\n\n const resolveTenantDatabaseName = (tenantId: string): string =>\n tenant.databaseName ? tenant.databaseName(tenantId) : tenantId;\n\n const resolveSqliteTenantFilename = (tenantId: string): string => {\n const baseDb = resolveBaseDatabaseName();\n const baseDir = tenant.databaseDir ?? path.dirname(baseDb ?? process.cwd());\n const name = tenant.databaseName\n ? tenant.databaseName(tenantId)\n : `${tenantId}${tenant.databaseSuffix ?? \".sqlite\"}`;\n return path.isAbsolute(name) ? name : path.join(baseDir, name);\n };\n\n const buildTenantConfig = (tenantId: string, password?: string): Knex.Config => {\n const tenantPassword = normalizePassword(password ?? basePassword);\n const hasTenantPassword = password !== undefined && password !== null;\n const tenantDb = isSqliteClient(client)\n ? resolveSqliteTenantFilename(tenantId)\n : resolveTenantDatabaseName(tenantId);\n const connection = buildBaseConnection(tenantDb) as Record<string, unknown>;\n\n if (!isSqliteClient(client) && hasTenantPassword) {\n connection.user = `${tenant.userPrefix ?? \"user_\"}${tenantId}`;\n if (tenantPassword !== undefined) {\n connection.password = tenantPassword;\n } else {\n delete connection.password;\n }\n }\n\n if (tenant.ssl !== undefined) {\n connection.ssl = tenant.ssl;\n }\n\n return ({\n client,\n useNullAsDefault: true,\n connection,\n pool: tenant.pool ?? base.pool ?? defaultPool,\n migrations: tenant.migrationsDir ? { directory: tenant.migrationsDir } : undefined,\n seeds: tenant.seedsDir ? { directory: tenant.seedsDir } : undefined,\n ...knexOptions,\n });\n };\n\n /**\n * Get (or create+cache) a Knex client for the tenant.\n * Password is optional; if omitted, the base user is used.\n */\n const getTenant = (tenantId: string, password?: string): Knex => {\n const cached = cache.get(tenantId);\n if (cached) return cached;\n\n const client = knex(buildTenantConfig(tenantId, password));\n cache.set(tenantId, client);\n return client;\n };\n\n const destroyTenant = async (tenantId: string) => {\n const client = cache.get(tenantId);\n if (client) {\n await client.destroy();\n cache.delete(tenantId);\n }\n };\n\n const destroyAll = async () => {\n await Promise.all([...cache.values()].map((k) => k.destroy()));\n cache.clear();\n await baseClient.destroy();\n };\n\n /**\n * Create tenant DB, optional user, then run migrations/seeds.\n * Mirrors createDB.ts from the reference project.\n */\n const createTenantDb = async (tenantId: string, password?: string) => {\n await ensureRegistryTable(baseClient, resolved);\n const tenantPassword = normalizePassword(password);\n const hasTenantPassword = password !== undefined && password !== null;\n\n if (isSqliteClient(client)) {\n const filename = resolveSqliteTenantFilename(tenantId);\n if (filename !== \":memory:\") {\n fs.mkdirSync(path.dirname(filename), { recursive: true });\n if (!fs.existsSync(filename)) {\n fs.closeSync(fs.openSync(filename, \"a\"));\n }\n }\n } else {\n // Reuse a short-lived connection to admin db so we can create the tenant DB/user\n const adminDb =\n base.adminDatabase ??\n (isPostgresClient(client)\n ? \"postgres\"\n : isMysqlClient(client)\n ? \"mysql\"\n : isMssqlClient(client)\n ? \"master\"\n : resolveBaseDatabaseName());\n const admin = knex({\n ...baseKnexConfig,\n connection: buildBaseConnection(adminDb),\n });\n\n try {\n if (isPostgresClient(client)) {\n const result = await admin.raw(`SELECT 1 FROM pg_database WHERE datname = ?`, [tenantId]);\n if (result?.rows?.length) {\n throw new Error(`Database \"${tenantId}\" already exists`);\n }\n\n const safeDb = escapePgIdent(tenantId);\n await admin.raw(`CREATE DATABASE \"${safeDb}\"`);\n\n if (tenantPassword && hasTenantPassword) {\n const userName = `${tenant.userPrefix ?? \"user_\"}${tenantId}`;\n const safeUser = escapePgIdent(userName);\n const safePwd = escapeSqlString(tenantPassword);\n await admin.raw(`CREATE USER \"${safeUser}\" WITH PASSWORD '${safePwd}'`);\n await admin.raw(`GRANT ALL PRIVILEGES ON DATABASE \"${safeDb}\" TO \"${safeUser}\"`);\n }\n } else if (isMysqlClient(client)) {\n const result = await admin.raw(\n `SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?`,\n [tenantId]\n );\n if (result?.[0]?.length) {\n throw new Error(`Database \"${tenantId}\" already exists`);\n }\n\n const safeDb = escapeMysqlIdent(tenantId);\n await admin.raw(`CREATE DATABASE \\`${safeDb}\\``);\n\n if (tenantPassword && hasTenantPassword) {\n const userName = `${tenant.userPrefix ?? \"user_\"}${tenantId}`;\n const safeUser = escapeSqlString(userName);\n const safePwd = escapeSqlString(tenantPassword);\n await admin.raw(`CREATE USER IF NOT EXISTS '${safeUser}'@'%' IDENTIFIED BY '${safePwd}'`);\n await admin.raw(`GRANT ALL PRIVILEGES ON \\`${safeDb}\\`.* TO '${safeUser}'@'%'`);\n }\n } else if (isMssqlClient(client)) {\n const safeDb = escapeMssqlIdent(tenantId);\n await admin.raw(\n `IF DB_ID(N'${escapeSqlString(tenantId)}') IS NULL CREATE DATABASE [${safeDb}]`\n );\n\n if (tenantPassword && hasTenantPassword) {\n const userName = `${tenant.userPrefix ?? \"user_\"}${tenantId}`;\n const safeUser = escapeMssqlIdent(userName);\n const safePwd = escapeSqlString(tenantPassword);\n await admin.raw(\n `IF NOT EXISTS (SELECT name FROM sys.server_principals WHERE name = N'${escapeSqlString(userName)}') CREATE LOGIN [${safeUser}] WITH PASSWORD = '${safePwd}'`\n );\n\n const tenantAdmin = knex({\n ...baseKnexConfig,\n connection: buildBaseConnection(tenantId),\n });\n try {\n await tenantAdmin.raw(\n `IF NOT EXISTS (SELECT name FROM sys.database_principals WHERE name = N'${escapeSqlString(userName)}') CREATE USER [${safeUser}] FOR LOGIN [${safeUser}]`\n );\n await tenantAdmin.raw(`EXEC sp_addrolemember 'db_owner', '${safeUser}'`);\n } finally {\n await tenantAdmin.destroy();\n }\n }\n } else {\n throw new Error(\n `Tenora: createTenantDb is only supported for Postgres, MySQL/MariaDB, SQLite, and SQL Server clients (got \"${client}\").`\n );\n }\n } finally {\n await admin.destroy();\n }\n }\n\n // Run tenant migrations if configured\n const tenantKnex = knex(buildTenantConfig(tenantId, tenantPassword));\n try {\n if (tenant.migrationsDir) {\n await tenantKnex.migrate.latest();\n }\n if (tenant.seedsDir) {\n await tenantKnex.seed.run();\n }\n } finally {\n await tenantKnex.destroy();\n cache.delete(tenantId);\n }\n\n await upsertTenantInRegistry(baseClient, resolved, tenantId, password);\n };\n\n return {\n getBase: () => baseClient,\n getTenant,\n createTenantDb,\n destroyTenant,\n destroyAll,\n };\n};\n\nexport const createTenoraFactory = (\n options?: MultiTenantOptions\n): TenantManager => {\n const resolved = options ?? loadDefaultConfig();\n return buildTenoraFactory(resolved);\n};\n\nexport const createTenoraFactoryAsync = async (\n options?: MultiTenantOptions\n): Promise<TenantManager> => {\n const resolved = options ?? await loadDefaultConfigAsync();\n return buildTenoraFactory(resolved);\n};\n\n// Backwards-compatible alias\nexport const createKnexFactory = createTenoraFactory;\n","import fs from \"fs\";\nimport path from \"path\";\nimport type { Knex } from \"knex\";\nimport type { MultiTenantOptions, TenantRecord, TenantRegistryOptions } from \"./types\";\nimport { decryptPassword as defaultDecrypt, encryptPassword as defaultEncrypt } from \"./password\";\n\nconst REGISTRY_MARKER = \"tenora:registry\";\n\nconst DEFAULT_REGISTRY: Required<TenantRegistryOptions> = {\n table: \"tenora_tenants\",\n idColumn: \"id\",\n passwordColumn: \"password\",\n encryptedPasswordColumn: \"encrypted_password\",\n createdAtColumn: \"created_at\",\n updatedAtColumn: \"updated_at\",\n};\n\nexport const resolveRegistry = (\n options: MultiTenantOptions\n): Required<TenantRegistryOptions> => ({\n ...DEFAULT_REGISTRY,\n ...(options.registry ?? {}),\n});\n\nconst resolveEncrypt = (options: MultiTenantOptions) => {\n if (options.encryptPassword) return options.encryptPassword;\n const key = process.env.TENORA_KEY;\n if (key) {\n return (plain: string) => defaultEncrypt(plain, key);\n }\n return undefined;\n};\n\nexport const resolveDecrypt = (options: MultiTenantOptions) => {\n if (options.decryptPassword) return options.decryptPassword;\n const key = process.env.TENORA_KEY;\n if (key) {\n return (encrypted: string) => defaultDecrypt(encrypted, key);\n }\n return undefined;\n};\n\nconst listMigrationFiles = (dir: string): string[] => {\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((name) => name.endsWith(\".js\") || name.endsWith(\".ts\"))\n .map((name) => path.join(dir, name));\n};\n\nexport const ensureRegistryMigration = (\n options: MultiTenantOptions\n): { created: boolean; filePath?: string } => {\n const configuredDir = options.base.migrationsDir;\n const migrationsDir = configuredDir\n ? (path.isAbsolute(configuredDir)\n ? configuredDir\n : path.join(process.cwd(), configuredDir))\n : undefined;\n if (!migrationsDir) {\n throw new Error(\n \"Tenora: base.migrationsDir is required to create the tenant registry migration.\"\n );\n }\n\n const registry = resolveRegistry(options);\n const files = listMigrationFiles(migrationsDir);\n const hasRegistry = files.some((file) => {\n try {\n return fs.readFileSync(file, \"utf8\").includes(REGISTRY_MARKER);\n } catch {\n return false;\n }\n });\n\n if (hasRegistry) {\n return { created: false };\n }\n\n fs.mkdirSync(migrationsDir, { recursive: true });\n const timestamp = new Date()\n .toISOString()\n .replace(/[-:T.Z]/g, \"\")\n .slice(0, 14);\n const fileName = `${timestamp}_create_tenant_registry.js`;\n const filePath = path.join(migrationsDir, fileName);\n\n const migration = `// ${REGISTRY_MARKER}\nexport const up = (knex) =>\n knex.schema.createTable(\"${registry.table}\", (t) => {\n t.string(\"${registry.idColumn}\").primary();\n t.string(\"${registry.passwordColumn}\");\n t.string(\"${registry.encryptedPasswordColumn}\");\n t.timestamp(\"${registry.createdAtColumn}\", { useTz: true }).defaultTo(knex.fn.now());\n t.timestamp(\"${registry.updatedAtColumn}\", { useTz: true }).defaultTo(knex.fn.now());\n });\n\nexport const down = (knex) => knex.schema.dropTableIfExists(\"${registry.table}\");\n`;\n\n fs.writeFileSync(filePath, migration);\n return { created: true, filePath };\n};\n\nexport const ensureRegistryTable = async (\n base: Knex,\n options: MultiTenantOptions\n) => {\n const registry = resolveRegistry(options);\n const exists = await base.schema.hasTable(registry.table);\n if (!exists) {\n throw new Error(\n `Tenora: tenant registry table \"${registry.table}\" not found. Run 'tenora migrate' to create it.`\n );\n }\n};\n\nexport const listTenantsFromRegistry = async (\n base: Knex,\n options: MultiTenantOptions\n): Promise<TenantRecord[]> => {\n const registry = resolveRegistry(options);\n await ensureRegistryTable(base, options);\n const rows = await base(registry.table).select({\n id: registry.idColumn,\n password: registry.passwordColumn,\n encryptedPassword: registry.encryptedPasswordColumn,\n });\n return rows as TenantRecord[];\n};\n\nexport const upsertTenantInRegistry = async (\n base: Knex,\n options: MultiTenantOptions,\n tenantId: string,\n password?: string\n) => {\n const registry = resolveRegistry(options);\n await ensureRegistryTable(base, options);\n\n const encrypt = resolveEncrypt(options);\n const encrypted = password && encrypt ? encrypt(password) : undefined;\n\n const record: Record<string, string> = {\n [registry.idColumn]: tenantId,\n };\n\n if (encrypted) {\n record[registry.encryptedPasswordColumn] = encrypted;\n } else if (password) {\n record[registry.passwordColumn] = password;\n }\n\n await base(registry.table)\n .insert(record)\n .onConflict(registry.idColumn)\n .merge(record);\n};\n","import CryptoJS from \"crypto-js\";\n\n/**\n * Generate a random password suitable for per-tenant DB users.\n */\nexport const generateTenantPassword = (length = 32): string => {\n const charset = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*\";\n let password = \"\";\n\n for (let i = 0; i < length; i++) {\n const randomIndex = Math.floor(Math.random() * charset.length);\n password += charset[randomIndex];\n }\n\n return password;\n};\n\nexport const encryptPassword = (password: string, cipherKey: string): string => {\n if (typeof cipherKey !== \"string\" || cipherKey.length === 0) {\n throw new Error(\"Tenora: cipherKey must be a non-empty string.\");\n }\n return CryptoJS.AES.encrypt(password, cipherKey).toString();\n};\n\nexport const decryptPassword = (encryptedPassword: string, cipherKey: string): string => {\n if (typeof cipherKey !== \"string\" || cipherKey.length === 0) {\n throw new Error(\"Tenora: cipherKey must be a non-empty string.\");\n }\n const bytes = CryptoJS.AES.decrypt(encryptedPassword, cipherKey);\n return bytes.toString(CryptoJS.enc.Utf8);\n};\n","import fs from \"fs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport { createRequire } from \"module\";\n\nconst require = createRequire(import.meta.url);\n\nexport const DEFAULT_CONFIG_FILES = [\n \"tenora.config.js\",\n \"tenora.config.mjs\",\n \"tenora.config.ts\",\n];\n\nexport const resolveConfigPath = (explicitPath?: string): string => {\n const cwd = process.cwd();\n if (explicitPath) {\n return path.isAbsolute(explicitPath)\n ? explicitPath\n : path.join(cwd, explicitPath);\n }\n\n for (const file of DEFAULT_CONFIG_FILES) {\n const full = path.join(cwd, file);\n if (fs.existsSync(full)) return full;\n }\n\n throw new Error(\n `Tenora: no config found. Looked for ${DEFAULT_CONFIG_FILES.join(\", \")} in ${cwd}.`\n );\n};\n\nexport const loadConfigModuleSync = (fullPath: string) => {\n const ext = path.extname(fullPath);\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const mod = require(fullPath);\n return mod;\n } catch (err: any) {\n if (ext === \".mjs\" || err?.code === \"ERR_REQUIRE_ESM\") {\n throw new Error(\n `Tenora: ${path.basename(fullPath)} is ESM. Use createTenoraFactoryAsync() or pass the config directly.`\n );\n }\n if (ext === \".ts\" || err?.code === \"ERR_UNKNOWN_FILE_EXTENSION\") {\n throw new Error(\n `Tenora: ${path.basename(fullPath)} is TypeScript. Use createTenoraFactoryAsync() with a TS loader (tsx/ts-node), or pass the config directly.`\n );\n }\n throw err;\n }\n};\n\nexport const loadConfigModuleAsync = async (fullPath: string) => {\n const href = pathToFileURL(fullPath).href;\n return import(href);\n};\n\nexport const unwrapConfig = (mod: any) => mod.default ?? mod.config ?? mod;\n"],"mappings":"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","names":["fs","knex","path","fs","path","fs","path","require","path","resolveBaseDatabaseName","applyConnectionDefaults","buildBaseConnection","conn","path","client","fs","path","fs","resolveClient","isPostgresClient","isMysqlClient","isSqliteClient","isMssqlClient","normalizePassword","escapePgIdent","escapeMysqlIdent","escapeMssqlIdent","resolveClient","isSqliteClient","isMssqlClient","normalizePassword","conn","fs","knex","path","isPostgresClient","isMysqlClient","escapePgIdent","escapeMysqlIdent","escapeMssqlIdent","decryptPassword"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Knex } from 'knex';
|
|
2
|
+
|
|
3
|
+
type TenoraClient = "pg" | "postgres" | "postgresql" | "mysql" | "mysql2" | "mariadb" | "sqlite3" | "better-sqlite3" | "sqlite" | "mssql" | "sqlserver";
|
|
4
|
+
type BaseConnectionConfig = {
|
|
5
|
+
/** Knex client/driver name (e.g., "pg", "mysql2"). Defaults to "pg". */
|
|
6
|
+
client?: TenoraClient;
|
|
7
|
+
/** Full Knex connection config override. */
|
|
8
|
+
connection?: Knex.ConnectionConfig;
|
|
9
|
+
host?: string;
|
|
10
|
+
port?: number;
|
|
11
|
+
user?: string;
|
|
12
|
+
password?: string;
|
|
13
|
+
database?: string;
|
|
14
|
+
/** admin database name (defaults to "postgres" for pg and "mysql" for mysql) */
|
|
15
|
+
adminDatabase?: string;
|
|
16
|
+
ssl?: Knex.PgConnectionConfig['ssl'] | Record<string, unknown>;
|
|
17
|
+
pool?: Knex.PoolConfig;
|
|
18
|
+
migrationsDir?: string;
|
|
19
|
+
seedsDir?: string;
|
|
20
|
+
};
|
|
21
|
+
type TenantConfig = {
|
|
22
|
+
/** prefix used when creating per-tenant users, defaults to `user_` */
|
|
23
|
+
userPrefix?: string;
|
|
24
|
+
/** directory with tenant migrations (passed to Knex) */
|
|
25
|
+
migrationsDir?: string;
|
|
26
|
+
/** directory with tenant seeds (passed to Knex) */
|
|
27
|
+
seedsDir?: string;
|
|
28
|
+
/** SQLite tenant DB directory (default: base DB dir or cwd) */
|
|
29
|
+
databaseDir?: string;
|
|
30
|
+
/** SQLite tenant DB filename suffix (default: ".sqlite") */
|
|
31
|
+
databaseSuffix?: string;
|
|
32
|
+
/** Optional tenant database name/filename resolver */
|
|
33
|
+
databaseName?: (tenantId: string) => string;
|
|
34
|
+
/** custom pool settings for tenant connections */
|
|
35
|
+
pool?: Knex.PoolConfig;
|
|
36
|
+
/** override ssl option for tenant connections */
|
|
37
|
+
ssl?: Knex.PgConnectionConfig['ssl'];
|
|
38
|
+
};
|
|
39
|
+
type TenantRegistryOptions = {
|
|
40
|
+
/** registry table name (default: tenora_tenants) */
|
|
41
|
+
table?: string;
|
|
42
|
+
/** primary key column for tenant id (default: id) */
|
|
43
|
+
idColumn?: string;
|
|
44
|
+
/** optional column for storing plaintext passwords (default: password) */
|
|
45
|
+
passwordColumn?: string;
|
|
46
|
+
/** optional column for storing encrypted passwords (default: encrypted_password) */
|
|
47
|
+
encryptedPasswordColumn?: string;
|
|
48
|
+
/** created timestamp column (default: created_at) */
|
|
49
|
+
createdAtColumn?: string;
|
|
50
|
+
/** updated timestamp column (default: updated_at) */
|
|
51
|
+
updatedAtColumn?: string;
|
|
52
|
+
};
|
|
53
|
+
type MultiTenantOptions = {
|
|
54
|
+
base: BaseConnectionConfig;
|
|
55
|
+
tenant?: TenantConfig;
|
|
56
|
+
knexOptions?: Omit<Knex.Config, "client" | "connection">;
|
|
57
|
+
registry?: TenantRegistryOptions;
|
|
58
|
+
/** optional encrypt hook for storing tenant DB passwords */
|
|
59
|
+
encryptPassword?: (plain: string) => string;
|
|
60
|
+
/** optional decrypt hook for reading encrypted passwords */
|
|
61
|
+
decryptPassword?: (encrypted: string) => string;
|
|
62
|
+
};
|
|
63
|
+
type TenantPasswordProvider = (tenantId: string) => Promise<string | undefined> | string | undefined;
|
|
64
|
+
type TenantAuthorizer<Req = any> = (tenantId: string, request: Req) => Promise<void> | void;
|
|
65
|
+
type TenantIdResolver<Req = any> = (request: Req) => Promise<string | undefined> | string | undefined;
|
|
66
|
+
interface TenantManager {
|
|
67
|
+
getBase(): Knex;
|
|
68
|
+
getTenant(tenantId: string, password?: string): Knex;
|
|
69
|
+
createTenantDb(tenantId: string, password?: string): Promise<void>;
|
|
70
|
+
destroyTenant(tenantId: string): Promise<void>;
|
|
71
|
+
destroyAll(): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
type TenantResolverOptions<Req extends {
|
|
74
|
+
[k: string]: any;
|
|
75
|
+
} = any> = {
|
|
76
|
+
manager: TenantManager;
|
|
77
|
+
tenantId: TenantIdResolver<Req>;
|
|
78
|
+
passwordProvider?: TenantPasswordProvider;
|
|
79
|
+
authorizer?: TenantAuthorizer<Req>;
|
|
80
|
+
attach?: (req: Req, tenantId: string, knex: Knex) => void;
|
|
81
|
+
};
|
|
82
|
+
type TenantResolver<Req> = (req: Req) => Promise<{
|
|
83
|
+
tenantId?: string;
|
|
84
|
+
knex?: Knex;
|
|
85
|
+
}>;
|
|
86
|
+
type TenantRecord = {
|
|
87
|
+
id: string;
|
|
88
|
+
password?: string;
|
|
89
|
+
encryptedPassword?: string;
|
|
90
|
+
};
|
|
91
|
+
type CliConfig = MultiTenantOptions;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Helper to get full config type hints in JS/TS config files.
|
|
95
|
+
*/
|
|
96
|
+
declare const defineTenoraConfig: <T extends MultiTenantOptions>(config: T) => T;
|
|
97
|
+
declare const createTenoraConfig: <T extends MultiTenantOptions>(config: T) => T;
|
|
98
|
+
|
|
99
|
+
declare const createTenoraFactory: (options?: MultiTenantOptions) => TenantManager;
|
|
100
|
+
declare const createTenoraFactoryAsync: (options?: MultiTenantOptions) => Promise<TenantManager>;
|
|
101
|
+
declare const createKnexFactory: (options?: MultiTenantOptions) => TenantManager;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Generate a random password suitable for per-tenant DB users.
|
|
105
|
+
*/
|
|
106
|
+
declare const generateTenantPassword: (length?: number) => string;
|
|
107
|
+
declare const encryptPassword: (password: string, cipherKey: string) => string;
|
|
108
|
+
declare const decryptPassword: (encryptedPassword: string, cipherKey: string) => string;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Framework-agnostic tenant resolver.
|
|
112
|
+
* Call the returned function inside your middleware/pre-handler to attach a tenant-bound knex to the request.
|
|
113
|
+
*/
|
|
114
|
+
declare const createTenantResolver: <Req extends {
|
|
115
|
+
[k: string]: any;
|
|
116
|
+
}>(opts: TenantResolverOptions<Req>) => TenantResolver<Req>;
|
|
117
|
+
|
|
118
|
+
export { type BaseConnectionConfig, type CliConfig, type MultiTenantOptions, type TenantAuthorizer, type TenantConfig, type TenantIdResolver, type TenantManager, type TenantPasswordProvider, type TenantRecord, type TenantRegistryOptions, type TenantResolver, type TenantResolverOptions, type TenoraClient, createKnexFactory, createTenantResolver, createTenoraConfig, createTenoraFactory, createTenoraFactoryAsync, decryptPassword, defineTenoraConfig, encryptPassword, generateTenantPassword };
|