@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/dist/index.js ADDED
@@ -0,0 +1,468 @@
1
+ // src/config.ts
2
+ var defineTenoraConfig = (config) => config;
3
+ var createTenoraConfig = defineTenoraConfig;
4
+
5
+ // src/knexFactory.ts
6
+ import knex from "knex";
7
+ import fs2 from "fs";
8
+ import path2 from "path";
9
+
10
+ // src/password.ts
11
+ import CryptoJS from "crypto-js";
12
+ var generateTenantPassword = (length = 32) => {
13
+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
14
+ let password = "";
15
+ for (let i = 0; i < length; i++) {
16
+ const randomIndex = Math.floor(Math.random() * charset.length);
17
+ password += charset[randomIndex];
18
+ }
19
+ return password;
20
+ };
21
+ var encryptPassword = (password, cipherKey) => {
22
+ if (typeof cipherKey !== "string" || cipherKey.length === 0) {
23
+ throw new Error("Tenora: cipherKey must be a non-empty string.");
24
+ }
25
+ return CryptoJS.AES.encrypt(password, cipherKey).toString();
26
+ };
27
+ var decryptPassword = (encryptedPassword, cipherKey) => {
28
+ if (typeof cipherKey !== "string" || cipherKey.length === 0) {
29
+ throw new Error("Tenora: cipherKey must be a non-empty string.");
30
+ }
31
+ const bytes = CryptoJS.AES.decrypt(encryptedPassword, cipherKey);
32
+ return bytes.toString(CryptoJS.enc.Utf8);
33
+ };
34
+
35
+ // src/tenantRegistry.ts
36
+ var DEFAULT_REGISTRY = {
37
+ table: "tenora_tenants",
38
+ idColumn: "id",
39
+ passwordColumn: "password",
40
+ encryptedPasswordColumn: "encrypted_password",
41
+ createdAtColumn: "created_at",
42
+ updatedAtColumn: "updated_at"
43
+ };
44
+ var resolveRegistry = (options) => ({
45
+ ...DEFAULT_REGISTRY,
46
+ ...options.registry ?? {}
47
+ });
48
+ var resolveEncrypt = (options) => {
49
+ if (options.encryptPassword) return options.encryptPassword;
50
+ const key = process.env.TENORA_KEY;
51
+ if (key) {
52
+ return (plain) => encryptPassword(plain, key);
53
+ }
54
+ return void 0;
55
+ };
56
+ var ensureRegistryTable = async (base, options) => {
57
+ const registry = resolveRegistry(options);
58
+ const exists = await base.schema.hasTable(registry.table);
59
+ if (!exists) {
60
+ throw new Error(
61
+ `Tenora: tenant registry table "${registry.table}" not found. Run 'tenora migrate' to create it.`
62
+ );
63
+ }
64
+ };
65
+ var upsertTenantInRegistry = async (base, options, tenantId, password) => {
66
+ const registry = resolveRegistry(options);
67
+ await ensureRegistryTable(base, options);
68
+ const encrypt = resolveEncrypt(options);
69
+ const encrypted = password && encrypt ? encrypt(password) : void 0;
70
+ const record = {
71
+ [registry.idColumn]: tenantId
72
+ };
73
+ if (encrypted) {
74
+ record[registry.encryptedPasswordColumn] = encrypted;
75
+ } else if (password) {
76
+ record[registry.passwordColumn] = password;
77
+ }
78
+ await base(registry.table).insert(record).onConflict(registry.idColumn).merge(record);
79
+ };
80
+
81
+ // src/configLoader.ts
82
+ import fs from "fs";
83
+ import path from "path";
84
+ import { pathToFileURL } from "url";
85
+ import { createRequire } from "module";
86
+ var require2 = createRequire(import.meta.url);
87
+ var DEFAULT_CONFIG_FILES = [
88
+ "tenora.config.js",
89
+ "tenora.config.mjs",
90
+ "tenora.config.ts"
91
+ ];
92
+ var resolveConfigPath = (explicitPath) => {
93
+ const cwd = process.cwd();
94
+ if (explicitPath) {
95
+ return path.isAbsolute(explicitPath) ? explicitPath : path.join(cwd, explicitPath);
96
+ }
97
+ for (const file of DEFAULT_CONFIG_FILES) {
98
+ const full = path.join(cwd, file);
99
+ if (fs.existsSync(full)) return full;
100
+ }
101
+ throw new Error(
102
+ `Tenora: no config found. Looked for ${DEFAULT_CONFIG_FILES.join(", ")} in ${cwd}.`
103
+ );
104
+ };
105
+ var loadConfigModuleSync = (fullPath) => {
106
+ const ext = path.extname(fullPath);
107
+ try {
108
+ const mod = require2(fullPath);
109
+ return mod;
110
+ } catch (err) {
111
+ if (ext === ".mjs" || err?.code === "ERR_REQUIRE_ESM") {
112
+ throw new Error(
113
+ `Tenora: ${path.basename(fullPath)} is ESM. Use createTenoraFactoryAsync() or pass the config directly.`
114
+ );
115
+ }
116
+ if (ext === ".ts" || err?.code === "ERR_UNKNOWN_FILE_EXTENSION") {
117
+ throw new Error(
118
+ `Tenora: ${path.basename(fullPath)} is TypeScript. Use createTenoraFactoryAsync() with a TS loader (tsx/ts-node), or pass the config directly.`
119
+ );
120
+ }
121
+ throw err;
122
+ }
123
+ };
124
+ var loadConfigModuleAsync = async (fullPath) => {
125
+ const href = pathToFileURL(fullPath).href;
126
+ return import(href);
127
+ };
128
+ var unwrapConfig = (mod) => mod.default ?? mod.config ?? mod;
129
+
130
+ // src/knexFactory.ts
131
+ var resolveClient = (value) => value ?? "pg";
132
+ var isPostgresClient = (client) => client === "pg" || client === "postgres" || client === "postgresql";
133
+ var isMysqlClient = (client) => client === "mysql" || client === "mysql2" || client === "mariadb";
134
+ var isSqliteClient = (client) => client === "sqlite3" || client === "better-sqlite3" || client === "sqlite";
135
+ var isMssqlClient = (client) => client === "mssql" || client === "sqlserver";
136
+ var normalizePassword = (value) => {
137
+ if (value === void 0 || value === null) return void 0;
138
+ if (typeof value === "string") return value;
139
+ return String(value);
140
+ };
141
+ var escapePgIdent = (value) => value.replace(/"/g, '""');
142
+ var escapeMysqlIdent = (value) => value.replace(/`/g, "``");
143
+ var escapeMssqlIdent = (value) => value.replace(/]/g, "]]");
144
+ var escapeSqlString = (value) => value.replace(/'/g, "''");
145
+ var loadDefaultConfig = () => {
146
+ const explicit = process.env.TENORA_CONFIG;
147
+ const fullPath = resolveConfigPath(explicit ?? void 0);
148
+ const module = loadConfigModuleSync(fullPath);
149
+ const cfg = unwrapConfig(module);
150
+ if (!cfg) {
151
+ throw new Error(
152
+ `Tenora: config file ${path2.basename(fullPath)} did not export a config object.`
153
+ );
154
+ }
155
+ return cfg;
156
+ };
157
+ var loadDefaultConfigAsync = async () => {
158
+ const explicit = process.env.TENORA_CONFIG;
159
+ const fullPath = resolveConfigPath(explicit ?? void 0);
160
+ const module = await loadConfigModuleAsync(fullPath);
161
+ const cfg = unwrapConfig(module);
162
+ if (!cfg) {
163
+ throw new Error(
164
+ `Tenora: config file ${path2.basename(fullPath)} did not export a config object.`
165
+ );
166
+ }
167
+ return cfg;
168
+ };
169
+ var defaultPool = { min: 2, max: 10, acquireTimeoutMillis: 6e4, idleTimeoutMillis: 6e4 };
170
+ var buildTenoraFactory = (resolved) => {
171
+ const { base, tenant = {}, knexOptions = {} } = resolved;
172
+ const cache = /* @__PURE__ */ new Map();
173
+ const basePassword = normalizePassword(base.password);
174
+ const client = resolveClient(base.client);
175
+ const resolveBaseDatabaseName = () => {
176
+ if (base.connection) {
177
+ const conn = base.connection;
178
+ if (isSqliteClient(client)) {
179
+ const filename = conn.filename ?? base.database;
180
+ if (!filename) {
181
+ throw new Error("Tenora: base.database or base.connection.filename is required for SQLite.");
182
+ }
183
+ return filename;
184
+ }
185
+ const dbName = conn.database ?? base.database;
186
+ if (!dbName) {
187
+ throw new Error("Tenora: base.database is required.");
188
+ }
189
+ return dbName;
190
+ }
191
+ if (!base.database) {
192
+ throw new Error("Tenora: base.database is required.");
193
+ }
194
+ return base.database;
195
+ };
196
+ const applyConnectionDefaults = (conn) => {
197
+ if (conn.user === void 0 && base.user !== void 0) conn.user = base.user;
198
+ if (conn.port === void 0 && base.port !== void 0) conn.port = base.port;
199
+ if (isMssqlClient(client)) {
200
+ if (conn.server === void 0 && base.host !== void 0) conn.server = base.host;
201
+ } else if (conn.host === void 0 && base.host !== void 0) {
202
+ conn.host = base.host;
203
+ }
204
+ if (conn.ssl === void 0 && base.ssl !== void 0) conn.ssl = base.ssl;
205
+ const normalized = normalizePassword(conn.password ?? basePassword);
206
+ if (normalized !== void 0) {
207
+ conn.password = normalized;
208
+ } else {
209
+ delete conn.password;
210
+ }
211
+ };
212
+ const buildBaseConnection = (databaseOverride) => {
213
+ if (base.connection) {
214
+ const conn2 = { ...base.connection };
215
+ applyConnectionDefaults(conn2);
216
+ if (databaseOverride !== void 0) {
217
+ if (isSqliteClient(client)) {
218
+ conn2.filename = databaseOverride;
219
+ delete conn2.database;
220
+ } else {
221
+ conn2.database = databaseOverride;
222
+ }
223
+ } else if (isSqliteClient(client)) {
224
+ if (conn2.filename === void 0 && base.database) conn2.filename = base.database;
225
+ } else if (conn2.database === void 0 && base.database) {
226
+ conn2.database = base.database;
227
+ }
228
+ return conn2;
229
+ }
230
+ if (isSqliteClient(client)) {
231
+ const filename = databaseOverride ?? base.database;
232
+ if (!filename) {
233
+ throw new Error("Tenora: base.database is required for SQLite.");
234
+ }
235
+ return { filename };
236
+ }
237
+ if (!base.host || !base.user || !base.database) {
238
+ throw new Error("Tenora: base connection is incomplete. Provide base.connection or host/user/database.");
239
+ }
240
+ const conn = {
241
+ ...isMssqlClient(client) ? { server: base.host } : { host: base.host },
242
+ port: base.port,
243
+ user: base.user,
244
+ database: databaseOverride ?? base.database,
245
+ ssl: base.ssl ?? false
246
+ };
247
+ if (basePassword !== void 0) conn.password = basePassword;
248
+ return conn;
249
+ };
250
+ const baseKnexConfig = {
251
+ client,
252
+ useNullAsDefault: true,
253
+ connection: buildBaseConnection(resolveBaseDatabaseName()),
254
+ pool: base.pool ?? defaultPool,
255
+ migrations: base.migrationsDir ? { directory: base.migrationsDir } : void 0,
256
+ seeds: base.seedsDir ? { directory: base.seedsDir } : void 0,
257
+ ...knexOptions
258
+ };
259
+ const baseClient = knex(baseKnexConfig);
260
+ const resolveTenantDatabaseName = (tenantId) => tenant.databaseName ? tenant.databaseName(tenantId) : tenantId;
261
+ const resolveSqliteTenantFilename = (tenantId) => {
262
+ const baseDb = resolveBaseDatabaseName();
263
+ const baseDir = tenant.databaseDir ?? path2.dirname(baseDb ?? process.cwd());
264
+ const name = tenant.databaseName ? tenant.databaseName(tenantId) : `${tenantId}${tenant.databaseSuffix ?? ".sqlite"}`;
265
+ return path2.isAbsolute(name) ? name : path2.join(baseDir, name);
266
+ };
267
+ const buildTenantConfig = (tenantId, password) => {
268
+ const tenantPassword = normalizePassword(password ?? basePassword);
269
+ const hasTenantPassword = password !== void 0 && password !== null;
270
+ const tenantDb = isSqliteClient(client) ? resolveSqliteTenantFilename(tenantId) : resolveTenantDatabaseName(tenantId);
271
+ const connection = buildBaseConnection(tenantDb);
272
+ if (!isSqliteClient(client) && hasTenantPassword) {
273
+ connection.user = `${tenant.userPrefix ?? "user_"}${tenantId}`;
274
+ if (tenantPassword !== void 0) {
275
+ connection.password = tenantPassword;
276
+ } else {
277
+ delete connection.password;
278
+ }
279
+ }
280
+ if (tenant.ssl !== void 0) {
281
+ connection.ssl = tenant.ssl;
282
+ }
283
+ return {
284
+ client,
285
+ useNullAsDefault: true,
286
+ connection,
287
+ pool: tenant.pool ?? base.pool ?? defaultPool,
288
+ migrations: tenant.migrationsDir ? { directory: tenant.migrationsDir } : void 0,
289
+ seeds: tenant.seedsDir ? { directory: tenant.seedsDir } : void 0,
290
+ ...knexOptions
291
+ };
292
+ };
293
+ const getTenant = (tenantId, password) => {
294
+ const cached = cache.get(tenantId);
295
+ if (cached) return cached;
296
+ const client2 = knex(buildTenantConfig(tenantId, password));
297
+ cache.set(tenantId, client2);
298
+ return client2;
299
+ };
300
+ const destroyTenant = async (tenantId) => {
301
+ const client2 = cache.get(tenantId);
302
+ if (client2) {
303
+ await client2.destroy();
304
+ cache.delete(tenantId);
305
+ }
306
+ };
307
+ const destroyAll = async () => {
308
+ await Promise.all([...cache.values()].map((k) => k.destroy()));
309
+ cache.clear();
310
+ await baseClient.destroy();
311
+ };
312
+ const createTenantDb = async (tenantId, password) => {
313
+ await ensureRegistryTable(baseClient, resolved);
314
+ const tenantPassword = normalizePassword(password);
315
+ const hasTenantPassword = password !== void 0 && password !== null;
316
+ if (isSqliteClient(client)) {
317
+ const filename = resolveSqliteTenantFilename(tenantId);
318
+ if (filename !== ":memory:") {
319
+ fs2.mkdirSync(path2.dirname(filename), { recursive: true });
320
+ if (!fs2.existsSync(filename)) {
321
+ fs2.closeSync(fs2.openSync(filename, "a"));
322
+ }
323
+ }
324
+ } else {
325
+ const adminDb = base.adminDatabase ?? (isPostgresClient(client) ? "postgres" : isMysqlClient(client) ? "mysql" : isMssqlClient(client) ? "master" : resolveBaseDatabaseName());
326
+ const admin = knex({
327
+ ...baseKnexConfig,
328
+ connection: buildBaseConnection(adminDb)
329
+ });
330
+ try {
331
+ if (isPostgresClient(client)) {
332
+ const result = await admin.raw(`SELECT 1 FROM pg_database WHERE datname = ?`, [tenantId]);
333
+ if (result?.rows?.length) {
334
+ throw new Error(`Database "${tenantId}" already exists`);
335
+ }
336
+ const safeDb = escapePgIdent(tenantId);
337
+ await admin.raw(`CREATE DATABASE "${safeDb}"`);
338
+ if (tenantPassword && hasTenantPassword) {
339
+ const userName = `${tenant.userPrefix ?? "user_"}${tenantId}`;
340
+ const safeUser = escapePgIdent(userName);
341
+ const safePwd = escapeSqlString(tenantPassword);
342
+ await admin.raw(`CREATE USER "${safeUser}" WITH PASSWORD '${safePwd}'`);
343
+ await admin.raw(`GRANT ALL PRIVILEGES ON DATABASE "${safeDb}" TO "${safeUser}"`);
344
+ }
345
+ } else if (isMysqlClient(client)) {
346
+ const result = await admin.raw(
347
+ `SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?`,
348
+ [tenantId]
349
+ );
350
+ if (result?.[0]?.length) {
351
+ throw new Error(`Database "${tenantId}" already exists`);
352
+ }
353
+ const safeDb = escapeMysqlIdent(tenantId);
354
+ await admin.raw(`CREATE DATABASE \`${safeDb}\``);
355
+ if (tenantPassword && hasTenantPassword) {
356
+ const userName = `${tenant.userPrefix ?? "user_"}${tenantId}`;
357
+ const safeUser = escapeSqlString(userName);
358
+ const safePwd = escapeSqlString(tenantPassword);
359
+ await admin.raw(`CREATE USER IF NOT EXISTS '${safeUser}'@'%' IDENTIFIED BY '${safePwd}'`);
360
+ await admin.raw(`GRANT ALL PRIVILEGES ON \`${safeDb}\`.* TO '${safeUser}'@'%'`);
361
+ }
362
+ } else if (isMssqlClient(client)) {
363
+ const safeDb = escapeMssqlIdent(tenantId);
364
+ await admin.raw(
365
+ `IF DB_ID(N'${escapeSqlString(tenantId)}') IS NULL CREATE DATABASE [${safeDb}]`
366
+ );
367
+ if (tenantPassword && hasTenantPassword) {
368
+ const userName = `${tenant.userPrefix ?? "user_"}${tenantId}`;
369
+ const safeUser = escapeMssqlIdent(userName);
370
+ const safePwd = escapeSqlString(tenantPassword);
371
+ await admin.raw(
372
+ `IF NOT EXISTS (SELECT name FROM sys.server_principals WHERE name = N'${escapeSqlString(userName)}') CREATE LOGIN [${safeUser}] WITH PASSWORD = '${safePwd}'`
373
+ );
374
+ const tenantAdmin = knex({
375
+ ...baseKnexConfig,
376
+ connection: buildBaseConnection(tenantId)
377
+ });
378
+ try {
379
+ await tenantAdmin.raw(
380
+ `IF NOT EXISTS (SELECT name FROM sys.database_principals WHERE name = N'${escapeSqlString(userName)}') CREATE USER [${safeUser}] FOR LOGIN [${safeUser}]`
381
+ );
382
+ await tenantAdmin.raw(`EXEC sp_addrolemember 'db_owner', '${safeUser}'`);
383
+ } finally {
384
+ await tenantAdmin.destroy();
385
+ }
386
+ }
387
+ } else {
388
+ throw new Error(
389
+ `Tenora: createTenantDb is only supported for Postgres, MySQL/MariaDB, SQLite, and SQL Server clients (got "${client}").`
390
+ );
391
+ }
392
+ } finally {
393
+ await admin.destroy();
394
+ }
395
+ }
396
+ const tenantKnex = knex(buildTenantConfig(tenantId, tenantPassword));
397
+ try {
398
+ if (tenant.migrationsDir) {
399
+ await tenantKnex.migrate.latest();
400
+ }
401
+ if (tenant.seedsDir) {
402
+ await tenantKnex.seed.run();
403
+ }
404
+ } finally {
405
+ await tenantKnex.destroy();
406
+ cache.delete(tenantId);
407
+ }
408
+ await upsertTenantInRegistry(baseClient, resolved, tenantId, password);
409
+ };
410
+ return {
411
+ getBase: () => baseClient,
412
+ getTenant,
413
+ createTenantDb,
414
+ destroyTenant,
415
+ destroyAll
416
+ };
417
+ };
418
+ var createTenoraFactory = (options) => {
419
+ const resolved = options ?? loadDefaultConfig();
420
+ return buildTenoraFactory(resolved);
421
+ };
422
+ var createTenoraFactoryAsync = async (options) => {
423
+ const resolved = options ?? await loadDefaultConfigAsync();
424
+ return buildTenoraFactory(resolved);
425
+ };
426
+ var createKnexFactory = createTenoraFactory;
427
+
428
+ // src/tenantResolver.ts
429
+ var createTenantResolver = (opts) => {
430
+ const {
431
+ manager,
432
+ tenantId: tenantIdResolver,
433
+ passwordProvider,
434
+ authorizer,
435
+ attach
436
+ } = opts;
437
+ return async (req) => {
438
+ const tenantId = await tenantIdResolver(req);
439
+ if (!tenantId) return {};
440
+ let password;
441
+ if (passwordProvider) {
442
+ password = await passwordProvider(tenantId);
443
+ }
444
+ const knex2 = manager.getTenant(tenantId, password);
445
+ if (authorizer) {
446
+ await authorizer(tenantId, req);
447
+ }
448
+ if (attach) {
449
+ attach(req, tenantId, knex2);
450
+ } else {
451
+ req.tenantId = tenantId;
452
+ req.knex = knex2;
453
+ }
454
+ return { tenantId, knex: knex2 };
455
+ };
456
+ };
457
+ export {
458
+ createKnexFactory,
459
+ createTenantResolver,
460
+ createTenoraConfig,
461
+ createTenoraFactory,
462
+ createTenoraFactoryAsync,
463
+ decryptPassword,
464
+ defineTenoraConfig,
465
+ encryptPassword,
466
+ generateTenantPassword
467
+ };
468
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/knexFactory.ts","../src/password.ts","../src/tenantRegistry.ts","../src/configLoader.ts","../src/tenantResolver.ts"],"sourcesContent":["import type { MultiTenantOptions } from \"./types\";\n\n/**\n * Helper to get full config type hints in JS/TS config files.\n */\nexport const defineTenoraConfig = <T extends MultiTenantOptions>(config: T): T => config;\n\n// Backwards-friendly alias\nexport const createTenoraConfig = defineTenoraConfig;\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 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 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 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","import type { Knex } from \"knex\";\nimport type { TenantResolverOptions, TenantResolver } from \"./types\";\n\n/**\n * Framework-agnostic tenant resolver.\n * Call the returned function inside your middleware/pre-handler to attach a tenant-bound knex to the request.\n */\nexport const createTenantResolver = <Req extends { [k: string]: any }>(\n opts: TenantResolverOptions<Req>\n): TenantResolver<Req> => {\n const {\n manager,\n tenantId: tenantIdResolver,\n passwordProvider,\n authorizer,\n attach,\n } = opts;\n\n return async (req: Req): Promise<{ tenantId?: string; knex?: Knex }> => {\n const tenantId = await tenantIdResolver(req);\n if (!tenantId) return {};\n\n let password: string | undefined;\n if (passwordProvider) {\n password = await passwordProvider(tenantId);\n }\n\n const knex = manager.getTenant(tenantId, password);\n\n if (authorizer) {\n await authorizer(tenantId, req);\n }\n\n // default attach shape: req.tenantId + req.knex\n if (attach) {\n attach(req, tenantId, knex);\n } else {\n (req as any).tenantId = tenantId;\n (req as any).knex = knex;\n }\n\n return { tenantId, knex };\n };\n};\n"],"mappings":";AAKO,IAAM,qBAAqB,CAA+B,WAAiB;AAG3E,IAAM,qBAAqB;;;ACRlC,OAAO,UAAoB;AAC3B,OAAOA,SAAQ;AACf,OAAOC,WAAU;;;ACFjB,OAAO,cAAc;AAKd,IAAM,yBAAyB,CAAC,SAAS,OAAe;AAC7D,QAAM,UAAU;AAChB,MAAI,WAAW;AAEf,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,cAAc,KAAK,MAAM,KAAK,OAAO,IAAI,QAAQ,MAAM;AAC7D,gBAAY,QAAQ,WAAW;AAAA,EACjC;AAEA,SAAO;AACT;AAEO,IAAM,kBAAkB,CAAC,UAAkB,cAA8B;AAC9E,MAAI,OAAO,cAAc,YAAY,UAAU,WAAW,GAAG;AAC3D,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AACA,SAAO,SAAS,IAAI,QAAQ,UAAU,SAAS,EAAE,SAAS;AAC5D;AAEO,IAAM,kBAAkB,CAAC,mBAA2B,cAA8B;AACvF,MAAI,OAAO,cAAc,YAAY,UAAU,WAAW,GAAG;AAC3D,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AACA,QAAM,QAAQ,SAAS,IAAI,QAAQ,mBAAmB,SAAS;AAC/D,SAAO,MAAM,SAAS,SAAS,IAAI,IAAI;AACzC;;;ACtBA,IAAM,mBAAoD;AAAA,EACxD,OAAO;AAAA,EACP,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,yBAAyB;AAAA,EACzB,iBAAiB;AAAA,EACjB,iBAAiB;AACnB;AAEO,IAAM,kBAAkB,CAC7B,aACqC;AAAA,EACrC,GAAG;AAAA,EACH,GAAI,QAAQ,YAAY,CAAC;AAC3B;AAEA,IAAM,iBAAiB,CAAC,YAAgC;AACtD,MAAI,QAAQ,gBAAiB,QAAO,QAAQ;AAC5C,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,KAAK;AACP,WAAO,CAAC,UAAkB,gBAAe,OAAO,GAAG;AAAA,EACrD;AACA,SAAO;AACT;AAyEO,IAAM,sBAAsB,OACjC,MACA,YACG;AACH,QAAM,WAAW,gBAAgB,OAAO;AACxC,QAAM,SAAS,MAAM,KAAK,OAAO,SAAS,SAAS,KAAK;AACxD,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR,kCAAkC,SAAS,KAAK;AAAA,IAClD;AAAA,EACF;AACF;AAgBO,IAAM,yBAAyB,OACpC,MACA,SACA,UACA,aACG;AACH,QAAM,WAAW,gBAAgB,OAAO;AACxC,QAAM,oBAAoB,MAAM,OAAO;AAEvC,QAAM,UAAU,eAAe,OAAO;AACtC,QAAM,YAAY,YAAY,UAAU,QAAQ,QAAQ,IAAI;AAE5D,QAAM,SAAiC;AAAA,IACrC,CAAC,SAAS,QAAQ,GAAG;AAAA,EACvB;AAEA,MAAI,WAAW;AACb,WAAO,SAAS,uBAAuB,IAAI;AAAA,EAC7C,WAAW,UAAU;AACnB,WAAO,SAAS,cAAc,IAAI;AAAA,EACpC;AAEA,QAAM,KAAK,SAAS,KAAK,EACtB,OAAO,MAAM,EACb,WAAW,SAAS,QAAQ,EAC5B,MAAM,MAAM;AACjB;;;AC7JA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAE9B,IAAMC,WAAU,cAAc,YAAY,GAAG;AAEtC,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,oBAAoB,CAAC,iBAAkC;AAClE,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,cAAc;AAChB,WAAO,KAAK,WAAW,YAAY,IAC/B,eACA,KAAK,KAAK,KAAK,YAAY;AAAA,EACjC;AAEA,aAAW,QAAQ,sBAAsB;AACvC,UAAM,OAAO,KAAK,KAAK,KAAK,IAAI;AAChC,QAAI,GAAG,WAAW,IAAI,EAAG,QAAO;AAAA,EAClC;AAEA,QAAM,IAAI;AAAA,IACR,uCAAuC,qBAAqB,KAAK,IAAI,CAAC,OAAO,GAAG;AAAA,EAClF;AACF;AAEO,IAAM,uBAAuB,CAAC,aAAqB;AACxD,QAAM,MAAM,KAAK,QAAQ,QAAQ;AACjC,MAAI;AAEF,UAAM,MAAMA,SAAQ,QAAQ;AAC5B,WAAO;AAAA,EACT,SAAS,KAAU;AACjB,QAAI,QAAQ,UAAU,KAAK,SAAS,mBAAmB;AACrD,YAAM,IAAI;AAAA,QACR,WAAW,KAAK,SAAS,QAAQ,CAAC;AAAA,MACpC;AAAA,IACF;AACA,QAAI,QAAQ,SAAS,KAAK,SAAS,8BAA8B;AAC/D,YAAM,IAAI;AAAA,QACR,WAAW,KAAK,SAAS,QAAQ,CAAC;AAAA,MACpC;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACF;AAEO,IAAM,wBAAwB,OAAO,aAAqB;AAC/D,QAAM,OAAO,cAAc,QAAQ,EAAE;AACrC,SAAO,OAAO;AAChB;AAEO,IAAM,eAAe,CAAC,QAAa,IAAI,WAAW,IAAI,UAAU;;;AHlDvE,IAAM,gBAAgB,CAAC,UAAuC,SAAS;AAEvE,IAAM,mBAAmB,CAAC,WACxB,WAAW,QAAQ,WAAW,cAAc,WAAW;AAEzD,IAAM,gBAAgB,CAAC,WACrB,WAAW,WAAW,WAAW,YAAY,WAAW;AAE1D,IAAM,iBAAiB,CAAC,WACtB,WAAW,aAAa,WAAW,oBAAoB,WAAW;AAEpE,IAAM,gBAAgB,CAAC,WACrB,WAAW,WAAW,WAAW;AAEnC,IAAM,oBAAoB,CAAC,UAAuC;AAChE,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO,OAAO,KAAK;AACrB;AAEA,IAAM,gBAAgB,CAAC,UAAkB,MAAM,QAAQ,MAAM,IAAM;AACnE,IAAM,mBAAmB,CAAC,UAAkB,MAAM,QAAQ,MAAM,IAAI;AACpE,IAAM,mBAAmB,CAAC,UAAkB,MAAM,QAAQ,MAAM,IAAI;AACpE,IAAM,kBAAkB,CAAC,UAAkB,MAAM,QAAQ,MAAM,IAAI;AAEnE,IAAM,oBAAoB,MAA0B;AAClD,QAAM,WAAW,QAAQ,IAAI;AAC7B,QAAM,WAAW,kBAAkB,YAAY,MAAS;AACxD,QAAM,SAAS,qBAAqB,QAAQ;AAC5C,QAAM,MAAM,aAAa,MAAM;AAC/B,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR,uBAAuBC,MAAK,SAAS,QAAQ,CAAC;AAAA,IAChD;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,yBAAyB,YAAyC;AAC7E,QAAM,WAAW,QAAQ,IAAI;AAC7B,QAAM,WAAW,kBAAkB,YAAY,MAAS;AACxD,QAAM,SAAS,MAAM,sBAAsB,QAAQ;AACnD,QAAM,MAAM,aAAa,MAAM;AAC/B,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR,uBAAuBA,MAAK,SAAS,QAAQ,CAAC;AAAA,IAChD;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAM,cAA+B,EAAE,KAAK,GAAG,KAAK,IAAI,sBAAsB,KAAQ,mBAAmB,IAAO;AAEhH,IAAM,qBAAqB,CAAC,aAAgD;AAC1E,QAAM,EAAE,MAAM,SAAS,CAAC,GAAG,cAAc,CAAC,EAAE,IAAI;AAChD,QAAM,QAAQ,oBAAI,IAAkB;AACpC,QAAM,eAAe,kBAAkB,KAAK,QAAQ;AACpD,QAAM,SAAS,cAAc,KAAK,MAAM;AAExC,QAAM,0BAA0B,MAAc;AAC5C,QAAI,KAAK,YAAY;AACnB,YAAM,OAAO,KAAK;AAClB,UAAI,eAAe,MAAM,GAAG;AAC1B,cAAM,WAAY,KAAK,YAAmC,KAAK;AAC/D,YAAI,CAAC,UAAU;AACb,gBAAM,IAAI,MAAM,2EAA2E;AAAA,QAC7F;AACA,eAAO;AAAA,MACT;AACA,YAAM,SAAU,KAAK,YAAmC,KAAK;AAC7D,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,oCAAoC;AAAA,MACtD;AACA,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AACA,WAAO,KAAK;AAAA,EACd;AAEA,QAAM,0BAA0B,CAAC,SAAkC;AACjE,QAAI,KAAK,SAAS,UAAa,KAAK,SAAS,OAAW,MAAK,OAAO,KAAK;AACzE,QAAI,KAAK,SAAS,UAAa,KAAK,SAAS,OAAW,MAAK,OAAO,KAAK;AACzE,QAAI,cAAc,MAAM,GAAG;AACzB,UAAI,KAAK,WAAW,UAAa,KAAK,SAAS,OAAW,MAAK,SAAS,KAAK;AAAA,IAC/E,WAAW,KAAK,SAAS,UAAa,KAAK,SAAS,QAAW;AAC7D,WAAK,OAAO,KAAK;AAAA,IACnB;AACA,QAAI,KAAK,QAAQ,UAAa,KAAK,QAAQ,OAAW,MAAK,MAAM,KAAK;AACtE,UAAM,aAAa,kBAAkB,KAAK,YAAY,YAAY;AAClE,QAAI,eAAe,QAAW;AAC5B,WAAK,WAAW;AAAA,IAClB,OAAO;AACL,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AAEA,QAAM,sBAAsB,CAAC,qBAA2D;AACtF,QAAI,KAAK,YAAY;AACnB,YAAMC,QAAO,EAAE,GAAI,KAAK,WAAkD;AAC1E,8BAAwBA,KAAI;AAC5B,UAAI,qBAAqB,QAAW;AAClC,YAAI,eAAe,MAAM,GAAG;AAC1B,UAAAA,MAAK,WAAW;AAChB,iBAAOA,MAAK;AAAA,QACd,OAAO;AACL,UAAAA,MAAK,WAAW;AAAA,QAClB;AAAA,MACF,WAAW,eAAe,MAAM,GAAG;AACjC,YAAIA,MAAK,aAAa,UAAa,KAAK,SAAU,CAAAA,MAAK,WAAW,KAAK;AAAA,MACzE,WAAWA,MAAK,aAAa,UAAa,KAAK,UAAU;AACvD,QAAAA,MAAK,WAAW,KAAK;AAAA,MACvB;AACA,aAAOA;AAAA,IACT;AAEA,QAAI,eAAe,MAAM,GAAG;AAC1B,YAAM,WAAW,oBAAoB,KAAK;AAC1C,UAAI,CAAC,UAAU;AACb,cAAM,IAAI,MAAM,+CAA+C;AAAA,MACjE;AACA,aAAO,EAAE,SAAS;AAAA,IACpB;AAEA,QAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,QAAQ,CAAC,KAAK,UAAU;AAC9C,YAAM,IAAI,MAAM,uFAAuF;AAAA,IACzG;AAEA,UAAM,OAAgC;AAAA,MACpC,GAAI,cAAc,MAAM,IAAI,EAAE,QAAQ,KAAK,KAAK,IAAI,EAAE,MAAM,KAAK,KAAK;AAAA,MACtE,MAAM,KAAK;AAAA,MACX,MAAM,KAAK;AAAA,MACX,UAAU,oBAAoB,KAAK;AAAA,MACnC,KAAK,KAAK,OAAO;AAAA,IACnB;AACA,QAAI,iBAAiB,OAAW,MAAK,WAAW;AAChD,WAAO;AAAA,EACT;AAEA,QAAM,iBAA8B;AAAA,IAClC;AAAA,IACA,kBAAkB;AAAA,IAClB,YAAY,oBAAoB,wBAAwB,CAAC;AAAA,IACzD,MAAM,KAAK,QAAQ;AAAA,IACnB,YAAY,KAAK,gBAAgB,EAAE,WAAW,KAAK,cAAc,IAAI;AAAA,IACrE,OAAO,KAAK,WAAW,EAAE,WAAW,KAAK,SAAS,IAAI;AAAA,IACtD,GAAG;AAAA,EACL;AAEA,QAAM,aAAa,KAAK,cAAc;AAEtC,QAAM,4BAA4B,CAAC,aACjC,OAAO,eAAe,OAAO,aAAa,QAAQ,IAAI;AAExD,QAAM,8BAA8B,CAAC,aAA6B;AAChE,UAAM,SAAS,wBAAwB;AACvC,UAAM,UAAU,OAAO,eAAeD,MAAK,QAAQ,UAAU,QAAQ,IAAI,CAAC;AAC1E,UAAM,OAAO,OAAO,eAChB,OAAO,aAAa,QAAQ,IAC5B,GAAG,QAAQ,GAAG,OAAO,kBAAkB,SAAS;AACpD,WAAOA,MAAK,WAAW,IAAI,IAAI,OAAOA,MAAK,KAAK,SAAS,IAAI;AAAA,EAC/D;AAEA,QAAM,oBAAoB,CAAC,UAAkB,aAAmC;AAC9E,UAAM,iBAAiB,kBAAkB,YAAY,YAAY;AACjE,UAAM,oBAAoB,aAAa,UAAa,aAAa;AACjE,UAAM,WAAW,eAAe,MAAM,IAClC,4BAA4B,QAAQ,IACpC,0BAA0B,QAAQ;AACtC,UAAM,aAAa,oBAAoB,QAAQ;AAE/C,QAAI,CAAC,eAAe,MAAM,KAAK,mBAAmB;AAChD,iBAAW,OAAO,GAAG,OAAO,cAAc,OAAO,GAAG,QAAQ;AAC5D,UAAI,mBAAmB,QAAW;AAChC,mBAAW,WAAW;AAAA,MACxB,OAAO;AACL,eAAO,WAAW;AAAA,MACpB;AAAA,IACF;AAEA,QAAI,OAAO,QAAQ,QAAW;AAC5B,iBAAW,MAAM,OAAO;AAAA,IAC1B;AAEA,WAAQ;AAAA,MACN;AAAA,MACA,kBAAkB;AAAA,MAClB;AAAA,MACA,MAAM,OAAO,QAAQ,KAAK,QAAQ;AAAA,MAClC,YAAY,OAAO,gBAAgB,EAAE,WAAW,OAAO,cAAc,IAAI;AAAA,MACzE,OAAO,OAAO,WAAW,EAAE,WAAW,OAAO,SAAS,IAAI;AAAA,MAC1D,GAAG;AAAA,IACL;AAAA,EACF;AAMA,QAAM,YAAY,CAAC,UAAkB,aAA4B;AAC/D,UAAM,SAAS,MAAM,IAAI,QAAQ;AACjC,QAAI,OAAQ,QAAO;AAEnB,UAAME,UAAS,KAAK,kBAAkB,UAAU,QAAQ,CAAC;AACzD,UAAM,IAAI,UAAUA,OAAM;AAC1B,WAAOA;AAAA,EACT;AAEA,QAAM,gBAAgB,OAAO,aAAqB;AAChD,UAAMA,UAAS,MAAM,IAAI,QAAQ;AACjC,QAAIA,SAAQ;AACV,YAAMA,QAAO,QAAQ;AACrB,YAAM,OAAO,QAAQ;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,aAAa,YAAY;AAC7B,UAAM,QAAQ,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAC7D,UAAM,MAAM;AACZ,UAAM,WAAW,QAAQ;AAAA,EAC3B;AAMA,QAAM,iBAAiB,OAAO,UAAkB,aAAsB;AACpE,UAAM,oBAAoB,YAAY,QAAQ;AAC9C,UAAM,iBAAiB,kBAAkB,QAAQ;AACjD,UAAM,oBAAoB,aAAa,UAAa,aAAa;AAEjE,QAAI,eAAe,MAAM,GAAG;AAC1B,YAAM,WAAW,4BAA4B,QAAQ;AACrD,UAAI,aAAa,YAAY;AAC3B,QAAAC,IAAG,UAAUH,MAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACxD,YAAI,CAACG,IAAG,WAAW,QAAQ,GAAG;AAC5B,UAAAA,IAAG,UAAUA,IAAG,SAAS,UAAU,GAAG,CAAC;AAAA,QACzC;AAAA,MACF;AAAA,IACF,OAAO;AAEL,YAAM,UACJ,KAAK,kBACJ,iBAAiB,MAAM,IACpB,aACA,cAAc,MAAM,IAClB,UACA,cAAc,MAAM,IAClB,WACA,wBAAwB;AAClC,YAAM,QAAQ,KAAK;AAAA,QACjB,GAAG;AAAA,QACH,YAAY,oBAAoB,OAAO;AAAA,MACzC,CAAC;AAED,UAAI;AACF,YAAI,iBAAiB,MAAM,GAAG;AAC5B,gBAAM,SAAS,MAAM,MAAM,IAAI,+CAA+C,CAAC,QAAQ,CAAC;AACxF,cAAI,QAAQ,MAAM,QAAQ;AACxB,kBAAM,IAAI,MAAM,aAAa,QAAQ,kBAAkB;AAAA,UACzD;AAEA,gBAAM,SAAS,cAAc,QAAQ;AACrC,gBAAM,MAAM,IAAI,oBAAoB,MAAM,GAAG;AAE7C,cAAI,kBAAkB,mBAAmB;AACvC,kBAAM,WAAW,GAAG,OAAO,cAAc,OAAO,GAAG,QAAQ;AAC3D,kBAAM,WAAW,cAAc,QAAQ;AACvC,kBAAM,UAAU,gBAAgB,cAAc;AAC9C,kBAAM,MAAM,IAAI,gBAAgB,QAAQ,oBAAoB,OAAO,GAAG;AACtE,kBAAM,MAAM,IAAI,qCAAqC,MAAM,SAAS,QAAQ,GAAG;AAAA,UACjF;AAAA,QACF,WAAW,cAAc,MAAM,GAAG;AAChC,gBAAM,SAAS,MAAM,MAAM;AAAA,YACzB;AAAA,YACA,CAAC,QAAQ;AAAA,UACX;AACA,cAAI,SAAS,CAAC,GAAG,QAAQ;AACvB,kBAAM,IAAI,MAAM,aAAa,QAAQ,kBAAkB;AAAA,UACzD;AAEA,gBAAM,SAAS,iBAAiB,QAAQ;AACxC,gBAAM,MAAM,IAAI,qBAAqB,MAAM,IAAI;AAE/C,cAAI,kBAAkB,mBAAmB;AACvC,kBAAM,WAAW,GAAG,OAAO,cAAc,OAAO,GAAG,QAAQ;AAC3D,kBAAM,WAAW,gBAAgB,QAAQ;AACzC,kBAAM,UAAU,gBAAgB,cAAc;AAC9C,kBAAM,MAAM,IAAI,8BAA8B,QAAQ,wBAAwB,OAAO,GAAG;AACxF,kBAAM,MAAM,IAAI,6BAA6B,MAAM,YAAY,QAAQ,OAAO;AAAA,UAChF;AAAA,QACF,WAAW,cAAc,MAAM,GAAG;AAChC,gBAAM,SAAS,iBAAiB,QAAQ;AACxC,gBAAM,MAAM;AAAA,YACV,cAAc,gBAAgB,QAAQ,CAAC,+BAA+B,MAAM;AAAA,UAC9E;AAEA,cAAI,kBAAkB,mBAAmB;AACvC,kBAAM,WAAW,GAAG,OAAO,cAAc,OAAO,GAAG,QAAQ;AAC3D,kBAAM,WAAW,iBAAiB,QAAQ;AAC1C,kBAAM,UAAU,gBAAgB,cAAc;AAC9C,kBAAM,MAAM;AAAA,cACV,wEAAwE,gBAAgB,QAAQ,CAAC,oBAAoB,QAAQ,sBAAsB,OAAO;AAAA,YAC5J;AAEA,kBAAM,cAAc,KAAK;AAAA,cACvB,GAAG;AAAA,cACH,YAAY,oBAAoB,QAAQ;AAAA,YAC1C,CAAC;AACD,gBAAI;AACF,oBAAM,YAAY;AAAA,gBAChB,0EAA0E,gBAAgB,QAAQ,CAAC,mBAAmB,QAAQ,gBAAgB,QAAQ;AAAA,cACxJ;AACA,oBAAM,YAAY,IAAI,sCAAsC,QAAQ,GAAG;AAAA,YACzE,UAAE;AACA,oBAAM,YAAY,QAAQ;AAAA,YAC5B;AAAA,UACF;AAAA,QACF,OAAO;AACL,gBAAM,IAAI;AAAA,YACR,8GAA8G,MAAM;AAAA,UACtH;AAAA,QACF;AAAA,MACF,UAAE;AACA,cAAM,MAAM,QAAQ;AAAA,MACtB;AAAA,IACF;AAGA,UAAM,aAAa,KAAK,kBAAkB,UAAU,cAAc,CAAC;AACnE,QAAI;AACF,UAAI,OAAO,eAAe;AACxB,cAAM,WAAW,QAAQ,OAAO;AAAA,MAClC;AACA,UAAI,OAAO,UAAU;AACnB,cAAM,WAAW,KAAK,IAAI;AAAA,MAC5B;AAAA,IACF,UAAE;AACA,YAAM,WAAW,QAAQ;AACzB,YAAM,OAAO,QAAQ;AAAA,IACvB;AAEA,UAAM,uBAAuB,YAAY,UAAU,UAAU,QAAQ;AAAA,EACvE;AAEA,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,sBAAsB,CACjC,YACkB;AAClB,QAAM,WAAW,WAAW,kBAAkB;AAC9C,SAAO,mBAAmB,QAAQ;AACpC;AAEO,IAAM,2BAA2B,OACtC,YAC2B;AAC3B,QAAM,WAAW,WAAW,MAAM,uBAAuB;AACzD,SAAO,mBAAmB,QAAQ;AACpC;AAGO,IAAM,oBAAoB;;;AInX1B,IAAM,uBAAuB,CAClC,SACwB;AACxB,QAAM;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,SAAO,OAAO,QAA0D;AACtE,UAAM,WAAW,MAAM,iBAAiB,GAAG;AAC3C,QAAI,CAAC,SAAU,QAAO,CAAC;AAEvB,QAAI;AACJ,QAAI,kBAAkB;AACpB,iBAAW,MAAM,iBAAiB,QAAQ;AAAA,IAC5C;AAEA,UAAMC,QAAO,QAAQ,UAAU,UAAU,QAAQ;AAEjD,QAAI,YAAY;AACd,YAAM,WAAW,UAAU,GAAG;AAAA,IAChC;AAGA,QAAI,QAAQ;AACV,aAAO,KAAK,UAAUA,KAAI;AAAA,IAC5B,OAAO;AACL,MAAC,IAAY,WAAW;AACxB,MAAC,IAAY,OAAOA;AAAA,IACtB;AAEA,WAAO,EAAE,UAAU,MAAAA,MAAK;AAAA,EAC1B;AACF;","names":["fs","path","require","path","conn","client","fs","knex"]}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@tenora/multi-tenant",
3
+ "version": "0.1.1",
4
+ "description": "Reusable multi-tenant helper for Knex + Objection (Fastify friendly)",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "tenora": "dist/cli.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "package.json"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "prepack": "npm run build",
19
+ "lint": "echo 'add your linter'"
20
+ },
21
+ "keywords": [
22
+ "knex",
23
+ "objection",
24
+ "multi-tenant",
25
+ "fastify",
26
+ "postgres"
27
+ ],
28
+ "author": "",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "commander": "^13.1.0",
32
+ "crypto-js": "^4.2.0",
33
+ "knex": "^3.1.0",
34
+ "objection": "^3.1.2"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.13.0",
38
+ "tsup": "^8.5.1",
39
+ "typescript": "^5.6.3"
40
+ },
41
+ "peerDependencies": {
42
+ "fastify": ">=4",
43
+ "pg": ">=8"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ }
48
+ }