drizzle-multitenant 1.3.0 → 1.3.2
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/cli/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {Command}from'commander';import
|
|
2
|
+
import {Command}from'commander';import M from'chalk';import {checkbox,confirm,input,select,Separator}from'@inquirer/prompts';import {mkdir,readdir,writeFile,readFile,access}from'fs/promises';import {resolve,join,basename,relative,extname,dirname}from'path';import {createHash}from'crypto';import {existsSync,mkdirSync,writeFileSync,readdirSync,statSync}from'fs';import {Pool}from'pg';import {drizzle}from'drizzle-orm/node-postgres';import st from'ora';import lr from'cli-table3';import {pathToFileURL}from'url';import Sr from'cli-progress';import {createInterface}from'readline';import {LRUCache}from'lru-cache';async function Me(n,e,t){if(!(await n.query(`SELECT EXISTS (
|
|
3
3
|
SELECT 1 FROM information_schema.tables
|
|
4
4
|
WHERE table_schema = $1 AND table_name = $2
|
|
5
5
|
) as exists`,[e,t])).rows[0]?.exists)return null;let a=await n.query(`SELECT column_name, data_type
|
|
6
6
|
FROM information_schema.columns
|
|
7
|
-
WHERE table_schema = $1 AND table_name = $2`,[e,t]),o=new Map(a.rows.map(s=>[s.column_name,s.data_type]));return o.has("name")?{format:"name",tableName:t,columns:{identifier:"name",timestamp:o.has("applied_at")?"applied_at":"created_at",timestampType:"timestamp"}}:o.has("hash")?o.get("created_at")==="bigint"?{format:"drizzle-kit",tableName:t,columns:{identifier:"hash",timestamp:"created_at",timestampType:"bigint"}}:{format:"hash",tableName:t,columns:{identifier:"hash",timestamp:"created_at",timestampType:"timestamp"}}:null}function Ee(n,e="__drizzle_migrations"){switch(n){case "name":return {format:"name",tableName:e,columns:{identifier:"name",timestamp:"applied_at",timestampType:"timestamp"}};case "hash":return {format:"hash",tableName:e,columns:{identifier:"hash",timestamp:"created_at",timestampType:"timestamp"}};case "drizzle-kit":return {format:"drizzle-kit",tableName:e,columns:{identifier:"hash",timestamp:"created_at",timestampType:"bigint"}}}}var
|
|
7
|
+
WHERE table_schema = $1 AND table_name = $2`,[e,t]),o=new Map(a.rows.map(s=>[s.column_name,s.data_type]));return o.has("name")?{format:"name",tableName:t,columns:{identifier:"name",timestamp:o.has("applied_at")?"applied_at":"created_at",timestampType:"timestamp"}}:o.has("hash")?o.get("created_at")==="bigint"?{format:"drizzle-kit",tableName:t,columns:{identifier:"hash",timestamp:"created_at",timestampType:"bigint"}}:{format:"hash",tableName:t,columns:{identifier:"hash",timestamp:"created_at",timestampType:"timestamp"}}:null}function Ee(n,e="__drizzle_migrations"){switch(n){case "name":return {format:"name",tableName:e,columns:{identifier:"name",timestamp:"applied_at",timestampType:"timestamp"}};case "hash":return {format:"hash",tableName:e,columns:{identifier:"hash",timestamp:"created_at",timestampType:"timestamp"}};case "drizzle-kit":return {format:"drizzle-kit",tableName:e,columns:{identifier:"hash",timestamp:"created_at",timestampType:"bigint"}}}}var fa="__drizzle_migrations",Be=class{constructor(e,t){this.config=e;this.migrationsTable=t??fa;}migrationsTable;getSchemaName(e){return this.config.isolation.schemaNameTemplate(e)}async createPool(e){return new Pool({connectionString:this.config.connection.url,...this.config.connection.poolConfig,options:`-c search_path="${e}",public`})}async createRootPool(){return new Pool({connectionString:this.config.connection.url,...this.config.connection.poolConfig})}async createSchema(e){let t=this.getSchemaName(e),r=await this.createRootPool();try{await r.query(`CREATE SCHEMA IF NOT EXISTS "${t}"`);}finally{await r.end();}}async dropSchema(e,t={}){let{cascade:r=true}=t,a=this.getSchemaName(e),o=await this.createRootPool();try{let s=r?"CASCADE":"RESTRICT";await o.query(`DROP SCHEMA IF EXISTS "${a}" ${s}`);}finally{await o.end();}}async schemaExists(e){let t=this.getSchemaName(e),r=await this.createRootPool();try{let a=await r.query("SELECT 1 FROM information_schema.schemata WHERE schema_name = $1",[t]);return a.rowCount!==null&&a.rowCount>0}finally{await r.end();}}async listSchemas(e){let t=await this.createRootPool();try{let r=e?"SELECT schema_name FROM information_schema.schemata WHERE schema_name LIKE $1 ORDER BY schema_name":"SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast') ORDER BY schema_name";return (await t.query(r,e?[e]:[])).rows.map(o=>o.schema_name)}finally{await t.end();}}async ensureMigrationsTable(e,t,r){let{identifier:a,timestamp:o,timestampType:s}=r.columns,i=a==="name"?"name VARCHAR(255) NOT NULL UNIQUE":"hash TEXT NOT NULL",c=s==="bigint"?`${o} BIGINT NOT NULL`:`${o} TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP`;await e.query(`
|
|
8
8
|
CREATE TABLE IF NOT EXISTS "${t}"."${r.tableName}" (
|
|
9
9
|
id SERIAL PRIMARY KEY,
|
|
10
10
|
${i},
|
|
@@ -56,20 +56,20 @@ import {Command}from'commander';import C from'chalk';import {checkbox,confirm,in
|
|
|
56
56
|
ON tc.constraint_name = cc.constraint_name
|
|
57
57
|
AND tc.constraint_type = 'CHECK'
|
|
58
58
|
WHERE tc.table_schema = $1 AND tc.table_name = $2
|
|
59
|
-
ORDER BY tc.constraint_name, kcu.ordinal_position`,[e,t]),a=new Map;for(let o of r.rows){let s=a.get(o.constraint_name);if(s)o.column_name&&!s.columns.includes(o.column_name)&&s.columns.push(o.column_name),o.foreign_column_name&&s.foreignColumns&&!s.foreignColumns.includes(o.foreign_column_name)&&s.foreignColumns.push(o.foreign_column_name);else {let i={name:o.constraint_name,type:o.constraint_type,columns:o.column_name?[o.column_name]:[]};o.foreign_table_name&&(i.foreignTable=o.foreign_table_name),o.foreign_column_name&&(i.foreignColumns=[o.foreign_column_name]),o.check_clause&&(i.checkExpression=o.check_clause),a.set(o.constraint_name,i);}}return Array.from(a.values())}function Xn(n,e){let t=[],r=new Map(n.map(o=>[o.name,o])),a=new Map(e.map(o=>[o.name,o]));for(let o of n){let s=a.get(o.name);if(!s){t.push({constraint:o.name,type:"missing",expected:`${o.type} on (${o.columns.join(", ")})`,description:`Constraint "${o.name}" (${o.type}) is missing`});continue}let i=o.columns.sort().join(","),c=s.columns.sort().join(",");(o.type!==s.type||i!==c)&&t.push({constraint:o.name,type:"definition_mismatch",expected:`${o.type} on (${o.columns.join(", ")})`,actual:`${s.type} on (${s.columns.join(", ")})`,description:`Constraint "${o.name}" definition differs`});}for(let o of e)r.has(o.name)||t.push({constraint:o.name,type:"extra",actual:`${o.type} on (${o.columns.join(", ")})`,description:`Extra constraint "${o.name}" (${o.type}) not in reference`});return t}var
|
|
59
|
+
ORDER BY tc.constraint_name, kcu.ordinal_position`,[e,t]),a=new Map;for(let o of r.rows){let s=a.get(o.constraint_name);if(s)o.column_name&&!s.columns.includes(o.column_name)&&s.columns.push(o.column_name),o.foreign_column_name&&s.foreignColumns&&!s.foreignColumns.includes(o.foreign_column_name)&&s.foreignColumns.push(o.foreign_column_name);else {let i={name:o.constraint_name,type:o.constraint_type,columns:o.column_name?[o.column_name]:[]};o.foreign_table_name&&(i.foreignTable=o.foreign_table_name),o.foreign_column_name&&(i.foreignColumns=[o.foreign_column_name]),o.check_clause&&(i.checkExpression=o.check_clause),a.set(o.constraint_name,i);}}return Array.from(a.values())}function Xn(n,e){let t=[],r=new Map(n.map(o=>[o.name,o])),a=new Map(e.map(o=>[o.name,o]));for(let o of n){let s=a.get(o.name);if(!s){t.push({constraint:o.name,type:"missing",expected:`${o.type} on (${o.columns.join(", ")})`,description:`Constraint "${o.name}" (${o.type}) is missing`});continue}let i=o.columns.sort().join(","),c=s.columns.sort().join(",");(o.type!==s.type||i!==c)&&t.push({constraint:o.name,type:"definition_mismatch",expected:`${o.type} on (${o.columns.join(", ")})`,actual:`${s.type} on (${s.columns.join(", ")})`,description:`Constraint "${o.name}" definition differs`});}for(let o of e)r.has(o.name)||t.push({constraint:o.name,type:"extra",actual:`${o.type} on (${o.columns.join(", ")})`,description:`Extra constraint "${o.name}" (${o.type}) not in reference`});return t}var ya="__drizzle_migrations",Ge=class{constructor(e,t,r){this.tenantConfig=e;this.schemaManager=t;this.driftConfig=r;this.migrationsTable=r.migrationsTable??ya;}migrationsTable;getSchemaName(e){return this.tenantConfig.isolation.schemaNameTemplate(e)}async createPool(e){return this.schemaManager.createPool(e)}async detectDrift(e={}){let t=Date.now(),{concurrency:r=10,includeIndexes:a=true,includeConstraints:o=true,excludeTables:s=[this.migrationsTable],onProgress:i}=e,c=e.tenantIds??await this.driftConfig.tenantDiscovery();if(c.length===0)return {referenceTenant:"",total:0,noDrift:0,withDrift:0,error:0,details:[],timestamp:new Date().toISOString(),durationMs:Date.now()-t};let l=e.referenceTenant??c[0];i?.(l,"starting"),i?.(l,"introspecting");let m=await this.introspectSchema(l,{includeIndexes:a,includeConstraints:o,excludeTables:s});if(!m)return {referenceTenant:l,total:c.length,noDrift:0,withDrift:0,error:c.length,details:c.map(h=>({tenantId:h,schemaName:this.getSchemaName(h),hasDrift:false,tables:[],issueCount:0,error:h===l?"Failed to introspect reference tenant":"Reference tenant introspection failed"})),timestamp:new Date().toISOString(),durationMs:Date.now()-t};i?.(l,"completed");let u=c.filter(h=>h!==l),p=[];p.push({tenantId:l,schemaName:m.schemaName,hasDrift:false,tables:[],issueCount:0});for(let h=0;h<u.length;h+=r){let g=u.slice(h,h+r),$=await Promise.all(g.map(async S=>{try{i?.(S,"starting"),i?.(S,"introspecting");let b=await this.introspectSchema(S,{includeIndexes:a,includeConstraints:o,excludeTables:s});if(!b)return i?.(S,"failed"),{tenantId:S,schemaName:this.getSchemaName(S),hasDrift:!1,tables:[],issueCount:0,error:"Failed to introspect schema"};i?.(S,"comparing");let x=this.compareSchemas(m,b,{includeIndexes:a,includeConstraints:o});return i?.(S,"completed"),x}catch(b){return i?.(S,"failed"),{tenantId:S,schemaName:this.getSchemaName(S),hasDrift:false,tables:[],issueCount:0,error:b.message}}}));p.push(...$);}return {referenceTenant:l,total:p.length,noDrift:p.filter(h=>!h.hasDrift&&!h.error).length,withDrift:p.filter(h=>h.hasDrift&&!h.error).length,error:p.filter(h=>!!h.error).length,details:p,timestamp:new Date().toISOString(),durationMs:Date.now()-t}}async compareTenant(e,t,r={}){let{includeIndexes:a=true,includeConstraints:o=true,excludeTables:s=[this.migrationsTable]}=r,i=await this.introspectSchema(t,{includeIndexes:a,includeConstraints:o,excludeTables:s});if(!i)return {tenantId:e,schemaName:this.getSchemaName(e),hasDrift:false,tables:[],issueCount:0,error:"Failed to introspect reference tenant"};let c=await this.introspectSchema(e,{includeIndexes:a,includeConstraints:o,excludeTables:s});return c?this.compareSchemas(i,c,{includeIndexes:a,includeConstraints:o}):{tenantId:e,schemaName:this.getSchemaName(e),hasDrift:false,tables:[],issueCount:0,error:"Failed to introspect tenant schema"}}async introspectSchema(e,t={}){let r=this.getSchemaName(e),a=await this.createPool(r);try{let o=await this.introspectTables(a,r,t);return {tenantId:e,schemaName:r,tables:o,introspectedAt:new Date}}catch{return null}finally{await a.end();}}compareSchemas(e,t,r={}){let{includeIndexes:a=true,includeConstraints:o=true}=r,s=[],i=0,c=new Map(e.tables.map(m=>[m.name,m])),l=new Map(t.tables.map(m=>[m.name,m]));for(let m of e.tables){let u=l.get(m.name);if(!u){s.push({table:m.name,status:"missing",columns:m.columns.map(S=>({column:S.name,type:"missing",expected:S.dataType,description:`Column "${S.name}" (${S.dataType}) is missing`})),indexes:[],constraints:[]}),i+=m.columns.length;continue}let p=Vn(m.columns,u.columns),h=a?Qn(m.indexes,u.indexes):[],g=o?Xn(m.constraints,u.constraints):[],$=p.length+h.length+g.length;i+=$,$>0&&s.push({table:m.name,status:"drifted",columns:p,indexes:h,constraints:g});}for(let m of t.tables)c.has(m.name)||(s.push({table:m.name,status:"extra",columns:m.columns.map(u=>({column:u.name,type:"extra",actual:u.dataType,description:`Extra column "${u.name}" (${u.dataType}) not in reference`})),indexes:[],constraints:[]}),i+=m.columns.length);return {tenantId:t.tenantId,schemaName:t.schemaName,hasDrift:i>0,tables:s,issueCount:i}}async introspectTables(e,t,r){let{includeIndexes:a=true,includeConstraints:o=true,excludeTables:s=[]}=r,i=await e.query(`SELECT table_name
|
|
60
60
|
FROM information_schema.tables
|
|
61
61
|
WHERE table_schema = $1
|
|
62
62
|
AND table_type = 'BASE TABLE'
|
|
63
|
-
ORDER BY table_name`,[t]),c=[];for(let l of i.rows){if(s.includes(l.table_name))continue;let m=await Kn(e,t,l.table_name),u=a?await Yn(e,t,l.table_name):[],p=o?await Zn(e,t,l.table_name):[];c.push({name:l.table_name,columns:m,indexes:u,constraints:p});}return c}};var Je=class{constructor(e,t){this.config=e;this.deps=t;}async seedTenant(e,t){let r=Date.now(),a=this.deps.schemaNameTemplate(e),o=await this.deps.createPool(a);try{let s=drizzle(o,{schema:this.deps.tenantSchema});return await t(s,e),{tenantId:e,schemaName:a,success:!0,durationMs:Date.now()-r}}catch(s){return {tenantId:e,schemaName:a,success:false,error:s.message,durationMs:Date.now()-r}}finally{await o.end();}}async seedAll(e,t={}){let{concurrency:r=10,onProgress:a,onError:o}=t,s=await this.config.tenantDiscovery(),i=[],c=false;for(let l=0;l<s.length&&!c;l+=r){let m=s.slice(l,l+r),u=await Promise.all(m.map(async p=>{if(c)return this.createSkippedResult(p);try{a?.(p,"starting"),a?.(p,"seeding");let h=await this.seedTenant(p,e);return a?.(p,h.success?"completed":"failed"),h}catch(h){return a?.(p,"failed"),o?.(p,h)==="abort"&&(c=true),this.createErrorResult(p,h)}}));i.push(...u);}if(c){let l=s.slice(i.length);for(let m of l)i.push(this.createSkippedResult(m));}return this.aggregateResults(i)}async seedTenants(e,t,r={}){let{concurrency:a=10,onProgress:o,onError:s}=r,i=[];for(let c=0;c<e.length;c+=a){let l=e.slice(c,c+a),m=await Promise.all(l.map(async u=>{try{o?.(u,"starting"),o?.(u,"seeding");let p=await this.seedTenant(u,t);return o?.(u,p.success?"completed":"failed"),p}catch(p){return o?.(u,"failed"),s?.(u,p),this.createErrorResult(u,p)}}));i.push(...m);}return this.aggregateResults(i)}createSkippedResult(e){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,error:"Skipped due to abort",durationMs:0}}createErrorResult(e,t){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,error:t.message,durationMs:0}}aggregateResults(e){return {total:e.length,succeeded:e.filter(t=>t.success).length,failed:e.filter(t=>!t.success&&t.error!=="Skipped due to abort").length,skipped:e.filter(t=>t.error==="Skipped due to abort").length,details:e}}};var
|
|
63
|
+
ORDER BY table_name`,[t]),c=[];for(let l of i.rows){if(s.includes(l.table_name))continue;let m=await Kn(e,t,l.table_name),u=a?await Yn(e,t,l.table_name):[],p=o?await Zn(e,t,l.table_name):[];c.push({name:l.table_name,columns:m,indexes:u,constraints:p});}return c}};var Je=class{constructor(e,t){this.config=e;this.deps=t;}async seedTenant(e,t){let r=Date.now(),a=this.deps.schemaNameTemplate(e),o=await this.deps.createPool(a);try{let s=drizzle(o,{schema:this.deps.tenantSchema});return await t(s,e),{tenantId:e,schemaName:a,success:!0,durationMs:Date.now()-r}}catch(s){return {tenantId:e,schemaName:a,success:false,error:s.message,durationMs:Date.now()-r}}finally{await o.end();}}async seedAll(e,t={}){let{concurrency:r=10,onProgress:a,onError:o}=t,s=await this.config.tenantDiscovery(),i=[],c=false;for(let l=0;l<s.length&&!c;l+=r){let m=s.slice(l,l+r),u=await Promise.all(m.map(async p=>{if(c)return this.createSkippedResult(p);try{a?.(p,"starting"),a?.(p,"seeding");let h=await this.seedTenant(p,e);return a?.(p,h.success?"completed":"failed"),h}catch(h){return a?.(p,"failed"),o?.(p,h)==="abort"&&(c=true),this.createErrorResult(p,h)}}));i.push(...u);}if(c){let l=s.slice(i.length);for(let m of l)i.push(this.createSkippedResult(m));}return this.aggregateResults(i)}async seedTenants(e,t,r={}){let{concurrency:a=10,onProgress:o,onError:s}=r,i=[];for(let c=0;c<e.length;c+=a){let l=e.slice(c,c+a),m=await Promise.all(l.map(async u=>{try{o?.(u,"starting"),o?.(u,"seeding");let p=await this.seedTenant(u,t);return o?.(u,p.success?"completed":"failed"),p}catch(p){return o?.(u,"failed"),s?.(u,p),this.createErrorResult(u,p)}}));i.push(...m);}return this.aggregateResults(i)}createSkippedResult(e){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,error:"Skipped due to abort",durationMs:0}}createErrorResult(e,t){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,error:t.message,durationMs:0}}aggregateResults(e){return {total:e.length,succeeded:e.filter(t=>t.success).length,failed:e.filter(t=>!t.success&&t.error!=="Skipped due to abort").length,skipped:e.filter(t=>t.error==="Skipped due to abort").length,details:e}}};var Ta="public",Ke=class{constructor(e,t){this.config=e;this.deps=t;this.schemaName=e.schemaName??Ta;}schemaName;async seed(e){let t=Date.now(),r=await this.deps.createPool();try{this.config.hooks?.onStart?.();let a=drizzle(r,{schema:this.deps.sharedSchema});return await e(a),this.config.hooks?.onComplete?.(),{schemaName:this.schemaName,success:!0,durationMs:Date.now()-t}}catch(a){return this.config.hooks?.onError?.(a),{schemaName:this.schemaName,success:false,error:a.message,durationMs:Date.now()-t}}finally{await r.end();}}};var Ve=class{constructor(e,t){this.config=e;this.deps=t;}async getSyncStatus(){let e=await this.config.tenantDiscovery(),t=await this.deps.loadMigrations(),r=[];for(let a of e)r.push(await this.getTenantSyncStatus(a,t));return {total:r.length,inSync:r.filter(a=>a.inSync&&!a.error).length,outOfSync:r.filter(a=>!a.inSync&&!a.error).length,error:r.filter(a=>!!a.error).length,details:r}}async getTenantSyncStatus(e,t){let r=this.deps.schemaNameTemplate(e),a=await this.deps.createPool(r);try{let o=t??await this.deps.loadMigrations(),s=new Set(o.map(g=>g.name)),i=new Set(o.map(g=>g.hash));if(!await this.deps.migrationsTableExists(a,r))return {tenantId:e,schemaName:r,missing:o.map(g=>g.name),orphans:[],inSync:o.length===0,format:null};let l=await this.deps.getOrDetectFormat(a,r),m=await this.getAppliedMigrations(a,r,l),u=new Set(m.map(g=>g.identifier)),p=o.filter(g=>!this.isMigrationApplied(g,u,l)).map(g=>g.name),h=m.filter(g=>(l.columns.identifier==="name"||!i.has(g.identifier))&&!s.has(g.identifier)).map(g=>g.identifier);return {tenantId:e,schemaName:r,missing:p,orphans:h,inSync:p.length===0&&h.length===0,format:l.format}}catch(o){return {tenantId:e,schemaName:r,missing:[],orphans:[],inSync:false,format:null,error:o.message}}finally{await a.end();}}async markMissing(e){let t=Date.now(),r=this.deps.schemaNameTemplate(e),a=[],o=await this.deps.createPool(r);try{let s=await this.getTenantSyncStatus(e);if(s.error)return {tenantId:e,schemaName:r,success:!1,markedMigrations:[],removedOrphans:[],error:s.error,durationMs:Date.now()-t};if(s.missing.length===0)return {tenantId:e,schemaName:r,success:!0,markedMigrations:[],removedOrphans:[],durationMs:Date.now()-t};let i=await this.deps.getOrDetectFormat(o,r);await this.deps.ensureMigrationsTable(o,r,i);let c=await this.deps.loadMigrations(),l=new Set(s.missing);for(let m of c)l.has(m.name)&&(await this.recordMigration(o,r,m,i),a.push(m.name));return {tenantId:e,schemaName:r,success:!0,markedMigrations:a,removedOrphans:[],durationMs:Date.now()-t}}catch(s){return {tenantId:e,schemaName:r,success:false,markedMigrations:a,removedOrphans:[],error:s.message,durationMs:Date.now()-t}}finally{await o.end();}}async markAllMissing(e={}){let{concurrency:t=10,onProgress:r,onError:a}=e,o=await this.config.tenantDiscovery(),s=[],i=false;for(let c=0;c<o.length&&!i;c+=t){let l=o.slice(c,c+t),m=await Promise.all(l.map(async u=>{if(i)return this.createSkippedSyncResult(u);try{r?.(u,"starting");let p=await this.markMissing(u);return r?.(u,p.success?"completed":"failed"),p}catch(p){return r?.(u,"failed"),a?.(u,p)==="abort"&&(i=true),this.createErrorSyncResult(u,p)}}));s.push(...m);}return this.aggregateSyncResults(s)}async cleanOrphans(e){let t=Date.now(),r=this.deps.schemaNameTemplate(e),a=[],o=await this.deps.createPool(r);try{let s=await this.getTenantSyncStatus(e);if(s.error)return {tenantId:e,schemaName:r,success:!1,markedMigrations:[],removedOrphans:[],error:s.error,durationMs:Date.now()-t};if(s.orphans.length===0)return {tenantId:e,schemaName:r,success:!0,markedMigrations:[],removedOrphans:[],durationMs:Date.now()-t};let i=await this.deps.getOrDetectFormat(o,r),c=i.columns.identifier;for(let l of s.orphans)await o.query(`DELETE FROM "${r}"."${i.tableName}" WHERE "${c}" = $1`,[l]),a.push(l);return {tenantId:e,schemaName:r,success:!0,markedMigrations:[],removedOrphans:a,durationMs:Date.now()-t}}catch(s){return {tenantId:e,schemaName:r,success:false,markedMigrations:[],removedOrphans:a,error:s.message,durationMs:Date.now()-t}}finally{await o.end();}}async cleanAllOrphans(e={}){let{concurrency:t=10,onProgress:r,onError:a}=e,o=await this.config.tenantDiscovery(),s=[],i=false;for(let c=0;c<o.length&&!i;c+=t){let l=o.slice(c,c+t),m=await Promise.all(l.map(async u=>{if(i)return this.createSkippedSyncResult(u);try{r?.(u,"starting");let p=await this.cleanOrphans(u);return r?.(u,p.success?"completed":"failed"),p}catch(p){return r?.(u,"failed"),a?.(u,p)==="abort"&&(i=true),this.createErrorSyncResult(u,p)}}));s.push(...m);}return this.aggregateSyncResults(s)}async getAppliedMigrations(e,t,r){let a=r.columns.identifier,o=r.columns.timestamp;return (await e.query(`SELECT id, "${a}" as identifier, "${o}" as applied_at
|
|
64
64
|
FROM "${t}"."${r.tableName}"
|
|
65
|
-
ORDER BY id`)).rows.map(i=>{let c=r.columns.timestampType==="bigint"?new Date(Number(i.applied_at)):new Date(i.applied_at);return {identifier:i.identifier,appliedAt:c}})}isMigrationApplied(e,t,r){return r.columns.identifier==="name"?t.has(e.name):t.has(e.hash)||t.has(e.name)}async recordMigration(e,t,r,a){let{identifier:o,timestamp:s,timestampType:i}=a.columns,c=o==="name"?r.name:r.hash,l=i==="bigint"?Date.now():new Date;await e.query(`INSERT INTO "${t}"."${a.tableName}" ("${o}", "${s}") VALUES ($1, $2)`,[c,l]);}createSkippedSyncResult(e){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,markedMigrations:[],removedOrphans:[],error:"Skipped due to abort",durationMs:0}}createErrorSyncResult(e,t){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,markedMigrations:[],removedOrphans:[],error:t.message,durationMs:0}}aggregateSyncResults(e){return {total:e.length,succeeded:e.filter(t=>t.success).length,failed:e.filter(t=>!t.success).length,details:e}}};var Ye=class{constructor(e,t){this.config=e;this.deps=t;}async migrateTenant(e,t,r={}){let a=Date.now(),o=this.deps.schemaNameTemplate(e),s=[],i=await this.deps.createPool(o);try{await this.config.hooks?.beforeTenant?.(e);let c=await this.deps.getOrDetectFormat(i,o);await this.deps.ensureMigrationsTable(i,o,c);let l=t??await this.deps.loadMigrations(),m=await this.getAppliedMigrations(i,o,c),u=new Set(m.map(g=>g.identifier)),p=l.filter(g=>!this.isMigrationApplied(g,u,c));if(r.dryRun)return {tenantId:e,schemaName:o,success:!0,appliedMigrations:p.map(g=>g.name),durationMs:Date.now()-a,format:c.format};for(let g of p){let
|
|
65
|
+
ORDER BY id`)).rows.map(i=>{let c=r.columns.timestampType==="bigint"?new Date(Number(i.applied_at)):new Date(i.applied_at);return {identifier:i.identifier,appliedAt:c}})}isMigrationApplied(e,t,r){return r.columns.identifier==="name"?t.has(e.name):t.has(e.hash)||t.has(e.name)}async recordMigration(e,t,r,a){let{identifier:o,timestamp:s,timestampType:i}=a.columns,c=o==="name"?r.name:r.hash,l=i==="bigint"?Date.now():new Date;await e.query(`INSERT INTO "${t}"."${a.tableName}" ("${o}", "${s}") VALUES ($1, $2)`,[c,l]);}createSkippedSyncResult(e){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,markedMigrations:[],removedOrphans:[],error:"Skipped due to abort",durationMs:0}}createErrorSyncResult(e,t){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,markedMigrations:[],removedOrphans:[],error:t.message,durationMs:0}}aggregateSyncResults(e){return {total:e.length,succeeded:e.filter(t=>t.success).length,failed:e.filter(t=>!t.success).length,details:e}}};var Ye=class{constructor(e,t){this.config=e;this.deps=t;}async migrateTenant(e,t,r={}){let a=Date.now(),o=this.deps.schemaNameTemplate(e),s=[],i=await this.deps.createPool(o);try{await this.config.hooks?.beforeTenant?.(e);let c=await this.deps.getOrDetectFormat(i,o);await this.deps.ensureMigrationsTable(i,o,c);let l=t??await this.deps.loadMigrations(),m=await this.getAppliedMigrations(i,o,c),u=new Set(m.map(g=>g.identifier)),p=l.filter(g=>!this.isMigrationApplied(g,u,c));if(r.dryRun)return {tenantId:e,schemaName:o,success:!0,appliedMigrations:p.map(g=>g.name),durationMs:Date.now()-a,format:c.format};for(let g of p){let $=Date.now();r.onProgress?.(e,"migrating",g.name),await this.config.hooks?.beforeMigration?.(e,g.name),await this.applyMigration(i,o,g,c),await this.config.hooks?.afterMigration?.(e,g.name,Date.now()-$),s.push(g.name);}let h={tenantId:e,schemaName:o,success:!0,appliedMigrations:s,durationMs:Date.now()-a,format:c.format};return await this.config.hooks?.afterTenant?.(e,h),h}catch(c){let l={tenantId:e,schemaName:o,success:false,appliedMigrations:s,error:c.message,durationMs:Date.now()-a};return await this.config.hooks?.afterTenant?.(e,l),l}finally{await i.end();}}async markAsApplied(e,t={}){let r=Date.now(),a=this.deps.schemaNameTemplate(e),o=[],s=await this.deps.createPool(a);try{await this.config.hooks?.beforeTenant?.(e);let i=await this.deps.getOrDetectFormat(s,a);await this.deps.ensureMigrationsTable(s,a,i);let c=await this.deps.loadMigrations(),l=await this.getAppliedMigrations(s,a,i),m=new Set(l.map(h=>h.identifier)),u=c.filter(h=>!this.isMigrationApplied(h,m,i));for(let h of u){let g=Date.now();t.onProgress?.(e,"migrating",h.name),await this.config.hooks?.beforeMigration?.(e,h.name),await this.recordMigration(s,a,h,i),await this.config.hooks?.afterMigration?.(e,h.name,Date.now()-g),o.push(h.name);}let p={tenantId:e,schemaName:a,success:!0,appliedMigrations:o,durationMs:Date.now()-r,format:i.format};return await this.config.hooks?.afterTenant?.(e,p),p}catch(i){let c={tenantId:e,schemaName:a,success:false,appliedMigrations:o,error:i.message,durationMs:Date.now()-r};return await this.config.hooks?.afterTenant?.(e,c),c}finally{await s.end();}}async getTenantStatus(e,t){let r=this.deps.schemaNameTemplate(e),a=await this.deps.createPool(r);try{let o=t??await this.deps.loadMigrations();if(!await this.deps.migrationsTableExists(a,r))return {tenantId:e,schemaName:r,appliedCount:0,pendingCount:o.length,pendingMigrations:o.map(u=>u.name),status:o.length>0?"behind":"ok",format:null};let i=await this.deps.getOrDetectFormat(a,r),c=await this.getAppliedMigrations(a,r,i),l=new Set(c.map(u=>u.identifier)),m=o.filter(u=>!this.isMigrationApplied(u,l,i));return {tenantId:e,schemaName:r,appliedCount:c.length,pendingCount:m.length,pendingMigrations:m.map(u=>u.name),status:m.length>0?"behind":"ok",format:i.format}}catch(o){return {tenantId:e,schemaName:r,appliedCount:0,pendingCount:0,pendingMigrations:[],status:"error",error:o.message,format:null}}finally{await a.end();}}async executeMigration(e,t,r,a,o){o?.markOnly?(o.onProgress?.("recording"),await this.recordMigration(e,t,r,a)):(o?.onProgress?.("applying"),await this.applyMigration(e,t,r,a));}async executeMigrations(e,t,r,a,o){let s=[];for(let i of r)await this.executeMigration(e,t,i,a,o),s.push(i.name);return s}async recordMigration(e,t,r,a){let{identifier:o,timestamp:s,timestampType:i}=a.columns,c=o==="name"?r.name:r.hash,l=i==="bigint"?Date.now():new Date;await e.query(`INSERT INTO "${t}"."${a.tableName}" ("${o}", "${s}") VALUES ($1, $2)`,[c,l]);}async getAppliedMigrations(e,t,r){let a=r.columns.identifier,o=r.columns.timestamp;return (await e.query(`SELECT id, "${a}" as identifier, "${o}" as applied_at
|
|
66
66
|
FROM "${t}"."${r.tableName}"
|
|
67
67
|
ORDER BY id`)).rows.map(i=>{let c=r.columns.timestampType==="bigint"?new Date(Number(i.applied_at)):new Date(i.applied_at);return {identifier:i.identifier,...r.columns.identifier==="name"?{name:i.identifier}:{hash:i.identifier},appliedAt:c}})}async getPendingMigrations(e,t,r,a){let o=await this.getAppliedMigrations(e,t,a),s=new Set(o.map(i=>i.identifier));return r.filter(i=>!this.isMigrationApplied(i,s,a))}isMigrationApplied(e,t,r){return r.columns.identifier==="name"?t.has(e.name):t.has(e.hash)||t.has(e.name)}async applyMigration(e,t,r,a){let o=await e.connect();try{await o.query("BEGIN"),await o.query(r.sql);let{identifier:s,timestamp:i,timestampType:c}=a.columns,l=s==="name"?r.name:r.hash,m=c==="bigint"?Date.now():new Date;await o.query(`INSERT INTO "${t}"."${a.tableName}" ("${s}", "${i}") VALUES ($1, $2)`,[l,m]),await o.query("COMMIT");}catch(s){throw await o.query("ROLLBACK"),s}finally{o.release();}}};var Qe=class{constructor(e,t,r){this.config=e;this.executor=t;this.loadMigrations=r;}async migrateAll(e={}){let{concurrency:t=10,onProgress:r,onError:a,dryRun:o=false}=e,s=await this.config.tenantDiscovery(),i=await this.loadMigrations(),c=[],l=false;for(let m=0;m<s.length&&!l;m+=t){let u=s.slice(m,m+t),p=await Promise.all(u.map(async h=>{if(l)return this.createSkippedResult(h);try{r?.(h,"starting");let g=await this.executor.migrateTenant(h,i,{dryRun:o,onProgress:r});return r?.(h,g.success?"completed":"failed"),g}catch(g){return r?.(h,"failed"),a?.(h,g)==="abort"&&(l=true),this.createErrorResult(h,g)}}));c.push(...p);}if(l){let m=s.slice(c.length);for(let u of m)c.push(this.createSkippedResult(u));}return this.aggregateResults(c)}async migrateTenants(e,t={}){let r=await this.loadMigrations(),a=[],{concurrency:o=10,onProgress:s,onError:i,dryRun:c=false}=t;for(let l=0;l<e.length;l+=o){let m=e.slice(l,l+o),u=await Promise.all(m.map(async p=>{try{s?.(p,"starting");let h=await this.executor.migrateTenant(p,r,{dryRun:c,onProgress:s});return s?.(p,h.success?"completed":"failed"),h}catch(h){return s?.(p,"failed"),i?.(p,h),this.createErrorResult(p,h)}}));a.push(...u);}return this.aggregateResults(a)}async markAllAsApplied(e={}){let{concurrency:t=10,onProgress:r,onError:a}=e,o=await this.config.tenantDiscovery(),s=[],i=false;for(let c=0;c<o.length&&!i;c+=t){let l=o.slice(c,c+t),m=await Promise.all(l.map(async u=>{if(i)return this.createSkippedResult(u);try{r?.(u,"starting");let p=await this.executor.markAsApplied(u,{onProgress:r});return r?.(u,p.success?"completed":"failed"),p}catch(p){return r?.(u,"failed"),a?.(u,p)==="abort"&&(i=true),this.createErrorResult(u,p)}}));s.push(...m);}if(i){let c=o.slice(s.length);for(let l of c)s.push(this.createSkippedResult(l));}return this.aggregateResults(s)}async getStatus(){let e=await this.config.tenantDiscovery(),t=await this.loadMigrations(),r=[];for(let a of e)r.push(await this.executor.getTenantStatus(a,t));return r}createSkippedResult(e){return {tenantId:e,schemaName:"",success:false,appliedMigrations:[],error:"Skipped due to abort",durationMs:0}}createErrorResult(e,t){return {tenantId:e,schemaName:"",success:false,appliedMigrations:[],error:t.message,durationMs:0}}aggregateResults(e){return {total:e.length,succeeded:e.filter(t=>t.success).length,failed:e.filter(t=>!t.success&&t.error!=="Skipped due to abort").length,skipped:e.filter(t=>t.error==="Skipped due to abort").length,details:e}}};async function er(n,e,t=[]){let r=t.length>0?t.map((o,s)=>`$${s+2}`).join(", "):"''::text";return (await n.query(`SELECT table_name
|
|
68
68
|
FROM information_schema.tables
|
|
69
69
|
WHERE table_schema = $1
|
|
70
70
|
AND table_type = 'BASE TABLE'
|
|
71
71
|
AND table_name NOT IN (${r})
|
|
72
|
-
ORDER BY table_name`,[e,...t])).rows.map(o=>o.table_name)}async function
|
|
72
|
+
ORDER BY table_name`,[e,...t])).rows.map(o=>o.table_name)}async function xa(n,e,t){return (await n.query(`SELECT
|
|
73
73
|
column_name,
|
|
74
74
|
data_type,
|
|
75
75
|
udt_name,
|
|
@@ -80,13 +80,13 @@ import {Command}from'commander';import C from'chalk';import {checkbox,confirm,in
|
|
|
80
80
|
numeric_scale
|
|
81
81
|
FROM information_schema.columns
|
|
82
82
|
WHERE table_schema = $1 AND table_name = $2
|
|
83
|
-
ORDER BY ordinal_position`,[e,t])).rows.map(a=>({columnName:a.column_name,dataType:a.data_type,udtName:a.udt_name,isNullable:a.is_nullable==="YES",columnDefault:a.column_default,characterMaximumLength:a.character_maximum_length,numericPrecision:a.numeric_precision,numericScale:a.numeric_scale}))}async function
|
|
83
|
+
ORDER BY ordinal_position`,[e,t])).rows.map(a=>({columnName:a.column_name,dataType:a.data_type,udtName:a.udt_name,isNullable:a.is_nullable==="YES",columnDefault:a.column_default,characterMaximumLength:a.character_maximum_length,numericPrecision:a.numeric_precision,numericScale:a.numeric_scale}))}async function wa(n,e,t){let a=(await xa(n,e,t)).map(o=>{let s=o.udtName;o.dataType==="character varying"&&o.characterMaximumLength?s=`varchar(${o.characterMaximumLength})`:o.dataType==="character"&&o.characterMaximumLength?s=`char(${o.characterMaximumLength})`:o.dataType==="numeric"&&o.numericPrecision?s=`numeric(${o.numericPrecision}${o.numericScale?`, ${o.numericScale}`:""})`:o.dataType==="ARRAY"&&(s=o.udtName.replace(/^_/,"")+"[]");let i=`"${o.columnName}" ${s}`;if(o.isNullable||(i+=" NOT NULL"),o.columnDefault){let c=o.columnDefault.replace(new RegExp(`"?${e}"?\\.`,"g"),"");i+=` DEFAULT ${c}`;}return i});return `CREATE TABLE IF NOT EXISTS "${t}" (
|
|
84
84
|
${a.join(`,
|
|
85
85
|
`)}
|
|
86
|
-
)`}async function
|
|
86
|
+
)`}async function $a(n,e,t,r){return (await n.query(`SELECT indexname, indexdef
|
|
87
87
|
FROM pg_indexes
|
|
88
88
|
WHERE schemaname = $1 AND tablename = $2
|
|
89
|
-
AND indexname NOT LIKE '%_pkey'`,[e,r])).rows.map(o=>o.indexdef.replace(new RegExp(`ON "${e}"\\."`,"g"),`ON "${t}"."`).replace(new RegExp(`"${e}"\\."`,"g"),`"${t}"."`))}async function
|
|
89
|
+
AND indexname NOT LIKE '%_pkey'`,[e,r])).rows.map(o=>o.indexdef.replace(new RegExp(`ON "${e}"\\."`,"g"),`ON "${t}"."`).replace(new RegExp(`"${e}"\\."`,"g"),`"${t}"."`))}async function Ca(n,e,t){let r=await n.query(`SELECT
|
|
90
90
|
tc.constraint_name,
|
|
91
91
|
kcu.column_name
|
|
92
92
|
FROM information_schema.table_constraints tc
|
|
@@ -96,7 +96,7 @@ import {Command}from'commander';import C from'chalk';import {checkbox,confirm,in
|
|
|
96
96
|
WHERE tc.table_schema = $1
|
|
97
97
|
AND tc.table_name = $2
|
|
98
98
|
AND tc.constraint_type = 'PRIMARY KEY'
|
|
99
|
-
ORDER BY kcu.ordinal_position`,[e,t]);if(r.rows.length===0)return null;let a=r.rows.map(s=>`"${s.column_name}"`).join(", "),o=r.rows[0].constraint_name;return `ALTER TABLE "${t}" ADD CONSTRAINT "${o}" PRIMARY KEY (${a})`}async function
|
|
99
|
+
ORDER BY kcu.ordinal_position`,[e,t]);if(r.rows.length===0)return null;let a=r.rows.map(s=>`"${s.column_name}"`).join(", "),o=r.rows[0].constraint_name;return `ALTER TABLE "${t}" ADD CONSTRAINT "${o}" PRIMARY KEY (${a})`}async function Ma(n,e,t,r){let a=await n.query(`SELECT
|
|
100
100
|
tc.constraint_name,
|
|
101
101
|
kcu.column_name,
|
|
102
102
|
ccu.table_name as foreign_table_name,
|
|
@@ -116,7 +116,7 @@ import {Command}from'commander';import C from'chalk';import {checkbox,confirm,in
|
|
|
116
116
|
WHERE tc.table_schema = $1
|
|
117
117
|
AND tc.table_name = $2
|
|
118
118
|
AND tc.constraint_type = 'FOREIGN KEY'
|
|
119
|
-
ORDER BY tc.constraint_name, kcu.ordinal_position`,[e,r]),o=new Map;for(let s of a.rows){let i=o.get(s.constraint_name);i?(i.columns.push(s.column_name),i.foreignColumns.push(s.foreign_column_name)):o.set(s.constraint_name,{columns:[s.column_name],foreignTable:s.foreign_table_name,foreignColumns:[s.foreign_column_name],updateRule:s.update_rule,deleteRule:s.delete_rule});}return Array.from(o.entries()).map(([s,i])=>{let c=i.columns.map(u=>`"${u}"`).join(", "),l=i.foreignColumns.map(u=>`"${u}"`).join(", "),m=`ALTER TABLE "${t}"."${r}" `;return m+=`ADD CONSTRAINT "${s}" FOREIGN KEY (${c}) `,m+=`REFERENCES "${t}"."${i.foreignTable}" (${l})`,i.updateRule!=="NO ACTION"&&(m+=` ON UPDATE ${i.updateRule}`),i.deleteRule!=="NO ACTION"&&(m+=` ON DELETE ${i.deleteRule}`),m})}async function
|
|
119
|
+
ORDER BY tc.constraint_name, kcu.ordinal_position`,[e,r]),o=new Map;for(let s of a.rows){let i=o.get(s.constraint_name);i?(i.columns.push(s.column_name),i.foreignColumns.push(s.foreign_column_name)):o.set(s.constraint_name,{columns:[s.column_name],foreignTable:s.foreign_table_name,foreignColumns:[s.foreign_column_name],updateRule:s.update_rule,deleteRule:s.delete_rule});}return Array.from(o.entries()).map(([s,i])=>{let c=i.columns.map(u=>`"${u}"`).join(", "),l=i.foreignColumns.map(u=>`"${u}"`).join(", "),m=`ALTER TABLE "${t}"."${r}" `;return m+=`ADD CONSTRAINT "${s}" FOREIGN KEY (${c}) `,m+=`REFERENCES "${t}"."${i.foreignTable}" (${l})`,i.updateRule!=="NO ACTION"&&(m+=` ON UPDATE ${i.updateRule}`),i.deleteRule!=="NO ACTION"&&(m+=` ON DELETE ${i.deleteRule}`),m})}async function Ea(n,e,t){let r=await n.query(`SELECT
|
|
120
120
|
tc.constraint_name,
|
|
121
121
|
kcu.column_name
|
|
122
122
|
FROM information_schema.table_constraints tc
|
|
@@ -126,7 +126,7 @@ import {Command}from'commander';import C from'chalk';import {checkbox,confirm,in
|
|
|
126
126
|
WHERE tc.table_schema = $1
|
|
127
127
|
AND tc.table_name = $2
|
|
128
128
|
AND tc.constraint_type = 'UNIQUE'
|
|
129
|
-
ORDER BY tc.constraint_name, kcu.ordinal_position`,[e,t]),a=new Map;for(let o of r.rows){let s=a.get(o.constraint_name);s?s.push(o.column_name):a.set(o.constraint_name,[o.column_name]);}return Array.from(a.entries()).map(([o,s])=>{let i=s.map(c=>`"${c}"`).join(", ");return `ALTER TABLE "${t}" ADD CONSTRAINT "${o}" UNIQUE (${i})`})}async function
|
|
129
|
+
ORDER BY tc.constraint_name, kcu.ordinal_position`,[e,t]),a=new Map;for(let o of r.rows){let s=a.get(o.constraint_name);s?s.push(o.column_name):a.set(o.constraint_name,[o.column_name]);}return Array.from(a.entries()).map(([o,s])=>{let i=s.map(c=>`"${c}"`).join(", ");return `ALTER TABLE "${t}" ADD CONSTRAINT "${o}" UNIQUE (${i})`})}async function Ra(n,e,t){return (await n.query(`SELECT
|
|
130
130
|
tc.constraint_name,
|
|
131
131
|
cc.check_clause
|
|
132
132
|
FROM information_schema.table_constraints tc
|
|
@@ -136,12 +136,12 @@ import {Command}from'commander';import C from'chalk';import {checkbox,confirm,in
|
|
|
136
136
|
WHERE tc.table_schema = $1
|
|
137
137
|
AND tc.table_name = $2
|
|
138
138
|
AND tc.constraint_type = 'CHECK'
|
|
139
|
-
AND tc.constraint_name NOT LIKE '%_not_null'`,[e,t])).rows.map(a=>`ALTER TABLE "${t}" ADD CONSTRAINT "${a.constraint_name}" CHECK (${a.check_clause})`)}async function
|
|
139
|
+
AND tc.constraint_name NOT LIKE '%_not_null'`,[e,t])).rows.map(a=>`ALTER TABLE "${t}" ADD CONSTRAINT "${a.constraint_name}" CHECK (${a.check_clause})`)}async function Da(n,e,t){let r=await n.query(`SELECT count(*) FROM "${e}"."${t}"`);return parseInt(r.rows[0].count,10)}async function tr(n,e,t,r){let[a,o,s,i,c,l,m]=await Promise.all([wa(n,e,r),$a(n,e,t,r),Ca(n,e,r),Ea(n,e,r),Ra(n,e,r),Ma(n,e,t,r),Da(n,e,r)]);return {name:r,createDdl:a,indexDdls:o,constraintDdls:[...s?[s]:[],...i,...c,...l],rowCount:m}}async function va(n,e,t){return (await n.query(`SELECT column_name
|
|
140
140
|
FROM information_schema.columns
|
|
141
141
|
WHERE table_schema = $1 AND table_name = $2
|
|
142
|
-
ORDER BY ordinal_position`,[e,t])).rows.map(a=>a.column_name)}function
|
|
142
|
+
ORDER BY ordinal_position`,[e,t])).rows.map(a=>a.column_name)}function _a(n){return n===null?"NULL":typeof n=="string"?`'${n.replace(/'/g,"''")}'`:typeof n=="boolean"?n?"TRUE":"FALSE":String(n)}async function Pa(n,e,t,r,a){let o=await va(n,e,r);if(o.length===0)return 0;let s=a?.[r]??{},i=o.map(u=>{if(u in s){let p=s[u];return `${_a(p)} as "${u}"`}return `"${u}"`}),c=o.map(u=>`"${u}"`).join(", "),l=i.join(", ");return (await n.query(`INSERT INTO "${t}"."${r}" (${c})
|
|
143
143
|
SELECT ${l}
|
|
144
|
-
FROM "${e}"."${r}"`)).rowCount??0}async function
|
|
144
|
+
FROM "${e}"."${r}"`)).rowCount??0}async function za(n,e,t){let r=await n.query(`SELECT DISTINCT
|
|
145
145
|
tc.table_name,
|
|
146
146
|
ccu.table_name as foreign_table_name
|
|
147
147
|
FROM information_schema.table_constraints tc
|
|
@@ -150,13 +150,13 @@ import {Command}from'commander';import C from'chalk';import {checkbox,confirm,in
|
|
|
150
150
|
AND tc.table_schema = ccu.table_schema
|
|
151
151
|
WHERE tc.table_schema = $1
|
|
152
152
|
AND tc.constraint_type = 'FOREIGN KEY'
|
|
153
|
-
AND tc.table_name != ccu.table_name`,[e]),a=new Map,o=new Set(t);for(let m of t)a.set(m,new Set);for(let m of r.rows)o.has(m.table_name)&&o.has(m.foreign_table_name)&&a.get(m.table_name).add(m.foreign_table_name);let s=[],i=new Map,c=[];for(let m of t)i.set(m,0);for(let[m,u]of a)for(let p of u)i.set(p,(i.get(p)??0)+1);for(let[m,u]of i)u===0&&c.push(m);for(;c.length>0;){let m=c.shift();s.push(m);for(let[u,p]of a)if(p.has(m)){p.delete(m);let h=(i.get(u)??0)-1;i.set(u,h),h===0&&c.push(u);}}let l=t.filter(m=>!s.includes(m));return [...s,...l]}async function nr(n,e,t,r,a,o){let s=0,i=await
|
|
153
|
+
AND tc.table_name != ccu.table_name`,[e]),a=new Map,o=new Set(t);for(let m of t)a.set(m,new Set);for(let m of r.rows)o.has(m.table_name)&&o.has(m.foreign_table_name)&&a.get(m.table_name).add(m.foreign_table_name);let s=[],i=new Map,c=[];for(let m of t)i.set(m,0);for(let[m,u]of a)for(let p of u)i.set(p,(i.get(p)??0)+1);for(let[m,u]of i)u===0&&c.push(m);for(;c.length>0;){let m=c.shift();s.push(m);for(let[u,p]of a)if(p.has(m)){p.delete(m);let h=(i.get(u)??0)-1;i.set(u,h),h===0&&c.push(u);}}let l=t.filter(m=>!s.includes(m));return [...s,...l]}async function nr(n,e,t,r,a,o){let s=0,i=await za(n,e,r);await n.query("SET session_replication_role = replica");try{for(let c=0;c<i.length;c++){let l=i[c];o?.("copying_data",{table:l,progress:c+1,total:i.length});let m=await Pa(n,e,t,l,a);s+=m;}}finally{await n.query("SET session_replication_role = DEFAULT");}return s}var ka="__drizzle_migrations",Ze=class{constructor(e,t){this.deps=t;this.migrationsTable=e.migrationsTable??ka;}migrationsTable;async cloneTenant(e,t,r={}){let a=Date.now(),{includeData:o=false,anonymize:s,excludeTables:i=[],onProgress:c}=r,l=this.deps.schemaNameTemplate(e),m=this.deps.schemaNameTemplate(t),u=[this.migrationsTable,...i],p=null,h=null;try{if(c?.("starting"),!await this.deps.schemaExists(e))return this.createErrorResult(e,t,m,`Source tenant "${e}" does not exist`,a);if(await this.deps.schemaExists(t))return this.createErrorResult(e,t,m,`Target tenant "${t}" already exists`,a);c?.("introspecting"),p=await this.deps.createPool(l);let S=await er(p,l,u);if(S.length===0)return c?.("creating_schema"),await this.deps.createSchema(t),c?.("completed"),{sourceTenant:e,targetTenant:t,targetSchema:m,success:!0,tables:[],durationMs:Date.now()-a};let b=await Promise.all(S.map(w=>tr(p,l,m,w)));await p.end(),p=null,c?.("creating_schema"),await this.deps.createSchema(t),h=await this.deps.createRootPool(),c?.("creating_tables");for(let w of b)await h.query(`SET search_path TO "${m}"; ${w.createDdl}`);c?.("creating_constraints");for(let w of b)for(let D of w.constraintDdls.filter(W=>!W.includes("FOREIGN KEY")))try{await h.query(`SET search_path TO "${m}"; ${D}`);}catch{}c?.("creating_indexes");for(let w of b)for(let D of w.indexDdls)try{await h.query(D);}catch{}let x=0;o&&(c?.("copying_data"),x=await nr(h,l,m,S,s?.enabled?s.rules:void 0,c));for(let w of b)for(let D of w.constraintDdls.filter(W=>W.includes("FOREIGN KEY")))try{await h.query(D);}catch{}c?.("completed");let y={sourceTenant:e,targetTenant:t,targetSchema:m,success:!0,tables:S,durationMs:Date.now()-a};return o&&(y.rowsCopied=x),y}catch(g){return r.onError?.(g),c?.("failed"),this.createErrorResult(e,t,m,g.message,a)}finally{p&&await p.end().catch(()=>{}),h&&await h.end().catch(()=>{});}}createErrorResult(e,t,r,a,o){return {sourceTenant:e,targetTenant:t,targetSchema:r,success:false,error:a,tables:[],durationMs:Date.now()-o}}};var Na="public",Xe=class{constructor(e,t){this.config=e;this.deps=t;this.schemaName=e.schemaName??Na;}schemaName;async migrate(e={}){let t=Date.now(),r=[],a=await this.deps.createPool();try{e.onProgress?.("starting"),await this.config.hooks?.beforeMigration?.();let o=await this.deps.getOrDetectFormat(a,this.schemaName);await this.deps.ensureMigrationsTable(a,this.schemaName,o);let s=await this.deps.loadMigrations(),i=await this.getAppliedMigrations(a,o),c=new Set(i.map(m=>m.identifier)),l=s.filter(m=>!this.isMigrationApplied(m,c,o));if(e.dryRun)return {schemaName:this.schemaName,success:!0,appliedMigrations:l.map(m=>m.name),durationMs:Date.now()-t,format:o.format};for(let m of l){let u=Date.now();e.onProgress?.("migrating",m.name),await this.applyMigration(a,m,o),await this.config.hooks?.afterMigration?.(m.name,Date.now()-u),r.push(m.name);}return e.onProgress?.("completed"),{schemaName:this.schemaName,success:!0,appliedMigrations:r,durationMs:Date.now()-t,format:o.format}}catch(o){return e.onProgress?.("failed"),{schemaName:this.schemaName,success:false,appliedMigrations:r,error:o.message,durationMs:Date.now()-t}}finally{await a.end();}}async markAsApplied(e={}){let t=Date.now(),r=[],a=await this.deps.createPool();try{e.onProgress?.("starting");let o=await this.deps.getOrDetectFormat(a,this.schemaName);await this.deps.ensureMigrationsTable(a,this.schemaName,o);let s=await this.deps.loadMigrations(),i=await this.getAppliedMigrations(a,o),c=new Set(i.map(m=>m.identifier)),l=s.filter(m=>!this.isMigrationApplied(m,c,o));for(let m of l)e.onProgress?.("migrating",m.name),await this.recordMigration(a,m,o),r.push(m.name);return e.onProgress?.("completed"),{schemaName:this.schemaName,success:!0,appliedMigrations:r,durationMs:Date.now()-t,format:o.format}}catch(o){return e.onProgress?.("failed"),{schemaName:this.schemaName,success:false,appliedMigrations:r,error:o.message,durationMs:Date.now()-t}}finally{await a.end();}}async getStatus(){let e=await this.deps.createPool();try{let t=await this.deps.loadMigrations();if(!await this.deps.migrationsTableExists(e,this.schemaName))return {schemaName:this.schemaName,appliedCount:0,pendingCount:t.length,pendingMigrations:t.map(c=>c.name),status:t.length>0?"behind":"ok",format:null};let a=await this.deps.getOrDetectFormat(e,this.schemaName),o=await this.getAppliedMigrations(e,a),s=new Set(o.map(c=>c.identifier)),i=t.filter(c=>!this.isMigrationApplied(c,s,a));return {schemaName:this.schemaName,appliedCount:o.length,pendingCount:i.length,pendingMigrations:i.map(c=>c.name),status:i.length>0?"behind":"ok",format:a.format}}catch(t){return {schemaName:this.schemaName,appliedCount:0,pendingCount:0,pendingMigrations:[],status:"error",error:t.message,format:null}}finally{await e.end();}}async getAppliedMigrations(e,t){let r=t.columns.identifier,a=t.columns.timestamp;return (await e.query(`SELECT id, "${r}" as identifier, "${a}" as applied_at
|
|
154
154
|
FROM "${this.schemaName}"."${t.tableName}"
|
|
155
|
-
ORDER BY id`)).rows.map(s=>{let i=t.columns.timestampType==="bigint"?new Date(Number(s.applied_at)):new Date(s.applied_at);return {identifier:s.identifier,...t.columns.identifier==="name"?{name:s.identifier}:{hash:s.identifier},appliedAt:i}})}isMigrationApplied(e,t,r){return r.columns.identifier==="name"?t.has(e.name):t.has(e.hash)||t.has(e.name)}async applyMigration(e,t,r){let a=await e.connect();try{await a.query("BEGIN"),await a.query(t.sql);let{identifier:o,timestamp:s,timestampType:i}=r.columns,c=o==="name"?t.name:t.hash,l=i==="bigint"?Date.now():new Date;await a.query(`INSERT INTO "${this.schemaName}"."${r.tableName}" ("${o}", "${s}") VALUES ($1, $2)`,[c,l]),await a.query("COMMIT");}catch(o){throw await a.query("ROLLBACK"),o}finally{a.release();}}async recordMigration(e,t,r){let{identifier:a,timestamp:o,timestampType:s}=r.columns,i=a==="name"?t.name:t.hash,c=s==="bigint"?Date.now():new Date;await e.query(`INSERT INTO "${this.schemaName}"."${r.tableName}" ("${a}", "${o}") VALUES ($1, $2)`,[i,c]);}};var
|
|
156
|
-
All tenants are up to date.`);let t=[
|
|
157
|
-
Pending migrations:`)];for(let[r,a]of e.entries())t.push(` ${
|
|
155
|
+
ORDER BY id`)).rows.map(s=>{let i=t.columns.timestampType==="bigint"?new Date(Number(s.applied_at)):new Date(s.applied_at);return {identifier:s.identifier,...t.columns.identifier==="name"?{name:s.identifier}:{hash:s.identifier},appliedAt:i}})}isMigrationApplied(e,t,r){return r.columns.identifier==="name"?t.has(e.name):t.has(e.hash)||t.has(e.name)}async applyMigration(e,t,r){let a=await e.connect();try{await a.query("BEGIN"),await a.query(t.sql);let{identifier:o,timestamp:s,timestampType:i}=r.columns,c=o==="name"?t.name:t.hash,l=i==="bigint"?Date.now():new Date;await a.query(`INSERT INTO "${this.schemaName}"."${r.tableName}" ("${o}", "${s}") VALUES ($1, $2)`,[c,l]),await a.query("COMMIT");}catch(o){throw await a.query("ROLLBACK"),o}finally{a.release();}}async recordMigration(e,t,r){let{identifier:a,timestamp:o,timestampType:s}=r.columns,i=a==="name"?t.name:t.hash,c=s==="bigint"?Date.now():new Date;await e.query(`INSERT INTO "${this.schemaName}"."${r.tableName}" ("${a}", "${o}") VALUES ($1, $2)`,[i,c]);}};var Aa="__drizzle_migrations",cr="__drizzle_shared_migrations",Rt=class{constructor(e,t){this.migratorConfig=t;if(this.migrationsTable=t.migrationsTable??Aa,this.schemaManager=new Be(e,this.migrationsTable),this.driftDetector=new Ge(e,this.schemaManager,{migrationsTable:this.migrationsTable,tenantDiscovery:t.tenantDiscovery}),this.seeder=new Je({tenantDiscovery:t.tenantDiscovery},{createPool:this.schemaManager.createPool.bind(this.schemaManager),schemaNameTemplate:e.isolation.schemaNameTemplate,tenantSchema:e.schemas.tenant}),this.syncManager=new Ve({tenantDiscovery:t.tenantDiscovery,migrationsFolder:t.migrationsFolder,migrationsTable:this.migrationsTable},{createPool:this.schemaManager.createPool.bind(this.schemaManager),schemaNameTemplate:e.isolation.schemaNameTemplate,migrationsTableExists:this.schemaManager.migrationsTableExists.bind(this.schemaManager),ensureMigrationsTable:this.schemaManager.ensureMigrationsTable.bind(this.schemaManager),getOrDetectFormat:this.getOrDetectFormat.bind(this),loadMigrations:this.loadMigrations.bind(this)}),this.migrationExecutor=new Ye({hooks:t.hooks},{createPool:this.schemaManager.createPool.bind(this.schemaManager),schemaNameTemplate:e.isolation.schemaNameTemplate,migrationsTableExists:this.schemaManager.migrationsTableExists.bind(this.schemaManager),ensureMigrationsTable:this.schemaManager.ensureMigrationsTable.bind(this.schemaManager),getOrDetectFormat:this.getOrDetectFormat.bind(this),loadMigrations:this.loadMigrations.bind(this)}),this.batchExecutor=new Qe({tenantDiscovery:t.tenantDiscovery},this.migrationExecutor,this.loadMigrations.bind(this)),this.cloner=new Ze({migrationsTable:this.migrationsTable},{createPool:this.schemaManager.createPool.bind(this.schemaManager),createRootPool:this.schemaManager.createRootPool.bind(this.schemaManager),schemaNameTemplate:e.isolation.schemaNameTemplate,schemaExists:this.schemaManager.schemaExists.bind(this.schemaManager),createSchema:this.schemaManager.createSchema.bind(this.schemaManager)}),t.sharedMigrationsFolder&&existsSync(t.sharedMigrationsFolder)){let r=t.sharedMigrationsTable??cr,a=t.sharedHooks,o={schemaName:"public",migrationsTable:r};(a?.beforeMigration||a?.afterApply)&&(o.hooks={},a.beforeMigration&&(o.hooks.beforeMigration=a.beforeMigration),a.afterApply&&(o.hooks.afterMigration=a.afterApply)),this.sharedMigrationExecutor=new Xe(o,{createPool:this.schemaManager.createRootPool.bind(this.schemaManager),migrationsTableExists:this.schemaManager.migrationsTableExists.bind(this.schemaManager),ensureMigrationsTable:this.schemaManager.ensureMigrationsTable.bind(this.schemaManager),getOrDetectFormat:this.getOrDetectSharedFormat.bind(this),loadMigrations:this.loadSharedMigrations.bind(this)});}else this.sharedMigrationExecutor=null;e.schemas.shared?this.sharedSeeder=new Ke({schemaName:"public"},{createPool:this.schemaManager.createRootPool.bind(this.schemaManager),sharedSchema:e.schemas.shared}):this.sharedSeeder=null;}migrationsTable;schemaManager;driftDetector;seeder;syncManager;migrationExecutor;batchExecutor;cloner;sharedMigrationExecutor;sharedSeeder;async migrateAll(e={}){return this.batchExecutor.migrateAll(e)}async migrateTenant(e,t,r={}){return this.migrationExecutor.migrateTenant(e,t,r)}async migrateTenants(e,t={}){return this.batchExecutor.migrateTenants(e,t)}async getStatus(){return this.batchExecutor.getStatus()}async getTenantStatus(e,t){return this.migrationExecutor.getTenantStatus(e,t)}async createTenant(e,t={}){let{migrate:r=true}=t;await this.schemaManager.createSchema(e),r&&await this.migrateTenant(e);}async dropTenant(e,t={}){await this.schemaManager.dropSchema(e,t);}async tenantExists(e){return this.schemaManager.schemaExists(e)}async cloneTenant(e,t,r={}){return this.cloner.cloneTenant(e,t,r)}async markAsApplied(e,t={}){return this.migrationExecutor.markAsApplied(e,t)}async markAllAsApplied(e={}){return this.batchExecutor.markAllAsApplied(e)}async getSyncStatus(){return this.syncManager.getSyncStatus()}async getTenantSyncStatus(e,t){return this.syncManager.getTenantSyncStatus(e,t)}async markMissing(e){return this.syncManager.markMissing(e)}async markAllMissing(e={}){return this.syncManager.markAllMissing(e)}async cleanOrphans(e){return this.syncManager.cleanOrphans(e)}async cleanAllOrphans(e={}){return this.syncManager.cleanAllOrphans(e)}async seedTenant(e,t){return this.seeder.seedTenant(e,t)}async seedAll(e,t={}){return this.seeder.seedAll(e,t)}async seedTenants(e,t,r={}){return this.seeder.seedTenants(e,t,r)}hasSharedSeeding(){return this.sharedSeeder!==null}async seedShared(e){return this.sharedSeeder?this.sharedSeeder.seed(e):{schemaName:"public",success:false,error:"Shared schema not configured. Set schemas.shared in tenant config.",durationMs:0}}async seedAllWithShared(e,t,r={}){let a=await this.seedShared(e),o=await this.seedAll(t,r);return {shared:a,tenants:o}}async loadMigrations(){let e=await readdir(this.migratorConfig.migrationsFolder),t=[];for(let r of e){if(!r.endsWith(".sql"))continue;let a=join(this.migratorConfig.migrationsFolder,r),o=await readFile(a,"utf-8"),s=r.match(/^(\d+)_/),i=s?.[1]?parseInt(s[1],10):0,c=createHash("sha256").update(o).digest("hex");t.push({name:basename(r,".sql"),path:a,sql:o,timestamp:i,hash:c});}return t.sort((r,a)=>r.timestamp-a.timestamp)}async getOrDetectFormat(e,t){let r=this.migratorConfig.tableFormat??"auto";if(r!=="auto")return Ee(r,this.migrationsTable);let a=await Me(e,t,this.migrationsTable);if(a)return a;let o=this.migratorConfig.defaultFormat??"name";return Ee(o,this.migrationsTable)}async loadSharedMigrations(){if(!this.migratorConfig.sharedMigrationsFolder)return [];let e=await readdir(this.migratorConfig.sharedMigrationsFolder),t=[];for(let r of e){if(!r.endsWith(".sql"))continue;let a=join(this.migratorConfig.sharedMigrationsFolder,r),o=await readFile(a,"utf-8"),s=r.match(/^(\d+)_/),i=s?.[1]?parseInt(s[1],10):0,c=createHash("sha256").update(o).digest("hex");t.push({name:basename(r,".sql"),path:a,sql:o,timestamp:i,hash:c});}return t.sort((r,a)=>r.timestamp-a.timestamp)}async getOrDetectSharedFormat(e,t){let r=this.migratorConfig.sharedMigrationsTable??cr,a=this.migratorConfig.sharedTableFormat??this.migratorConfig.tableFormat??"auto";if(a!=="auto")return Ee(a,r);let o=await Me(e,t,r);if(o)return o;let s=this.migratorConfig.sharedDefaultFormat??this.migratorConfig.defaultFormat??"name";return Ee(s,r)}hasSharedMigrations(){return this.sharedMigrationExecutor!==null}async migrateShared(e={}){return this.sharedMigrationExecutor?this.sharedMigrationExecutor.migrate(e):{schemaName:"public",success:false,appliedMigrations:[],error:"Shared migrations not configured. Set sharedMigrationsFolder in migrator config.",durationMs:0}}async getSharedStatus(){return this.sharedMigrationExecutor?this.sharedMigrationExecutor.getStatus():{schemaName:"public",appliedCount:0,pendingCount:0,pendingMigrations:[],status:"error",error:"Shared migrations not configured. Set sharedMigrationsFolder in migrator config.",format:null}}async markSharedAsApplied(e={}){return this.sharedMigrationExecutor?this.sharedMigrationExecutor.markAsApplied(e):{schemaName:"public",success:false,appliedMigrations:[],error:"Shared migrations not configured. Set sharedMigrationsFolder in migrator config.",durationMs:0}}async migrateAllWithShared(e={}){let{sharedOptions:t,...r}=e,a=await this.migrateShared(t??{}),o=await this.migrateAll(r);return {shared:a,tenants:o}}async getSchemaDrift(e={}){return this.driftDetector.detectDrift(e)}async getTenantSchemaDrift(e,t,r={}){return this.driftDetector.compareTenant(e,t,r)}async introspectTenantSchema(e,t={}){return this.driftDetector.introspectSchema(e,t)}};function I(n,e){return new Rt(n,e)}function R(n){return st({text:n,color:"cyan"})}function mr(n){if(n===null)return M.dim("(new)");switch(n){case "name":return M.blue("name");case "hash":return M.magenta("hash");case "drizzle-kit":return M.cyan("drizzle-kit")}}function dr(n){let e=new lr({head:[M.cyan("Tenant"),M.cyan("Schema"),M.cyan("Format"),M.cyan("Applied"),M.cyan("Pending"),M.cyan("Status")],style:{head:[],border:[]}});for(let t of n){let r=Oa(t.status),a=La(t.status);e.push([t.tenantId,M.dim(t.schemaName),mr(t.format),M.green(t.appliedCount.toString()),t.pendingCount>0?M.yellow(t.pendingCount.toString()):M.dim("0"),`${r} ${a}`]);}return e.toString()}function ur(n){let e=new lr({head:[M.cyan("Tenant"),M.cyan("Format"),M.cyan("Migrations"),M.cyan("Duration"),M.cyan("Status")],style:{head:[],border:[]}});for(let t of n){let r=t.success?M.green("\u2713"):M.red("\u2717"),a=t.success?M.green("OK"):M.red(t.error??"Failed");e.push([t.tenantId,mr(t.format??null),t.appliedMigrations.length.toString(),`${t.durationMs}ms`,`${r} ${a}`]);}return e.toString()}function pr(n){let e=new Map;for(let r of n)for(let a of r.pendingMigrations)e.set(a,(e.get(a)||0)+1);if(e.size===0)return M.green(`
|
|
156
|
+
All tenants are up to date.`);let t=[M.yellow(`
|
|
157
|
+
Pending migrations:`)];for(let[r,a]of e.entries())t.push(` ${M.dim("-")} ${r} ${M.dim(`(${a} tenant${a>1?"s":""})`)}`);return t.push(M.dim(`
|
|
158
158
|
Run 'drizzle-multitenant migrate --all' to apply pending migrations.`)),t.join(`
|
|
159
|
-
`)}function
|
|
159
|
+
`)}function Oa(n){switch(n){case "ok":return M.green("\u2713");case "behind":return M.yellow("\u26A0");case "error":return M.red("\u2717")}}function La(n){switch(n){case "ok":return M.green("OK");case "behind":return M.yellow("Behind");case "error":return M.red("Error")}}var ja=["tenant.config.ts","tenant.config.js","tenant.config.mjs","drizzle-multitenant.config.ts","drizzle-multitenant.config.js","drizzle-multitenant.config.mjs"],qa=["drizzle.config.ts","drizzle.config.js","drizzle.config.mjs"];async function k(n){let e=process.cwd(),t;if(n){if(t=resolve(e,n),!existsSync(t))throw new Error(`Config file not found: ${t}`)}else for(let h of ja){let g=resolve(e,h);if(existsSync(g)){t=g;break}}if(!t)throw new Error("Config file not found. Create a tenant.config.ts or use --config flag.");extname(t)===".ts"&&await fr();let o=await import(pathToFileURL(t).href),s=o.default??o;if(!s.connection||!s.isolation||!s.schemas)throw new Error("Invalid config file. Expected an object with connection, isolation, and schemas properties.");let i=await Ua(),c,l=null;s.migrations?.sharedFolder?(c=s.migrations.sharedFolder,l="tenant.config.ts"):i?.config.out&&(c=i.config.out,l="drizzle.config.ts");let m=s.migrations?.sharedTable??i?.config.migrations?.table??"__drizzle_migrations",u=s.migrations?.sharedTableFormat??"auto",p=s.migrations?.sharedDefaultFormat;return {config:s,migrationsFolder:s.migrations?.tenantFolder??s.migrations?.folder,migrationsTable:s.migrations?.migrationsTable??s.migrations?.table,tenantDiscovery:s.migrations?.tenantDiscovery,sharedMigrationsFolder:c,sharedMigrationsTable:m,sharedTableFormat:u,sharedDefaultFormat:p,drizzleKitConfig:i?.config??null,drizzleKitConfigFile:i?.fileName??null,sharedConfigSource:l}}async function fr(){try{await import('tsx/esm');}catch{try{await import('ts-node/esm');}catch{throw new Error("TypeScript config requires tsx or ts-node. Install with: npm install -D tsx")}}}async function Ua(){let n=process.cwd();for(let e of qa){let t=resolve(n,e);if(existsSync(t)){extname(t)===".ts"&&await fr();try{let o=await import(pathToFileURL(t).href),s=o.default??o;return {config:{out:s.out,schema:s.schema,dialect:s.dialect,dbCredentials:s.dbCredentials,migrations:s.migrations},fileName:e}}catch(a){return console.warn(`Warning: Found ${e} but failed to load it:`,a instanceof Error?a.message:a),null}}}return null}function O(n){let e=process.cwd(),r=resolve(e,n??"./drizzle/tenant");if(!existsSync(r))throw new Error(`Migrations folder not found: ${r}`);return r}var re={isInteractive:process.stdout.isTTY??false,jsonMode:false,verbose:false,quiet:false,noColor:false};function yr(n){re={isInteractive:process.stdout.isTTY??false,jsonMode:n.json??false,verbose:n.verbose??false,quiet:n.quiet??false,noColor:n.noColor??!process.stdout.isTTY},re.noColor&&(M.level=0);}function L(){return re}function me(){return re.isInteractive&&!re.jsonMode&&!re.quiet}function Wa(){return !re.jsonMode&&!re.quiet}function Ha(){return re.verbose&&!re.jsonMode}function d(n){Wa()&&console.log(n);}function H(n){Ha()&&console.log(M.dim(`[debug] ${n}`));}function N(n){re.jsonMode&&console.log(JSON.stringify(n,null,2));}function E(n){return M.green("\u2713 ")+n}function v(n){return M.red("\u2717 ")+n}function C(n){return M.yellow("\u26A0 ")+n}function J(n){return M.blue("\u2139 ")+n}function f(n){return M.dim(n)}function T(n){return M.bold(n)}function G(n){return M.cyan(n)}function Y(n){return M.green(n)}function te(n){return M.red(n)}function Z(n){return M.yellow(n)}function ae(n){let{total:e}=n;if(!me())return {start:()=>{},update:()=>{},increment:()=>{},stop:()=>{}};let t=n.format||`${M.cyan("Migrating")} ${M.cyan("{bar}")} ${M.yellow("{percentage}%")} | {value}/{total} | {tenant} | ${f("{eta}s")}`,r=new Sr.SingleBar({format:t,barCompleteChar:"\u2588",barIncompleteChar:"\u2591",hideCursor:true,clearOnComplete:false,stopOnComplete:true,etaBuffer:10},Sr.Presets.shades_classic),a=0;return {start(){r.start(e,0,{tenant:"starting...",status:""});},update(o,s){a=o;let i=s?.status==="success"?Y("\u2713"):s?.status==="error"?te("\u2717"):"";r.update(o,{tenant:s?.tenant?`${i} ${s.tenant}`:""});},increment(o){a++;let s=o?.status==="success"?Y("\u2713"):o?.status==="error"?te("\u2717"):"";r.update(a,{tenant:o?.tenant?`${s} ${o.tenant}`:""});},stop(){r.stop();}}}var X=class extends Error{constructor(t,r,a,o){super(t);this.suggestion=r;this.example=a;this.docs=o;this.name="CLIError";}format(){let t=[v(this.message)];return this.suggestion&&(t.push(""),t.push(f(" Suggestion: ")+this.suggestion)),this.example&&(t.push(""),t.push(f(" Example:")),t.push(G(" "+this.example))),this.docs&&(t.push(""),t.push(f(" Docs: ")+this.docs)),t.join(`
|
|
160
160
|
`)}toJSON(){return {error:this.message,suggestion:this.suggestion,example:this.example,docs:this.docs}}},Q={configNotFound:n=>new X("Configuration file not found","Create a tenant.config.ts file or use --config <path>",`export default defineConfig({
|
|
161
161
|
connection: process.env.DATABASE_URL,
|
|
162
162
|
isolation: { type: 'schema', schemaNameTemplate: (id) => \`tenant_\${id}\` },
|
|
@@ -184,67 +184,67 @@ Examples:
|
|
|
184
184
|
$ drizzle-multitenant migrate --all --concurrency=5
|
|
185
185
|
$ drizzle-multitenant migrate --all --json
|
|
186
186
|
$ drizzle-multitenant migrate --all --mark-applied
|
|
187
|
-
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await
|
|
188
|
-
`));let x=await i();if(x.length===0){d(
|
|
187
|
+
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await k(n.config),c=n.migrationsFolder?O(n.migrationsFolder):O(o);H(`Using migrations folder: ${c}`);let l,m;if(n.tenant)l=async()=>[n.tenant],m=[n.tenant];else if(n.tenants){let x=n.tenants.split(",").map(y=>y.trim());l=async()=>x,m=x;}else if(n.all){if(!i)throw Q.noTenantDiscovery();l=i,r.text="Discovering tenants...",m=await i();}else if(r.stop(),me()&&i){d(J(`No tenants specified. Fetching available tenants...
|
|
188
|
+
`));let x=await i();if(x.length===0){d(C("No tenants found."));return}let y=await checkbox({message:"Select tenants to migrate:",choices:x.map(w=>({name:w,value:w})),pageSize:15});if(y.length===0){d(C("No tenants selected. Aborting."));return}l=async()=>y,m=y;}else throw Q.noTenantSpecified();if(m.length===0){r.stop(),d(C("No tenants found."));return}r.text=`Found ${m.length} tenant${m.length>1?"s":""}`,r.succeed(),n.dryRun&&d(J(T(`
|
|
189
189
|
Dry run mode - no changes will be made
|
|
190
190
|
`))),n.markApplied&&d(J(T(`
|
|
191
191
|
Mark-applied mode - migrations will be recorded without executing SQL
|
|
192
192
|
`)));let u=n.markApplied?"Marking":"Migrating";d(J(`${u} ${m.length} tenant${m.length>1?"s":""}...
|
|
193
|
-
`));let p=I(a,{migrationsFolder:c,...s&&{migrationsTable:s},tenantDiscovery:l}),h=parseInt(n.concurrency||"10",10),g=ae({total:m.length});g.start();let
|
|
194
|
-
`+T("Results:")),d(ur(
|
|
195
|
-
`+T("Summary:")),d(` Total: ${
|
|
196
|
-
`+T("Failed tenants:"));for(let x of
|
|
197
|
-
`+f("Run with --verbose to see more details."));}
|
|
193
|
+
`));let p=I(a,{migrationsFolder:c,...s&&{migrationsTable:s},tenantDiscovery:l}),h=parseInt(n.concurrency||"10",10),g=ae({total:m.length});g.start();let $={concurrency:h,onProgress:(x,y,w)=>{if(y==="completed")g.increment({tenant:x,status:"success"}),H(`Completed: ${x}`);else if(y==="failed")g.increment({tenant:x,status:"error"}),H(`Failed: ${x}`);else if(y==="migrating"&&w){let D=n.markApplied?"Marking":"Applying";H(`${x}: ${D} ${w}`);}},onError:(x,y)=>(H(`Error on ${x}: ${y.message}`),"continue")},S=n.markApplied?await p.markAllAsApplied($):await p.migrateAll({...$,dryRun:!!n.dryRun});g.stop();let b=Date.now()-e;if(t.jsonMode){let x={results:S.details.map(y=>({tenantId:y.tenantId,schema:y.schemaName,success:y.success,appliedMigrations:y.appliedMigrations,durationMs:y.durationMs,format:y.format,error:y.error})),summary:{total:S.total,succeeded:S.succeeded,failed:S.failed,skipped:S.skipped,durationMs:b,averageMs:S.total>0?Math.round(b/S.total):void 0}};N(x),process.exit(S.failed>0?1:0);}if(d(`
|
|
194
|
+
`+T("Results:")),d(ur(S.details)),d(`
|
|
195
|
+
`+T("Summary:")),d(` Total: ${S.total}`),d(` Succeeded: ${E(S.succeeded.toString())}`),S.failed>0&&d(` Failed: ${v(S.failed.toString())}`),S.skipped>0&&d(` Skipped: ${C(S.skipped.toString())}`),d(` Duration: ${f(br(b))}`),S.total>0&&d(` Average: ${f(br(Math.round(b/S.total))+"/tenant")}`),S.failed>0){d(`
|
|
196
|
+
`+T("Failed tenants:"));for(let x of S.details.filter(y=>!y.success))d(` ${v(x.tenantId)}: ${f(x.error||"Unknown error")}`);d(`
|
|
197
|
+
`+f("Run with --verbose to see more details."));}S.failed>0&&process.exit(1);}catch(a){r.fail(a.message),j(a);}});function br(n){if(n<1e3)return `${n}ms`;if(n<6e4)return `${(n/1e3).toFixed(1)}s`;let e=Math.floor(n/6e4),t=Math.round(n%6e4/1e3);return `${e}m ${t}s`}var _t=new Command("migrate:shared").description("Apply pending migrations to the shared schema (public)").option("-c, --config <path>","Path to config file").option("--migrations-folder <path>","Path to shared migrations folder").option("--dry-run","Show what would be applied without executing").option("--mark-applied","Mark migrations as applied without executing SQL").addHelpText("after",`
|
|
198
198
|
Examples:
|
|
199
199
|
$ drizzle-multitenant migrate:shared
|
|
200
200
|
$ drizzle-multitenant migrate:shared --dry-run
|
|
201
201
|
$ drizzle-multitenant migrate:shared --mark-applied
|
|
202
202
|
$ drizzle-multitenant migrate:shared --migrations-folder=./drizzle/shared-migrations
|
|
203
203
|
$ drizzle-multitenant migrate:shared --json
|
|
204
|
-
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let a=await
|
|
205
|
-
No pending migrations.`)),t.jsonMode){let y={schemaName:
|
|
204
|
+
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let a=await k(n.config),{config:o,sharedMigrationsFolder:s,migrationsTable:i,tenantDiscovery:c,sharedMigrationsTable:l,sharedTableFormat:m,sharedDefaultFormat:u}=a,p=n.migrationsFolder?O(n.migrationsFolder):s?O(s):null;if(!p)throw r.fail("Shared migrations folder not configured"),Q.create("Shared migrations folder not configured","Set sharedMigrationsFolder in your config file or use --migrations-folder option","drizzle-multitenant migrate:shared --migrations-folder=./drizzle/shared-migrations");H(`Using shared migrations folder: ${p}`),r.text="Checking shared migrations...";let h=I(o,{migrationsFolder:a.migrationsFolder??"./drizzle/tenant-migrations",sharedMigrationsFolder:p,...i&&{migrationsTable:i},...l&&{sharedMigrationsTable:l},...m&&m!=="auto"&&{sharedTableFormat:m},...u&&{sharedDefaultFormat:u},tenantDiscovery:c??(async()=>[])});if(!h.hasSharedMigrations())throw r.fail("Shared migrations not available"),Q.create("Shared migrations folder does not exist or is empty",`Create the folder at: ${p}`,`mkdir -p ${p}`);let g=await h.getSharedStatus();if(g.status==="error")throw r.fail("Failed to get shared migration status"),new Error(g.error);if(g.pendingCount===0){if(r.succeed("Shared schema is up to date"),d(J(`
|
|
205
|
+
No pending migrations.`)),t.jsonMode){let y={schemaName:g.schemaName,success:!0,appliedMigrations:[],durationMs:Date.now()-e,format:g.format??void 0};N(y);}return}r.succeed(`Found ${g.pendingCount} pending migration${g.pendingCount>1?"s":""}`),n.dryRun&&d(J(T(`
|
|
206
206
|
Dry run mode - no changes will be made
|
|
207
207
|
`))),n.markApplied&&d(J(T(`
|
|
208
208
|
Mark-applied mode - migrations will be recorded without executing SQL
|
|
209
|
-
`)));let
|
|
210
|
-
${
|
|
211
|
-
`));for(let y of
|
|
212
|
-
`+T("Applied migrations:"));for(let y of
|
|
213
|
-
${f(`Duration: ${
|
|
214
|
-
${v("Error:")} ${
|
|
209
|
+
`)));let $=n.markApplied?"Marking":"Applying";d(J(`
|
|
210
|
+
${$} ${g.pendingCount} migration${g.pendingCount>1?"s":""} to shared schema...
|
|
211
|
+
`));for(let y of g.pendingMigrations)d(f(` - ${y}`));d("");let S=R(`${$} migrations...`);S.start();let b=n.markApplied?await h.markSharedAsApplied({onProgress:(y,w)=>{y==="migrating"&&w&&(S.text=`Marking: ${w}`);}}):await h.migrateShared({dryRun:n.dryRun,onProgress:(y,w)=>{y==="migrating"&&w&&(S.text=`Applying: ${w}`);}}),x=Date.now()-e;if(b.success?S.succeed(`${n.dryRun?"Would apply":"Applied"} ${b.appliedMigrations.length} migration${b.appliedMigrations.length>1?"s":""}`):S.fail("Migration failed"),t.jsonMode){let y={schemaName:b.schemaName,success:b.success,appliedMigrations:b.appliedMigrations,durationMs:x,format:b.format,error:b.error};N(y),process.exit(b.success?0:1);}if(b.success){d(`
|
|
212
|
+
`+T("Applied migrations:"));for(let y of b.appliedMigrations)d(` ${E("\u2713")} ${y}`);d(`
|
|
213
|
+
${f(`Duration: ${Ka(x)}`)}`);}else d(`
|
|
214
|
+
${v("Error:")} ${b.error}`),process.exit(1);}catch(a){r.fail(a.message),j(a);}});function Ka(n){if(n<1e3)return `${n}ms`;if(n<6e4)return `${(n/1e3).toFixed(1)}s`;let e=Math.floor(n/6e4),t=Math.round(n%6e4/1e3);return `${e}m ${t}s`}var Pt=new Command("status").description("Show migration status for all tenants").option("-c, --config <path>","Path to config file").option("--migrations-folder <path>","Path to migrations folder").addHelpText("after",`
|
|
215
215
|
Examples:
|
|
216
216
|
$ drizzle-multitenant status
|
|
217
217
|
$ drizzle-multitenant status --json
|
|
218
218
|
$ drizzle-multitenant status --json | jq '.tenants[] | select(.pending > 0)'
|
|
219
219
|
$ drizzle-multitenant status --verbose
|
|
220
|
-
`).action(async n=>{let e=L(),t=R("Loading configuration...");try{t.start();let{config:r,migrationsFolder:a,migrationsTable:o,tenantDiscovery:s}=await
|
|
221
|
-
`+T("Migration Status:")),d(dr(l)),d(pr(l));}catch(r){t.fail(r.message),j(r);}});var
|
|
220
|
+
`).action(async n=>{let e=L(),t=R("Loading configuration...");try{t.start();let{config:r,migrationsFolder:a,migrationsTable:o,tenantDiscovery:s}=await k(n.config);if(!s)throw Q.noTenantDiscovery();let i=n.migrationsFolder?O(n.migrationsFolder):O(a);H(`Using migrations folder: ${i}`),t.text="Discovering tenants...";let c=I(r,{migrationsFolder:i,...o&&{migrationsTable:o},tenantDiscovery:s});t.text="Fetching migration status...";let l=await c.getStatus();t.succeed(`Found ${l.length} tenant${l.length>1?"s":""}`);let m={total:l.length,upToDate:l.filter(u=>u.status==="ok").length,behind:l.filter(u=>u.status==="behind").length,error:l.filter(u=>u.status==="error").length};if(H(`Summary: ${m.upToDate} up-to-date, ${m.behind} behind, ${m.error} errors`),e.jsonMode){let u={tenants:l.map(p=>({id:p.tenantId,schema:p.schemaName,format:p.format,applied:p.appliedCount,pending:p.pendingCount,status:p.status,pendingMigrations:p.pendingMigrations,error:p.error})),summary:m};N(u);return}d(`
|
|
221
|
+
`+T("Migration Status:")),d(dr(l)),d(pr(l));}catch(r){t.fail(r.message),j(r);}});var zt=new Command("sync").description("Detect and fix divergences between migrations on disk and tracking in database").option("-c, --config <path>","Path to config file").option("-s, --status","Show sync status without making changes").option("--mark-missing","Mark missing migrations as applied").option("--clean-orphans","Remove orphan records from tracking table").option("--concurrency <number>","Number of concurrent operations","10").option("--migrations-folder <path>","Path to migrations folder").addHelpText("after",`
|
|
222
222
|
Examples:
|
|
223
223
|
$ drizzle-multitenant sync --status
|
|
224
224
|
$ drizzle-multitenant sync --mark-missing
|
|
225
225
|
$ drizzle-multitenant sync --clean-orphans
|
|
226
226
|
$ drizzle-multitenant sync --mark-missing --clean-orphans
|
|
227
227
|
$ drizzle-multitenant sync --status --json
|
|
228
|
-
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await
|
|
229
|
-
`+T("Sync Status:")),d(
|
|
228
|
+
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await k(n.config);if(!i)throw Q.noTenantDiscovery();let c=n.migrationsFolder?O(n.migrationsFolder):O(o);H(`Using migrations folder: ${c}`);let l=I(a,{migrationsFolder:c,...s&&{migrationsTable:s},tenantDiscovery:i});if(n.status||!n.markMissing&&!n.cleanOrphans){r.text="Fetching sync status...";let p=await l.getSyncStatus();if(r.succeed(`Found ${p.total} tenant${p.total>1?"s":""}`),t.jsonMode){let h={tenants:p.details.map(g=>({id:g.tenantId,schema:g.schemaName,format:g.format,inSync:g.inSync,missing:g.missing,orphans:g.orphans,error:g.error})),summary:{total:p.total,inSync:p.inSync,outOfSync:p.outOfSync,error:p.error}};N(h);return}d(`
|
|
229
|
+
`+T("Sync Status:")),d(Qa(p.details)),d(Za(p));return}let u=parseInt(n.concurrency||"10",10);if(n.markMissing){r.text="Marking missing migrations...",r.succeed();let p=await i();d(J(`
|
|
230
230
|
Marking missing migrations for ${p.length} tenant${p.length>1?"s":""}...
|
|
231
|
-
`));let h=ae({total:p.length});h.start();let g=await l.markAllMissing({concurrency:u,onProgress:(
|
|
232
|
-
`+T("Results:")),d(
|
|
233
|
-
`+T("Summary:")),d(` Total: ${g.total}`),d(` Succeeded: ${E(g.succeeded.toString())}`),g.failed>0&&d(` Failed: ${v(g.failed.toString())}`),d(` Duration: ${f(
|
|
231
|
+
`));let h=ae({total:p.length});h.start();let g=await l.markAllMissing({concurrency:u,onProgress:(S,b)=>{b==="completed"?h.increment({tenant:S,status:"success"}):b==="failed"&&h.increment({tenant:S,status:"error"});},onError:(S,b)=>(H(`Error on ${S}: ${b.message}`),"continue")});h.stop();let $=Date.now()-e;t.jsonMode&&(N({action:"mark-missing",results:g.details.map(S=>({tenantId:S.tenantId,schema:S.schemaName,success:S.success,markedMigrations:S.markedMigrations,durationMs:S.durationMs,error:S.error})),summary:{total:g.total,succeeded:g.succeeded,failed:g.failed,durationMs:$}}),process.exit(g.failed>0?1:0)),d(`
|
|
232
|
+
`+T("Results:")),d(xr(g.details,"mark-missing")),d(`
|
|
233
|
+
`+T("Summary:")),d(` Total: ${g.total}`),d(` Succeeded: ${E(g.succeeded.toString())}`),g.failed>0&&d(` Failed: ${v(g.failed.toString())}`),d(` Duration: ${f(Tr($))}`);}if(n.cleanOrphans){n.markMissing&&d(`
|
|
234
234
|
`),r.text="Cleaning orphan records...",r.succeed();let p=await i();d(J(`
|
|
235
235
|
Cleaning orphan records for ${p.length} tenant${p.length>1?"s":""}...
|
|
236
|
-
`));let h=ae({total:p.length});h.start();let g=await l.cleanAllOrphans({concurrency:u,onProgress:(
|
|
237
|
-
`+T("Results:")),d(
|
|
238
|
-
`+T("Summary:")),d(` Total: ${g.total}`),d(` Succeeded: ${E(g.succeeded.toString())}`),g.failed>0&&d(` Failed: ${v(g.failed.toString())}`),d(` Duration: ${f(
|
|
239
|
-
`;let e=[];for(let t of n)if(t.error)e.push(` ${v(t.tenantId)}: ${f(t.error)}`);else if(t.inSync)e.push(` ${E(t.tenantId)}: ${f("In sync")}`);else {let r=[];t.missing.length>0&&r.push(`${t.missing.length} missing`),t.orphans.length>0&&r.push(`${t.orphans.length} orphan${t.orphans.length>1?"s":""}`),e.push(` ${
|
|
236
|
+
`));let h=ae({total:p.length});h.start();let g=await l.cleanAllOrphans({concurrency:u,onProgress:(S,b)=>{b==="completed"?h.increment({tenant:S,status:"success"}):b==="failed"&&h.increment({tenant:S,status:"error"});},onError:(S,b)=>(H(`Error on ${S}: ${b.message}`),"continue")});h.stop();let $=Date.now()-e;t.jsonMode&&(N({action:"clean-orphans",results:g.details.map(S=>({tenantId:S.tenantId,schema:S.schemaName,success:S.success,removedOrphans:S.removedOrphans,durationMs:S.durationMs,error:S.error})),summary:{total:g.total,succeeded:g.succeeded,failed:g.failed,durationMs:$}}),process.exit(g.failed>0?1:0)),d(`
|
|
237
|
+
`+T("Results:")),d(xr(g.details,"clean-orphans")),d(`
|
|
238
|
+
`+T("Summary:")),d(` Total: ${g.total}`),d(` Succeeded: ${E(g.succeeded.toString())}`),g.failed>0&&d(` Failed: ${v(g.failed.toString())}`),d(` Duration: ${f(Tr($))}`);}}catch(a){r.fail(a.message),j(a);}});function Tr(n){if(n<1e3)return `${n}ms`;if(n<6e4)return `${(n/1e3).toFixed(1)}s`;let e=Math.floor(n/6e4),t=Math.round(n%6e4/1e3);return `${e}m ${t}s`}function Qa(n){if(n.length===0)return ` No tenants found.
|
|
239
|
+
`;let e=[];for(let t of n)if(t.error)e.push(` ${v(t.tenantId)}: ${f(t.error)}`);else if(t.inSync)e.push(` ${E(t.tenantId)}: ${f("In sync")}`);else {let r=[];t.missing.length>0&&r.push(`${t.missing.length} missing`),t.orphans.length>0&&r.push(`${t.orphans.length} orphan${t.orphans.length>1?"s":""}`),e.push(` ${C(t.tenantId)}: ${r.join(", ")}`),t.missing.length>0&&e.push(` ${f("Missing:")} ${t.missing.slice(0,3).join(", ")}${t.missing.length>3?`, +${t.missing.length-3} more`:""}`),t.orphans.length>0&&e.push(` ${f("Orphans:")} ${t.orphans.slice(0,3).join(", ")}${t.orphans.length>3?`, +${t.orphans.length-3} more`:""}`);}return e.join(`
|
|
240
240
|
`)+`
|
|
241
|
-
`}function
|
|
242
|
-
`+T("Summary:")),e.push(` Total: ${n.total}`),e.push(` In Sync: ${E(n.inSync.toString())}`),n.outOfSync>0&&e.push(` Out of Sync: ${
|
|
241
|
+
`}function Za(n){let e=[];return e.push(`
|
|
242
|
+
`+T("Summary:")),e.push(` Total: ${n.total}`),e.push(` In Sync: ${E(n.inSync.toString())}`),n.outOfSync>0&&e.push(` Out of Sync: ${C(n.outOfSync.toString())}`),n.error>0&&e.push(` Errors: ${v(n.error.toString())}`),n.outOfSync>0&&(e.push(`
|
|
243
243
|
`+f("Run with --mark-missing to mark missing migrations as applied.")),e.push(f("Run with --clean-orphans to remove orphan records."))),e.join(`
|
|
244
|
-
`)}function
|
|
244
|
+
`)}function xr(n,e){if(n.length===0)return ` No tenants processed.
|
|
245
245
|
`;let t=[];for(let r of n)r.error?t.push(` ${v(r.tenantId)}: ${f(r.error)}`):e==="mark-missing"?r.markedMigrations.length>0?t.push(` ${E(r.tenantId)}: Marked ${r.markedMigrations.length} migration${r.markedMigrations.length>1?"s":""}`):t.push(` ${f(r.tenantId)}: Nothing to mark`):r.removedOrphans.length>0?t.push(` ${E(r.tenantId)}: Removed ${r.removedOrphans.length} orphan${r.removedOrphans.length>1?"s":""}`):t.push(` ${f(r.tenantId)}: No orphans found`);return t.join(`
|
|
246
246
|
`)+`
|
|
247
|
-
`}var
|
|
247
|
+
`}var kt=new Command("diff").description("Detect schema drift between tenant schemas").option("-c, --config <path>","Path to config file").option("-r, --reference <tenant>","Tenant ID to use as reference (default: first tenant)").option("-t, --tenant <tenant>","Check only this tenant against reference").option("--tenants <tenants>","Check only these tenants (comma-separated)").option("--concurrency <number>","Number of concurrent operations","10").option("--no-indexes","Skip index comparison").option("--no-constraints","Skip constraint comparison").option("--exclude-tables <tables>","Tables to exclude from comparison (comma-separated)").addHelpText("after",`
|
|
248
248
|
Examples:
|
|
249
249
|
$ drizzle-multitenant diff
|
|
250
250
|
$ drizzle-multitenant diff --reference=tenant-1
|
|
@@ -253,18 +253,18 @@ Examples:
|
|
|
253
253
|
$ drizzle-multitenant diff --no-indexes --no-constraints
|
|
254
254
|
$ drizzle-multitenant diff --exclude-tables=logs,audit
|
|
255
255
|
$ drizzle-multitenant diff --json
|
|
256
|
-
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await
|
|
257
|
-
Using "${
|
|
258
|
-
`));let b=ae({total
|
|
259
|
-
`+T("Schema Drift Status:")),d(
|
|
260
|
-
`+T("Drift Details:"));for(let D of
|
|
261
|
-
${
|
|
262
|
-
`;let t=[];for(let r of n){let o=r.tenantId===e?f("(ref) "):"";r.error?t.push(` ${v(r.tenantId)}: ${o}${f(r.error)}`):r.hasDrift?t.push(` ${
|
|
256
|
+
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await k(n.config);if(!i)throw Q.noTenantDiscovery();H(`Using config from: ${n.config||"default location"}`);let c=I(a,{migrationsFolder:o??"./drizzle",...s&&{migrationsTable:s},tenantDiscovery:i}),l=parseInt(n.concurrency||"10",10),m=n.indexes!==!1,u=n.constraints!==!1,p=n.excludeTables?n.excludeTables.split(",").map(D=>D.trim()):[],h;n.tenant?h=[n.tenant]:n.tenants&&(h=n.tenants.split(",").map(D=>D.trim())),r.text="Detecting schema drift...";let g=await i(),$=h??g;if($.length===0){r.fail("No tenants found");return}r.succeed(`Found ${g.length} tenant${g.length>1?"s":""}`);let S=n.reference??$[0];d(J(`
|
|
257
|
+
Using "${S}" as reference tenant
|
|
258
|
+
`));let b=ae({total:$.length});b.start();let x=await c.getSchemaDrift({referenceTenant:S,tenantIds:$,concurrency:l,includeIndexes:m,includeConstraints:u,excludeTables:p,onProgress:(D,W)=>{W==="completed"?b.increment({tenant:D,status:"success"}):W==="failed"&&b.increment({tenant:D,status:"error"});}});b.stop();let y=Date.now()-e;if(t.jsonMode){let D={referenceTenant:x.referenceTenant,tenants:x.details.map(W=>({id:W.tenantId,schema:W.schemaName,hasDrift:W.hasDrift,issueCount:W.issueCount,tables:W.tables.map(z=>({name:z.table,status:z.status,columns:z.columns,indexes:z.indexes,constraints:z.constraints})),error:W.error})),summary:{total:x.total,noDrift:x.noDrift,withDrift:x.withDrift,error:x.error,durationMs:y}};N(D),process.exit(x.withDrift>0?1:0);}d(`
|
|
259
|
+
`+T("Schema Drift Status:")),d(to(x.details,x.referenceTenant)),d(no(x,y));let w=x.details.filter(D=>D.hasDrift);if(w.length>0){d(`
|
|
260
|
+
`+T("Drift Details:"));for(let D of w){d(`
|
|
261
|
+
${C(D.tenantId)} (${D.schemaName}):`);for(let W of D.tables)if(W.status==="missing")d(` ${v("\u2717")} Table "${W.table}" is missing`);else if(W.status==="extra")d(` ${C("+")} Table "${W.table}" is extra (not in reference)`);else if(W.status==="drifted"){d(` ${C("~")} Table "${W.table}":`);for(let z of W.columns){let B=z.type==="missing"?v("\u2717"):z.type==="extra"?C("+"):C("~");d(` ${B} ${z.description}`);}for(let z of W.indexes){let B=z.type==="missing"?v("\u2717"):z.type==="extra"?C("+"):C("~");d(` ${B} ${z.description}`);}for(let z of W.constraints){let B=z.type==="missing"?v("\u2717"):z.type==="extra"?C("+"):C("~");d(` ${B} ${z.description}`);}}}}process.exit(x.withDrift>0?1:0);}catch(a){r.fail(a.message),j(a);}});function eo(n){if(n<1e3)return `${n}ms`;if(n<6e4)return `${(n/1e3).toFixed(1)}s`;let e=Math.floor(n/6e4),t=Math.round(n%6e4/1e3);return `${e}m ${t}s`}function to(n,e){if(n.length===0)return ` No tenants found.
|
|
262
|
+
`;let t=[];for(let r of n){let o=r.tenantId===e?f("(ref) "):"";r.error?t.push(` ${v(r.tenantId)}: ${o}${f(r.error)}`):r.hasDrift?t.push(` ${C(r.tenantId)}: ${o}${r.issueCount} issue${r.issueCount>1?"s":""} detected`):t.push(` ${E(r.tenantId)}: ${o}${f("No drift")}`);}return t.join(`
|
|
263
263
|
`)+`
|
|
264
|
-
`}function
|
|
265
|
-
`+T("Summary:")),t.push(` Total: ${n.total}`),t.push(` No Drift: ${E(n.noDrift.toString())}`),n.withDrift>0&&t.push(` With Drift: ${
|
|
264
|
+
`}function no(n,e){let t=[];return t.push(`
|
|
265
|
+
`+T("Summary:")),t.push(` Total: ${n.total}`),t.push(` No Drift: ${E(n.noDrift.toString())}`),n.withDrift>0&&t.push(` With Drift: ${C(n.withDrift.toString())}`),n.error>0&&t.push(` Errors: ${v(n.error.toString())}`),t.push(` Duration: ${f(eo(e))}`),n.withDrift>0&&t.push(`
|
|
266
266
|
`+f("Run migrations to fix drift: drizzle-multitenant migrate --all")),t.join(`
|
|
267
|
-
`)}var It=new Command("generate").description("Generate a new migration file").requiredOption("-n, --name <name>","Migration name").option("-c, --config <path>","Path to config file").option("--type <type>","Migration type: tenant or shared","tenant").option("--migrations-folder <path>","Path to migrations folder").action(async n=>{let e=R("Loading configuration...");try{e.start();let{migrationsFolder:t}=await
|
|
267
|
+
`)}var It=new Command("generate").description("Generate a new migration file").requiredOption("-n, --name <name>","Migration name").option("-c, --config <path>","Path to config file").option("--type <type>","Migration type: tenant or shared","tenant").option("--migrations-folder <path>","Path to migrations folder").action(async n=>{let e=R("Loading configuration...");try{e.start();let{migrationsFolder:t}=await k(n.config),r=process.cwd(),a;n.migrationsFolder?a=resolve(r,n.migrationsFolder):t?a=resolve(r,t):a=resolve(r,n.type==="shared"?"./drizzle/shared":"./drizzle/tenant"),existsSync(a)||(await mkdir(a,{recursive:!0}),e.text=`Created migrations folder: ${a}`),e.text="Generating migration...";let s=(existsSync(a)?await readdir(a):[]).filter(h=>h.endsWith(".sql")),i=0;for(let h of s){let g=h.match(/^(\d+)_/);if(g?.[1]){let $=parseInt(g[1],10);$>i&&(i=$);}}let c=(i+1).toString().padStart(4,"0"),l=n.name.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,""),m=`${c}_${l}.sql`,u=join(a,m),p=`-- Migration: ${n.name}
|
|
268
268
|
-- Created at: ${new Date().toISOString()}
|
|
269
269
|
-- Type: ${n.type}
|
|
270
270
|
|
|
@@ -279,7 +279,7 @@ Examples:
|
|
|
279
279
|
$ drizzle-multitenant generate:shared --name add-permissions --migrations-folder=./drizzle/shared
|
|
280
280
|
|
|
281
281
|
Alias for: drizzle-multitenant generate --name <name> --type shared
|
|
282
|
-
`).action(async n=>{let e=R("Loading configuration...");try{e.start();let{sharedMigrationsFolder:t}=await
|
|
282
|
+
`).action(async n=>{let e=R("Loading configuration...");try{e.start();let{sharedMigrationsFolder:t}=await k(n.config),r=process.cwd(),a;n.migrationsFolder?a=resolve(r,n.migrationsFolder):t?a=resolve(r,t):a=resolve(r,"./drizzle/shared-migrations"),existsSync(a)||(await mkdir(a,{recursive:!0}),e.text=`Created shared migrations folder: ${a}`),e.text="Generating shared migration...";let s=(existsSync(a)?await readdir(a):[]).filter(h=>h.endsWith(".sql")),i=0;for(let h of s){let g=h.match(/^(\d+)_/);if(g?.[1]){let $=parseInt(g[1],10);$>i&&(i=$);}}let c=(i+1).toString().padStart(4,"0"),l=n.name.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,""),m=`${c}_${l}.sql`,u=join(a,m),p=`-- Migration: ${n.name}
|
|
283
283
|
-- Created at: ${new Date().toISOString()}
|
|
284
284
|
-- Type: shared (public schema)
|
|
285
285
|
|
|
@@ -288,14 +288,14 @@ Alias for: drizzle-multitenant generate --name <name> --type shared
|
|
|
288
288
|
|
|
289
289
|
`;await writeFile(u,p,"utf-8"),e.succeed("Shared migration generated"),console.log(`
|
|
290
290
|
`+E(`Created: ${f(u)}`)),console.log(f(`
|
|
291
|
-
Edit this file to add your shared schema migration SQL.`)),console.log(f("Run `drizzle-multitenant migrate:shared` to apply."));}catch(t){e.fail(t.message),process.exit(1);}});var Ot=new Command("tenant:create").description("Create a new tenant schema and apply all migrations").requiredOption("--id <tenantId>","Tenant ID").option("-c, --config <path>","Path to config file").option("--migrations-folder <path>","Path to migrations folder").option("--no-migrate","Skip applying migrations after creating schema").action(async n=>{let e=R("Loading configuration...");try{e.start();let{config:t,migrationsFolder:r,migrationsTable:a}=await
|
|
292
|
-
`+E("Schema created: ")+f(i)),n.migrate?console.log(E("All migrations applied")):console.log(
|
|
293
|
-
You can now use this tenant:`)),console.log(f(` const db = tenants.getDb('${n.id}');`));}catch(t){e.fail(t.message),process.exit(1);}});var Lt=new Command("tenant:drop").description("Drop a tenant schema (DESTRUCTIVE)").requiredOption("--id <tenantId>","Tenant ID").option("-c, --config <path>","Path to config file").option("--migrations-folder <path>","Path to migrations folder").option("-f, --force","Skip confirmation prompt").option("--no-cascade","Use RESTRICT instead of CASCADE").action(async n=>{let e=R("Loading configuration...");try{e.start();let{config:t,migrationsFolder:r,migrationsTable:a}=await
|
|
291
|
+
Edit this file to add your shared schema migration SQL.`)),console.log(f("Run `drizzle-multitenant migrate:shared` to apply."));}catch(t){e.fail(t.message),process.exit(1);}});var Ot=new Command("tenant:create").description("Create a new tenant schema and apply all migrations").requiredOption("--id <tenantId>","Tenant ID").option("-c, --config <path>","Path to config file").option("--migrations-folder <path>","Path to migrations folder").option("--no-migrate","Skip applying migrations after creating schema").action(async n=>{let e=R("Loading configuration...");try{e.start();let{config:t,migrationsFolder:r,migrationsTable:a}=await k(n.config),o=n.migrationsFolder?O(n.migrationsFolder):O(r),s=I(t,{migrationsFolder:o,...a&&{migrationsTable:a},tenantDiscovery:async()=>[]}),i=t.isolation.schemaNameTemplate(n.id);if(e.text=`Checking if tenant ${n.id} exists...`,await s.tenantExists(n.id)){if(e.warn(`Tenant ${n.id} already exists (${i})`),n.migrate){e.start(),e.text="Applying pending migrations...";let l=await s.migrateTenant(n.id);if(l.appliedMigrations.length>0){e.succeed(`Applied ${l.appliedMigrations.length} migration(s)`);for(let m of l.appliedMigrations)console.log(` ${f("-")} ${m}`);}else e.succeed("No pending migrations");}return}e.text=`Creating tenant schema ${i}...`,await s.createTenant(n.id,{migrate:n.migrate}),e.succeed(`Tenant ${n.id} created`),console.log(`
|
|
292
|
+
`+E("Schema created: ")+f(i)),n.migrate?console.log(E("All migrations applied")):console.log(C("Migrations skipped. Run migrate to apply.")),console.log(f(`
|
|
293
|
+
You can now use this tenant:`)),console.log(f(` const db = tenants.getDb('${n.id}');`));}catch(t){e.fail(t.message),process.exit(1);}});var Lt=new Command("tenant:drop").description("Drop a tenant schema (DESTRUCTIVE)").requiredOption("--id <tenantId>","Tenant ID").option("-c, --config <path>","Path to config file").option("--migrations-folder <path>","Path to migrations folder").option("-f, --force","Skip confirmation prompt").option("--no-cascade","Use RESTRICT instead of CASCADE").action(async n=>{let e=R("Loading configuration...");try{e.start();let{config:t,migrationsFolder:r,migrationsTable:a}=await k(n.config),o=n.migrationsFolder?O(n.migrationsFolder):O(r),s=I(t,{migrationsFolder:o,...a&&{migrationsTable:a},tenantDiscovery:async()=>[]}),i=t.isolation.schemaNameTemplate(n.id);if(e.text=`Checking if tenant ${n.id} exists...`,!await s.tenantExists(n.id)){e.warn(`Tenant ${n.id} does not exist`);return}if(e.stop(),!n.force&&(console.log(te(T(`
|
|
294
294
|
\u26A0\uFE0F WARNING: This action is DESTRUCTIVE and IRREVERSIBLE!`))),console.log(f(`
|
|
295
295
|
You are about to drop schema: ${i}`)),console.log(f(`All tables and data in this schema will be permanently deleted.
|
|
296
|
-
`)),!await
|
|
297
|
-
|
|
298
|
-
`+E("Schema deleted: ")+f(i));}catch(t){e.fail(t.message),process.exit(1);}});async function
|
|
296
|
+
`)),!await yo(`Type "${n.id}" to confirm deletion: `,n.id))){console.log(`
|
|
297
|
+
`+C("Operation cancelled."));return}e.start(),e.text=`Dropping tenant schema ${i}...`,await s.dropTenant(n.id,{cascade:n.cascade}),e.succeed(`Tenant ${n.id} dropped`),console.log(`
|
|
298
|
+
`+E("Schema deleted: ")+f(i));}catch(t){e.fail(t.message),process.exit(1);}});async function yo(n,e){let t=createInterface({input:process.stdin,output:process.stdout});return new Promise(r=>{t.question(n,a=>{t.close(),r(a.trim()===e);});})}var jt=new Command("tenant:clone").description("Clone a tenant schema to a new tenant").requiredOption("--from <tenantId>","Source tenant ID").requiredOption("--to <tenantId>","Target tenant ID").option("-c, --config <path>","Path to config file").option("--migrations-folder <path>","Path to migrations folder").option("--include-data","Include data in clone (default: schema only)").option("--anonymize","Anonymize sensitive data (requires --include-data)").option("--anonymize-config <path>","Path to anonymization config file").option("-f, --force","Skip confirmation prompt").option("--json","Output as JSON").addHelpText("after",`
|
|
299
299
|
Examples:
|
|
300
300
|
# Clone schema only
|
|
301
301
|
$ drizzle-multitenant tenant:clone --from=production --to=dev
|
|
@@ -308,13 +308,13 @@ Examples:
|
|
|
308
308
|
|
|
309
309
|
# Clone with custom anonymization rules
|
|
310
310
|
$ drizzle-multitenant tenant:clone --from=prod --to=dev --include-data --anonymize --anonymize-config=./anonymize.config.js
|
|
311
|
-
`).action(async n=>{let e=R("Loading configuration...");try{e.start();let{config:t,migrationsFolder:r,migrationsTable:a}=await
|
|
311
|
+
`).action(async n=>{let e=R("Loading configuration...");try{e.start();let{config:t,migrationsFolder:r,migrationsTable:a}=await k(n.config),o=n.migrationsFolder?O(n.migrationsFolder):O(r),s=I(t,{migrationsFolder:o,...a&&{migrationsTable:a},tenantDiscovery:async()=>[]}),i=t.isolation.schemaNameTemplate(n.from),c=t.isolation.schemaNameTemplate(n.to);if(e.text=`Checking source tenant ${n.from}...`,await s.tenantExists(n.from)||(e.fail(`Source tenant ${n.from} does not exist`),process.exit(1)),await s.tenantExists(n.to)&&(e.fail(`Target tenant ${n.to} already exists`),process.exit(1)),e.stop(),!n.force&&(console.log(""),console.log(T("Clone Configuration:")),console.log(f(` Source: ${n.from} (${i})`)),console.log(f(` Target: ${n.to} (${c})`)),n.includeData?(console.log(C(`
|
|
312
312
|
This will copy all data from source tenant.`)),n.anonymize&&console.log(f(" Sensitive data will be anonymized."))):console.log(f(`
|
|
313
|
-
Schema only (no data will be copied).`)),!await
|
|
313
|
+
Schema only (no data will be copied).`)),!await To(`
|
|
314
314
|
Type "${n.to}" to confirm: `,n.to))){console.log(`
|
|
315
|
-
|
|
315
|
+
`+C("Operation cancelled."));return}let u;if(n.anonymize&&n.anonymizeConfig)try{let g=await import(await import('path').then($=>$.resolve(process.cwd(),n.anonymizeConfig)));u=g.default||g;}catch(h){e.fail(`Failed to load anonymization config: ${h.message}`),process.exit(1);}e.start(),e.text=`Cloning ${n.from} to ${n.to}...`;let p=await s.cloneTenant(n.from,n.to,{includeData:n.includeData,anonymize:n.anonymize?{enabled:!0,rules:u}:void 0,onProgress:(h,g)=>{switch(h){case "introspecting":e.text="Introspecting source schema...";break;case "creating_schema":e.text="Creating target schema...";break;case "creating_tables":e.text="Creating tables...";break;case "creating_indexes":e.text="Creating indexes...";break;case "creating_constraints":e.text="Creating constraints...";break;case "copying_data":g?.table?e.text=`Copying data: ${g.table} (${g.progress}/${g.total})...`:e.text="Copying data...";break}}});if(n.json){e.stop(),console.log(JSON.stringify(p,null,2));return}p.success||(e.fail(p.error??"Clone failed"),process.exit(1)),e.succeed(`Tenant ${n.to} cloned successfully`),console.log(""),console.log(E("Source: ")+f(`${n.from} (${i})`)),console.log(E("Target: ")+f(`${n.to} (${c})`)),console.log(E("Tables: ")+f(p.tables.length.toString())),n.includeData&&p.rowsCopied!==void 0&&(console.log(E("Rows copied: ")+f(p.rowsCopied.toLocaleString())),n.anonymize&&console.log(E("Data anonymized: ")+f("Yes"))),console.log(f(`
|
|
316
316
|
Duration: ${p.durationMs}ms`)),console.log(f(`
|
|
317
|
-
Usage:`)),console.log(f(` const db = tenants.getDb('${n.to}');`));}catch(t){e.fail(t.message),process.exit(1);}});async function
|
|
317
|
+
Usage:`)),console.log(f(` const db = tenants.getDb('${n.to}');`));}catch(t){e.fail(t.message),process.exit(1);}});async function To(n,e){let t=createInterface({input:process.stdin,output:process.stdout});return new Promise(r=>{t.question(n,a=>{t.close(),r(a.trim()===e);});})}async function Do(n){let e=await readdir(n),t=[];for(let r of e){if(!r.endsWith(".sql"))continue;let a=join(n,r),o=await readFile(a,"utf-8"),s=createHash("sha256").update(o).digest("hex");t.push({name:basename(r,".sql"),hash:s});}return t}async function vo(n,e,t,r,a,o,s){let i=new Map(r.map(m=>[m.hash,m.name])),c=new Map(r.map(m=>[m.name,m.hash])),l=await n.connect();try{let m=a.columns.identifier,u=await l.query(`SELECT id, "${m}" as identifier FROM "${e}"."${t}" ORDER BY id`);if(s){let p=[];for(let h of u.rows)if(o==="name"&&a.columns.identifier==="hash"){let g=i.get(h.identifier);g?p.push(` ${f(h.identifier.slice(0,8))}... -> ${g}`):p.push(` ${C(h.identifier.slice(0,8))}... -> ${v("unknown")}`);}else if(o!=="name"&&a.columns.identifier==="name"){let g=c.get(h.identifier);g?p.push(` ${h.identifier} -> ${f(g.slice(0,16))}...`):p.push(` ${h.identifier} -> ${v("unknown")}`);}return {success:!0,message:p.length>0?p.join(`
|
|
318
318
|
`):" No conversions needed"}}await l.query("BEGIN");try{if(o==="name"&&a.columns.identifier==="hash"){await l.query(`
|
|
319
319
|
ALTER TABLE "${e}"."${t}"
|
|
320
320
|
ADD COLUMN IF NOT EXISTS name VARCHAR(255)
|
|
@@ -352,27 +352,27 @@ Usage:`)),console.log(f(` const db = tenants.getDb('${n.to}');`));}catch(t){e.f
|
|
|
352
352
|
ALTER TABLE "${e}"."${t}"
|
|
353
353
|
ALTER COLUMN created_at TYPE BIGINT
|
|
354
354
|
USING (EXTRACT(EPOCH FROM created_at) * 1000)::BIGINT
|
|
355
|
-
`)),await l.query("COMMIT"),{success:!0,message:`Converted ${p} records to ${o} format`}}return await l.query("COMMIT"),{success:!0,message:"No conversion needed"}}catch(p){throw await l.query("ROLLBACK"),p}}finally{l.release();}}var qt=new Command("convert-format").description("Convert migration table format between name/hash/drizzle-kit").requiredOption("--to <format>","Target format: name, hash, or drizzle-kit").option("-c, --config <path>","Path to config file").option("-t, --tenant <id>","Convert a specific tenant only").option("--dry-run","Preview changes without applying").option("--migrations-folder <path>","Path to migrations folder").action(async n=>{let e=R("Loading configuration...");try{let t=n.to;if(!["name","hash","drizzle-kit"].includes(t))throw new Error(`Invalid format: ${n.to}. Use: name, hash, or drizzle-kit`);e.start();let{config:r,migrationsFolder:a,migrationsTable:o,tenantDiscovery:s}=await
|
|
355
|
+
`)),await l.query("COMMIT"),{success:!0,message:`Converted ${p} records to ${o} format`}}return await l.query("COMMIT"),{success:!0,message:"No conversion needed"}}catch(p){throw await l.query("ROLLBACK"),p}}finally{l.release();}}var qt=new Command("convert-format").description("Convert migration table format between name/hash/drizzle-kit").requiredOption("--to <format>","Target format: name, hash, or drizzle-kit").option("-c, --config <path>","Path to config file").option("-t, --tenant <id>","Convert a specific tenant only").option("--dry-run","Preview changes without applying").option("--migrations-folder <path>","Path to migrations folder").action(async n=>{let e=R("Loading configuration...");try{let t=n.to;if(!["name","hash","drizzle-kit"].includes(t))throw new Error(`Invalid format: ${n.to}. Use: name, hash, or drizzle-kit`);e.start();let{config:r,migrationsFolder:a,migrationsTable:o,tenantDiscovery:s}=await k(n.config),i=o??"__drizzle_migrations",c=n.migrationsFolder?O(n.migrationsFolder):O(a);e.text="Loading migration files...";let l=await Do(c);e.text="Discovering tenants...";let m;if(n.tenant)m=[n.tenant];else {if(!s)throw new Error("No tenant discovery function configured. Add migrations.tenantDiscovery to your config.");m=await s();}e.succeed(`Found ${m.length} tenant${m.length>1?"s":""}`),n.dryRun&&console.log(C(T(`
|
|
356
356
|
Dry run mode - no changes will be made
|
|
357
357
|
`))),console.log(T(`
|
|
358
358
|
Converting to ${t} format:
|
|
359
|
-
`));let u=0,p=0,h=0;for(let g of m){let
|
|
360
|
-
Summary:`)),console.log(` Converted: ${E(u.toString())}`),console.log(` Skipped: ${f(p.toString())}`),h>0&&(console.log(` Failed: ${v(h.toString())}`),process.exit(1));}catch(t){e.fail(t.message),process.exit(1);}});var ce={minimal:{name:"Minimal (config only)",description:"Just the configuration file - you set up everything else"},standard:{name:"Standard (config + folder structure)",description:"Configuration with organized folder structure for migrations and seeds"},full:{name:"Full (config + folders + example schemas)",description:"Complete setup with example tenant and shared schemas"},enterprise:{name:"Enterprise (full + CI/CD + Docker)",description:"Production-ready setup with Docker, CI/CD, and monitoring"}},oe={none:{name:"None (standalone)",description:"No framework integration - use directly with Drizzle"},express:{name:"Express",description:"Middleware for Express.js applications"},fastify:{name:"Fastify",description:"Plugin for Fastify applications"},nestjs:{name:"NestJS",description:"Full module with decorators and guards"},hono:{name:"Hono",description:"Middleware for Hono applications"}},
|
|
359
|
+
`));let u=0,p=0,h=0;for(let g of m){let $=r.isolation.schemaNameTemplate(g),S=new Pool({connectionString:r.connection.url,...r.connection.poolConfig});try{let b=await Me(S,$,i);if(!b){console.log(`${f(g)}: ${f("No migrations table found, skipping")}`),p++;continue}if(b.format===t){console.log(`${f(g)}: ${f(`Already using ${t} format`)}`),p++;continue}console.log(`${T(g)}: ${b.format} -> ${t}`);let x=await vo(S,$,i,l,b,t,n.dryRun);x.success?(n.dryRun?console.log(x.message):console.log(` ${E(x.message)}`),u++):(console.log(` ${v(x.message)}`),h++);}catch(b){console.log(` ${v(b.message)}`),h++;}finally{await S.end();}}console.log(T(`
|
|
360
|
+
Summary:`)),console.log(` Converted: ${E(u.toString())}`),console.log(` Skipped: ${f(p.toString())}`),h>0&&(console.log(` Failed: ${v(h.toString())}`),process.exit(1));}catch(t){e.fail(t.message),process.exit(1);}});var ce={minimal:{name:"Minimal (config only)",description:"Just the configuration file - you set up everything else"},standard:{name:"Standard (config + folder structure)",description:"Configuration with organized folder structure for migrations and seeds"},full:{name:"Full (config + folders + example schemas)",description:"Complete setup with example tenant and shared schemas"},enterprise:{name:"Enterprise (full + CI/CD + Docker)",description:"Production-ready setup with Docker, CI/CD, and monitoring"}},oe={none:{name:"None (standalone)",description:"No framework integration - use directly with Drizzle"},express:{name:"Express",description:"Middleware for Express.js applications"},fastify:{name:"Fastify",description:"Plugin for Fastify applications"},nestjs:{name:"NestJS",description:"Full module with decorators and guards"},hono:{name:"Hono",description:"Middleware for Hono applications"}},Cr={sharedSchema:{name:"Shared schema support",description:"Support for shared tables (plans, roles, etc.)",default:true},crossSchemaQueries:{name:"Cross-schema queries",description:"Type-safe queries joining tenant and shared schemas",default:true},healthChecks:{name:"Health check endpoints",description:"Pool health monitoring and metrics",default:true},metrics:{name:"Metrics (Prometheus)",description:"Prometheus-compatible metrics endpoint",default:false},debug:{name:"Debug mode",description:"Structured logging for development",default:false}},he={manual:{name:"I'll configure manually",description:"Set up DATABASE_URL yourself"},"docker-compose":{name:"Generate docker-compose.yml",description:"Create a Docker Compose file for PostgreSQL"},"existing-url":{name:"Use existing DATABASE_URL",description:"Use your current database connection"}};function Mr(n){let e=n.useTypeScript?_o(n):Po(n);return {path:n.useTypeScript?"tenant.config.ts":"tenant.config.js",content:e}}function _o(n){let e=zo(n),t=No(n),r=Er(n);return `${e}
|
|
361
361
|
${t}
|
|
362
362
|
|
|
363
363
|
export default defineConfig({
|
|
364
364
|
${r}
|
|
365
365
|
});
|
|
366
|
-
`}function
|
|
366
|
+
`}function Po(n){let e=ko(n),t=Io(n),r=Er(n);return `// @ts-check
|
|
367
367
|
${e}
|
|
368
368
|
${t}
|
|
369
369
|
|
|
370
370
|
module.exports = defineConfig({
|
|
371
371
|
${r}
|
|
372
372
|
});
|
|
373
|
-
`}function
|
|
374
|
-
`)}function
|
|
375
|
-
`)}function
|
|
373
|
+
`}function zo(n){let e=["import { defineConfig } from 'drizzle-multitenant';"];return n.template==="minimal"&&e.push("import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';"),e.join(`
|
|
374
|
+
`)}function ko(n){let e=["const { defineConfig } = require('drizzle-multitenant');"];return n.template==="minimal"&&e.push("const { pgTable, text, timestamp, uuid } = require('drizzle-orm/pg-core');"),e.join(`
|
|
375
|
+
`)}function No(n){if(n.template==="minimal")return `
|
|
376
376
|
// Example tenant schema - customize this for your needs
|
|
377
377
|
const users = pgTable('users', {
|
|
378
378
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
@@ -381,7 +381,7 @@ const users = pgTable('users', {
|
|
|
381
381
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
382
382
|
});
|
|
383
383
|
`;let e=[];return e.push("import * as tenantSchema from './src/db/schema/tenant/index.js';"),n.features.sharedSchema&&e.push("import * as sharedSchema from './src/db/schema/shared/index.js';"),e.join(`
|
|
384
|
-
`)}function
|
|
384
|
+
`)}function Io(n){if(n.template==="minimal")return `
|
|
385
385
|
// Example tenant schema - customize this for your needs
|
|
386
386
|
const { pgTable, text, timestamp, uuid } = require('drizzle-orm/pg-core');
|
|
387
387
|
const users = pgTable('users', {
|
|
@@ -391,18 +391,18 @@ const users = pgTable('users', {
|
|
|
391
391
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
392
392
|
});
|
|
393
393
|
`;let e=[];return e.push("const tenantSchema = require('./src/db/schema/tenant/index.js');"),n.features.sharedSchema&&e.push("const sharedSchema = require('./src/db/schema/shared/index.js');"),e.join(`
|
|
394
|
-
`)}function
|
|
394
|
+
`)}function Er(n){let e=[];return e.push(Ao(n)),e.push(Fo(n)),e.push(Oo(n)),e.push(Lo(n)),e.push(jo(n)),n.features.debug&&e.push(qo()),e.join(`
|
|
395
395
|
|
|
396
|
-
`)}function
|
|
396
|
+
`)}function Ao(n){return n.databaseSetup==="existing-url"&&n.databaseUrl?` // Database connection
|
|
397
397
|
connection: process.env.${n.dbEnvVar} || '${n.databaseUrl}',`:` // Database connection
|
|
398
|
-
connection: process.env.${n.dbEnvVar}!,`}function
|
|
398
|
+
connection: process.env.${n.dbEnvVar}!,`}function Fo(n){return n.isolationType==="rls"?` // Isolation strategy
|
|
399
399
|
isolation: {
|
|
400
400
|
type: 'rls',
|
|
401
401
|
},`:` // Isolation strategy
|
|
402
402
|
isolation: {
|
|
403
403
|
type: 'schema',
|
|
404
404
|
schemaNameTemplate: (id) => \`${n.schemaTemplate.replace("${id}","${id}")}\`,
|
|
405
|
-
},`}function
|
|
405
|
+
},`}function Oo(n){if(n.template==="minimal"){let e=` // Schema definitions
|
|
406
406
|
schemas: {
|
|
407
407
|
tenant: {
|
|
408
408
|
users,
|
|
@@ -420,7 +420,7 @@ const users = pgTable('users', {
|
|
|
420
420
|
},`:` // Schema definitions
|
|
421
421
|
schemas: {
|
|
422
422
|
tenant: tenantSchema,
|
|
423
|
-
},`}function
|
|
423
|
+
},`}function Lo(n){let e=` // Migration settings
|
|
424
424
|
migrations: {
|
|
425
425
|
folder: '${n.migrationsFolder}',
|
|
426
426
|
table: '__drizzle_migrations',`;return n.features.sharedSchema&&(e+=`
|
|
@@ -438,7 +438,7 @@ const users = pgTable('users', {
|
|
|
438
438
|
console.warn('\u26A0\uFE0F tenantDiscovery not configured - returning empty array');
|
|
439
439
|
return [];
|
|
440
440
|
},
|
|
441
|
-
},`,e}function
|
|
441
|
+
},`,e}function jo(n){let e=` // Pool configuration
|
|
442
442
|
pool: {
|
|
443
443
|
maxPools: 50,
|
|
444
444
|
poolTtlMs: 3600000, // 1 hour`;return n.features.healthChecks&&(e+=`
|
|
@@ -455,13 +455,13 @@ const users = pgTable('users', {
|
|
|
455
455
|
console.error(\`Pool error for tenant \${tenantId}:\`, error.message);
|
|
456
456
|
},
|
|
457
457
|
},`),e+=`
|
|
458
|
-
},`,e}function
|
|
458
|
+
},`,e}function qo(){return ` // Debug configuration (development only)
|
|
459
459
|
debug: {
|
|
460
460
|
enabled: process.env.NODE_ENV === 'development',
|
|
461
461
|
logQueries: true,
|
|
462
462
|
logPoolEvents: true,
|
|
463
463
|
slowQueryThreshold: 1000,
|
|
464
|
-
},`}function
|
|
464
|
+
},`}function Dr(n){switch(n.template){case "minimal":return Rr(n);case "standard":return vr(n);case "full":return _r(n);case "enterprise":return Uo(n);default:return Rr(n)}}function Rr(n){let e=[n.migrationsFolder];return n.features.sharedSchema&&e.push(n.sharedMigrationsFolder),{folders:e,files:[{path:`${n.migrationsFolder}/.gitkeep`,content:""},...n.features.sharedSchema?[{path:`${n.sharedMigrationsFolder}/.gitkeep`,content:""}]:[]]}}function vr(n){let e=[n.migrationsFolder,"drizzle/seeds/tenant","src/db/schema/tenant"],t=[{path:`${n.migrationsFolder}/.gitkeep`,content:""},{path:"drizzle/seeds/tenant/.gitkeep",content:""},{path:"src/db/schema/tenant/.gitkeep",content:""}];return n.features.sharedSchema&&(e.push(n.sharedMigrationsFolder),e.push("drizzle/seeds/shared"),e.push("src/db/schema/shared"),t.push({path:`${n.sharedMigrationsFolder}/.gitkeep`,content:""}),t.push({path:"drizzle/seeds/shared/.gitkeep",content:""}),t.push({path:"src/db/schema/shared/.gitkeep",content:""})),t.push(Wo(n)),{folders:e,files:t}}function _r(n){let e=vr(n);return e.files.push(...Ho(n)),e.files.push(...Bo(n)),e.files.push(Go(n)),e}function Uo(n){let e=_r(n);return e.folders.push(".github/workflows"),e.files.push(Jo()),e.files.push(Ko()),e.files.push(Vo(n)),e.files.push(Yo()),e.files.push(Qo()),e}function Wo(n){let e=`# Database Configuration
|
|
465
465
|
${n.dbEnvVar}=postgresql://postgres:postgres@localhost:5432/myapp
|
|
466
466
|
|
|
467
467
|
# Environment
|
|
@@ -469,7 +469,7 @@ NODE_ENV=development
|
|
|
469
469
|
`;return n.features.debug&&(e+=`
|
|
470
470
|
# Debug
|
|
471
471
|
DEBUG=drizzle-multitenant:*
|
|
472
|
-
`),{path:".env.example",content:e}}function
|
|
472
|
+
`),{path:".env.example",content:e}}function Ho(n){let e=[],t=n.useTypeScript?"ts":"js";return e.push({path:`src/db/schema/tenant/users.${t}`,content:n.useTypeScript?`import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core';
|
|
473
473
|
|
|
474
474
|
export const users = pgTable('users', {
|
|
475
475
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
@@ -531,7 +531,7 @@ module.exports = { plans };
|
|
|
531
531
|
`:`module.exports = {
|
|
532
532
|
...require('./plans.js'),
|
|
533
533
|
};
|
|
534
|
-
`})),e}function
|
|
534
|
+
`})),e}function Bo(n){let e=[],t=n.useTypeScript?"ts":"js";return e.push({path:`drizzle/seeds/tenant/initial.${t}`,content:n.useTypeScript?`import type { SeedFunction } from 'drizzle-multitenant';
|
|
535
535
|
import { users } from '../../../src/db/schema/tenant/index.js';
|
|
536
536
|
|
|
537
537
|
export const seed: SeedFunction = async (db) => {
|
|
@@ -621,7 +621,7 @@ const seed = async (db) => {
|
|
|
621
621
|
};
|
|
622
622
|
|
|
623
623
|
module.exports = { seed };
|
|
624
|
-
`}),e}function
|
|
624
|
+
`}),e}function Go(n){let e=n.useTypeScript?"ts":"js";if(n.useTypeScript){let r=`import { createTenantManager, createTenantContext } from 'drizzle-multitenant';
|
|
625
625
|
import config from '../../tenant.config.js';
|
|
626
626
|
|
|
627
627
|
// Create tenant manager (singleton)
|
|
@@ -659,7 +659,7 @@ module.exports = {
|
|
|
659
659
|
isInTenantContext: tenantContext.isInTenantContext,
|
|
660
660
|
sharedDb,
|
|
661
661
|
};
|
|
662
|
-
`}}function
|
|
662
|
+
`}}function Jo(){return {path:"Dockerfile",content:`# Build stage
|
|
663
663
|
FROM node:20-alpine AS builder
|
|
664
664
|
|
|
665
665
|
WORKDIR /app
|
|
@@ -703,7 +703,7 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
|
|
|
703
703
|
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
|
|
704
704
|
|
|
705
705
|
CMD ["node", "dist/index.js"]
|
|
706
|
-
`}}function
|
|
706
|
+
`}}function Ko(){return {path:".dockerignore",content:`node_modules
|
|
707
707
|
npm-debug.log
|
|
708
708
|
.git
|
|
709
709
|
.gitignore
|
|
@@ -717,7 +717,7 @@ coverage
|
|
|
717
717
|
.nyc_output
|
|
718
718
|
dist
|
|
719
719
|
*.log
|
|
720
|
-
`}}function
|
|
720
|
+
`}}function Vo(n){return {path:".github/workflows/ci.yml",content:`name: CI
|
|
721
721
|
|
|
722
722
|
on:
|
|
723
723
|
push:
|
|
@@ -786,14 +786,14 @@ jobs:
|
|
|
786
786
|
|
|
787
787
|
- name: Lint database schemas
|
|
788
788
|
run: npx drizzle-multitenant lint --format=github || true
|
|
789
|
-
`}}function
|
|
789
|
+
`}}function Yo(){return {path:".prettierrc",content:`{
|
|
790
790
|
"semi": true,
|
|
791
791
|
"singleQuote": true,
|
|
792
792
|
"trailingComma": "es5",
|
|
793
793
|
"tabWidth": 2,
|
|
794
794
|
"printWidth": 100
|
|
795
795
|
}
|
|
796
|
-
`}}function
|
|
796
|
+
`}}function Qo(){return {path:"eslint.config.js",content:`import eslint from '@eslint/js';
|
|
797
797
|
import tseslint from 'typescript-eslint';
|
|
798
798
|
|
|
799
799
|
export default tseslint.config(
|
|
@@ -809,7 +809,7 @@ export default tseslint.config(
|
|
|
809
809
|
},
|
|
810
810
|
}
|
|
811
811
|
);
|
|
812
|
-
`}}function
|
|
812
|
+
`}}function Pr(n){return {path:"docker-compose.yml",content:`# Docker Compose for local development
|
|
813
813
|
# Start with: docker-compose up -d
|
|
814
814
|
|
|
815
815
|
version: '3.8'
|
|
@@ -854,7 +854,7 @@ volumes:
|
|
|
854
854
|
# networks:
|
|
855
855
|
# default:
|
|
856
856
|
# name: app-network
|
|
857
|
-
`}}function
|
|
857
|
+
`}}function zr(n){return {path:"init-db.sql",content:`-- Initial database setup
|
|
858
858
|
-- This file is executed when the PostgreSQL container is first created
|
|
859
859
|
|
|
860
860
|
-- Create extensions
|
|
@@ -895,7 +895,7 @@ ON CONFLICT (id) DO NOTHING;
|
|
|
895
895
|
|
|
896
896
|
-- Grant permissions (adjust as needed)
|
|
897
897
|
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO your_app_user;
|
|
898
|
-
`}}function
|
|
898
|
+
`}}function kr(n){switch(n.framework){case "express":return Zo(n);case "fastify":return Xo(n);case "nestjs":return es();case "hono":return ts(n);default:return []}}function Ut(n){switch(n){case "express":return {dependencies:["express"],devDependencies:["@types/express"]};case "fastify":return {dependencies:["fastify","fastify-plugin"],devDependencies:[]};case "nestjs":return {dependencies:["@nestjs/common","@nestjs/core","@nestjs/platform-express","reflect-metadata","rxjs"],devDependencies:["@nestjs/cli","@nestjs/testing"]};case "hono":return {dependencies:["hono"],devDependencies:[]};default:return {dependencies:[],devDependencies:[]}}}function Zo(n){let e=n.useTypeScript?"ts":"js",t=[];return t.push({path:`src/middleware/tenant.${e}`,content:n.useTypeScript?`import type { Request, Response, NextFunction } from 'express';
|
|
899
899
|
import { createTenantMiddleware } from 'drizzle-multitenant/express';
|
|
900
900
|
import { tenantManager } from '../db/index.js';
|
|
901
901
|
|
|
@@ -1057,7 +1057,7 @@ app.get('/api/users', async (req, res) => {
|
|
|
1057
1057
|
});
|
|
1058
1058
|
|
|
1059
1059
|
module.exports = app;
|
|
1060
|
-
`}),t}function
|
|
1060
|
+
`}),t}function Xo(n){let e=n.useTypeScript?"ts":"js",t=[];return t.push({path:`src/plugins/tenant.${e}`,content:n.useTypeScript?`import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
|
1061
1061
|
import fp from 'fastify-plugin';
|
|
1062
1062
|
import { createTenantPlugin } from 'drizzle-multitenant/fastify';
|
|
1063
1063
|
import { tenantManager } from '../db/index.js';
|
|
@@ -1157,7 +1157,7 @@ app.get('/api/users', async (request, reply) => {
|
|
|
1157
1157
|
});
|
|
1158
1158
|
|
|
1159
1159
|
module.exports = app;
|
|
1160
|
-
`}),t}function
|
|
1160
|
+
`}),t}function es(n){let e=[];return e.push({path:"src/tenant/tenant.module.ts",content:`import { Module, Global } from '@nestjs/common';
|
|
1161
1161
|
import { TenantModule as DrizzleTenantModule } from 'drizzle-multitenant/nestjs';
|
|
1162
1162
|
import config from '../../tenant.config.js';
|
|
1163
1163
|
|
|
@@ -1309,7 +1309,7 @@ async function bootstrap() {
|
|
|
1309
1309
|
}
|
|
1310
1310
|
|
|
1311
1311
|
bootstrap();
|
|
1312
|
-
`}),e}function
|
|
1312
|
+
`}),e}function ts(n){let e=n.useTypeScript?"ts":"js",t=[];return t.push({path:`src/middleware/tenant.${e}`,content:n.useTypeScript?`import type { Context, Next } from 'hono';
|
|
1313
1313
|
import { tenantManager, runWithTenant } from '../db/index.js';
|
|
1314
1314
|
|
|
1315
1315
|
export const extractTenantId = (c: Context): string | null => {
|
|
@@ -1459,12 +1459,12 @@ Examples:
|
|
|
1459
1459
|
$ drizzle-multitenant init --template=full
|
|
1460
1460
|
$ drizzle-multitenant init --template=enterprise --framework=nestjs
|
|
1461
1461
|
$ drizzle-multitenant init --force --no-typescript
|
|
1462
|
-
`).action(async n=>{try{!me()&&!n.template&&(d(
|
|
1462
|
+
`).action(async n=>{try{!me()&&!n.template&&(d(C("Interactive mode required. Please run in a terminal or use --template flag.")),process.exit(1)),d(T(`
|
|
1463
1463
|
\u{1F680} drizzle-multitenant Setup Wizard
|
|
1464
|
-
`));let e=
|
|
1465
|
-
Setup cancelled.`));return}j(e);}});function
|
|
1464
|
+
`));let e=os();if(e&&!n.force&&(d(C(`Configuration file already exists: ${e}`)),!await confirm({message:"Do you want to overwrite it?",default:!1}))){d(J("Setup cancelled."));return}let t=await ss(n),r=await us(t);await ps(r,t),hs(t);}catch(e){if(e.name==="ExitPromptError"){d(J(`
|
|
1465
|
+
Setup cancelled.`));return}j(e);}});function os(){return ["tenant.config.ts","tenant.config.js","drizzle-multitenant.config.ts","drizzle-multitenant.config.js"].find(e=>existsSync(join(process.cwd(),e)))||null}async function ss(n){let e=n.template||await is(),t=n.framework||await cs(),r=await ls(e),a=await ms(),o=await ds(),s=await input({message:"Environment variable for database connection:",default:"DATABASE_URL"}),i;a==="existing-url"&&(i=await input({message:"Enter your database URL:",default:process.env.DATABASE_URL||"postgresql://postgres:postgres@localhost:5432/myapp"}));let c=await input({message:"Tenant migrations folder path:",default:"./drizzle/tenant-migrations"}),l="./drizzle/shared-migrations";r.sharedSchema&&(l=await input({message:"Shared migrations folder path:",default:"./drizzle/shared-migrations"}));let m="tenant_${id}";o==="schema"&&(m=await input({message:"Schema name template (use ${id} for tenant ID):",default:"tenant_${id}"}));let u=n.typescript!==void 0?n.typescript:await confirm({message:"Use TypeScript for configuration?",default:true});return {template:e,framework:t,features:r,databaseSetup:a,isolationType:o,dbEnvVar:s,databaseUrl:i,migrationsFolder:c,sharedMigrationsFolder:l,schemaTemplate:m,useTypeScript:u}}async function is(){return await select({message:"Project template:",choices:[{name:ce.minimal.name,value:"minimal",description:ce.minimal.description},{name:ce.standard.name,value:"standard",description:ce.standard.description},{name:ce.full.name,value:"full",description:ce.full.description},{name:ce.enterprise.name,value:"enterprise",description:ce.enterprise.description}]})}async function cs(){return await select({message:"Framework integration:",choices:[{name:oe.none.name,value:"none",description:oe.none.description},{name:oe.express.name,value:"express",description:oe.express.description},{name:oe.fastify.name,value:"fastify",description:oe.fastify.description},{name:oe.nestjs.name,value:"nestjs",description:oe.nestjs.description},{name:oe.hono.name,value:"hono",description:oe.hono.description}]})}async function ls(n){if(n==="minimal")return {sharedSchema:false,crossSchemaQueries:false,healthChecks:false,metrics:false,debug:false};let e=Object.entries(Cr).map(([r,a])=>({name:`${a.name}`,value:r,checked:a.default})),t=await checkbox({message:"Features to include:",choices:e});return {sharedSchema:t.includes("sharedSchema"),crossSchemaQueries:t.includes("crossSchemaQueries"),healthChecks:t.includes("healthChecks"),metrics:t.includes("metrics"),debug:t.includes("debug")}}async function ms(){return await select({message:"Database setup:",choices:[{name:he.manual.name,value:"manual",description:he.manual.description},{name:he["docker-compose"].name,value:"docker-compose",description:he["docker-compose"].description},{name:he["existing-url"].name,value:"existing-url",description:he["existing-url"].description}]})}async function ds(){return await select({message:"Which isolation strategy do you want to use?",choices:[{name:"Schema-based isolation (recommended)",value:"schema",description:"Each tenant has its own PostgreSQL schema"},{name:"Row-level security (RLS)",value:"rls",description:"Shared tables with tenant_id column and RLS policies"}]})}async function us(n){let e={folders:[],files:[],dependencies:[],devDependencies:[]},t=Mr(n);e.files.push(t);let r=Dr(n);if(e.folders.push(...r.folders),e.files.push(...r.files),n.databaseSetup==="docker-compose"&&(e.files.push(Pr()),e.files.push(zr())),n.framework!=="none"){let a=kr(n);e.files.push(...a);let o=Ut(n.framework);e.dependencies.push(...o.dependencies),e.devDependencies.push(...o.devDependencies),(n.framework==="express"||n.framework==="fastify"||n.framework==="hono")&&e.folders.push("src/middleware"),n.framework==="fastify"&&e.folders.push("src/plugins"),n.framework==="nestjs"&&(e.folders.push("src/tenant"),e.folders.push("src/users"),e.folders.push("src/health"));}return n.template!=="minimal"&&e.folders.push("src/db"),e}async function ps(n,e){let t=process.cwd(),r=[...new Set(n.folders)].sort();for(let o of r){let s=join(t,o);existsSync(s)||(mkdirSync(s,{recursive:true}),d(E(`Created folder: ${o}`)));}let a=0;for(let o of n.files){let s=join(t,o.path),i=join(t,o.path.split("/").slice(0,-1).join("/"));if(i&&!existsSync(i)&&mkdirSync(i,{recursive:true}),basename(o.path)===".gitkeep"){existsSync(s)||writeFileSync(s,o.content);continue}writeFileSync(s,o.content),a++,d(E(`Created: ${o.path}`));}d(f(`
|
|
1466
1466
|
${a} files created
|
|
1467
|
-
`));}function
|
|
1467
|
+
`));}function hs(n){d(T(`\u2728 Setup complete!
|
|
1468
1468
|
`)),d(`Next steps:
|
|
1469
1469
|
`);let e=1;if(n.databaseSetup==="docker-compose"&&(d(f(`${e}. Start the database:`)),d(G(" docker-compose up -d")),e++),n.framework!=="none"){let t=Ut(n.framework);t.dependencies.length>0&&(d(f(`${e}. Install framework dependencies:`)),d(G(` npm install ${t.dependencies.join(" ")}`)),e++),t.devDependencies.length>0&&(d(f(`${e}. Install dev dependencies:`)),d(G(` npm install -D ${t.devDependencies.join(" ")}`)),e++);}n.template!=="minimal"&&(d(f(`${e}. Copy environment file:`)),d(G(" cp .env.example .env")),e++),n.template==="minimal"?(d(f(`${e}. Update your schema definitions in the config file`)),e++):(d(f(`${e}. Customize your schemas in src/db/schema/`)),e++),d(f(`${e}. Configure tenant discovery function in tenant.config.ts`)),e++,d(f(`${e}. Generate your first migration:`)),d(G(" npx drizzle-multitenant generate --name initial")),e++,n.features.sharedSchema&&(d(f(`${e}. Generate shared schema migration:`)),d(G(" npx drizzle-multitenant generate:shared --name initial")),e++),d(f(`${e}. Create a tenant:`)),d(G(" npx drizzle-multitenant tenant:create --id my-first-tenant")),e++,d(f(`${e}. Apply migrations:`)),n.features.sharedSchema&&d(G(" npx drizzle-multitenant migrate:shared")),d(G(" npx drizzle-multitenant migrate --all")),e++,d(f(`${e}. Check status:`)),d(G(" npx drizzle-multitenant status")),d(""),d(f("\u{1F4D6} Documentation: https://drizzle-multitenant.dev")),d(f("\u{1F4A1} Interactive UI: npx drizzle-multitenant")),d("");}var Ht=new Command("completion").description("Generate shell completion scripts").argument("<shell>","Shell type: bash, zsh, or fish").addHelpText("after",`
|
|
1470
1470
|
Examples:
|
|
@@ -1475,7 +1475,7 @@ Examples:
|
|
|
1475
1475
|
After adding the completion script, restart your shell or run:
|
|
1476
1476
|
$ source ~/.bashrc # for bash
|
|
1477
1477
|
$ source ~/.zshrc # for zsh
|
|
1478
|
-
`).action(n=>{switch(n.toLowerCase()){case "bash":console.log(
|
|
1478
|
+
`).action(n=>{switch(n.toLowerCase()){case "bash":console.log(fs());break;case "zsh":console.log(ys());break;case "fish":console.log(Ss());break;default:d(C(`Unknown shell: ${n}`)),d(J("Supported shells: bash, zsh, fish")),process.exit(1);}});function fs(){return `# drizzle-multitenant bash completion
|
|
1479
1479
|
# Add this to ~/.bashrc or ~/.bash_completion
|
|
1480
1480
|
|
|
1481
1481
|
_drizzle_multitenant() {
|
|
@@ -1537,7 +1537,7 @@ _drizzle_multitenant() {
|
|
|
1537
1537
|
|
|
1538
1538
|
complete -F _drizzle_multitenant drizzle-multitenant
|
|
1539
1539
|
complete -F _drizzle_multitenant npx drizzle-multitenant
|
|
1540
|
-
`}function
|
|
1540
|
+
`}function ys(){return `#compdef drizzle-multitenant
|
|
1541
1541
|
# drizzle-multitenant zsh completion
|
|
1542
1542
|
# Add this to ~/.zshrc or place in a file in your $fpath
|
|
1543
1543
|
|
|
@@ -1636,7 +1636,7 @@ _drizzle_multitenant() {
|
|
|
1636
1636
|
}
|
|
1637
1637
|
|
|
1638
1638
|
_drizzle_multitenant "$@"
|
|
1639
|
-
`}function
|
|
1639
|
+
`}function Ss(){return `# drizzle-multitenant fish completion
|
|
1640
1640
|
# Save to ~/.config/fish/completions/drizzle-multitenant.fish
|
|
1641
1641
|
|
|
1642
1642
|
# Disable file completion by default
|
|
@@ -1701,62 +1701,62 @@ complete -c drizzle-multitenant -n "__fish_seen_subcommand_from convert-format"
|
|
|
1701
1701
|
|
|
1702
1702
|
# completion options
|
|
1703
1703
|
complete -c drizzle-multitenant -n "__fish_seen_subcommand_from completion" -a "bash zsh fish" -d "Shell type"
|
|
1704
|
-
`}function
|
|
1704
|
+
`}function Fr(){let e="Drizzle Multitenant CLI",t="Multi-tenancy toolkit for Drizzle",r=Math.floor((43-e.length)/2),a=Math.floor((43-t.length)/2);console.log(""),console.log(M.cyan.bold(" \u2554"+"\u2550".repeat(43)+"\u2557")),console.log(M.cyan.bold(" \u2551")+M.white.bold(" ".repeat(r)+e+" ".repeat(43-r-e.length))+M.cyan.bold("\u2551")),console.log(M.cyan.bold(" \u2551")+M.dim(" ".repeat(a)+t+" ".repeat(43-a-t.length))+M.cyan.bold("\u2551")),console.log(M.cyan.bold(" \u255A"+"\u2550".repeat(43)+"\u255D")),console.log("");}var V=class{clearScreen(){console.clear();}showHeader(e){console.log(""),console.log(M.cyan.bold(` ${e}`)),console.log(M.dim(" "+"\u2500".repeat(e.length+4))),console.log("");}showStatus(e,t="info"){let r={success:M.green("\u2713"),warning:M.yellow("\u26A0"),error:M.red("\u2717"),info:M.blue("\u2139")},a={success:M.green,warning:M.yellow,error:M.red,info:M.blue};console.log(`
|
|
1705
1705
|
${r[t]} ${a[t](e)}
|
|
1706
|
-
`);}formatTenantStatus(e){switch(e){case "ok":return
|
|
1707
|
-
`));let r=await checkbox({message:"Select tenants to migrate:",choices:t.map(o=>({name:`${o.tenantId} ${
|
|
1708
|
-
`));let a=await t.migrateAll({concurrency:10,onProgress:(s,i,c)=>{(i==="completed"||i==="failed"||i==="migrating")&&this.renderer.showProgress(s,i,c);},onError:(s,i)=>(this.renderer.showError(i.message),"continue")}),o=Date.now()-r;this.renderer.showResults(a,o),console.log(""),await this.renderer.pressEnterToContinue();}};var fe=class{constructor(e,t){this.ctx=e;this.renderer=t||new V;}renderer;async showCreate(){this.renderer.clearScreen(),this.renderer.showHeader("Create Tenant");let e=await input({message:"Tenant ID:",validate:s=>s.trim()?/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(s)?true:"Invalid tenant ID format (use alphanumeric, dashes, underscores)":"Tenant ID cannot be empty"}),t=this.ctx.config.isolation.schemaNameTemplate(e);console.log(
|
|
1709
|
-
Schema name: ${t}`));let r=await confirm({message:"Apply all migrations after creation?",default:true});if(!await confirm({message:`Create tenant "${e}"?`,default:true}))return {type:"back"};let o=
|
|
1710
|
-
Usage:`)),console.log(
|
|
1711
|
-
${s.message}`));}return console.log(""),await this.renderer.pressEnterToContinue(),{type:"refresh"}}async showDrop(e){if(this.renderer.clearScreen(),this.renderer.showHeader("Drop Tenant"),e.length===0)return this.renderer.showStatus("No tenants found","warning"),await this.renderer.pressEnterToContinue(),{type:"back"};let t=e.map(c=>({name:`${c.tenantId} ${
|
|
1712
|
-
Schema ${a.schemaName} has been removed.`));}catch(c){i.fail("Failed to drop tenant"),console.log(
|
|
1713
|
-
${c.message}`));}return console.log(""),await this.renderer.pressEnterToContinue(),{type:"refresh"}}async showClone(e){if(this.renderer.clearScreen(),this.renderer.showHeader("Clone Tenant"),e.length===0)return this.renderer.showStatus("No tenants found to clone from","warning"),await this.renderer.pressEnterToContinue(),{type:"back"};let t=e.map(u=>({name:`${u.tenantId} ${
|
|
1714
|
-
Target schema: ${s}`));let i=await confirm({message:"Include data in clone?",default:false}),c=false;if(i&&(c=await confirm({message:"Anonymize sensitive data?",default:false})),console.log(""),console.log(
|
|
1715
|
-
Duration: ${p.durationMs}ms`)),console.log(
|
|
1716
|
-
Usage:`)),console.log(
|
|
1717
|
-
${p.error}`)));}catch(u){m.fail("Clone failed"),console.log(
|
|
1718
|
-
${u.message}`));}return console.log(""),await this.renderer.pressEnterToContinue(),{type:"refresh"}}};var ve=class{constructor(e,t){this.ctx=e;this.renderer=t||new V;}renderer;async show(e){this.renderer.clearScreen(),this.renderer.showHeader("Seeding");let t=!!this.ctx.config.schemas?.shared,r=[{name:"Seed tenants",value:"tenants"},...t?[{name:"Seed shared schema",value:"shared"}]:[],...t?[{name:"Seed all (shared + tenants)",value:"all"}]:[],{name:
|
|
1719
|
-
Expected format:`)),console.log(
|
|
1720
|
-
File not found: ${e}`)):console.log(
|
|
1721
|
-
${a.message}`)),await this.renderer.pressEnterToContinue(),null}}async loadSharedSeedFile(e){let t=
|
|
1722
|
-
Expected format:`)),console.log(
|
|
1723
|
-
File not found: ${e}`)):console.log(
|
|
1724
|
-
${a.message}`)),await this.renderer.pressEnterToContinue(),null}}async runSeeding(e,t){this.renderer.clearScreen(),this.renderer.showHeader("Running Seed");let r=I(this.ctx.config,{migrationsFolder:this.ctx.migrationsFolder,...this.ctx.migrationsTable&&{migrationsTable:this.ctx.migrationsTable},tenantDiscovery:async()=>e}),a=Date.now();console.log(
|
|
1725
|
-
`));let o=await r.seedAll(t,{concurrency:10,onProgress:(i,c)=>{c==="completed"?console.log(
|
|
1726
|
-
Error: ${o.error}`)),console.log(""),await this.renderer.pressEnterToContinue();}async runAllSeeding(e,t,r){this.renderer.clearScreen(),this.renderer.showHeader("Running Full Seed");let a=I(this.ctx.config,{migrationsFolder:this.ctx.migrationsFolder,...this.ctx.migrationsTable&&{migrationsTable:this.ctx.migrationsTable},tenantDiscovery:async()=>r}),o=Date.now();console.log(
|
|
1706
|
+
`);}formatTenantStatus(e){switch(e){case "ok":return M.green("\u25CF up to date");case "behind":return M.yellow("\u25CF pending");case "error":return M.red("\u25CF error")}}formatPendingCount(e){return e===0?M.dim("0"):M.yellow(`${e} pending`)}formatDuration(e){if(e<1e3)return `${e}ms`;if(e<6e4)return `${(e/1e3).toFixed(1)}s`;let t=Math.floor(e/6e4),r=Math.round(e%6e4/1e3);return `${t}m ${r}s`}createStatusTable(e){let t=new lr({head:[M.cyan("Tenant"),M.cyan("Schema"),M.cyan("Applied"),M.cyan("Pending"),M.cyan("Status")],style:{head:[],border:[]}});for(let r of e)t.push([r.tenantId,M.dim(r.schemaName),M.green(r.appliedCount.toString()),this.formatPendingCount(r.pendingCount),this.formatTenantStatus(r.status)]);return t}getStatusSummary(e){let t=e.filter(s=>s.status==="ok").length,r=e.filter(s=>s.status==="behind").length,a=e.filter(s=>s.status==="error").length,o=e.reduce((s,i)=>s+i.pendingCount,0);return {upToDate:t,behind:r,error:a,totalPending:o}}showSummary(e){let t=this.getStatusSummary(e);console.log(""),console.log(M.bold(" Summary:")),console.log(` Total tenants: ${e.length}`),console.log(` Up to date: ${M.green(t.upToDate.toString())}`),t.behind>0&&console.log(` Behind: ${M.yellow(t.behind.toString())}`),t.error>0&&console.log(` Errors: ${M.red(t.error.toString())}`);}showPendingMigrations(e){let t=new Map;for(let r of e)for(let a of r.pendingMigrations)t.set(a,(t.get(a)||0)+1);if(t.size>0){console.log(""),console.log(M.yellow(" Pending migrations:"));for(let[r,a]of t.entries())console.log(` ${M.dim("-")} ${r} ${M.dim(`(${a} tenant${a>1?"s":""})`)}`);}}showResults(e,t){if(console.log(""),console.log(M.bold(" Results:")),console.log(` Succeeded: ${M.green(e.succeeded.toString())}`),e.failed>0&&console.log(` Failed: ${M.red(e.failed.toString())}`),console.log(` Duration: ${M.dim(this.formatDuration(t))}`),e.failed>0){console.log(""),console.log(M.red(" Failed tenants:"));for(let r of e.details.filter(a=>!a.success))console.log(` ${M.red("\u2717")} ${r.tenantId}: ${M.dim(r.error||"Unknown error")}`);}}async pressEnterToContinue(){await select({message:"Press Enter to continue...",choices:[{name:"Continue",value:"continue"}]});}showProgress(e,t,r){t==="completed"?console.log(M.green(` \u2713 ${e}`)):t==="failed"?console.log(M.red(` \u2717 ${e}`)):t==="migrating"&&r&&console.log(M.dim(` Applying: ${r}`));}showError(e){console.log(M.red(` Error: ${e}`));}};var De=class{renderer;constructor(e,t){this.renderer=t||new V;}async show(e){if(this.renderer.clearScreen(),this.renderer.showHeader("Migration Status"),e.length===0)return this.renderer.showStatus("No tenants found","warning"),await this.renderer.pressEnterToContinue(),{type:"back"};let t=this.renderer.createStatusTable(e);console.log(t.toString()),this.renderer.showSummary(e),this.renderer.showPendingMigrations(e),console.log("");let r=this.renderer.getStatusSummary(e),a=await select({message:"What would you like to do?",choices:[{name:"Migrate all pending",value:"migrate",disabled:r.totalPending===0},{name:"View tenant details",value:"details"},{name:M.gray("\u2190 Back to main menu"),value:"back"}]});return a==="migrate"?{type:"navigate",screen:"migrate",params:{statuses:e}}:a==="details"?this.showTenantSelection(e):{type:"back"}}async showTenantSelection(e){this.renderer.clearScreen(),this.renderer.showHeader("Select Tenant");let t=e.map(o=>({name:`${this.renderer.formatTenantStatus(o.status).split(" ")[0]} ${o.tenantId} ${M.dim(`(${o.schemaName})`)}`,value:o.tenantId}));t.push({name:M.gray("\u2190 Back"),value:"back"});let r=await select({message:"Select a tenant to view details:",choices:t});if(r==="back")return this.show(e);let a=e.find(o=>o.tenantId===r);return a?this.showTenantDetails(a,e):this.show(e)}async showTenantDetails(e,t){if(this.renderer.clearScreen(),this.renderer.showHeader(`Tenant: ${e.tenantId}`),console.log(M.bold(" Details:")),console.log(` Schema: ${e.schemaName}`),console.log(` Format: ${e.format||M.dim("(new)")}`),console.log(` Applied: ${M.green(e.appliedCount.toString())}`),console.log(` Pending: ${this.renderer.formatPendingCount(e.pendingCount)}`),console.log(` Status: ${this.renderer.formatTenantStatus(e.status)}`),e.pendingMigrations.length>0){console.log(""),console.log(M.yellow(" Pending migrations:"));for(let a of e.pendingMigrations)console.log(` ${M.dim("-")} ${a}`);}return e.error&&(console.log(""),console.log(M.red(` Error: ${e.error}`))),console.log(""),await select({message:"What would you like to do?",choices:[{name:"Migrate this tenant",value:"migrate",disabled:e.pendingCount===0},{name:M.gray("\u2190 Back to list"),value:"back"}]})==="migrate"?{type:"navigate",screen:"migrate-single",params:{tenantId:e.tenantId}}:this.showTenantSelection(t)}};var ge=class{constructor(e,t){this.ctx=e;this.renderer=t||new V;}renderer;async show(e){this.renderer.clearScreen(),this.renderer.showHeader("Migrate Tenants");let t=e.filter(o=>o.pendingCount>0);if(t.length===0)return this.renderer.showStatus("All tenants are up to date","success"),await this.renderer.pressEnterToContinue(),{type:"back"};console.log(M.dim(` Found ${t.length} tenant(s) with pending migrations
|
|
1707
|
+
`));let r=await checkbox({message:"Select tenants to migrate:",choices:t.map(o=>({name:`${o.tenantId} ${M.yellow(`(${o.pendingCount} pending)`)}`,value:o.tenantId,checked:true})),pageSize:15});return r.length===0?(this.renderer.showStatus("No tenants selected","warning"),await this.renderer.pressEnterToContinue(),{type:"back"}):await confirm({message:`Migrate ${r.length} tenant(s)?`,default:true})?(await this.runMigration(r),{type:"refresh"}):{type:"back"}}async migrateSingle(e){return await this.runMigration([e]),{type:"refresh"}}async runMigration(e){this.renderer.clearScreen(),this.renderer.showHeader("Running Migrations");let t=I(this.ctx.config,{migrationsFolder:this.ctx.migrationsFolder,...this.ctx.migrationsTable&&{migrationsTable:this.ctx.migrationsTable},tenantDiscovery:async()=>e}),r=Date.now();console.log(M.dim(` Migrating ${e.length} tenant(s)...
|
|
1708
|
+
`));let a=await t.migrateAll({concurrency:10,onProgress:(s,i,c)=>{(i==="completed"||i==="failed"||i==="migrating")&&this.renderer.showProgress(s,i,c);},onError:(s,i)=>(this.renderer.showError(i.message),"continue")}),o=Date.now()-r;this.renderer.showResults(a,o),console.log(""),await this.renderer.pressEnterToContinue();}};var fe=class{constructor(e,t){this.ctx=e;this.renderer=t||new V;}renderer;async showCreate(){this.renderer.clearScreen(),this.renderer.showHeader("Create Tenant");let e=await input({message:"Tenant ID:",validate:s=>s.trim()?/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(s)?true:"Invalid tenant ID format (use alphanumeric, dashes, underscores)":"Tenant ID cannot be empty"}),t=this.ctx.config.isolation.schemaNameTemplate(e);console.log(M.dim(`
|
|
1709
|
+
Schema name: ${t}`));let r=await confirm({message:"Apply all migrations after creation?",default:true});if(!await confirm({message:`Create tenant "${e}"?`,default:true}))return {type:"back"};let o=st("Creating tenant...").start();try{let s=I(this.ctx.config,{migrationsFolder:this.ctx.migrationsFolder,...this.ctx.migrationsTable&&{migrationsTable:this.ctx.migrationsTable},tenantDiscovery:async()=>[]});if(await s.tenantExists(e))return o.warn(`Tenant ${e} already exists`),await this.renderer.pressEnterToContinue(),{type:"back"};await s.createTenant(e,{migrate:r}),o.succeed(`Tenant ${e} created successfully`),console.log(""),console.log(M.green(" \u2713 Schema created: ")+M.dim(t)),r&&console.log(M.green(" \u2713 All migrations applied")),console.log(M.dim(`
|
|
1710
|
+
Usage:`)),console.log(M.dim(` const db = tenants.getDb('${e}');`));}catch(s){o.fail("Failed to create tenant"),console.log(M.red(`
|
|
1711
|
+
${s.message}`));}return console.log(""),await this.renderer.pressEnterToContinue(),{type:"refresh"}}async showDrop(e){if(this.renderer.clearScreen(),this.renderer.showHeader("Drop Tenant"),e.length===0)return this.renderer.showStatus("No tenants found","warning"),await this.renderer.pressEnterToContinue(),{type:"back"};let t=e.map(c=>({name:`${c.tenantId} ${M.dim(`(${c.schemaName})`)}`,value:c.tenantId}));t.push({name:M.gray("\u2190 Cancel"),value:"cancel"});let r=await select({message:"Select tenant to drop:",choices:t});if(r==="cancel")return {type:"back"};let a=e.find(c=>c.tenantId===r);if(!a)return {type:"back"};if(console.log(""),console.log(M.red.bold(" WARNING: This action is irreversible!")),console.log(M.dim(` Schema ${a.schemaName} and all its data will be deleted.`)),console.log(""),!await confirm({message:`Are you sure you want to drop tenant "${r}"?`,default:false}))return {type:"back"};if(await input({message:`Type "${r}" to confirm deletion:`})!==r)return this.renderer.showStatus("Tenant ID does not match. Operation cancelled.","warning"),await this.renderer.pressEnterToContinue(),{type:"back"};let i=st("Dropping tenant...").start();try{await I(this.ctx.config,{migrationsFolder:this.ctx.migrationsFolder,...this.ctx.migrationsTable&&{migrationsTable:this.ctx.migrationsTable},tenantDiscovery:async()=>[]}).dropTenant(r),i.succeed(`Tenant ${r} dropped successfully`),console.log(M.dim(`
|
|
1712
|
+
Schema ${a.schemaName} has been removed.`));}catch(c){i.fail("Failed to drop tenant"),console.log(M.red(`
|
|
1713
|
+
${c.message}`));}return console.log(""),await this.renderer.pressEnterToContinue(),{type:"refresh"}}async showClone(e){if(this.renderer.clearScreen(),this.renderer.showHeader("Clone Tenant"),e.length===0)return this.renderer.showStatus("No tenants found to clone from","warning"),await this.renderer.pressEnterToContinue(),{type:"back"};let t=e.map(u=>({name:`${u.tenantId} ${M.dim(`(${u.schemaName})`)}`,value:u.tenantId}));t.push({name:M.gray("\u2190 Cancel"),value:"cancel"});let r=await select({message:"Select source tenant:",choices:t});if(r==="cancel")return {type:"back"};let a=new Set(e.map(u=>u.tenantId)),o=await input({message:"New tenant ID:",validate:u=>u.trim()?/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(u)?a.has(u)?"Tenant already exists":true:"Invalid tenant ID format (use alphanumeric, dashes, underscores)":"Tenant ID cannot be empty"}),s=this.ctx.config.isolation.schemaNameTemplate(o);console.log(M.dim(`
|
|
1714
|
+
Target schema: ${s}`));let i=await confirm({message:"Include data in clone?",default:false}),c=false;if(i&&(c=await confirm({message:"Anonymize sensitive data?",default:false})),console.log(""),console.log(M.dim(" Clone configuration:")),console.log(M.dim(` Source: ${r}`)),console.log(M.dim(` Target: ${o} (${s})`)),console.log(M.dim(` Data: ${i?c?"Yes (anonymized)":"Yes":"No (schema only)"}`)),console.log(""),!await confirm({message:`Clone "${r}" to "${o}"?`,default:true}))return {type:"back"};let m=st("Cloning tenant...").start();try{let p=await I(this.ctx.config,{migrationsFolder:this.ctx.migrationsFolder,...this.ctx.migrationsTable&&{migrationsTable:this.ctx.migrationsTable},tenantDiscovery:async()=>[]}).cloneTenant(r,o,{includeData:i,anonymize:c?{enabled:!0}:void 0,onProgress:(h,g)=>{switch(h){case "introspecting":m.text="Introspecting source schema...";break;case "creating_schema":m.text="Creating target schema...";break;case "creating_tables":m.text="Creating tables...";break;case "creating_indexes":m.text="Creating indexes...";break;case "creating_constraints":m.text="Creating constraints...";break;case "copying_data":g?.table?m.text=`Copying: ${g.table} (${g.progress}/${g.total})...`:m.text="Copying data...";break}}});p.success?(m.succeed(`Tenant ${o} cloned successfully`),console.log(""),console.log(M.green(" \u2713 Tables: ")+M.dim(p.tables.length.toString())),i&&p.rowsCopied!==void 0&&(console.log(M.green(" \u2713 Rows copied: ")+M.dim(p.rowsCopied.toLocaleString())),c&&console.log(M.green(" \u2713 Data anonymized"))),console.log(M.dim(`
|
|
1715
|
+
Duration: ${p.durationMs}ms`)),console.log(M.dim(`
|
|
1716
|
+
Usage:`)),console.log(M.dim(` const db = tenants.getDb('${o}');`))):(m.fail("Clone failed"),console.log(M.red(`
|
|
1717
|
+
${p.error}`)));}catch(u){m.fail("Clone failed"),console.log(M.red(`
|
|
1718
|
+
${u.message}`));}return console.log(""),await this.renderer.pressEnterToContinue(),{type:"refresh"}}};var ve=class{constructor(e,t){this.ctx=e;this.renderer=t||new V;}renderer;async show(e){this.renderer.clearScreen(),this.renderer.showHeader("Seeding");let t=!!this.ctx.config.schemas?.shared,r=[{name:"Seed tenants",value:"tenants"},...t?[{name:"Seed shared schema",value:"shared"}]:[],...t?[{name:"Seed all (shared + tenants)",value:"all"}]:[],{name:M.dim("\u2190 Back"),value:"back"}],a=await select({message:"Select seeding action:",choices:r});return a==="back"?{type:"back"}:a==="shared"?this.showSharedSeeding():a==="all"?this.showAllSeeding(e):this.showTenantSeeding(e)}async showTenantSeeding(e){if(this.renderer.clearScreen(),this.renderer.showHeader("Seed Tenants"),e.length===0)return this.renderer.showStatus("No tenants found","warning"),await this.renderer.pressEnterToContinue(),{type:"back"};let t=await input({message:"Seed file path:",default:"./seeds/tenant/initial.ts",validate:s=>s.trim()?true:"Seed file path cannot be empty"}),r=await this.loadSeedFile(t);if(!r)return {type:"back"};console.log("");let a=await checkbox({message:"Select tenants to seed:",choices:e.map(s=>({name:`${s.tenantId} ${M.dim(`(${s.schemaName})`)}`,value:s.tenantId,checked:true})),pageSize:15});return a.length===0?(this.renderer.showStatus("No tenants selected","warning"),await this.renderer.pressEnterToContinue(),{type:"back"}):await confirm({message:`Seed ${a.length} tenant(s)?`,default:true})?(await this.runSeeding(a,r),{type:"back"}):{type:"back"}}async showSharedSeeding(){this.renderer.clearScreen(),this.renderer.showHeader("Seed Shared Schema");let e=await input({message:"Shared seed file path:",default:"./seeds/shared/plans.ts",validate:a=>a.trim()?true:"Seed file path cannot be empty"}),t=await this.loadSharedSeedFile(e);return t?await confirm({message:"Seed shared schema (public)?",default:true})?(await this.runSharedSeeding(t),{type:"back"}):{type:"back"}:{type:"back"}}async showAllSeeding(e){if(this.renderer.clearScreen(),this.renderer.showHeader("Seed All (Shared + Tenants)"),e.length===0)return this.renderer.showStatus("No tenants found","warning"),await this.renderer.pressEnterToContinue(),{type:"back"};let t=await input({message:"Shared seed file path:",default:"./seeds/shared/plans.ts",validate:i=>i.trim()?true:"Seed file path cannot be empty"}),r=await this.loadSharedSeedFile(t);if(!r)return {type:"back"};console.log("");let a=await input({message:"Tenant seed file path:",default:"./seeds/tenant/initial.ts",validate:i=>i.trim()?true:"Seed file path cannot be empty"}),o=await this.loadSeedFile(a);return o?(console.log(""),await confirm({message:`Seed shared schema and ${e.length} tenant(s)?`,default:true})?(await this.runAllSeeding(r,o,e.map(i=>i.tenantId)),{type:"back"}):{type:"back"}):{type:"back"}}async loadSeedFile(e){let t=st("Loading seed file...").start();try{let r=resolve(process.cwd(),e),o=await import(pathToFileURL(r).href),s=o.seed||o.default;return typeof s!="function"?(t.fail('Seed file must export a "seed" function or default export'),console.log(M.dim(`
|
|
1719
|
+
Expected format:`)),console.log(M.dim(" export const seed: SeedFunction = async (db, tenantId) => { ... };")),await this.renderer.pressEnterToContinue(),null):(t.succeed("Seed file loaded"),s)}catch(r){t.fail("Failed to load seed file");let a=r;return a.message.includes("Cannot find module")?console.log(M.red(`
|
|
1720
|
+
File not found: ${e}`)):console.log(M.red(`
|
|
1721
|
+
${a.message}`)),await this.renderer.pressEnterToContinue(),null}}async loadSharedSeedFile(e){let t=st("Loading shared seed file...").start();try{let r=resolve(process.cwd(),e),o=await import(pathToFileURL(r).href),s=o.seed||o.default;return typeof s!="function"?(t.fail('Seed file must export a "seed" function or default export'),console.log(M.dim(`
|
|
1722
|
+
Expected format:`)),console.log(M.dim(" export const seed: SharedSeedFunction = async (db) => { ... };")),await this.renderer.pressEnterToContinue(),null):(t.succeed("Shared seed file loaded"),s)}catch(r){t.fail("Failed to load shared seed file");let a=r;return a.message.includes("Cannot find module")?console.log(M.red(`
|
|
1723
|
+
File not found: ${e}`)):console.log(M.red(`
|
|
1724
|
+
${a.message}`)),await this.renderer.pressEnterToContinue(),null}}async runSeeding(e,t){this.renderer.clearScreen(),this.renderer.showHeader("Running Seed");let r=I(this.ctx.config,{migrationsFolder:this.ctx.migrationsFolder,...this.ctx.migrationsTable&&{migrationsTable:this.ctx.migrationsTable},tenantDiscovery:async()=>e}),a=Date.now();console.log(M.dim(` Seeding ${e.length} tenant(s)...
|
|
1725
|
+
`));let o=await r.seedAll(t,{concurrency:10,onProgress:(i,c)=>{c==="completed"?console.log(M.green(` \u2713 ${i}`)):c==="failed"&&console.log(M.red(` \u2717 ${i}`));},onError:(i,c)=>(console.log(M.red(` Error: ${c.message}`)),"continue")}),s=Date.now()-a;this.renderer.showResults(o,s),console.log(""),await this.renderer.pressEnterToContinue();}async runSharedSeeding(e){this.renderer.clearScreen(),this.renderer.showHeader("Running Shared Seed");let t=I(this.ctx.config,{migrationsFolder:this.ctx.migrationsFolder,...this.ctx.migrationsTable&&{migrationsTable:this.ctx.migrationsTable},tenantDiscovery:async()=>[]}),r=st("Seeding shared schema...").start(),a=Date.now(),o=await t.seedShared(e),s=Date.now()-a;o.success?r.succeed(`Shared schema seeded successfully in ${s}ms`):r.fail(`Failed to seed shared schema: ${o.error}`),console.log(""),console.log(M.bold("Summary:")),console.log(` Schema: ${o.schemaName}`),console.log(` Status: ${o.success?M.green("success"):M.red("failed")}`),console.log(` Duration: ${M.dim(`${s}ms`)}`),o.error&&console.log(M.red(`
|
|
1726
|
+
Error: ${o.error}`)),console.log(""),await this.renderer.pressEnterToContinue();}async runAllSeeding(e,t,r){this.renderer.clearScreen(),this.renderer.showHeader("Running Full Seed");let a=I(this.ctx.config,{migrationsFolder:this.ctx.migrationsFolder,...this.ctx.migrationsTable&&{migrationsTable:this.ctx.migrationsTable},tenantDiscovery:async()=>r}),o=Date.now();console.log(M.bold(`
|
|
1727
1727
|
[1/2] Seeding shared schema...
|
|
1728
|
-
`));let s=
|
|
1728
|
+
`));let s=st("Seeding shared schema...").start(),i=await a.seedShared(e);i.success?s.succeed(`Shared schema seeded in ${i.durationMs}ms`):s.fail(`Failed: ${i.error}`),console.log(M.bold(`
|
|
1729
1729
|
[2/2] Seeding ${r.length} tenant(s)...
|
|
1730
|
-
`));let c=await a.seedAll(t,{concurrency:10,onProgress:(m,u)=>{u==="completed"?console.log(
|
|
1731
|
-
Tenants:`)),console.log(` Total: ${c.total}`),console.log(` Succeeded: ${
|
|
1732
|
-
Total:`)),console.log(` Duration: ${C.dim(`${l}ms`)}`),console.log(""),await this.renderer.pressEnterToContinue();}};var Te=class{renderer;constructor(e,t){this.renderer=t||new V;}async show(){this.renderer.clearScreen(),this.renderer.showHeader("Generate Migration");let e=await input({message:"Migration name:",validate:t=>t.trim()?/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t)?true:"Invalid migration name format":"Migration name cannot be empty"});return console.log(""),console.log(C.dim(" To generate a migration, run:")),console.log(""),console.log(C.cyan(` npx drizzle-kit generate --name=${e}`)),console.log(""),console.log(C.dim(" Or use drizzle-multitenant generate:")),console.log(""),console.log(C.cyan(` npx drizzle-multitenant generate --name=${e}`)),console.log(""),await this.renderer.pressEnterToContinue(),{type:"back"}}async showShared(){this.renderer.clearScreen(),this.renderer.showHeader("Generate Shared Migration");let e=await input({message:"Shared migration name:",validate:t=>t.trim()?/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t)?true:"Invalid migration name format":"Migration name cannot be empty"});return console.log(""),console.log(C.dim(" To generate a shared migration, run:")),console.log(""),console.log(C.cyan(` npx drizzle-multitenant generate:shared --name=${e}`)),console.log(""),console.log(C.dim(" This will create a migration for the public schema.")),console.log(""),await this.renderer.pressEnterToContinue(),{type:"back"}}};var Yt={"table-naming":["warn",{style:"snake_case"}],"column-naming":["warn",{style:"snake_case"}],"require-primary-key":"error","prefer-uuid-pk":"warn","require-timestamps":"off","index-foreign-keys":"warn","no-cascade-delete":"off","require-soft-delete":"off"};function $s(n){return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(n)}function Cs(n){return /^[a-z][a-zA-Z0-9]*$/.test(n)&&!n.includes("_")}function Ms(n){return /^[A-Z][a-zA-Z0-9]*$/.test(n)&&!n.includes("_")}function Es(n){return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(n)}function Lr(n,e){switch(e){case "snake_case":return $s(n);case "camelCase":return Cs(n);case "PascalCase":return Ms(n);case "kebab-case":return Es(n);default:return true}}function jr(n,e){let t=n.replace(/([a-z])([A-Z])/g,"$1_$2").replace(/-/g,"_").toLowerCase().split("_").filter(Boolean);switch(e){case "snake_case":return t.join("_");case "camelCase":return t.map((r,a)=>a===0?r:r.charAt(0).toUpperCase()+r.slice(1)).join("");case "PascalCase":return t.map(r=>r.charAt(0).toUpperCase()+r.slice(1)).join("");case "kebab-case":return t.join("-");default:return n}}function qr(n,e){return e?.length?e.some(t=>new RegExp("^"+t.replace(/\*/g,".*")+"$").test(n)):false}var Qt={name:"table-naming",description:"Enforce consistent table naming convention",defaultSeverity:"warn",validateTable(n,e){let t=e.options,r=t.style??"snake_case",a=t.exceptions??[];if(!qr(n.name,a)&&!Lr(n.name,r)){let o=jr(n.name,r);e.report({message:`Table "${n.name}" does not follow ${r} convention`,filePath:n.filePath,table:n.name,suggestion:`Consider renaming to "${o}"`});}}},Zt={name:"column-naming",description:"Enforce consistent column naming convention",defaultSeverity:"warn",validateColumn(n,e,t){let r=t.options,a=r.style??"snake_case",o=r.exceptions??[];if(!qr(n.name,o)&&!Lr(n.name,a)){let s=jr(n.name,a);t.report({message:`Column "${n.name}" in table "${e.name}" does not follow ${a} convention`,filePath:e.filePath,table:e.name,column:n.name,suggestion:`Consider renaming to "${s}"`});}}},ot=[Qt,Zt];var Xt={name:"require-primary-key",description:"Require every table to have a primary key",defaultSeverity:"error",validateTable(n,e){n.columns.some(r=>r.isPrimaryKey)||e.report({message:`Table "${n.name}" does not have a primary key`,filePath:n.filePath,table:n.name,suggestion:"Add a primary key column (e.g., id: uuid().primaryKey())"});}},en={name:"prefer-uuid-pk",description:"Prefer UUID over serial/integer for primary keys",defaultSeverity:"warn",validateColumn(n,e,t){if(!n.isPrimaryKey)return;let r=["serial","bigserial","smallserial","integer","bigint","smallint"],a=n.dataType.toLowerCase();r.some(o=>a.includes(o))&&t.report({message:`Table "${e.name}" uses ${n.dataType} for primary key instead of UUID`,filePath:e.filePath,table:e.name,column:n.name,suggestion:"Consider using uuid() for better distribution and security"});}},tn={name:"require-timestamps",description:"Require timestamp columns (created_at, updated_at) on tables",defaultSeverity:"warn",validateTable(n,e){let r=e.options.columns??["created_at","updated_at"],a=n.columns.map(o=>o.name.toLowerCase());for(let o of r){let s=o.toLowerCase(),i=o.replace(/_([a-z])/g,(c,l)=>l.toUpperCase()).toLowerCase();!a.includes(s)&&!a.includes(i)&&e.report({message:`Table "${n.name}" is missing "${o}" column`,filePath:n.filePath,table:n.name,suggestion:`Add ${s}: timestamp('${s}').defaultNow().notNull()`});}}},nn={name:"index-foreign-keys",description:"Require indexes on foreign key columns",defaultSeverity:"warn",validateTable(n,e){let t=n.columns.filter(o=>o.references),r=new Set;for(let o of n.indexes){let s=o.columns[0];s&&r.add(s.toLowerCase());}let a=n.columns.filter(o=>o.isPrimaryKey);for(let o of a)r.add(o.name.toLowerCase());for(let o of t)r.has(o.name.toLowerCase())||e.report({message:`Foreign key column "${o.name}" in table "${n.name}" is not indexed`,filePath:n.filePath,table:n.name,column:o.name,suggestion:`Add index: index('${n.name}_${o.name}_idx').on(${n.name}.${o.name})`});}},st=[Xt,en,tn,nn];var rn={name:"no-cascade-delete",description:"Warn about CASCADE DELETE on foreign keys",defaultSeverity:"warn",validateColumn(n,e,t){if(!n.references)return;n.references.onDelete?.toLowerCase()==="cascade"&&t.report({message:`Foreign key "${n.name}" in table "${e.name}" uses CASCADE DELETE`,filePath:e.filePath,table:e.name,column:n.name,suggestion:"Consider using SET NULL, RESTRICT, or implementing soft delete to prevent accidental data loss"});}},an={name:"require-soft-delete",description:"Require soft delete column on tables",defaultSeverity:"off",validateTable(n,e){let r=e.options.column??"deleted_at",a=r.toLowerCase(),o=r.replace(/_([a-z])/g,(i,c)=>c.toUpperCase()).toLowerCase(),s=n.columns.map(i=>i.name.toLowerCase());!s.includes(a)&&!s.includes(o)&&e.report({message:`Table "${n.name}" does not have a soft delete column ("${r}")`,filePath:n.filePath,table:n.name,suggestion:`Add ${a}: timestamp('${a}') for soft delete support`});}},it=[rn,an];var Pe=[...ot,...st,...it];var Rs=Symbol.for("drizzle:Table"),Ds=Symbol.for("drizzle:Column");function vs(n){if(!n||typeof n!="object")return false;let e=n;if(e._&&typeof e._=="object"){let t=e._;return typeof t.name=="string"&&(t.schema===void 0||typeof t.schema=="string")}return !!Object.getOwnPropertySymbols(e).some(t=>t===Rs)}function Ps(n){if(!n||typeof n!="object")return false;let e=n;return typeof e.name=="string"||typeof e.columnType=="string"||typeof e.dataType=="string"||Object.getOwnPropertySymbols(e).some(t=>t===Ds)}function _s(n,e){let t=e.name??n,r="unknown";if(typeof e.dataType=="string")r=e.dataType;else if(typeof e.columnType=="string")r=e.columnType;else if(typeof e.getSQLType=="function")try{r=e.getSQLType();}catch{}let a=e.primary===true||e.isPrimaryKey===true||typeof e.primaryKey=="function"&&e._isPrimaryKey===true,o=e.notNull!==true&&e.isNotNull!==true,s=e.hasDefault===true||e.default!==void 0,i=e.default!==void 0?String(e.default):null,c;if(e.references&&typeof e.references=="object"){let l=e.references;c={table:l.table??"unknown",column:l.column??"unknown",onDelete:l.onDelete,onUpdate:l.onUpdate};}return {name:t,dataType:r,isPrimaryKey:a,isNullable:o,hasDefault:s,defaultValue:i,references:c}}function ks(n){let e=[],t=n._indexes??n.indexes;if(t&&typeof t=="object")for(let[r,a]of Object.entries(t)){if(!a||typeof a!="object")continue;let o=a,s=[];if(Array.isArray(o.columns))for(let i of o.columns)typeof i=="string"?s.push(i):i&&typeof i=="object"&&"name"in i&&s.push(String(i.name));e.push({name:r,columns:s,isUnique:o.isUnique===true||o.unique===true});}return e}function Se(n,e,t){let r=[];for(let[a,o]of Object.entries(n)){if(!vs(o))continue;let s=o,c=s._?.name??a,l=[];for(let[u,p]of Object.entries(s)){if(u==="_"||u.startsWith("_")||!Ps(p))continue;let h=_s(u,p);l.push(h);}let m=ks(s);r.push({name:c,schemaType:t,columns:l,indexes:m,filePath:e});}return r}async function ue(n,e){let{glob:t}=await import('glob'),{resolve:r}=await import('path'),a=r(n,"**/*.ts");return (await t(a,{ignore:["**/*.test.ts","**/*.spec.ts","**/node_modules/**"]})).map(s=>({filePath:s,type:e}))}async function pe(n,e){try{let t=await import(n);return Se(t,n,e)}catch(t){return console.warn(`Warning: Could not parse schema file ${n}:`,t.message),[]}}function Ns(n){return n?typeof n=="string"?n:n[0]:"off"}function Is(n){return n?typeof n=="string"?{}:n[1]??{}:{}}var ct=class{rules=new Map;config;constructor(e){this.config={...Yt,...e?.rules};for(let t of Pe)this.rules.set(t.name,t);}getEffectiveSeverity(e){let t=this.config[e];return Ns(t)}getRuleOptions(e){let t=this.config[e];return Is(t)}isRuleEnabled(e){return this.getEffectiveSeverity(e)!=="off"}lintTable(e){let t=[];for(let[r,a]of this.rules){let o=this.getEffectiveSeverity(r);if(o==="off")continue;let s=this.getRuleOptions(r),i={severity:o,options:s,report:c=>{t.push({rule:r,severity:o,...c});}};if(a.validateTable&&a.validateTable(e,i),a.validateColumn)for(let c of e.columns)a.validateColumn(c,e,i);}return t}lintTables(e){let t=Date.now(),r=new Map;for(let i of e){r.has(i.filePath)||r.set(i.filePath,{filePath:i.filePath,issues:[],tables:0,columns:0});let c=r.get(i.filePath);c.tables++,c.columns+=i.columns.length;let l=this.lintTable(i);c.issues.push(...l);}for(let[i,c]of this.rules){if(!c.validateSchema)continue;let l=this.getEffectiveSeverity(i);if(l==="off")continue;let m=this.getRuleOptions(i),u={severity:l,options:m,report:p=>{let h=p.filePath||"schema";r.has(h)||r.set(h,{filePath:h,issues:[],tables:0,columns:0}),r.get(h).issues.push({rule:i,severity:l,...p});}};c.validateSchema(e,u);}let a=Array.from(r.values()),o=a.flatMap(i=>i.issues),s={totalFiles:a.length,totalTables:a.reduce((i,c)=>i+c.tables,0),totalColumns:a.reduce((i,c)=>i+c.columns,0),errors:o.filter(i=>i.severity==="error").length,warnings:o.filter(i=>i.severity==="warn").length};return {files:a,summary:s,durationMs:Date.now()-t,passed:s.errors===0}}lintModule(e,t,r="tenant"){let a=Se(e,t,r);return this.lintTables(a)}async lintDirectory(e,t="tenant"){let r=Date.now(),a=await ue(e,t),o=[];for(let i of a){let c=await pe(i.filePath,i.type);o.push(...c);}let s=this.lintTables(o);return s.durationMs=Date.now()-r,s}async lintDirectories(e){let t=Date.now(),r=[];if(e.tenant){let o=await ue(e.tenant,"tenant");for(let s of o){let i=await pe(s.filePath,s.type);r.push(...i);}}if(e.shared){let o=await ue(e.shared,"shared");for(let s of o){let i=await pe(s.filePath,s.type);r.push(...i);}}let a=this.lintTables(r);return a.durationMs=Date.now()-t,a}getRules(){return Array.from(this.rules.values())}getRule(e){return this.rules.get(e)}registerRule(e){this.rules.set(e.name,e);}setRuleConfig(e,t){this.config[e]=t;}getConfig(){return {...this.config}}};function _e(n){return new ct(n)}var Ur={reset:"\x1B[0m",red:"\x1B[31m",yellow:"\x1B[33m",green:"\x1B[32m",cyan:"\x1B[36m",dim:"\x1B[2m",bold:"\x1B[1m"};function se(n,e,t){return t?`${Ur[e]}${n}${Ur.reset}`:n}function As(n,e){let t=n.severity==="error"?se("error","red",e):se("warn","yellow",e),r=n.column?`${n.table}.${n.column}`:n.table??n.filePath,a=se(`(${n.rule})`,"dim",e),o=` ${t} ${n.message} ${a}`;return o+=`
|
|
1730
|
+
`));let c=await a.seedAll(t,{concurrency:10,onProgress:(m,u)=>{u==="completed"?console.log(M.green(` \u2713 ${m}`)):u==="failed"&&console.log(M.red(` \u2717 ${m}`));},onError:(m,u)=>(console.log(M.red(` Error: ${u.message}`)),"continue")}),l=Date.now()-o;console.log(""),console.log(M.bold("Summary:")),console.log(M.bold(" Shared:")),console.log(` Schema: ${i.schemaName}`),console.log(` Status: ${i.success?M.green("success"):M.red("failed")}`),console.log(` Duration: ${M.dim(`${i.durationMs}ms`)}`),console.log(M.bold(`
|
|
1731
|
+
Tenants:`)),console.log(` Total: ${c.total}`),console.log(` Succeeded: ${M.green(c.succeeded.toString())}`),c.failed>0&&console.log(` Failed: ${M.red(c.failed.toString())}`),c.skipped>0&&console.log(` Skipped: ${M.yellow(c.skipped.toString())}`),console.log(M.bold(`
|
|
1732
|
+
Total:`)),console.log(` Duration: ${M.dim(`${l}ms`)}`),console.log(""),await this.renderer.pressEnterToContinue();}};var Te=class{renderer;constructor(e,t){this.renderer=t||new V;}async show(){this.renderer.clearScreen(),this.renderer.showHeader("Generate Migration");let e=await input({message:"Migration name:",validate:t=>t.trim()?/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t)?true:"Invalid migration name format":"Migration name cannot be empty"});return console.log(""),console.log(M.dim(" To generate a migration, run:")),console.log(""),console.log(M.cyan(` npx drizzle-kit generate --name=${e}`)),console.log(""),console.log(M.dim(" Or use drizzle-multitenant generate:")),console.log(""),console.log(M.cyan(` npx drizzle-multitenant generate --name=${e}`)),console.log(""),await this.renderer.pressEnterToContinue(),{type:"back"}}async showShared(){this.renderer.clearScreen(),this.renderer.showHeader("Generate Shared Migration");let e=await input({message:"Shared migration name:",validate:t=>t.trim()?/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t)?true:"Invalid migration name format":"Migration name cannot be empty"});return console.log(""),console.log(M.dim(" To generate a shared migration, run:")),console.log(""),console.log(M.cyan(` npx drizzle-multitenant generate:shared --name=${e}`)),console.log(""),console.log(M.dim(" This will create a migration for the public schema.")),console.log(""),await this.renderer.pressEnterToContinue(),{type:"back"}}};var Yt={"table-naming":["warn",{style:"snake_case"}],"column-naming":["warn",{style:"snake_case"}],"require-primary-key":"error","prefer-uuid-pk":"warn","require-timestamps":"off","index-foreign-keys":"warn","no-cascade-delete":"off","require-soft-delete":"off"};function Ms(n){return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(n)}function Es(n){return /^[a-z][a-zA-Z0-9]*$/.test(n)&&!n.includes("_")}function Rs(n){return /^[A-Z][a-zA-Z0-9]*$/.test(n)&&!n.includes("_")}function Ds(n){return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(n)}function Ur(n,e){switch(e){case "snake_case":return Ms(n);case "camelCase":return Es(n);case "PascalCase":return Rs(n);case "kebab-case":return Ds(n);default:return true}}function Wr(n,e){let t=n.replace(/([a-z])([A-Z])/g,"$1_$2").replace(/-/g,"_").toLowerCase().split("_").filter(Boolean);switch(e){case "snake_case":return t.join("_");case "camelCase":return t.map((r,a)=>a===0?r:r.charAt(0).toUpperCase()+r.slice(1)).join("");case "PascalCase":return t.map(r=>r.charAt(0).toUpperCase()+r.slice(1)).join("");case "kebab-case":return t.join("-");default:return n}}function Hr(n,e){return e?.length?e.some(t=>new RegExp("^"+t.replace(/\*/g,".*")+"$").test(n)):false}var Qt={name:"table-naming",description:"Enforce consistent table naming convention",defaultSeverity:"warn",validateTable(n,e){let t=e.options,r=t.style??"snake_case",a=t.exceptions??[];if(!Hr(n.name,a)&&!Ur(n.name,r)){let o=Wr(n.name,r);e.report({message:`Table "${n.name}" does not follow ${r} convention`,filePath:n.filePath,table:n.name,suggestion:`Consider renaming to "${o}"`});}}},Zt={name:"column-naming",description:"Enforce consistent column naming convention",defaultSeverity:"warn",validateColumn(n,e,t){let r=t.options,a=r.style??"snake_case",o=r.exceptions??[];if(!Hr(n.name,o)&&!Ur(n.name,a)){let s=Wr(n.name,a);t.report({message:`Column "${n.name}" in table "${e.name}" does not follow ${a} convention`,filePath:e.filePath,table:e.name,column:n.name,suggestion:`Consider renaming to "${s}"`});}}},it=[Qt,Zt];var Xt={name:"require-primary-key",description:"Require every table to have a primary key",defaultSeverity:"error",validateTable(n,e){n.columns.some(r=>r.isPrimaryKey)||e.report({message:`Table "${n.name}" does not have a primary key`,filePath:n.filePath,table:n.name,suggestion:"Add a primary key column (e.g., id: uuid().primaryKey())"});}},en={name:"prefer-uuid-pk",description:"Prefer UUID over serial/integer for primary keys",defaultSeverity:"warn",validateColumn(n,e,t){if(!n.isPrimaryKey)return;let r=["serial","bigserial","smallserial","integer","bigint","smallint"],a=n.dataType.toLowerCase();r.some(o=>a.includes(o))&&t.report({message:`Table "${e.name}" uses ${n.dataType} for primary key instead of UUID`,filePath:e.filePath,table:e.name,column:n.name,suggestion:"Consider using uuid() for better distribution and security"});}},tn={name:"require-timestamps",description:"Require timestamp columns (created_at, updated_at) on tables",defaultSeverity:"warn",validateTable(n,e){let r=e.options.columns??["created_at","updated_at"],a=n.columns.map(o=>o.name.toLowerCase());for(let o of r){let s=o.toLowerCase(),i=o.replace(/_([a-z])/g,(c,l)=>l.toUpperCase()).toLowerCase();!a.includes(s)&&!a.includes(i)&&e.report({message:`Table "${n.name}" is missing "${o}" column`,filePath:n.filePath,table:n.name,suggestion:`Add ${s}: timestamp('${s}').defaultNow().notNull()`});}}},nn={name:"index-foreign-keys",description:"Require indexes on foreign key columns",defaultSeverity:"warn",validateTable(n,e){let t=n.columns.filter(o=>o.references),r=new Set;for(let o of n.indexes){let s=o.columns[0];s&&r.add(s.toLowerCase());}let a=n.columns.filter(o=>o.isPrimaryKey);for(let o of a)r.add(o.name.toLowerCase());for(let o of t)r.has(o.name.toLowerCase())||e.report({message:`Foreign key column "${o.name}" in table "${n.name}" is not indexed`,filePath:n.filePath,table:n.name,column:o.name,suggestion:`Add index: index('${n.name}_${o.name}_idx').on(${n.name}.${o.name})`});}},ct=[Xt,en,tn,nn];var rn={name:"no-cascade-delete",description:"Warn about CASCADE DELETE on foreign keys",defaultSeverity:"warn",validateColumn(n,e,t){if(!n.references)return;n.references.onDelete?.toLowerCase()==="cascade"&&t.report({message:`Foreign key "${n.name}" in table "${e.name}" uses CASCADE DELETE`,filePath:e.filePath,table:e.name,column:n.name,suggestion:"Consider using SET NULL, RESTRICT, or implementing soft delete to prevent accidental data loss"});}},an={name:"require-soft-delete",description:"Require soft delete column on tables",defaultSeverity:"off",validateTable(n,e){let r=e.options.column??"deleted_at",a=r.toLowerCase(),o=r.replace(/_([a-z])/g,(i,c)=>c.toUpperCase()).toLowerCase(),s=n.columns.map(i=>i.name.toLowerCase());!s.includes(a)&&!s.includes(o)&&e.report({message:`Table "${n.name}" does not have a soft delete column ("${r}")`,filePath:n.filePath,table:n.name,suggestion:`Add ${a}: timestamp('${a}') for soft delete support`});}},lt=[rn,an];var _e=[...it,...ct,...lt];var vs=Symbol.for("drizzle:Table"),_s=Symbol.for("drizzle:Column");function Ps(n){if(!n||typeof n!="object")return false;let e=n;if(e._&&typeof e._=="object"){let t=e._;return typeof t.name=="string"&&(t.schema===void 0||typeof t.schema=="string")}return !!Object.getOwnPropertySymbols(e).some(t=>t===vs)}function zs(n){if(!n||typeof n!="object")return false;let e=n;return typeof e.name=="string"||typeof e.columnType=="string"||typeof e.dataType=="string"||Object.getOwnPropertySymbols(e).some(t=>t===_s)}function ks(n,e){let t=e.name??n,r="unknown";if(typeof e.dataType=="string")r=e.dataType;else if(typeof e.columnType=="string")r=e.columnType;else if(typeof e.getSQLType=="function")try{r=e.getSQLType();}catch{}let a=e.primary===true||e.isPrimaryKey===true||typeof e.primaryKey=="function"&&e._isPrimaryKey===true,o=e.notNull!==true&&e.isNotNull!==true,s=e.hasDefault===true||e.default!==void 0,i=e.default!==void 0?String(e.default):null,c;if(e.references&&typeof e.references=="object"){let l=e.references;c={table:l.table??"unknown",column:l.column??"unknown",onDelete:l.onDelete,onUpdate:l.onUpdate};}return {name:t,dataType:r,isPrimaryKey:a,isNullable:o,hasDefault:s,defaultValue:i,references:c}}function Ns(n){let e=[],t=n._indexes??n.indexes;if(t&&typeof t=="object")for(let[r,a]of Object.entries(t)){if(!a||typeof a!="object")continue;let o=a,s=[];if(Array.isArray(o.columns))for(let i of o.columns)typeof i=="string"?s.push(i):i&&typeof i=="object"&&"name"in i&&s.push(String(i.name));e.push({name:r,columns:s,isUnique:o.isUnique===true||o.unique===true});}return e}function Se(n,e,t){let r=[];for(let[a,o]of Object.entries(n)){if(!Ps(o))continue;let s=o,c=s._?.name??a,l=[];for(let[u,p]of Object.entries(s)){if(u==="_"||u.startsWith("_")||!zs(p))continue;let h=ks(u,p);l.push(h);}let m=Ns(s);r.push({name:c,schemaType:t,columns:l,indexes:m,filePath:e});}return r}async function ue(n,e){let{glob:t}=await import('glob'),{resolve:r}=await import('path'),a=r(n,"**/*.ts");return (await t(a,{ignore:["**/*.test.ts","**/*.spec.ts","**/node_modules/**"]})).map(s=>({filePath:s,type:e}))}async function pe(n,e){try{let t=await import(n);return Se(t,n,e)}catch(t){return console.warn(`Warning: Could not parse schema file ${n}:`,t.message),[]}}function As(n){return n?typeof n=="string"?n:n[0]:"off"}function Fs(n){return n?typeof n=="string"?{}:n[1]??{}:{}}var mt=class{rules=new Map;config;constructor(e){this.config={...Yt,...e?.rules};for(let t of _e)this.rules.set(t.name,t);}getEffectiveSeverity(e){let t=this.config[e];return As(t)}getRuleOptions(e){let t=this.config[e];return Fs(t)}isRuleEnabled(e){return this.getEffectiveSeverity(e)!=="off"}lintTable(e){let t=[];for(let[r,a]of this.rules){let o=this.getEffectiveSeverity(r);if(o==="off")continue;let s=this.getRuleOptions(r),i={severity:o,options:s,report:c=>{t.push({rule:r,severity:o,...c});}};if(a.validateTable&&a.validateTable(e,i),a.validateColumn)for(let c of e.columns)a.validateColumn(c,e,i);}return t}lintTables(e){let t=Date.now(),r=new Map;for(let i of e){r.has(i.filePath)||r.set(i.filePath,{filePath:i.filePath,issues:[],tables:0,columns:0});let c=r.get(i.filePath);c.tables++,c.columns+=i.columns.length;let l=this.lintTable(i);c.issues.push(...l);}for(let[i,c]of this.rules){if(!c.validateSchema)continue;let l=this.getEffectiveSeverity(i);if(l==="off")continue;let m=this.getRuleOptions(i),u={severity:l,options:m,report:p=>{let h=p.filePath||"schema";r.has(h)||r.set(h,{filePath:h,issues:[],tables:0,columns:0}),r.get(h).issues.push({rule:i,severity:l,...p});}};c.validateSchema(e,u);}let a=Array.from(r.values()),o=a.flatMap(i=>i.issues),s={totalFiles:a.length,totalTables:a.reduce((i,c)=>i+c.tables,0),totalColumns:a.reduce((i,c)=>i+c.columns,0),errors:o.filter(i=>i.severity==="error").length,warnings:o.filter(i=>i.severity==="warn").length};return {files:a,summary:s,durationMs:Date.now()-t,passed:s.errors===0}}lintModule(e,t,r="tenant"){let a=Se(e,t,r);return this.lintTables(a)}async lintDirectory(e,t="tenant"){let r=Date.now(),a=await ue(e,t),o=[];for(let i of a){let c=await pe(i.filePath,i.type);o.push(...c);}let s=this.lintTables(o);return s.durationMs=Date.now()-r,s}async lintDirectories(e){let t=Date.now(),r=[];if(e.tenant){let o=await ue(e.tenant,"tenant");for(let s of o){let i=await pe(s.filePath,s.type);r.push(...i);}}if(e.shared){let o=await ue(e.shared,"shared");for(let s of o){let i=await pe(s.filePath,s.type);r.push(...i);}}let a=this.lintTables(r);return a.durationMs=Date.now()-t,a}getRules(){return Array.from(this.rules.values())}getRule(e){return this.rules.get(e)}registerRule(e){this.rules.set(e.name,e);}setRuleConfig(e,t){this.config[e]=t;}getConfig(){return {...this.config}}};function Pe(n){return new mt(n)}var Br={reset:"\x1B[0m",red:"\x1B[31m",yellow:"\x1B[33m",green:"\x1B[32m",cyan:"\x1B[36m",dim:"\x1B[2m",bold:"\x1B[1m"};function se(n,e,t){return t?`${Br[e]}${n}${Br.reset}`:n}function Os(n,e){let t=n.severity==="error"?se("error","red",e):se("warn","yellow",e),r=n.column?`${n.table}.${n.column}`:n.table??n.filePath,a=se(`(${n.rule})`,"dim",e),o=` ${t} ${n.message} ${a}`;return o+=`
|
|
1733
1733
|
${se(r,"dim",e)}`,n.suggestion&&(o+=`
|
|
1734
|
-
${se("\u2192 "+n.suggestion,"cyan",e)}`),o}function
|
|
1735
|
-
`)}function
|
|
1736
|
-
`)}function lt(n,e){switch(e.format){case "json":return Os(n);case "github":return Ls(n);default:return Fs(n,e)}}var ke=class{renderer;ctx;constructor(e,t){this.ctx=e,this.renderer=t||new V;}async show(){switch(this.renderer.clearScreen(),this.renderer.showHeader("Schema Lint"),await select({message:"What would you like to do?",choices:[{name:"Lint schemas with default rules",value:"default"},{name:"Configure rules and lint",value:"configure"},{name:"View available rules",value:"rules"},{name:C.gray("\u2190 Back to main menu"),value:"back"}]})){case "default":return this.runLint();case "configure":return this.configureAndLint();case "rules":return this.showRules();default:return {type:"back"}}}async runLint(e){this.renderer.clearScreen(),this.renderer.showHeader("Linting Schemas...");let{tenantDir:t,sharedDir:r}=await this.findSchemaDirectories();if(!t&&!r)return this.renderer.showStatus("No schema directories found","error"),console.log(""),console.log(" Please specify schema directories or create them in:"),console.log(" - ./src/db/schema/tenant"),console.log(" - ./src/db/schema/shared"),console.log(""),await this.renderer.pressEnterToContinue(),this.show();console.log(` Tenant schemas: ${t??C.dim("not found")}`),console.log(` Shared schemas: ${r??C.dim("not found")}`),console.log("");try{let o=await _e({rules:e}).lintDirectories({tenant:t,shared:r});return this.showResults(o)}catch(a){return this.renderer.showStatus(a.message,"error"),await this.renderer.pressEnterToContinue(),this.show()}}async findSchemaDirectories(){let e={tenant:["./src/db/schema/tenant","./src/schema/tenant","./drizzle/schema/tenant","./db/schema/tenant"],shared:["./src/db/schema/shared","./src/schema/shared","./drizzle/schema/shared","./db/schema/shared"]},t,r;for(let a of e.tenant){let o=resolve(process.cwd(),a);if(existsSync(o)){t=o;break}}for(let a of e.shared){let o=resolve(process.cwd(),a);if(existsSync(o)){r=o;break}}return {tenantDir:t,sharedDir:r}}async configureAndLint(){this.renderer.clearScreen(),this.renderer.showHeader("Configure Lint Rules");let e=await checkbox({message:"Select rules to enable:",choices:Pe.map(a=>({name:`${a.name} - ${a.description}`,value:a.name,checked:a.defaultSeverity!=="off"}))});if(e.length===0)return this.renderer.showStatus("No rules selected","warning"),await this.renderer.pressEnterToContinue(),this.show();let t=await select({message:"Default severity for selected rules:",choices:[{name:"Error (fail on issues)",value:"error"},{name:"Warning (report but pass)",value:"warn"}]}),r={};for(let a of e)r[a]=t;return this.runLint(r)}async showResults(e){this.renderer.clearScreen(),this.renderer.showHeader("Lint Results");let{errors:t,warnings:r,totalTables:a,totalFiles:o}=e.summary;if(t===0&&r===0)console.log(C.green.bold(" \u2713 All schemas passed validation!")),console.log(""),console.log(` Files: ${o}`),console.log(` Tables: ${a}`),console.log(` Issues: ${C.green("0")}`),console.log(""),console.log(C.dim(` Completed in ${e.durationMs}ms`));else {for(let i of e.files)if(i.issues.length!==0){console.log(""),console.log(C.bold(i.filePath));for(let c of i.issues)this.printIssue(c);}console.log(""),console.log(C.bold(" Summary:")),console.log(` Files: ${o}`),console.log(` Tables: ${a}`),console.log(` Errors: ${t>0?C.red(t.toString()):C.green("0")}`),console.log(` Warnings: ${r>0?C.yellow(r.toString()):C.green("0")}`),console.log(""),console.log(C.dim(` Completed in ${e.durationMs}ms`)),console.log(""),e.passed?console.log(C.yellow.bold(" \u26A0 Schema validation passed with warnings")):console.log(C.red.bold(" \u2717 Schema validation failed"));}switch(console.log(""),await select({message:"What would you like to do?",choices:[{name:"Run lint again",value:"again"},{name:"Configure rules and re-run",value:"configure"},{name:"View rule details",value:"rules"},{name:C.gray("\u2190 Back to main menu"),value:"back"}]})){case "again":return this.runLint();case "configure":return this.configureAndLint();case "rules":return this.showRules();default:return {type:"back"}}}printIssue(e){let t=e.severity==="error"?C.red("\u2717"):C.yellow("\u26A0"),r=e.severity==="error"?C.red("error"):C.yellow("warn"),a=C.dim(`(${e.rule})`);if(console.log(` ${t} ${r} ${e.message} ${a}`),e.table){let o=e.column?`${e.table}.${e.column}`:e.table;console.log(C.dim(` ${o}`));}e.suggestion&&console.log(C.cyan(` \u2192 ${e.suggestion}`));}async showRules(){return this.renderer.clearScreen(),this.renderer.showHeader("Available Lint Rules"),console.log(C.bold(" Naming Rules:")),console.log(` ${C.cyan("table-naming")} Enforce table naming convention`),console.log(` ${C.cyan("column-naming")} Enforce column naming convention`),console.log(""),console.log(C.bold(" Convention Rules:")),console.log(` ${C.cyan("require-primary-key")} Require every table to have a PK`),console.log(` ${C.cyan("prefer-uuid-pk")} Prefer UUID over serial for PKs`),console.log(` ${C.cyan("require-timestamps")} Require created_at/updated_at`),console.log(` ${C.cyan("index-foreign-keys")} Require indexes on FK columns`),console.log(""),console.log(C.bold(" Security Rules:")),console.log(` ${C.cyan("no-cascade-delete")} Warn about CASCADE DELETE`),console.log(` ${C.cyan("require-soft-delete")} Require soft delete column`),console.log(""),console.log(C.dim(" Configure rules in tenant.config.ts:")),console.log(C.dim(" lint: {")),console.log(C.dim(" rules: {")),console.log(C.dim(" 'table-naming': ['error', { style: 'snake_case' }],")),console.log(C.dim(" 'require-primary-key': 'error',")),console.log(C.dim(" }")),console.log(C.dim(" }")),console.log(""),await this.renderer.pressEnterToContinue(),this.show()}};var ee={maxPools:50,poolTtlMs:36e5,cleanupIntervalMs:6e4,poolConfig:{max:10,idleTimeoutMillis:3e4,connectionTimeoutMillis:5e3},retry:{maxAttempts:3,initialDelayMs:100,maxDelayMs:5e3,backoffMultiplier:2,jitter:true}};var le="[drizzle-multitenant]";var sn=class{enabled;logQueries;logPoolEvents;slowQueryThreshold;logger;constructor(e){this.enabled=e?.enabled??false,this.logQueries=e?.logQueries??true,this.logPoolEvents=e?.logPoolEvents??true,this.slowQueryThreshold=e?.slowQueryThreshold??1e3,this.logger=e?.logger??this.defaultLogger;}isEnabled(){return this.enabled}logQuery(e,t,r){if(!this.enabled||!this.logQueries)return;let a=r>=this.slowQueryThreshold,s={type:a?"slow_query":"query",tenantId:e,query:this.truncateQuery(t),durationMs:r};a?this.logger(`${le} tenant=${e} SLOW_QUERY duration=${r}ms query="${this.truncateQuery(t)}"`,s):this.logger(`${le} tenant=${e} query="${this.truncateQuery(t)}" duration=${r}ms`,s);}logPoolCreated(e,t){if(!this.enabled||!this.logPoolEvents)return;let r={type:"pool_created",tenantId:e,schemaName:t};this.logger(`${le} tenant=${e} POOL_CREATED schema=${t}`,r);}logPoolEvicted(e,t,r){if(!this.enabled||!this.logPoolEvents)return;let a={type:"pool_evicted",tenantId:e,schemaName:t,metadata:r?{reason:r}:void 0},o=r?` reason=${r}`:"";this.logger(`${le} tenant=${e} POOL_EVICTED schema=${t}${o}`,a);}logPoolError(e,t){if(!this.enabled||!this.logPoolEvents)return;let r={type:"pool_error",tenantId:e,error:t.message};this.logger(`${le} tenant=${e} POOL_ERROR error="${t.message}"`,r);}logWarmup(e,t,r,a){if(!this.enabled||!this.logPoolEvents)return;let o={type:"warmup",tenantId:e,durationMs:r,metadata:{success:t,alreadyWarm:a}},s=a?"already_warm":t?"success":"failed";this.logger(`${le} tenant=${e} WARMUP status=${s} duration=${r}ms`,o);}logConnectionRetry(e,t,r,a,o){if(!this.enabled||!this.logPoolEvents)return;let s={type:"connection_retry",tenantId:e,error:a.message,metadata:{attempt:t,maxAttempts:r,delayMs:o}};this.logger(`${le} tenant=${e} CONNECTION_RETRY attempt=${t}/${r} delay=${o}ms error="${a.message}"`,s);}logConnectionSuccess(e,t,r){if(!this.enabled||!this.logPoolEvents)return;let a={type:"pool_created",tenantId:e,durationMs:r,metadata:{attempts:t}};t>1&&this.logger(`${le} tenant=${e} CONNECTION_SUCCESS attempts=${t} totalTime=${r}ms`,a);}log(e,t){this.enabled&&this.logger(`${le} ${e}`,t);}defaultLogger(e,t){console.log(e);}truncateQuery(e,t=100){let r=e.replace(/\s+/g," ").trim();return r.length<=t?r:r.substring(0,t-3)+"..."}};function Br(n){return new sn(n)}var ze=class{cache;poolTtlMs;onDispose;constructor(e){this.poolTtlMs=e.poolTtlMs,this.onDispose=e.onDispose,this.cache=new LRUCache({max:e.maxPools,dispose:(t,r)=>{this.handleDispose(r,t);},noDisposeOnSet:true});}get(e){return this.cache.get(e)}set(e,t){this.cache.set(e,t);}has(e){return this.cache.has(e)}delete(e){return this.cache.delete(e)}size(){return this.cache.size}keys(){return Array.from(this.cache.keys())}*entries(){for(let[e,t]of this.cache.entries())yield [e,t];}async clear(){this.cache.clear(),await Promise.resolve();}evictLRU(){let e=Array.from(this.cache.keys());if(e.length===0)return;let t=e[e.length-1];return this.cache.delete(t),t}async evictExpired(){if(!this.poolTtlMs)return [];let e=Date.now(),t=[];for(let[r,a]of this.cache.entries())e-a.lastAccess>this.poolTtlMs&&t.push(r);for(let r of t)this.cache.delete(r);return t}touch(e){let t=this.cache.get(e);t&&(t.lastAccess=Date.now());}getMaxPools(){return this.cache.max}getTtlMs(){return this.poolTtlMs}isExpired(e){return this.poolTtlMs?Date.now()-e.lastAccess>this.poolTtlMs:false}async handleDispose(e,t){this.onDispose&&await this.onDispose(e,t);}};function cn(n){let e=n.message.toLowerCase();return !!(e.includes("econnrefused")||e.includes("econnreset")||e.includes("etimedout")||e.includes("enotfound")||e.includes("connection refused")||e.includes("connection reset")||e.includes("connection terminated")||e.includes("connection timed out")||e.includes("timeout expired")||e.includes("socket hang up")||e.includes("too many connections")||e.includes("sorry, too many clients")||e.includes("the database system is starting up")||e.includes("the database system is shutting down")||e.includes("server closed the connection unexpectedly")||e.includes("could not connect to server")||e.includes("ssl connection")||e.includes("ssl handshake"))}function Us(n){return new Promise(e=>setTimeout(e,n))}var Ne=class{config;constructor(e){this.config={maxAttempts:e?.maxAttempts??ee.retry.maxAttempts,initialDelayMs:e?.initialDelayMs??ee.retry.initialDelayMs,maxDelayMs:e?.maxDelayMs??ee.retry.maxDelayMs,backoffMultiplier:e?.backoffMultiplier??ee.retry.backoffMultiplier,jitter:e?.jitter??ee.retry.jitter,isRetryable:e?.isRetryable??cn,onRetry:e?.onRetry};}async withRetry(e,t){let r=t?{...this.config,...t}:this.config,a=Date.now(),o=null;for(let s=0;s<r.maxAttempts;s++)try{return {result:await e(),attempts:s+1,totalTimeMs:Date.now()-a}}catch(i){o=i;let c=s>=r.maxAttempts-1,l=r.isRetryable??this.isRetryable;if(c||!l(o))throw o;let m=this.calculateDelay(s,r);r.onRetry?.(s+1,o,m),await Us(m);}throw o??new Error("Retry failed with no error")}calculateDelay(e,t){let r=t?{...this.config,...t}:this.config,a=r.initialDelayMs*Math.pow(r.backoffMultiplier,e),o=Math.min(a,r.maxDelayMs);if(r.jitter){let s=1+Math.random()*.25;return Math.floor(o*s)}return Math.floor(o)}isRetryable(e){return (this.config.isRetryable??cn)(e)}getConfig(){return {...this.config}}getMaxAttempts(){return this.config.maxAttempts}};var Ie=class{constructor(e){this.deps=e;}async checkHealth(e={}){let t=Date.now(),{ping:r=true,pingTimeoutMs:a=5e3,includeShared:o=true,tenantIds:s}=e,i=[],c="ok",l,m,p=this.getPoolsToCheck(s).map(async({schemaName:x,tenantId:S,entry:M})=>this.checkPoolHealth(S,x,M,r,a));i.push(...await Promise.all(p));let h=this.deps.getSharedPool();if(o&&h){let x=await this.checkSharedDbHealth(h,r,a);c=x.status,l=x.responseTimeMs,m=x.error;}let g=i.filter(x=>x.status==="degraded").length,w=i.filter(x=>x.status==="unhealthy").length,b={healthy:w===0&&c!=="unhealthy",pools:i,sharedDb:c,totalPools:i.length,degradedPools:g,unhealthyPools:w,timestamp:new Date().toISOString(),durationMs:Date.now()-t};return l!==void 0&&(b.sharedDbResponseTimeMs=l),m!==void 0&&(b.sharedDbError=m),b}getPoolsToCheck(e){let t=[];if(e&&e.length>0)for(let r of e){let a=this.deps.getSchemaName(r),o=this.deps.getPoolEntry(a);o&&t.push({schemaName:a,tenantId:r,entry:o});}else for(let[r,a]of this.deps.getPoolEntries()){let o=this.deps.getTenantIdBySchema(r)??r;t.push({schemaName:r,tenantId:o,entry:a});}return t}async checkPoolHealth(e,t,r,a,o){let s=r.pool,i=s.totalCount,c=s.idleCount,l=s.waitingCount,m="ok",u,p;if(l>0&&(m="degraded"),a){let g=await this.executePingQuery(s,o);u=g.responseTimeMs,g.success?g.responseTimeMs&&g.responseTimeMs>o/2&&m==="ok"&&(m="degraded"):(m="unhealthy",p=g.error);}let h={tenantId:e,schemaName:t,status:m,totalConnections:i,idleConnections:c,waitingRequests:l};return u!==void 0&&(h.responseTimeMs=u),p!==void 0&&(h.error=p),h}async checkSharedDbHealth(e,t,r){let a="ok",o,s;if(e.waitingCount>0&&(a="degraded"),t){let l=await this.executePingQuery(e,r);o=l.responseTimeMs,l.success?l.responseTimeMs&&l.responseTimeMs>r/2&&a==="ok"&&(a="degraded"):(a="unhealthy",s=l.error);}let c={status:a};return o!==void 0&&(c.responseTimeMs=o),s!==void 0&&(c.error=s),c}async executePingQuery(e,t){let r=Date.now();try{let a=new Promise((s,i)=>{setTimeout(()=>i(new Error("Health check ping timeout")),t);}),o=e.query("SELECT 1");return await Promise.race([o,a]),{success:!0,responseTimeMs:Date.now()-r}}catch(a){return {success:false,responseTimeMs:Date.now()-r,error:a.message}}}determineOverallHealth(e,t="ok"){return e.filter(a=>a.status==="unhealthy").length===0&&t!=="unhealthy"}};var xe=class{constructor(e){this.config=e;let t=e.isolation.maxPools??ee.maxPools,r=e.isolation.poolTtlMs??ee.poolTtlMs;this.debugLogger=Br(e.debug),this.retryHandler=new Ne(e.connection.retry),this.poolCache=new ze({maxPools:t,poolTtlMs:r,onDispose:(a,o)=>{this.disposePoolEntry(o,a);}}),this.healthChecker=new Ie({getPoolEntries:()=>this.poolCache.entries(),getTenantIdBySchema:a=>this.tenantIdBySchema.get(a),getPoolEntry:a=>this.poolCache.get(a),getSchemaName:a=>this.config.isolation.schemaNameTemplate(a),getSharedPool:()=>this.sharedPool});}poolCache;tenantIdBySchema=new Map;pendingConnections=new Map;sharedPool=null;sharedDb=null;sharedDbPending=null;cleanupInterval=null;disposed=false;debugLogger;retryHandler;healthChecker;getDb(e){this.ensureNotDisposed();let t=this.config.isolation.schemaNameTemplate(e),r=this.poolCache.get(t);return r||(r=this.createPoolEntry(e,t),this.poolCache.set(t,r),this.tenantIdBySchema.set(t,e),this.debugLogger.logPoolCreated(e,t),this.config.hooks?.onPoolCreated?.(e)),this.poolCache.touch(t),r.db}async getDbAsync(e){this.ensureNotDisposed();let t=this.config.isolation.schemaNameTemplate(e),r=this.poolCache.get(t);if(r)return this.poolCache.touch(t),r.db;let a=this.pendingConnections.get(t);if(a)return r=await a,this.poolCache.touch(t),r.db;let o=this.connectWithRetry(e,t);this.pendingConnections.set(t,o);try{return r=await o,this.poolCache.set(t,r),this.tenantIdBySchema.set(t,e),this.debugLogger.logPoolCreated(e,t),this.config.hooks?.onPoolCreated?.(e),this.poolCache.touch(t),r.db}finally{this.pendingConnections.delete(t);}}async connectWithRetry(e,t){let r=this.retryHandler.getConfig(),a=r.maxAttempts,o=await this.retryHandler.withRetry(async()=>{let s=this.createPoolEntry(e,t);try{return await s.pool.query("SELECT 1"),s}catch(i){try{await s.pool.end();}catch{}throw i}},{onRetry:(s,i,c)=>{this.debugLogger.logConnectionRetry(e,s,a,i,c),r.onRetry?.(s,i,c);}});return this.debugLogger.logConnectionSuccess(e,o.attempts,o.totalTimeMs),o.result}getSharedDb(){return this.ensureNotDisposed(),this.sharedDb||(this.sharedPool=new Pool({connectionString:this.config.connection.url,...ee.poolConfig,...this.config.connection.poolConfig}),this.sharedPool.on("error",e=>{this.config.hooks?.onError?.("shared",e);}),this.sharedDb=drizzle(this.sharedPool,{schema:this.config.schemas.shared})),this.sharedDb}async getSharedDbAsync(){if(this.ensureNotDisposed(),this.sharedDb)return this.sharedDb;if(this.sharedDbPending)return this.sharedDbPending;this.sharedDbPending=this.connectSharedWithRetry();try{return await this.sharedDbPending}finally{this.sharedDbPending=null;}}async connectSharedWithRetry(){let e=this.retryHandler.getConfig(),t=e.maxAttempts,r=await this.retryHandler.withRetry(async()=>{let a=new Pool({connectionString:this.config.connection.url,...ee.poolConfig,...this.config.connection.poolConfig});try{return await a.query("SELECT 1"),a.on("error",o=>{this.config.hooks?.onError?.("shared",o);}),this.sharedPool=a,this.sharedDb=drizzle(a,{schema:this.config.schemas.shared}),this.sharedDb}catch(o){try{await a.end();}catch{}throw o}},{onRetry:(a,o,s)=>{this.debugLogger.logConnectionRetry("shared",a,t,o,s),e.onRetry?.(a,o,s);}});return this.debugLogger.logConnectionSuccess("shared",r.attempts,r.totalTimeMs),r.result}getSchemaName(e){return this.config.isolation.schemaNameTemplate(e)}hasPool(e){let t=this.config.isolation.schemaNameTemplate(e);return this.poolCache.has(t)}getPoolCount(){return this.poolCache.size()}getActiveTenantIds(){return Array.from(this.tenantIdBySchema.values())}getRetryConfig(){return this.retryHandler.getConfig()}async warmup(e,t={}){this.ensureNotDisposed();let r=Date.now(),{concurrency:a=10,ping:o=true,onProgress:s}=t,i=[];for(let c=0;c<e.length;c+=a){let l=e.slice(c,c+a),m=await Promise.all(l.map(async u=>{let p=Date.now();s?.(u,"starting");try{let h=this.hasPool(u);o?await this.getDbAsync(u):this.getDb(u);let g=Date.now()-p;return s?.(u,"completed"),this.debugLogger.logWarmup(u,!0,g,h),{tenantId:u,success:!0,alreadyWarm:h,durationMs:g}}catch(h){let g=Date.now()-p;return s?.(u,"failed"),this.debugLogger.logWarmup(u,false,g,false),{tenantId:u,success:false,alreadyWarm:false,durationMs:g,error:h.message}}}));i.push(...m);}return {total:i.length,succeeded:i.filter(c=>c.success).length,failed:i.filter(c=>!c.success).length,alreadyWarm:i.filter(c=>c.alreadyWarm).length,durationMs:Date.now()-r,details:i}}getMetrics(){this.ensureNotDisposed();let e=this.config.isolation.maxPools??ee.maxPools,t=[];for(let[r,a]of this.poolCache.entries()){let o=this.tenantIdBySchema.get(r)??r,s=a.pool;t.push({tenantId:o,schemaName:r,connections:{total:s.totalCount,idle:s.idleCount,waiting:s.waitingCount},lastAccessedAt:new Date(a.lastAccess).toISOString()});}return {pools:{total:t.length,maxPools:e,tenants:t},shared:{initialized:this.sharedPool!==null,connections:this.sharedPool?{total:this.sharedPool.totalCount,idle:this.sharedPool.idleCount,waiting:this.sharedPool.waitingCount}:null},timestamp:new Date().toISOString()}}async healthCheck(e={}){return this.ensureNotDisposed(),this.healthChecker.checkHealth(e)}async evictPool(e,t="manual"){let r=this.config.isolation.schemaNameTemplate(e),a=this.poolCache.get(r);a&&(this.debugLogger.logPoolEvicted(e,r,t),this.poolCache.delete(r),this.tenantIdBySchema.delete(r),await this.closePool(a.pool,e));}startCleanup(){if(this.cleanupInterval)return;let e=ee.cleanupIntervalMs;this.cleanupInterval=setInterval(()=>{this.cleanupIdlePools();},e),this.cleanupInterval.unref();}stopCleanup(){this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null);}async dispose(){if(this.disposed)return;this.disposed=true,this.stopCleanup();let e=[];for(let[t,r]of this.poolCache.entries()){let a=this.tenantIdBySchema.get(t);e.push(this.closePool(r.pool,a??t));}await this.poolCache.clear(),this.tenantIdBySchema.clear(),this.sharedPool&&(e.push(this.closePool(this.sharedPool,"shared")),this.sharedPool=null,this.sharedDb=null),await Promise.all(e);}createPoolEntry(e,t){let r=new Pool({connectionString:this.config.connection.url,...ee.poolConfig,...this.config.connection.poolConfig,options:`-c search_path=${t},public`});return r.on("error",async o=>{this.debugLogger.logPoolError(e,o),this.config.hooks?.onError?.(e,o),await this.evictPool(e,"error");}),{db:drizzle(r,{schema:this.config.schemas.tenant}),pool:r,lastAccess:Date.now(),schemaName:t}}disposePoolEntry(e,t){let r=this.tenantIdBySchema.get(t);this.tenantIdBySchema.delete(t),r&&this.debugLogger.logPoolEvicted(r,t,"lru_eviction"),this.closePool(e.pool,r??t).then(()=>{r&&this.config.hooks?.onPoolEvicted?.(r);});}async closePool(e,t){try{await e.end();}catch(r){this.config.hooks?.onError?.(t,r);}}async cleanupIdlePools(){let e=await this.poolCache.evictExpired();for(let t of e){let r=this.tenantIdBySchema.get(t);r&&(this.debugLogger.logPoolEvicted(r,t,"ttl_expired"),this.tenantIdBySchema.delete(t));}}ensureNotDisposed(){if(this.disposed)throw new Error("[drizzle-multitenant] TenantManager has been disposed")}};var we=class{constructor(e){this.tenantManager=e;}async collect(e={}){let{includeHealth:t=false,healthPingTimeoutMs:r=5e3,tenantIds:a}=e,o=this.tenantManager.getMetrics(),s;return t&&(s=await this.tenantManager.healthCheck({ping:true,pingTimeoutMs:r,includeShared:true,...a&&{tenantIds:a}})),{pools:o,health:s,collectedAt:new Date().toISOString()}}getPoolMetrics(){return this.tenantManager.getMetrics()}async getHealthMetrics(e){return this.tenantManager.healthCheck({ping:true,pingTimeoutMs:e?.pingTimeoutMs??5e3,includeShared:true,...e?.tenantIds&&{tenantIds:e.tenantIds}})}getRuntimeMetrics(){let e=process.memoryUsage();return {uptimeSeconds:process.uptime(),memoryUsage:{heapTotal:e.heapTotal,heapUsed:e.heapUsed,external:e.external,rss:e.rss},activeHandles:process._getActiveHandles?.()?.length??0,activeRequests:process._getActiveRequests?.()?.length??0}}calculateSummary(e){let t=e.pools,r=e.health,a=0,o=0,s=0;for(let m of t.pools.tenants)a+=m.connections.total,o+=m.connections.idle,s+=m.connections.waiting;t.shared.connections&&(a+=t.shared.connections.total,o+=t.shared.connections.idle,s+=t.shared.connections.waiting);let i=r?r.pools.filter(m=>m.status==="ok").length:t.pools.total,c=r?.degradedPools??0,l=r?.unhealthyPools??0;return {activePools:t.pools.total,totalConnections:a,idleConnections:o,waitingRequests:s,healthyPools:i,degradedPools:c,unhealthyPools:l}}};var Ws="drizzle_multitenant",Hs=[{name:"pools_active",help:"Number of active database pools",type:"gauge",extract:n=>[{value:n.pools.pools.total}]},{name:"pools_max",help:"Maximum number of pools allowed",type:"gauge",extract:n=>[{value:n.pools.pools.maxPools}]},{name:"pool_connections_total",help:"Total connections in pool",type:"gauge",labels:["tenant","schema"],extract:n=>n.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:e.connections.total}))},{name:"pool_connections_idle",help:"Idle connections in pool",type:"gauge",labels:["tenant","schema"],extract:n=>n.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:e.connections.idle}))},{name:"pool_connections_waiting",help:"Waiting requests in pool queue",type:"gauge",labels:["tenant","schema"],extract:n=>n.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:e.connections.waiting}))},{name:"pool_last_access_timestamp",help:"Last access timestamp for pool (unix epoch)",type:"gauge",labels:["tenant","schema"],extract:n=>n.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:new Date(e.lastAccessedAt).getTime()/1e3}))},{name:"shared_pool_initialized",help:"Whether shared database pool is initialized (1=yes, 0=no)",type:"gauge",extract:n=>[{value:n.pools.shared.initialized?1:0}]},{name:"shared_pool_connections_total",help:"Total connections in shared pool",type:"gauge",extract:n=>[{value:n.pools.shared.connections?.total??0}]},{name:"shared_pool_connections_idle",help:"Idle connections in shared pool",type:"gauge",extract:n=>[{value:n.pools.shared.connections?.idle??0}]},{name:"shared_pool_connections_waiting",help:"Waiting requests in shared pool queue",type:"gauge",extract:n=>[{value:n.pools.shared.connections?.waiting??0}]},{name:"health_status",help:"Overall health status (1=healthy, 0=unhealthy)",type:"gauge",extract:n=>n.health?[{value:n.health.healthy?1:0}]:[]},{name:"health_pools_total",help:"Total number of pools checked",type:"gauge",extract:n=>n.health?[{value:n.health.totalPools}]:[]},{name:"health_pools_degraded",help:"Number of degraded pools",type:"gauge",extract:n=>n.health?[{value:n.health.degradedPools}]:[]},{name:"health_pools_unhealthy",help:"Number of unhealthy pools",type:"gauge",extract:n=>n.health?[{value:n.health.unhealthyPools}]:[]},{name:"health_check_duration_seconds",help:"Duration of health check in seconds",type:"gauge",extract:n=>n.health?[{value:n.health.durationMs/1e3}]:[]},{name:"pool_health_status",help:"Health status per pool (1=ok, 0.5=degraded, 0=unhealthy)",type:"gauge",labels:["tenant","schema","status"],extract:n=>n.health?.pools.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName,status:e.status},value:e.status==="ok"?1:e.status==="degraded"?.5:0}))??[]},{name:"pool_response_time_seconds",help:"Pool ping response time in seconds",type:"gauge",labels:["tenant","schema"],extract:n=>n.health?.pools.filter(e=>e.responseTimeMs!==void 0).map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:(e.responseTimeMs??0)/1e3}))??[]},{name:"shared_db_health_status",help:"Shared database health status (1=ok, 0.5=degraded, 0=unhealthy)",type:"gauge",extract:n=>{if(!n.health)return [];let e=n.health.sharedDb;return [{value:e==="ok"?1:e==="degraded"?.5:0}]}},{name:"shared_db_response_time_seconds",help:"Shared database ping response time in seconds",type:"gauge",extract:n=>n.health?.sharedDbResponseTimeMs!==void 0?[{value:n.health.sharedDbResponseTimeMs/1e3}]:[]}],Bs=[{name:"process_uptime_seconds",help:"Process uptime in seconds",type:"gauge",extract:n=>[{value:n.uptimeSeconds}]},{name:"process_heap_bytes_total",help:"Total heap memory in bytes",type:"gauge",extract:n=>[{value:n.memoryUsage.heapTotal}]},{name:"process_heap_bytes_used",help:"Used heap memory in bytes",type:"gauge",extract:n=>[{value:n.memoryUsage.heapUsed}]},{name:"process_external_bytes",help:"External memory in bytes",type:"gauge",extract:n=>[{value:n.memoryUsage.external}]},{name:"process_rss_bytes",help:"Resident Set Size in bytes",type:"gauge",extract:n=>[{value:n.memoryUsage.rss}]},{name:"process_active_handles",help:"Number of active handles",type:"gauge",extract:n=>[{value:n.activeHandles}]},{name:"process_active_requests",help:"Number of active requests",type:"gauge",extract:n=>[{value:n.activeRequests}]}],$e=class{prefix;includeTenantLabels;includeTimestamps;defaultLabels;constructor(e={}){this.prefix=e.prefix??Ws,this.includeTenantLabels=e.includeTenantLabels??true,this.includeTimestamps=e.includeTimestamps??false,this.defaultLabels=e.defaultLabels??{};}export(e,t){let r=this.toPrometheusMetrics(e),a=[];for(let o of r)a.push(...this.formatMetric(o));if(t){let o=this.extractRuntimeMetrics(t);for(let s of o)a.push(...this.formatMetric(s));}return a.join(`
|
|
1734
|
+
${se("\u2192 "+n.suggestion,"cyan",e)}`),o}function Ls(n,e){let t=e.colors!==false,r=[];for(let a of n.files)if(!(a.issues.length===0&&!e.verbose)){if(r.push(""),r.push(se(a.filePath,"bold",t)),a.issues.length===0){r.push(se(" No issues found","green",t));continue}for(let o of a.issues)r.push(Os(o,t));}if(!e.quiet){r.push("");let{errors:a,warnings:o,totalTables:s}=n.summary;if(a===0&&o===0)r.push(se(`\u2713 ${s} tables validated, no issues found`,"green",t));else {let i=[];i.push(`${s} tables validated`),o>0&&i.push(se(`${o} warning${o>1?"s":""}`,"yellow",t)),a>0&&i.push(se(`${a} error${a>1?"s":""}`,"red",t)),r.push(i.join(", "));}r.push(se(`Completed in ${n.durationMs}ms`,"dim",t));}return r.join(`
|
|
1735
|
+
`)}function js(n){return JSON.stringify({passed:n.passed,summary:n.summary,files:n.files.map(e=>({filePath:e.filePath,tables:e.tables,columns:e.columns,issues:e.issues})),durationMs:n.durationMs},null,2)}function qs(n){let e=[];for(let o of n.files)for(let s of o.issues){let i=s.severity==="error"?"error":"warning",c=`[${s.rule}]`,l=s.filePath,m=s.line??1,u=`::${i} file=${l},line=${m},title=${c}::${s.message}`;s.suggestion&&(u+=` (${s.suggestion})`),e.push(u);}let{errors:t,warnings:r,totalTables:a}=n.summary;return t>0||r>0?e.push(`::notice::Schema lint: ${a} tables, ${t} errors, ${r} warnings`):e.push(`::notice::Schema lint: ${a} tables validated, no issues`),e.join(`
|
|
1736
|
+
`)}function dt(n,e){switch(e.format){case "json":return js(n);case "github":return qs(n);default:return Ls(n,e)}}var ze=class{renderer;ctx;constructor(e,t){this.ctx=e,this.renderer=t||new V;}async show(){switch(this.renderer.clearScreen(),this.renderer.showHeader("Schema Lint"),await select({message:"What would you like to do?",choices:[{name:"Lint schemas with default rules",value:"default"},{name:"Configure rules and lint",value:"configure"},{name:"View available rules",value:"rules"},{name:M.gray("\u2190 Back to main menu"),value:"back"}]})){case "default":return this.runLint();case "configure":return this.configureAndLint();case "rules":return this.showRules();default:return {type:"back"}}}async runLint(e){this.renderer.clearScreen(),this.renderer.showHeader("Linting Schemas...");let{tenantDir:t,sharedDir:r}=await this.findSchemaDirectories();if(!t&&!r)return this.renderer.showStatus("No schema directories found","error"),console.log(""),console.log(" Please specify schema directories or create them in:"),console.log(" - ./src/db/schema/tenant"),console.log(" - ./src/db/schema/shared"),console.log(""),await this.renderer.pressEnterToContinue(),this.show();console.log(` Tenant schemas: ${t??M.dim("not found")}`),console.log(` Shared schemas: ${r??M.dim("not found")}`),console.log("");try{let o=await Pe({rules:e}).lintDirectories({tenant:t,shared:r});return this.showResults(o)}catch(a){return this.renderer.showStatus(a.message,"error"),await this.renderer.pressEnterToContinue(),this.show()}}async findSchemaDirectories(){let e={tenant:["./src/db/schema/tenant","./src/schema/tenant","./drizzle/schema/tenant","./db/schema/tenant"],shared:["./src/db/schema/shared","./src/schema/shared","./drizzle/schema/shared","./db/schema/shared"]},t,r;for(let a of e.tenant){let o=resolve(process.cwd(),a);if(existsSync(o)){t=o;break}}for(let a of e.shared){let o=resolve(process.cwd(),a);if(existsSync(o)){r=o;break}}return {tenantDir:t,sharedDir:r}}async configureAndLint(){this.renderer.clearScreen(),this.renderer.showHeader("Configure Lint Rules");let e=await checkbox({message:"Select rules to enable:",choices:_e.map(a=>({name:`${a.name} - ${a.description}`,value:a.name,checked:a.defaultSeverity!=="off"}))});if(e.length===0)return this.renderer.showStatus("No rules selected","warning"),await this.renderer.pressEnterToContinue(),this.show();let t=await select({message:"Default severity for selected rules:",choices:[{name:"Error (fail on issues)",value:"error"},{name:"Warning (report but pass)",value:"warn"}]}),r={};for(let a of e)r[a]=t;return this.runLint(r)}async showResults(e){this.renderer.clearScreen(),this.renderer.showHeader("Lint Results");let{errors:t,warnings:r,totalTables:a,totalFiles:o}=e.summary;if(t===0&&r===0)console.log(M.green.bold(" \u2713 All schemas passed validation!")),console.log(""),console.log(` Files: ${o}`),console.log(` Tables: ${a}`),console.log(` Issues: ${M.green("0")}`),console.log(""),console.log(M.dim(` Completed in ${e.durationMs}ms`));else {for(let i of e.files)if(i.issues.length!==0){console.log(""),console.log(M.bold(i.filePath));for(let c of i.issues)this.printIssue(c);}console.log(""),console.log(M.bold(" Summary:")),console.log(` Files: ${o}`),console.log(` Tables: ${a}`),console.log(` Errors: ${t>0?M.red(t.toString()):M.green("0")}`),console.log(` Warnings: ${r>0?M.yellow(r.toString()):M.green("0")}`),console.log(""),console.log(M.dim(` Completed in ${e.durationMs}ms`)),console.log(""),e.passed?console.log(M.yellow.bold(" \u26A0 Schema validation passed with warnings")):console.log(M.red.bold(" \u2717 Schema validation failed"));}switch(console.log(""),await select({message:"What would you like to do?",choices:[{name:"Run lint again",value:"again"},{name:"Configure rules and re-run",value:"configure"},{name:"View rule details",value:"rules"},{name:M.gray("\u2190 Back to main menu"),value:"back"}]})){case "again":return this.runLint();case "configure":return this.configureAndLint();case "rules":return this.showRules();default:return {type:"back"}}}printIssue(e){let t=e.severity==="error"?M.red("\u2717"):M.yellow("\u26A0"),r=e.severity==="error"?M.red("error"):M.yellow("warn"),a=M.dim(`(${e.rule})`);if(console.log(` ${t} ${r} ${e.message} ${a}`),e.table){let o=e.column?`${e.table}.${e.column}`:e.table;console.log(M.dim(` ${o}`));}e.suggestion&&console.log(M.cyan(` \u2192 ${e.suggestion}`));}async showRules(){return this.renderer.clearScreen(),this.renderer.showHeader("Available Lint Rules"),console.log(M.bold(" Naming Rules:")),console.log(` ${M.cyan("table-naming")} Enforce table naming convention`),console.log(` ${M.cyan("column-naming")} Enforce column naming convention`),console.log(""),console.log(M.bold(" Convention Rules:")),console.log(` ${M.cyan("require-primary-key")} Require every table to have a PK`),console.log(` ${M.cyan("prefer-uuid-pk")} Prefer UUID over serial for PKs`),console.log(` ${M.cyan("require-timestamps")} Require created_at/updated_at`),console.log(` ${M.cyan("index-foreign-keys")} Require indexes on FK columns`),console.log(""),console.log(M.bold(" Security Rules:")),console.log(` ${M.cyan("no-cascade-delete")} Warn about CASCADE DELETE`),console.log(` ${M.cyan("require-soft-delete")} Require soft delete column`),console.log(""),console.log(M.dim(" Configure rules in tenant.config.ts:")),console.log(M.dim(" lint: {")),console.log(M.dim(" rules: {")),console.log(M.dim(" 'table-naming': ['error', { style: 'snake_case' }],")),console.log(M.dim(" 'require-primary-key': 'error',")),console.log(M.dim(" }")),console.log(M.dim(" }")),console.log(""),await this.renderer.pressEnterToContinue(),this.show()}};var ee={maxPools:50,poolTtlMs:36e5,cleanupIntervalMs:6e4,poolConfig:{max:10,idleTimeoutMillis:3e4,connectionTimeoutMillis:5e3},retry:{maxAttempts:3,initialDelayMs:100,maxDelayMs:5e3,backoffMultiplier:2,jitter:true}};var le="[drizzle-multitenant]";var sn=class{enabled;logQueries;logPoolEvents;slowQueryThreshold;logger;constructor(e){this.enabled=e?.enabled??false,this.logQueries=e?.logQueries??true,this.logPoolEvents=e?.logPoolEvents??true,this.slowQueryThreshold=e?.slowQueryThreshold??1e3,this.logger=e?.logger??this.defaultLogger;}isEnabled(){return this.enabled}logQuery(e,t,r){if(!this.enabled||!this.logQueries)return;let a=r>=this.slowQueryThreshold,s={type:a?"slow_query":"query",tenantId:e,query:this.truncateQuery(t),durationMs:r};a?this.logger(`${le} tenant=${e} SLOW_QUERY duration=${r}ms query="${this.truncateQuery(t)}"`,s):this.logger(`${le} tenant=${e} query="${this.truncateQuery(t)}" duration=${r}ms`,s);}logPoolCreated(e,t){if(!this.enabled||!this.logPoolEvents)return;let r={type:"pool_created",tenantId:e,schemaName:t};this.logger(`${le} tenant=${e} POOL_CREATED schema=${t}`,r);}logPoolEvicted(e,t,r){if(!this.enabled||!this.logPoolEvents)return;let a={type:"pool_evicted",tenantId:e,schemaName:t,metadata:r?{reason:r}:void 0},o=r?` reason=${r}`:"";this.logger(`${le} tenant=${e} POOL_EVICTED schema=${t}${o}`,a);}logPoolError(e,t){if(!this.enabled||!this.logPoolEvents)return;let r={type:"pool_error",tenantId:e,error:t.message};this.logger(`${le} tenant=${e} POOL_ERROR error="${t.message}"`,r);}logWarmup(e,t,r,a){if(!this.enabled||!this.logPoolEvents)return;let o={type:"warmup",tenantId:e,durationMs:r,metadata:{success:t,alreadyWarm:a}},s=a?"already_warm":t?"success":"failed";this.logger(`${le} tenant=${e} WARMUP status=${s} duration=${r}ms`,o);}logConnectionRetry(e,t,r,a,o){if(!this.enabled||!this.logPoolEvents)return;let s={type:"connection_retry",tenantId:e,error:a.message,metadata:{attempt:t,maxAttempts:r,delayMs:o}};this.logger(`${le} tenant=${e} CONNECTION_RETRY attempt=${t}/${r} delay=${o}ms error="${a.message}"`,s);}logConnectionSuccess(e,t,r){if(!this.enabled||!this.logPoolEvents)return;let a={type:"pool_created",tenantId:e,durationMs:r,metadata:{attempts:t}};t>1&&this.logger(`${le} tenant=${e} CONNECTION_SUCCESS attempts=${t} totalTime=${r}ms`,a);}log(e,t){this.enabled&&this.logger(`${le} ${e}`,t);}defaultLogger(e,t){console.log(e);}truncateQuery(e,t=100){let r=e.replace(/\s+/g," ").trim();return r.length<=t?r:r.substring(0,t-3)+"..."}};function Kr(n){return new sn(n)}var ke=class{cache;poolTtlMs;onDispose;constructor(e){this.poolTtlMs=e.poolTtlMs,this.onDispose=e.onDispose,this.cache=new LRUCache({max:e.maxPools,dispose:(t,r)=>{this.handleDispose(r,t);},noDisposeOnSet:true});}get(e){return this.cache.get(e)}set(e,t){this.cache.set(e,t);}has(e){return this.cache.has(e)}delete(e){return this.cache.delete(e)}size(){return this.cache.size}keys(){return Array.from(this.cache.keys())}*entries(){for(let[e,t]of this.cache.entries())yield [e,t];}async clear(){this.cache.clear(),await Promise.resolve();}evictLRU(){let e=Array.from(this.cache.keys());if(e.length===0)return;let t=e[e.length-1];return this.cache.delete(t),t}async evictExpired(){if(!this.poolTtlMs)return [];let e=Date.now(),t=[];for(let[r,a]of this.cache.entries())e-a.lastAccess>this.poolTtlMs&&t.push(r);for(let r of t)this.cache.delete(r);return t}touch(e){let t=this.cache.get(e);t&&(t.lastAccess=Date.now());}getMaxPools(){return this.cache.max}getTtlMs(){return this.poolTtlMs}isExpired(e){return this.poolTtlMs?Date.now()-e.lastAccess>this.poolTtlMs:false}async handleDispose(e,t){this.onDispose&&await this.onDispose(e,t);}};function cn(n){let e=n.message.toLowerCase();return !!(e.includes("econnrefused")||e.includes("econnreset")||e.includes("etimedout")||e.includes("enotfound")||e.includes("connection refused")||e.includes("connection reset")||e.includes("connection terminated")||e.includes("connection timed out")||e.includes("timeout expired")||e.includes("socket hang up")||e.includes("too many connections")||e.includes("sorry, too many clients")||e.includes("the database system is starting up")||e.includes("the database system is shutting down")||e.includes("server closed the connection unexpectedly")||e.includes("could not connect to server")||e.includes("ssl connection")||e.includes("ssl handshake"))}function Hs(n){return new Promise(e=>setTimeout(e,n))}var Ne=class{config;constructor(e){this.config={maxAttempts:e?.maxAttempts??ee.retry.maxAttempts,initialDelayMs:e?.initialDelayMs??ee.retry.initialDelayMs,maxDelayMs:e?.maxDelayMs??ee.retry.maxDelayMs,backoffMultiplier:e?.backoffMultiplier??ee.retry.backoffMultiplier,jitter:e?.jitter??ee.retry.jitter,isRetryable:e?.isRetryable??cn,onRetry:e?.onRetry};}async withRetry(e,t){let r=t?{...this.config,...t}:this.config,a=Date.now(),o=null;for(let s=0;s<r.maxAttempts;s++)try{return {result:await e(),attempts:s+1,totalTimeMs:Date.now()-a}}catch(i){o=i;let c=s>=r.maxAttempts-1,l=r.isRetryable??this.isRetryable;if(c||!l(o))throw o;let m=this.calculateDelay(s,r);r.onRetry?.(s+1,o,m),await Hs(m);}throw o??new Error("Retry failed with no error")}calculateDelay(e,t){let r=t?{...this.config,...t}:this.config,a=r.initialDelayMs*Math.pow(r.backoffMultiplier,e),o=Math.min(a,r.maxDelayMs);if(r.jitter){let s=1+Math.random()*.25;return Math.floor(o*s)}return Math.floor(o)}isRetryable(e){return (this.config.isRetryable??cn)(e)}getConfig(){return {...this.config}}getMaxAttempts(){return this.config.maxAttempts}};var Ie=class{constructor(e){this.deps=e;}async checkHealth(e={}){let t=Date.now(),{ping:r=true,pingTimeoutMs:a=5e3,includeShared:o=true,tenantIds:s}=e,i=[],c="ok",l,m,p=this.getPoolsToCheck(s).map(async({schemaName:x,tenantId:y,entry:w})=>this.checkPoolHealth(y,x,w,r,a));i.push(...await Promise.all(p));let h=this.deps.getSharedPool();if(o&&h){let x=await this.checkSharedDbHealth(h,r,a);c=x.status,l=x.responseTimeMs,m=x.error;}let g=i.filter(x=>x.status==="degraded").length,$=i.filter(x=>x.status==="unhealthy").length,b={healthy:$===0&&c!=="unhealthy",pools:i,sharedDb:c,totalPools:i.length,degradedPools:g,unhealthyPools:$,timestamp:new Date().toISOString(),durationMs:Date.now()-t};return l!==void 0&&(b.sharedDbResponseTimeMs=l),m!==void 0&&(b.sharedDbError=m),b}getPoolsToCheck(e){let t=[];if(e&&e.length>0)for(let r of e){let a=this.deps.getSchemaName(r),o=this.deps.getPoolEntry(a);o&&t.push({schemaName:a,tenantId:r,entry:o});}else for(let[r,a]of this.deps.getPoolEntries()){let o=this.deps.getTenantIdBySchema(r)??r;t.push({schemaName:r,tenantId:o,entry:a});}return t}async checkPoolHealth(e,t,r,a,o){let s=r.pool,i=s.totalCount,c=s.idleCount,l=s.waitingCount,m="ok",u,p;if(l>0&&(m="degraded"),a){let g=await this.executePingQuery(s,o);u=g.responseTimeMs,g.success?g.responseTimeMs&&g.responseTimeMs>o/2&&m==="ok"&&(m="degraded"):(m="unhealthy",p=g.error);}let h={tenantId:e,schemaName:t,status:m,totalConnections:i,idleConnections:c,waitingRequests:l};return u!==void 0&&(h.responseTimeMs=u),p!==void 0&&(h.error=p),h}async checkSharedDbHealth(e,t,r){let a="ok",o,s;if(e.waitingCount>0&&(a="degraded"),t){let l=await this.executePingQuery(e,r);o=l.responseTimeMs,l.success?l.responseTimeMs&&l.responseTimeMs>r/2&&a==="ok"&&(a="degraded"):(a="unhealthy",s=l.error);}let c={status:a};return o!==void 0&&(c.responseTimeMs=o),s!==void 0&&(c.error=s),c}async executePingQuery(e,t){let r=Date.now();try{let a=new Promise((s,i)=>{setTimeout(()=>i(new Error("Health check ping timeout")),t);}),o=e.query("SELECT 1");return await Promise.race([o,a]),{success:!0,responseTimeMs:Date.now()-r}}catch(a){return {success:false,responseTimeMs:Date.now()-r,error:a.message}}}determineOverallHealth(e,t="ok"){return e.filter(a=>a.status==="unhealthy").length===0&&t!=="unhealthy"}};var xe=class{constructor(e){this.config=e;let t=e.isolation.maxPools??ee.maxPools,r=e.isolation.poolTtlMs??ee.poolTtlMs;this.debugLogger=Kr(e.debug),this.retryHandler=new Ne(e.connection.retry),this.poolCache=new ke({maxPools:t,poolTtlMs:r,onDispose:(a,o)=>{this.disposePoolEntry(o,a);}}),this.healthChecker=new Ie({getPoolEntries:()=>this.poolCache.entries(),getTenantIdBySchema:a=>this.tenantIdBySchema.get(a),getPoolEntry:a=>this.poolCache.get(a),getSchemaName:a=>this.config.isolation.schemaNameTemplate(a),getSharedPool:()=>this.sharedPool});}poolCache;tenantIdBySchema=new Map;pendingConnections=new Map;sharedPool=null;sharedDb=null;sharedDbPending=null;cleanupInterval=null;disposed=false;debugLogger;retryHandler;healthChecker;getDb(e){this.ensureNotDisposed();let t=this.config.isolation.schemaNameTemplate(e),r=this.poolCache.get(t);return r||(r=this.createPoolEntry(e,t),this.poolCache.set(t,r),this.tenantIdBySchema.set(t,e),this.debugLogger.logPoolCreated(e,t),this.config.hooks?.onPoolCreated?.(e)),this.poolCache.touch(t),r.db}async getDbAsync(e){this.ensureNotDisposed();let t=this.config.isolation.schemaNameTemplate(e),r=this.poolCache.get(t);if(r)return this.poolCache.touch(t),r.db;let a=this.pendingConnections.get(t);if(a)return r=await a,this.poolCache.touch(t),r.db;let o=this.connectWithRetry(e,t);this.pendingConnections.set(t,o);try{return r=await o,this.poolCache.set(t,r),this.tenantIdBySchema.set(t,e),this.debugLogger.logPoolCreated(e,t),this.config.hooks?.onPoolCreated?.(e),this.poolCache.touch(t),r.db}finally{this.pendingConnections.delete(t);}}async connectWithRetry(e,t){let r=this.retryHandler.getConfig(),a=r.maxAttempts,o=await this.retryHandler.withRetry(async()=>{let s=this.createPoolEntry(e,t);try{return await s.pool.query("SELECT 1"),s}catch(i){try{await s.pool.end();}catch{}throw i}},{onRetry:(s,i,c)=>{this.debugLogger.logConnectionRetry(e,s,a,i,c),r.onRetry?.(s,i,c);}});return this.debugLogger.logConnectionSuccess(e,o.attempts,o.totalTimeMs),o.result}getSharedDb(){return this.ensureNotDisposed(),this.sharedDb||(this.sharedPool=new Pool({connectionString:this.config.connection.url,...ee.poolConfig,...this.config.connection.poolConfig}),this.sharedPool.on("error",e=>{this.config.hooks?.onError?.("shared",e);}),this.sharedDb=drizzle(this.sharedPool,{schema:this.config.schemas.shared})),this.sharedDb}async getSharedDbAsync(){if(this.ensureNotDisposed(),this.sharedDb)return this.sharedDb;if(this.sharedDbPending)return this.sharedDbPending;this.sharedDbPending=this.connectSharedWithRetry();try{return await this.sharedDbPending}finally{this.sharedDbPending=null;}}async connectSharedWithRetry(){let e=this.retryHandler.getConfig(),t=e.maxAttempts,r=await this.retryHandler.withRetry(async()=>{let a=new Pool({connectionString:this.config.connection.url,...ee.poolConfig,...this.config.connection.poolConfig});try{return await a.query("SELECT 1"),a.on("error",o=>{this.config.hooks?.onError?.("shared",o);}),this.sharedPool=a,this.sharedDb=drizzle(a,{schema:this.config.schemas.shared}),this.sharedDb}catch(o){try{await a.end();}catch{}throw o}},{onRetry:(a,o,s)=>{this.debugLogger.logConnectionRetry("shared",a,t,o,s),e.onRetry?.(a,o,s);}});return this.debugLogger.logConnectionSuccess("shared",r.attempts,r.totalTimeMs),r.result}getSchemaName(e){return this.config.isolation.schemaNameTemplate(e)}hasPool(e){let t=this.config.isolation.schemaNameTemplate(e);return this.poolCache.has(t)}getPoolCount(){return this.poolCache.size()}getActiveTenantIds(){return Array.from(this.tenantIdBySchema.values())}getRetryConfig(){return this.retryHandler.getConfig()}async warmup(e,t={}){this.ensureNotDisposed();let r=Date.now(),{concurrency:a=10,ping:o=true,onProgress:s}=t,i=[];for(let c=0;c<e.length;c+=a){let l=e.slice(c,c+a),m=await Promise.all(l.map(async u=>{let p=Date.now();s?.(u,"starting");try{let h=this.hasPool(u);o?await this.getDbAsync(u):this.getDb(u);let g=Date.now()-p;return s?.(u,"completed"),this.debugLogger.logWarmup(u,!0,g,h),{tenantId:u,success:!0,alreadyWarm:h,durationMs:g}}catch(h){let g=Date.now()-p;return s?.(u,"failed"),this.debugLogger.logWarmup(u,false,g,false),{tenantId:u,success:false,alreadyWarm:false,durationMs:g,error:h.message}}}));i.push(...m);}return {total:i.length,succeeded:i.filter(c=>c.success).length,failed:i.filter(c=>!c.success).length,alreadyWarm:i.filter(c=>c.alreadyWarm).length,durationMs:Date.now()-r,details:i}}getMetrics(){this.ensureNotDisposed();let e=this.config.isolation.maxPools??ee.maxPools,t=[];for(let[r,a]of this.poolCache.entries()){let o=this.tenantIdBySchema.get(r)??r,s=a.pool;t.push({tenantId:o,schemaName:r,connections:{total:s.totalCount,idle:s.idleCount,waiting:s.waitingCount},lastAccessedAt:new Date(a.lastAccess).toISOString()});}return {pools:{total:t.length,maxPools:e,tenants:t},shared:{initialized:this.sharedPool!==null,connections:this.sharedPool?{total:this.sharedPool.totalCount,idle:this.sharedPool.idleCount,waiting:this.sharedPool.waitingCount}:null},timestamp:new Date().toISOString()}}async healthCheck(e={}){return this.ensureNotDisposed(),this.healthChecker.checkHealth(e)}async evictPool(e,t="manual"){let r=this.config.isolation.schemaNameTemplate(e),a=this.poolCache.get(r);a&&(this.debugLogger.logPoolEvicted(e,r,t),this.poolCache.delete(r),this.tenantIdBySchema.delete(r),await this.closePool(a.pool,e));}startCleanup(){if(this.cleanupInterval)return;let e=ee.cleanupIntervalMs;this.cleanupInterval=setInterval(()=>{this.cleanupIdlePools();},e),this.cleanupInterval.unref();}stopCleanup(){this.cleanupInterval&&(clearInterval(this.cleanupInterval),this.cleanupInterval=null);}async dispose(){if(this.disposed)return;this.disposed=true,this.stopCleanup();let e=[];for(let[t,r]of this.poolCache.entries()){let a=this.tenantIdBySchema.get(t);e.push(this.closePool(r.pool,a??t));}await this.poolCache.clear(),this.tenantIdBySchema.clear(),this.sharedPool&&(e.push(this.closePool(this.sharedPool,"shared")),this.sharedPool=null,this.sharedDb=null),await Promise.all(e);}createPoolEntry(e,t){let r=new Pool({connectionString:this.config.connection.url,...ee.poolConfig,...this.config.connection.poolConfig,options:`-c search_path=${t},public`});return r.on("error",async o=>{this.debugLogger.logPoolError(e,o),this.config.hooks?.onError?.(e,o),await this.evictPool(e,"error");}),{db:drizzle(r,{schema:this.config.schemas.tenant}),pool:r,lastAccess:Date.now(),schemaName:t}}disposePoolEntry(e,t){let r=this.tenantIdBySchema.get(t);this.tenantIdBySchema.delete(t),r&&this.debugLogger.logPoolEvicted(r,t,"lru_eviction"),this.closePool(e.pool,r??t).then(()=>{r&&this.config.hooks?.onPoolEvicted?.(r);});}async closePool(e,t){try{await e.end();}catch(r){this.config.hooks?.onError?.(t,r);}}async cleanupIdlePools(){let e=await this.poolCache.evictExpired();for(let t of e){let r=this.tenantIdBySchema.get(t);r&&(this.debugLogger.logPoolEvicted(r,t,"ttl_expired"),this.tenantIdBySchema.delete(t));}}ensureNotDisposed(){if(this.disposed)throw new Error("[drizzle-multitenant] TenantManager has been disposed")}};var we=class{constructor(e){this.tenantManager=e;}async collect(e={}){let{includeHealth:t=false,healthPingTimeoutMs:r=5e3,tenantIds:a}=e,o=this.tenantManager.getMetrics(),s;return t&&(s=await this.tenantManager.healthCheck({ping:true,pingTimeoutMs:r,includeShared:true,...a&&{tenantIds:a}})),{pools:o,health:s,collectedAt:new Date().toISOString()}}getPoolMetrics(){return this.tenantManager.getMetrics()}async getHealthMetrics(e){return this.tenantManager.healthCheck({ping:true,pingTimeoutMs:e?.pingTimeoutMs??5e3,includeShared:true,...e?.tenantIds&&{tenantIds:e.tenantIds}})}getRuntimeMetrics(){let e=process.memoryUsage();return {uptimeSeconds:process.uptime(),memoryUsage:{heapTotal:e.heapTotal,heapUsed:e.heapUsed,external:e.external,rss:e.rss},activeHandles:process._getActiveHandles?.()?.length??0,activeRequests:process._getActiveRequests?.()?.length??0}}calculateSummary(e){let t=e.pools,r=e.health,a=0,o=0,s=0;for(let m of t.pools.tenants)a+=m.connections.total,o+=m.connections.idle,s+=m.connections.waiting;t.shared.connections&&(a+=t.shared.connections.total,o+=t.shared.connections.idle,s+=t.shared.connections.waiting);let i=r?r.pools.filter(m=>m.status==="ok").length:t.pools.total,c=r?.degradedPools??0,l=r?.unhealthyPools??0;return {activePools:t.pools.total,totalConnections:a,idleConnections:o,waitingRequests:s,healthyPools:i,degradedPools:c,unhealthyPools:l}}};var Bs="drizzle_multitenant",Gs=[{name:"pools_active",help:"Number of active database pools",type:"gauge",extract:n=>[{value:n.pools.pools.total}]},{name:"pools_max",help:"Maximum number of pools allowed",type:"gauge",extract:n=>[{value:n.pools.pools.maxPools}]},{name:"pool_connections_total",help:"Total connections in pool",type:"gauge",labels:["tenant","schema"],extract:n=>n.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:e.connections.total}))},{name:"pool_connections_idle",help:"Idle connections in pool",type:"gauge",labels:["tenant","schema"],extract:n=>n.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:e.connections.idle}))},{name:"pool_connections_waiting",help:"Waiting requests in pool queue",type:"gauge",labels:["tenant","schema"],extract:n=>n.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:e.connections.waiting}))},{name:"pool_last_access_timestamp",help:"Last access timestamp for pool (unix epoch)",type:"gauge",labels:["tenant","schema"],extract:n=>n.pools.pools.tenants.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:new Date(e.lastAccessedAt).getTime()/1e3}))},{name:"shared_pool_initialized",help:"Whether shared database pool is initialized (1=yes, 0=no)",type:"gauge",extract:n=>[{value:n.pools.shared.initialized?1:0}]},{name:"shared_pool_connections_total",help:"Total connections in shared pool",type:"gauge",extract:n=>[{value:n.pools.shared.connections?.total??0}]},{name:"shared_pool_connections_idle",help:"Idle connections in shared pool",type:"gauge",extract:n=>[{value:n.pools.shared.connections?.idle??0}]},{name:"shared_pool_connections_waiting",help:"Waiting requests in shared pool queue",type:"gauge",extract:n=>[{value:n.pools.shared.connections?.waiting??0}]},{name:"health_status",help:"Overall health status (1=healthy, 0=unhealthy)",type:"gauge",extract:n=>n.health?[{value:n.health.healthy?1:0}]:[]},{name:"health_pools_total",help:"Total number of pools checked",type:"gauge",extract:n=>n.health?[{value:n.health.totalPools}]:[]},{name:"health_pools_degraded",help:"Number of degraded pools",type:"gauge",extract:n=>n.health?[{value:n.health.degradedPools}]:[]},{name:"health_pools_unhealthy",help:"Number of unhealthy pools",type:"gauge",extract:n=>n.health?[{value:n.health.unhealthyPools}]:[]},{name:"health_check_duration_seconds",help:"Duration of health check in seconds",type:"gauge",extract:n=>n.health?[{value:n.health.durationMs/1e3}]:[]},{name:"pool_health_status",help:"Health status per pool (1=ok, 0.5=degraded, 0=unhealthy)",type:"gauge",labels:["tenant","schema","status"],extract:n=>n.health?.pools.map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName,status:e.status},value:e.status==="ok"?1:e.status==="degraded"?.5:0}))??[]},{name:"pool_response_time_seconds",help:"Pool ping response time in seconds",type:"gauge",labels:["tenant","schema"],extract:n=>n.health?.pools.filter(e=>e.responseTimeMs!==void 0).map(e=>({labels:{tenant:e.tenantId,schema:e.schemaName},value:(e.responseTimeMs??0)/1e3}))??[]},{name:"shared_db_health_status",help:"Shared database health status (1=ok, 0.5=degraded, 0=unhealthy)",type:"gauge",extract:n=>{if(!n.health)return [];let e=n.health.sharedDb;return [{value:e==="ok"?1:e==="degraded"?.5:0}]}},{name:"shared_db_response_time_seconds",help:"Shared database ping response time in seconds",type:"gauge",extract:n=>n.health?.sharedDbResponseTimeMs!==void 0?[{value:n.health.sharedDbResponseTimeMs/1e3}]:[]}],Js=[{name:"process_uptime_seconds",help:"Process uptime in seconds",type:"gauge",extract:n=>[{value:n.uptimeSeconds}]},{name:"process_heap_bytes_total",help:"Total heap memory in bytes",type:"gauge",extract:n=>[{value:n.memoryUsage.heapTotal}]},{name:"process_heap_bytes_used",help:"Used heap memory in bytes",type:"gauge",extract:n=>[{value:n.memoryUsage.heapUsed}]},{name:"process_external_bytes",help:"External memory in bytes",type:"gauge",extract:n=>[{value:n.memoryUsage.external}]},{name:"process_rss_bytes",help:"Resident Set Size in bytes",type:"gauge",extract:n=>[{value:n.memoryUsage.rss}]},{name:"process_active_handles",help:"Number of active handles",type:"gauge",extract:n=>[{value:n.activeHandles}]},{name:"process_active_requests",help:"Number of active requests",type:"gauge",extract:n=>[{value:n.activeRequests}]}],$e=class{prefix;includeTenantLabels;includeTimestamps;defaultLabels;constructor(e={}){this.prefix=e.prefix??Bs,this.includeTenantLabels=e.includeTenantLabels??true,this.includeTimestamps=e.includeTimestamps??false,this.defaultLabels=e.defaultLabels??{};}export(e,t){let r=this.toPrometheusMetrics(e),a=[];for(let o of r)a.push(...this.formatMetric(o));if(t){let o=this.extractRuntimeMetrics(t);for(let s of o)a.push(...this.formatMetric(s));}return a.join(`
|
|
1737
1737
|
`)+`
|
|
1738
|
-
`}toPrometheusMetrics(e){let t=[];for(let r of
|
|
1739
|
-
`));try{let t=await this.collector.collect({includeHealth:!1}),r=this.collector.calculateSummary(t);this.printPoolMetrics(t,r);}catch(t){this.renderer.showStatus(t.message,"error");}switch(console.log(""),await select({message:"What next?",choices:[{name:"Refresh metrics",value:"refresh"},{name:"Include health check",value:"health"},{name:"View Prometheus format",value:"prometheus"},{name:
|
|
1740
|
-
`));try{let t=Date.now(),r=await this.collector.collect({includeHealth:!0}),a=this.collector.calculateSummary(r),o=Date.now()-t;this.printHealthMetrics(r,a,o);}catch(t){this.renderer.showStatus(t.message,"error");}switch(console.log(""),await select({message:"What next?",choices:[{name:"Run health check again",value:"refresh"},{name:"View pool metrics only",value:"pools"},{name:"View Prometheus format",value:"prometheus"},{name:
|
|
1741
|
-
`));try{let t=await this.collector.collect({includeHealth:!0}),r=this.collector.getRuntimeMetrics(),o=new $e({prefix:"drizzle_multitenant"}).export(t,r);console.log(
|
|
1742
|
-
`))if(s.startsWith("# HELP"))console.log(
|
|
1738
|
+
`}toPrometheusMetrics(e){let t=[];for(let r of Gs){let a=r.extract(e);if(a.length===0)continue;let s=(this.includeTenantLabels?a:a.map(i=>{if(!i.labels)return i;let{tenant:c,schema:l,...m}=i.labels;return {...i,labels:Object.keys(m).length>0?m:void 0}})).map(i=>({...i,labels:{...this.defaultLabels,...i.labels}}));t.push({name:`${this.prefix}_${r.name}`,help:r.help,type:r.type,labels:r.labels,values:s});}return t}extractRuntimeMetrics(e){let t=[];for(let r of Js){let a=r.extract(e);if(a.length===0)continue;let o=a.map(s=>({...s,labels:{...this.defaultLabels,...s.labels}}));t.push({name:`${this.prefix}_${r.name}`,help:r.help,type:r.type,values:o});}return t}formatMetric(e){let t=[];t.push(`# HELP ${e.name} ${e.help}`),t.push(`# TYPE ${e.name} ${e.type}`);for(let r of e.values){let a=this.formatLabels(r.labels),o=this.includeTimestamps&&r.timestamp?` ${r.timestamp}`:"";t.push(`${e.name}${a} ${r.value}${o}`);}return t}formatLabels(e){return !e||Object.keys(e).length===0?"":`{${Object.entries(e).filter(([,r])=>r!=null).map(([r,a])=>`${r}="${this.escapeLabel(a)}"`).join(",")}}`}escapeLabel(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n")}static get contentType(){return "text/plain; version=0.0.4; charset=utf-8"}};var Fe=class{renderer;ctx;manager=null;collector=null;constructor(e,t){this.ctx=e,this.renderer=t||new V;}async show(){switch(this.renderer.clearScreen(),this.renderer.showHeader("Metrics Dashboard"),await select({message:"What would you like to view?",choices:[{name:"Pool metrics (fast)",value:"pools"},{name:"Health check (includes ping)",value:"health"},{name:"Prometheus export",value:"prometheus"},{name:"Runtime metrics",value:"runtime"},{name:M.gray("\u2190 Back to main menu"),value:"back"}]})){case "pools":return this.showPoolMetrics();case "health":return this.showHealthMetrics();case "prometheus":return this.showPrometheusExport();case "runtime":return this.showRuntimeMetrics();default:return {type:"back"}}}async ensureInitialized(){if(this.collector)return true;try{return this.manager=new xe(this.ctx.config),this.collector=new we(this.manager),!0}catch(e){return this.renderer.showStatus(e.message,"error"),await this.renderer.pressEnterToContinue(),false}}async cleanup(){this.manager&&(await this.manager.dispose(),this.manager=null,this.collector=null);}async showPoolMetrics(){if(this.renderer.clearScreen(),this.renderer.showHeader("Pool Metrics"),!await this.ensureInitialized())return this.show();console.log(M.dim(` Collecting pool metrics...
|
|
1739
|
+
`));try{let t=await this.collector.collect({includeHealth:!1}),r=this.collector.calculateSummary(t);this.printPoolMetrics(t,r);}catch(t){this.renderer.showStatus(t.message,"error");}switch(console.log(""),await select({message:"What next?",choices:[{name:"Refresh metrics",value:"refresh"},{name:"Include health check",value:"health"},{name:"View Prometheus format",value:"prometheus"},{name:M.gray("\u2190 Back to metrics menu"),value:"menu"},{name:M.gray("\u2190 Back to main menu"),value:"back"}]})){case "refresh":return this.showPoolMetrics();case "health":return this.showHealthMetrics();case "prometheus":return this.showPrometheusExport();case "menu":return this.show();default:return await this.cleanup(),{type:"back"}}}async showHealthMetrics(){if(this.renderer.clearScreen(),this.renderer.showHeader("Health Check"),!await this.ensureInitialized())return this.show();console.log(M.dim(` Running health check (pinging all pools)...
|
|
1740
|
+
`));try{let t=Date.now(),r=await this.collector.collect({includeHealth:!0}),a=this.collector.calculateSummary(r),o=Date.now()-t;this.printHealthMetrics(r,a,o);}catch(t){this.renderer.showStatus(t.message,"error");}switch(console.log(""),await select({message:"What next?",choices:[{name:"Run health check again",value:"refresh"},{name:"View pool metrics only",value:"pools"},{name:"View Prometheus format",value:"prometheus"},{name:M.gray("\u2190 Back to metrics menu"),value:"menu"},{name:M.gray("\u2190 Back to main menu"),value:"back"}]})){case "refresh":return this.showHealthMetrics();case "pools":return this.showPoolMetrics();case "prometheus":return this.showPrometheusExport();case "menu":return this.show();default:return await this.cleanup(),{type:"back"}}}async showPrometheusExport(){if(this.renderer.clearScreen(),this.renderer.showHeader("Prometheus Export"),!await this.ensureInitialized())return this.show();console.log(M.dim(` Collecting metrics for Prometheus export...
|
|
1741
|
+
`));try{let t=await this.collector.collect({includeHealth:!0}),r=this.collector.getRuntimeMetrics(),o=new $e({prefix:"drizzle_multitenant"}).export(t,r);console.log(M.bold(" Prometheus Text Format:")),console.log("");for(let s of o.split(`
|
|
1742
|
+
`))if(s.startsWith("# HELP"))console.log(M.dim(` ${s}`));else if(s.startsWith("# TYPE"))console.log(M.dim(` ${s}`));else if(s.trim()){let[i,c]=s.split(" ");if(i&&c!==void 0){let l=i.includes("{")?i.replace(/\{([^}]+)\}/,(m,u)=>`{${M.cyan(u)}}`):i;console.log(` ${M.green(l)} ${M.yellow(c)}`);}else console.log(` ${s}`);}console.log(""),console.log(M.dim(' Tip: Use "drizzle-multitenant metrics --prometheus" to pipe to curl'));}catch(t){this.renderer.showStatus(t.message,"error");}switch(console.log(""),await select({message:"What next?",choices:[{name:"Refresh export",value:"refresh"},{name:"View pool metrics",value:"pools"},{name:"View health check",value:"health"},{name:M.gray("\u2190 Back to metrics menu"),value:"menu"},{name:M.gray("\u2190 Back to main menu"),value:"back"}]})){case "refresh":return this.showPrometheusExport();case "pools":return this.showPoolMetrics();case "health":return this.showHealthMetrics();case "menu":return this.show();default:return await this.cleanup(),{type:"back"}}}async showRuntimeMetrics(){if(this.renderer.clearScreen(),this.renderer.showHeader("Runtime Metrics"),!await this.ensureInitialized())return this.show();try{let t=this.collector.getRuntimeMetrics();console.log(M.bold(" Node.js Process Metrics:")),console.log(""),console.log(` Uptime: ${M.cyan(this.formatUptime(t.uptimeSeconds))}`),console.log(""),console.log(M.bold(" Memory Usage:")),console.log(` Heap Total: ${M.cyan(this.formatBytes(t.memoryUsage.heapTotal))}`),console.log(` Heap Used: ${M.cyan(this.formatBytes(t.memoryUsage.heapUsed))}`),console.log(` External: ${M.cyan(this.formatBytes(t.memoryUsage.external))}`),console.log(` RSS: ${M.cyan(this.formatBytes(t.memoryUsage.rss))}`),console.log(""),console.log(M.bold(" Event Loop:")),console.log(` Active Handles: ${t.activeHandles}`),console.log(` Active Requests: ${t.activeRequests}`);}catch(t){this.renderer.showStatus(t.message,"error");}switch(console.log(""),await select({message:"What next?",choices:[{name:"Refresh runtime metrics",value:"refresh"},{name:"View pool metrics",value:"pools"},{name:"View health check",value:"health"},{name:M.gray("\u2190 Back to metrics menu"),value:"menu"},{name:M.gray("\u2190 Back to main menu"),value:"back"}]})){case "refresh":return this.showRuntimeMetrics();case "pools":return this.showPoolMetrics();case "health":return this.showHealthMetrics();case "menu":return this.show();default:return await this.cleanup(),{type:"back"}}}printPoolMetrics(e,t){if(console.log(M.bold(" Pool Status:")),console.log(` Active Pools: ${M.cyan(String(e.pools.pools.total))} / ${e.pools.pools.maxPools}`),console.log(` Total Connections: ${t.totalConnections}`),console.log(` Idle Connections: ${M.green(String(t.idleConnections))}`),console.log(` Waiting Requests: ${t.waitingRequests>0?M.yellow(String(t.waitingRequests)):String(t.waitingRequests)}`),console.log(""),console.log(M.bold(" Shared Database:")),e.pools.shared.initialized){let a=e.pools.shared.connections;console.log(` Status: ${M.green("Initialized")}`),console.log(` Connections: ${a.total} total, ${a.idle} idle, ${a.waiting} waiting`);}else console.log(` Status: ${M.dim("Not initialized")}`);let r=e.pools.pools.tenants;if(r.length>0&&r.length<=15){console.log(""),console.log(M.bold(" Active Tenants:"));for(let a of r){let o=a.connections.waiting>0?M.yellow:s=>s;console.log(` ${M.cyan(a.tenantId)}: ${a.connections.total} conn, ${a.connections.idle} idle, ${o(String(a.connections.waiting))} waiting`);}}else r.length>15&&(console.log(""),console.log(M.dim(` (${r.length} active tenants)`)));console.log(""),console.log(M.dim(` Collected at ${e.collectedAt}`));}printHealthMetrics(e,t,r){if(!e.health){this.printPoolMetrics(e,t);return}console.log(M.bold(" Health Status:"));let a=e.health.healthy?M.green("\u2713"):M.red("\u2717");console.log(` Overall: ${a} ${e.health.healthy?"Healthy":"Unhealthy"}`),console.log(` Pools: ${M.green(String(t.healthyPools))} healthy, ${M.yellow(String(t.degradedPools))} degraded, ${M.red(String(t.unhealthyPools))} unhealthy`),console.log(` Shared DB: ${this.getStatusColor(e.health.sharedDb)(e.health.sharedDb)}`),e.health.sharedDbResponseTimeMs!==void 0&&console.log(` Shared DB Latency: ${e.health.sharedDbResponseTimeMs}ms`),console.log(` Check Duration: ${r}ms`);let o=e.health.pools;if(o.length>0&&o.length<=15){console.log(""),console.log(M.bold(" Pool Health:"));for(let s of o){let i=this.getStatusIcon(s.status),c=s.responseTimeMs?M.dim(` (${s.responseTimeMs}ms)`):"";console.log(` ${i} ${M.cyan(s.tenantId)}${c}`),s.error&&console.log(` ${M.red(s.error)}`);}}else o.length>15&&(console.log(""),console.log(M.dim(` (${o.length} pools checked)`)));console.log(""),console.log(M.dim(` Collected at ${e.collectedAt}`));}getStatusIcon(e){switch(e){case "ok":return M.green("\u25CF");case "degraded":return M.yellow("\u25D0");case "unhealthy":return M.red("\u25CB")}}getStatusColor(e){switch(e){case "ok":return M.green;case "degraded":return M.yellow;case "unhealthy":return M.red}}formatBytes(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:e<1024*1024*1024?`${(e/(1024*1024)).toFixed(1)} MB`:`${(e/(1024*1024*1024)).toFixed(1)} GB`}formatUptime(e){if(e<60)return `${Math.floor(e)}s`;if(e<3600)return `${Math.floor(e/60)}m ${Math.floor(e%60)}s`;let t=Math.floor(e/3600),r=Math.floor(e%3600/60);return `${t}h ${r}m`}};var un=class{constructor(e){this.configPath=e;this.renderer=new V;}renderer;ctx=null;statuses=[];sharedStatus=null;hasSharedMigrations=false;async start(){if(this.renderer.clearScreen(),Fr(),this.ctx=await this.loadMenuContext(),!this.ctx){await this.renderer.pressEnterToContinue();return}await this.refreshStatuses(),await this.loop();}async loop(){for(;;){let e=await this.showMainMenu();e.type==="exit"&&(console.log(M.cyan(`
|
|
1743
1743
|
Goodbye!
|
|
1744
|
-
`)),process.exit(0)),e.type==="refresh"&&await this.refreshStatuses();}}async showMainMenu(){let e=this.renderer.getStatusSummary(this.statuses),t=[{name:`Migration Status ${
|
|
1744
|
+
`)),process.exit(0)),e.type==="refresh"&&await this.refreshStatuses();}}async showMainMenu(){let e=this.renderer.getStatusSummary(this.statuses),t=[{name:`Migration Status ${M.gray(`(${M.green(e.upToDate)} ok, ${M.yellow(e.behind)} pending)`)}`,value:"status"},{name:`Migrate Tenants ${e.totalPending>0?M.yellow(`(${e.totalPending} pending)`):M.dim("(all up to date)")}`,value:"migrate"}];if(this.hasSharedMigrations&&this.sharedStatus){let a=this.sharedStatus.pendingCount;t.push({name:`Migrate Shared Schema ${a>0?M.yellow(`(${a} pending)`):M.dim("(up to date)")}`,value:"migrate-shared"});}t.push({name:"Seed Tenants",value:"seed"},new Separator,{name:"Create Tenant",value:"create"},{name:"Clone Tenant",value:"clone"},{name:"Drop Tenant",value:"drop"},new Separator,{name:"Generate Migration",value:"generate"}),this.hasSharedMigrations&&t.push({name:"Generate Shared Migration",value:"generate-shared"}),t.push(new Separator,{name:"Schema Lint",value:"lint"},{name:"Metrics Dashboard",value:"metrics"},{name:"Refresh",value:"refresh"},new Separator,{name:"Exit",value:"exit"});let r=await select({message:"drizzle-multitenant - Main Menu",choices:t});return this.handleChoice(r)}async handleChoice(e){if(!this.ctx)return {type:"exit"};switch(e){case "status":{let r=await new De(this.ctx,this.renderer).show(this.statuses);return this.handleScreenAction(r)}case "migrate":{let r=await new ge(this.ctx,this.renderer).show(this.statuses);return this.handleScreenAction(r)}case "seed":{let r=await new ve(this.ctx,this.renderer).show(this.statuses);return this.handleScreenAction(r)}case "create":{let r=await new fe(this.ctx,this.renderer).showCreate();return this.handleScreenAction(r)}case "clone":{let r=await new fe(this.ctx,this.renderer).showClone(this.statuses);return this.handleScreenAction(r)}case "drop":{let r=await new fe(this.ctx,this.renderer).showDrop(this.statuses);return this.handleScreenAction(r)}case "generate":return await new Te(this.ctx,this.renderer).show(),{type:"back"};case "migrate-shared":return await this.migrateShared(),await this.refreshStatuses(),{type:"refresh"};case "generate-shared":return await new Te(this.ctx,this.renderer).showShared(),{type:"back"};case "lint":return await new ze(this.ctx,this.renderer).show(),{type:"back"};case "metrics":return await new Fe(this.ctx,this.renderer).show(),{type:"back"};case "refresh":return await this.refreshStatuses(),{type:"refresh"};case "exit":return {type:"exit"};default:return {type:"back"}}}async migrateShared(){if(!this.ctx||!this.sharedStatus)return;if(this.sharedStatus.pendingCount===0){console.log(M.green(`
|
|
1745
1745
|
Shared schema is already up to date.
|
|
1746
|
-
`)),await this.renderer.pressEnterToContinue();return}console.log(
|
|
1746
|
+
`)),await this.renderer.pressEnterToContinue();return}console.log(M.cyan(`
|
|
1747
1747
|
Pending shared migrations: ${this.sharedStatus.pendingCount}
|
|
1748
|
-
`));for(let t of this.sharedStatus.pendingMigrations)console.log(
|
|
1749
|
-
Error: ${r.error}`));}catch(t){e.fail("Migration failed"),console.log(
|
|
1750
|
-
${t.message}`));}console.log(""),await this.renderer.pressEnterToContinue();}async handleScreenAction(e){if(!this.ctx)return {type:"exit"};if(e.type==="navigate")switch(e.screen){case "migrate":{let r=await new ge(this.ctx,this.renderer).show(this.statuses);return this.handleScreenAction(r)}case "migrate-single":{let t=e.params?.tenantId;if(t){let a=await new ge(this.ctx,this.renderer).migrateSingle(t);return await this.refreshStatuses(),a}return {type:"back"}}default:return {type:"back"}}return e.type==="refresh"&&await this.refreshStatuses(),e}async loadMenuContext(){let e=
|
|
1751
|
-
Add migrations.tenantDiscovery to your config:`)),console.log(
|
|
1752
|
-
${t.message}`)),null}}async refreshStatuses(){if(!this.ctx)return;let e=
|
|
1753
|
-
${t.message}`)),this.statuses=[],this.sharedStatus=null;}}};async function
|
|
1748
|
+
`));for(let t of this.sharedStatus.pendingMigrations)console.log(M.dim(` - ${t}`));console.log("");let e=st("Migrating shared schema...").start();try{let r=await I(this.ctx.config,{migrationsFolder:this.ctx.migrationsFolder,sharedMigrationsFolder:this.ctx.sharedMigrationsFolder,...this.ctx.migrationsTable&&{migrationsTable:this.ctx.migrationsTable},tenantDiscovery:this.ctx.tenantDiscovery}).migrateShared({onProgress:(a,o)=>{a==="migrating"&&o&&(e.text=`Applying: ${o}`);}});if(r.success){e.succeed(`Applied ${r.appliedMigrations.length} migration(s)`),console.log("");for(let a of r.appliedMigrations)console.log(M.green(` \u2713 ${a}`));}else e.fail("Migration failed"),console.log(M.red(`
|
|
1749
|
+
Error: ${r.error}`));}catch(t){e.fail("Migration failed"),console.log(M.red(`
|
|
1750
|
+
${t.message}`));}console.log(""),await this.renderer.pressEnterToContinue();}async handleScreenAction(e){if(!this.ctx)return {type:"exit"};if(e.type==="navigate")switch(e.screen){case "migrate":{let r=await new ge(this.ctx,this.renderer).show(this.statuses);return this.handleScreenAction(r)}case "migrate-single":{let t=e.params?.tenantId;if(t){let a=await new ge(this.ctx,this.renderer).migrateSingle(t);return await this.refreshStatuses(),a}return {type:"back"}}default:return {type:"back"}}return e.type==="refresh"&&await this.refreshStatuses(),e}async loadMenuContext(){let e=st("Loading configuration...").start();try{let{config:t,migrationsFolder:r,migrationsTable:a,tenantDiscovery:o,sharedMigrationsFolder:s,sharedConfigSource:i,drizzleKitConfigFile:c}=await k(this.configPath);if(!o)return e.fail("No tenant discovery function found in config"),console.log(M.dim(`
|
|
1751
|
+
Add migrations.tenantDiscovery to your config:`)),console.log(M.dim(" migrations: {")),console.log(M.dim(" tenantDiscovery: async () => ['tenant-1', 'tenant-2'],")),console.log(M.dim(" }")),null;let l=O(r),m;if(s)try{m=O(s),this.hasSharedMigrations=!0;}catch{this.hasSharedMigrations=!1;}return e.succeed("Configuration loaded"),m&&i?console.log(M.dim(` \u2514\u2500 Shared schema: ${s} ${M.cyan(`(from ${i})`)}`)):c&&!s&&console.log(M.dim(` \u2514\u2500 Found ${c} (no shared migrations folder configured)`)),{config:t,migrationsFolder:l,migrationsTable:a,tenantDiscovery:o,sharedMigrationsFolder:m,sharedConfigSource:i??void 0,drizzleKitConfigFile:c??void 0}}catch(t){return e.fail("Failed to load configuration"),console.log(M.red(`
|
|
1752
|
+
${t.message}`)),null}}async refreshStatuses(){if(!this.ctx)return;let e=st("Fetching status...").start();try{let t=I(this.ctx.config,{migrationsFolder:this.ctx.migrationsFolder,sharedMigrationsFolder:this.ctx.sharedMigrationsFolder,...this.ctx.migrationsTable&&{migrationsTable:this.ctx.migrationsTable},tenantDiscovery:this.ctx.tenantDiscovery});this.statuses=await t.getStatus(),this.hasSharedMigrations&&(this.sharedStatus=await t.getSharedStatus()),e.stop();}catch(t){e.fail("Failed to fetch status"),console.log(M.red(`
|
|
1753
|
+
${t.message}`)),this.statuses=[],this.sharedStatus=null;}}};async function pt(n){await new un(n).start();}process.on("SIGINT",()=>{console.log(M.cyan(`
|
|
1754
1754
|
|
|
1755
1755
|
Goodbye!
|
|
1756
|
-
`)),process.exit(0);});process.on("uncaughtException",n=>{n.name==="ExitPromptError"&&(console.log(
|
|
1756
|
+
`)),process.exit(0);});process.on("uncaughtException",n=>{n.name==="ExitPromptError"&&(console.log(M.cyan(`
|
|
1757
1757
|
|
|
1758
1758
|
Goodbye!
|
|
1759
|
-
`)),process.exit(0)),console.error(
|
|
1759
|
+
`)),process.exit(0)),console.error(M.red(`
|
|
1760
1760
|
Unexpected error:`),n.message),process.exit(1);});var hn=new Command("interactive").alias("i").description("Launch interactive TUI mode").option("-c, --config <path>","Path to config file").addHelpText("after",`
|
|
1761
1761
|
Examples:
|
|
1762
1762
|
$ drizzle-multitenant interactive
|
|
@@ -1769,7 +1769,7 @@ Interactive mode provides a menu-driven interface to:
|
|
|
1769
1769
|
- Create new tenants
|
|
1770
1770
|
- Drop existing tenants
|
|
1771
1771
|
- Generate new migrations
|
|
1772
|
-
`).action(async n=>{await
|
|
1772
|
+
`).action(async n=>{await pt(n.config);});var gn=new Command("seed").description("Seed tenant databases with initial data").requiredOption("-f, --file <path>","Path to seed file (TypeScript or JavaScript)").option("-c, --config <path>","Path to config file").option("-a, --all","Seed all tenants").option("-t, --tenant <id>","Seed a specific tenant").option("--tenants <ids>","Seed specific tenants (comma-separated)").option("--concurrency <number>","Number of concurrent seed operations","10").addHelpText("after",`
|
|
1773
1773
|
Examples:
|
|
1774
1774
|
$ drizzle-multitenant seed --file=./seeds/initial.ts --all
|
|
1775
1775
|
$ drizzle-multitenant seed --file=./seeds/initial.ts --tenant=my-tenant
|
|
@@ -1786,15 +1786,15 @@ Seed File Format:
|
|
|
1786
1786
|
{ name: 'user', permissions: ['read'] },
|
|
1787
1787
|
]);
|
|
1788
1788
|
};
|
|
1789
|
-
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await
|
|
1790
|
-
${
|
|
1789
|
+
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await k(n.config),c=O(o);H(`Using migrations folder: ${c}`),r.text="Loading seed file...";let l=resolve(process.cwd(),n.file),m=pathToFileURL(l).href,u;try{let y=await import(m);if(u=y.seed||y.default,typeof u!="function")throw new Error('Seed file must export a "seed" function or default export')}catch(y){r.fail("Failed to load seed file");let w=y;w.message.includes("Cannot find module")?(d(`
|
|
1790
|
+
${C(`Seed file not found: ${l}`)}`),d(f(`
|
|
1791
1791
|
Make sure the file exists and has the correct format:`)),d(f(" export const seed: SeedFunction = async (db, tenantId) => { ... };"))):d(`
|
|
1792
|
-
${
|
|
1793
|
-
`));let
|
|
1794
|
-
`));let g=I(a,{migrationsFolder:c,...s&&{migrationsTable:s},tenantDiscovery:p})
|
|
1795
|
-
`+T("Summary:")),d(` Total: ${b.total}`),d(` Succeeded: ${E(b.succeeded.toString())}`),b.failed>0&&d(` Failed: ${v(b.failed.toString())}`),b.skipped>0&&d(` Skipped: ${
|
|
1796
|
-
`+T("Failed tenants:"));for(let
|
|
1797
|
-
`+f("Run with --verbose to see more details."));}b.failed>0&&process.exit(1);}catch(a){r.fail(a.message),j(a);}});function
|
|
1792
|
+
${C(w.message)}`),process.exit(1);}let p,h;if(n.tenant)p=async()=>[n.tenant],h=[n.tenant];else if(n.tenants){let y=n.tenants.split(",").map(w=>w.trim());p=async()=>y,h=y;}else if(n.all){if(!i)throw Q.noTenantDiscovery();p=i,r.text="Discovering tenants...",h=await i();}else if(r.stop(),me()&&i){d(J(`No tenants specified. Fetching available tenants...
|
|
1793
|
+
`));let y=await i();if(y.length===0){d(C("No tenants found."));return}let w=await checkbox({message:"Select tenants to seed:",choices:y.map(D=>({name:D,value:D})),pageSize:15});if(w.length===0){d(C("No tenants selected. Aborting."));return}p=async()=>w,h=w;}else throw Q.noTenantSpecified();if(h.length===0){r.stop(),d(C("No tenants found."));return}r.text=`Found ${h.length} tenant${h.length>1?"s":""}`,r.succeed(),d(J(`Seeding ${h.length} tenant${h.length>1?"s":""}...
|
|
1794
|
+
`));let g=I(a,{migrationsFolder:c,...s&&{migrationsTable:s},tenantDiscovery:p}),$=parseInt(n.concurrency||"10",10),S=ae({total:h.length});S.start();let b=await g.seedAll(u,{concurrency:$,onProgress:(y,w)=>{w==="completed"?(S.increment({tenant:y,status:"success"}),H(`Completed: ${y}`)):w==="failed"&&(S.increment({tenant:y,status:"error"}),H(`Failed: ${y}`));},onError:(y,w)=>(H(`Error on ${y}: ${w.message}`),"continue")});S.stop();let x=Date.now()-e;if(t.jsonMode){let y={results:b.details.map(w=>({tenantId:w.tenantId,schema:w.schemaName,success:w.success,durationMs:w.durationMs,error:w.error})),summary:{total:b.total,succeeded:b.succeeded,failed:b.failed,skipped:b.skipped,durationMs:x}};N(y),process.exit(b.failed>0?1:0);}if(d(`
|
|
1795
|
+
`+T("Summary:")),d(` Total: ${b.total}`),d(` Succeeded: ${E(b.succeeded.toString())}`),b.failed>0&&d(` Failed: ${v(b.failed.toString())}`),b.skipped>0&&d(` Skipped: ${C(b.skipped.toString())}`),d(` Duration: ${f(ei(x))}`),b.failed>0){d(`
|
|
1796
|
+
`+T("Failed tenants:"));for(let y of b.details.filter(w=>!w.success))d(` ${v(y.tenantId)}: ${f(y.error||"Unknown error")}`);d(`
|
|
1797
|
+
`+f("Run with --verbose to see more details."));}b.failed>0&&process.exit(1);}catch(a){r.fail(a.message),j(a);}});function ei(n){if(n<1e3)return `${n}ms`;if(n<6e4)return `${(n/1e3).toFixed(1)}s`;let e=Math.floor(n/6e4),t=Math.round(n%6e4/1e3);return `${e}m ${t}s`}var fn=new Command("seed-shared").description("Seed the shared schema (public) with initial data").requiredOption("-f, --file <path>","Path to shared seed file (TypeScript or JavaScript)").option("-c, --config <path>","Path to config file").addHelpText("after",`
|
|
1798
1798
|
Examples:
|
|
1799
1799
|
$ drizzle-multitenant seed-shared --file=./seeds/shared/plans.ts
|
|
1800
1800
|
|
|
@@ -1809,14 +1809,14 @@ Seed File Format:
|
|
|
1809
1809
|
{ id: 'enterprise', name: 'Enterprise', price: 99 },
|
|
1810
1810
|
]).onConflictDoNothing();
|
|
1811
1811
|
};
|
|
1812
|
-
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await
|
|
1813
|
-
${
|
|
1814
|
-
Add a shared schema to your config file:`)),d(f(" schemas: {")),d(f(" tenant: tenantSchema,")),d(f(" shared: sharedSchema, // <-- Add this")),d(f(" }")),process.exit(1)),r.text="Loading seed file...";let l=resolve(process.cwd(),n.file),m=pathToFileURL(l).href,u;try{let
|
|
1815
|
-
${
|
|
1812
|
+
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await k(n.config),c=O(o);H(`Using migrations folder: ${c}`),a.schemas?.shared||(r.fail("Shared schema not configured"),d(`
|
|
1813
|
+
${C("The shared schema (schemas.shared) is not configured.")}`),d(f(`
|
|
1814
|
+
Add a shared schema to your config file:`)),d(f(" schemas: {")),d(f(" tenant: tenantSchema,")),d(f(" shared: sharedSchema, // <-- Add this")),d(f(" }")),process.exit(1)),r.text="Loading seed file...";let l=resolve(process.cwd(),n.file),m=pathToFileURL(l).href,u;try{let $=await import(m);if(u=$.seed||$.default,typeof u!="function")throw new Error('Seed file must export a "seed" function or default export')}catch($){r.fail("Failed to load seed file");let S=$;S.message.includes("Cannot find module")?(d(`
|
|
1815
|
+
${C(`Seed file not found: ${l}`)}`),d(f(`
|
|
1816
1816
|
Make sure the file exists and has the correct format:`)),d(f(" export const seed: SharedSeedFunction = async (db) => { ... };"))):d(`
|
|
1817
|
-
${
|
|
1818
|
-
`+T("Summary:")),d(` Schema: ${h.schemaName}`),d(` Status: ${h.success?E("success"):v("failed")}`),d(` Duration: ${f(
|
|
1819
|
-
${v("Error:")} ${h.error}`),h.success||process.exit(1);}catch(a){r.fail(a.message),j(a);}});function
|
|
1817
|
+
${C(S.message)}`),process.exit(1);}r.text="Seeding shared schema...";let h=await I(a,{migrationsFolder:c,...s&&{migrationsTable:s},tenantDiscovery:i||(()=>Promise.resolve([]))}).seedShared(u),g=Date.now()-e;if(h.success?r.succeed("Shared schema seeded successfully"):r.fail("Failed to seed shared schema"),t.jsonMode){let $={result:{schema:h.schemaName,success:h.success,durationMs:h.durationMs,error:h.error}};N($),process.exit(h.success?0:1);}d(`
|
|
1818
|
+
`+T("Summary:")),d(` Schema: ${h.schemaName}`),d(` Status: ${h.success?E("success"):v("failed")}`),d(` Duration: ${f(ai(g))}`),h.error&&d(`
|
|
1819
|
+
${v("Error:")} ${h.error}`),h.success||process.exit(1);}catch(a){r.fail(a.message),j(a);}});function ai(n){if(n<1e3)return `${n}ms`;if(n<6e4)return `${(n/1e3).toFixed(1)}s`;let e=Math.floor(n/6e4),t=Math.round(n%6e4/1e3);return `${e}m ${t}s`}var yn=new Command("seed-all").description("Seed shared schema first, then all tenants").requiredOption("--shared-file <path>","Path to shared schema seed file").requiredOption("--tenant-file <path>","Path to tenant seed file").option("-c, --config <path>","Path to config file").option("--concurrency <number>","Number of concurrent tenant seed operations","10").addHelpText("after",`
|
|
1820
1820
|
Examples:
|
|
1821
1821
|
$ drizzle-multitenant seed-all \\
|
|
1822
1822
|
--shared-file=./seeds/shared/plans.ts \\
|
|
@@ -1841,29 +1841,29 @@ Seed File Formats:
|
|
|
1841
1841
|
export const seed: SeedFunction = async (db, tenantId) => {
|
|
1842
1842
|
await db.insert(roles).values([...]);
|
|
1843
1843
|
};
|
|
1844
|
-
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await
|
|
1845
|
-
${
|
|
1846
|
-
${
|
|
1847
|
-
${
|
|
1848
|
-
${
|
|
1849
|
-
${
|
|
1850
|
-
${
|
|
1851
|
-
[1/2] Seeding shared schema...`));let x=R("Seeding shared schema...");x.start();let
|
|
1852
|
-
[2/2] Seeding ${
|
|
1853
|
-
`+T("Summary:")),d(T(" Shared:")),d(` Schema: ${
|
|
1854
|
-
Tenants:`)),d(` Total: ${D.total}`),d(` Succeeded: ${E(D.succeeded.toString())}`),D.failed>0&&d(` Failed: ${v(D.failed.toString())}`),D.skipped>0&&d(` Skipped: ${
|
|
1855
|
-
Total:`)),d(` Duration: ${f(
|
|
1856
|
-
`+T("Failed tenants:"));for(let
|
|
1844
|
+
`).action(async n=>{let e=Date.now(),t=L(),r=R("Loading configuration...");try{r.start();let{config:a,migrationsFolder:o,migrationsTable:s,tenantDiscovery:i}=await k(n.config);i||(r.fail("Tenant discovery not configured"),d(`
|
|
1845
|
+
${C("The tenantDiscovery function is not configured in your config file.")}`),process.exit(1));let c=O(o);H(`Using migrations folder: ${c}`),a.schemas?.shared||(r.fail("Shared schema not configured"),d(`
|
|
1846
|
+
${C("The shared schema (schemas.shared) is not configured.")}`),process.exit(1)),r.text="Loading shared seed file...";let l=resolve(process.cwd(),n.sharedFile),m=pathToFileURL(l).href,u;try{let z=await import(m);if(u=z.seed||z.default,typeof u!="function")throw new Error('Shared seed file must export a "seed" function or default export')}catch(z){r.fail("Failed to load shared seed file");let B=z;B.message.includes("Cannot find module")?d(`
|
|
1847
|
+
${C(`Shared seed file not found: ${l}`)}`):d(`
|
|
1848
|
+
${C(B.message)}`),process.exit(1);}r.text="Loading tenant seed file...";let p=resolve(process.cwd(),n.tenantFile),h=pathToFileURL(p).href,g;try{let z=await import(h);if(g=z.seed||z.default,typeof g!="function")throw new Error('Tenant seed file must export a "seed" function or default export')}catch(z){r.fail("Failed to load tenant seed file");let B=z;B.message.includes("Cannot find module")?d(`
|
|
1849
|
+
${C(`Tenant seed file not found: ${p}`)}`):d(`
|
|
1850
|
+
${C(B.message)}`),process.exit(1);}r.text="Discovering tenants...";let $=await i();if($.length===0){r.stop(),d(C("No tenants found."));return}r.text=`Found ${$.length} tenant${$.length>1?"s":""}`,r.succeed();let S=I(a,{migrationsFolder:c,...s&&{migrationsTable:s},tenantDiscovery:i}),b=parseInt(n.concurrency||"10",10);d(T(`
|
|
1851
|
+
[1/2] Seeding shared schema...`));let x=R("Seeding shared schema...");x.start();let y=await S.seedShared(u);y.success?x.succeed(`Shared schema seeded in ${y.durationMs}ms`):(x.fail(`Failed to seed shared schema: ${y.error}`),t.jsonMode||process.exit(1)),d(T(`
|
|
1852
|
+
[2/2] Seeding ${$.length} tenant${$.length>1?"s":""}...`));let w=ae({total:$.length});w.start();let D=await S.seedAll(g,{concurrency:b,onProgress:(z,B)=>{B==="completed"?(w.increment({tenant:z,status:"success"}),H(`Completed: ${z}`)):B==="failed"&&(w.increment({tenant:z,status:"error"}),H(`Failed: ${z}`));},onError:(z,B)=>(H(`Error on ${z}: ${B.message}`),"continue")});w.stop();let W=Date.now()-e;if(t.jsonMode){let z={shared:{schema:y.schemaName,success:y.success,durationMs:y.durationMs,error:y.error},tenants:{results:D.details.map(B=>({tenantId:B.tenantId,schema:B.schemaName,success:B.success,durationMs:B.durationMs,error:B.error})),summary:{total:D.total,succeeded:D.succeeded,failed:D.failed,skipped:D.skipped}},totalDurationMs:W};N(z),process.exit(y.success&&D.failed===0?0:1);}if(d(`
|
|
1853
|
+
`+T("Summary:")),d(T(" Shared:")),d(` Schema: ${y.schemaName}`),d(` Status: ${y.success?E("success"):v("failed")}`),d(` Duration: ${f(Qr(y.durationMs))}`),d(T(`
|
|
1854
|
+
Tenants:`)),d(` Total: ${D.total}`),d(` Succeeded: ${E(D.succeeded.toString())}`),D.failed>0&&d(` Failed: ${v(D.failed.toString())}`),D.skipped>0&&d(` Skipped: ${C(D.skipped.toString())}`),d(T(`
|
|
1855
|
+
Total:`)),d(` Duration: ${f(Qr(W))}`),D.failed>0){d(`
|
|
1856
|
+
`+T("Failed tenants:"));for(let z of D.details.filter(B=>!B.success))d(` ${v(z.tenantId)}: ${f(z.error||"Unknown error")}`);}(!y.success||D.failed>0)&&process.exit(1);}catch(a){r.fail(a.message),j(a);}});function Qr(n){if(n<1e3)return `${n}ms`;if(n<6e4)return `${(n/1e3).toFixed(1)}s`;let e=Math.floor(n/6e4),t=Math.round(n%6e4/1e3);return `${e}m ${t}s`}var Sn=new Command("doctor").description("Diagnose configuration and environment issues").option("-c, --config <path>","Path to config file").addHelpText("after",`
|
|
1857
1857
|
Examples:
|
|
1858
1858
|
$ drizzle-multitenant doctor
|
|
1859
1859
|
$ drizzle-multitenant doctor --json
|
|
1860
1860
|
$ drizzle-multitenant doctor --config ./custom.config.ts
|
|
1861
|
-
`).action(async n=>{let e=L(),t=R("Running diagnostics..."),r=[],a=[],o=Date.now();try{t.start(),t.text="Checking configuration...";let s=await
|
|
1861
|
+
`).action(async n=>{let e=L(),t=R("Running diagnostics..."),r=[],a=[],o=Date.now();try{t.start(),t.text="Checking configuration...";let s=await di(n.config);if(r.push(s.check),s.recommendation&&a.push(s.recommendation),!s.config){t.fail("Configuration check failed"),e.jsonMode&&N({healthy:!1,checks:r,recommendations:a,durationMs:Date.now()-o});return}let{config:i,migrationsFolder:c,tenantDiscovery:l,sharedMigrationsFolder:m}=s.config;t.text="Checking database connection...";let u=await ui(i.connection.url);r.push(u.check),u.recommendation&&a.push(u.recommendation),t.text="Checking tenant discovery...";let p=await pi(l);r.push(p.check),p.recommendation&&a.push(p.recommendation),t.text="Checking migrations folder...";let h=Xr(c,"tenant");r.push(h.check),h.recommendation&&a.push(h.recommendation),t.text="Checking shared migrations folder...";let g=Xr(m,"shared");r.push(g.check),g.recommendation&&a.push(g.recommendation),t.text="Checking schema isolation...";let $=hi(i.isolation);r.push($.check),t.text="Checking pool configuration...";let S=gi(i.isolation,p.tenantCount);r.push(S.check),S.recommendation&&a.push(S.recommendation),t.succeed("Diagnostics complete");let b=r.some(y=>y.status==="warn"),x=r.some(y=>y.status==="error");if(e.jsonMode){let y={healthy:!x,checks:r,recommendations:a,poolConfig:{maxPools:i.isolation.maxPools??50,poolTtlMs:i.isolation.poolTtlMs??36e5},durationMs:Date.now()-o};u.dbInfo&&(y.database=u.dbInfo),p.tenantCount!==void 0&&(y.tenantCount=p.tenantCount),N(y);return}d(`
|
|
1862
1862
|
`+T(G("\u{1F50D} drizzle-multitenant Doctor"))+`
|
|
1863
|
-
`);for(let
|
|
1863
|
+
`);for(let y of r){let w=fi(y.status),D=yi(y.status);d(`${w} ${D(y.name)}: ${y.message}`),y.details&&d(f(` ${y.details}`));}a.length>0&&(d(`
|
|
1864
1864
|
`+T(Z("\u26A0 Recommendations:"))+`
|
|
1865
|
-
`),a.forEach((
|
|
1866
|
-
`+T("\u{1F4CA} Summary:")),u.dbInfo&&d(` Database: PostgreSQL ${u.dbInfo.version} (${u.dbInfo.latencyMs}ms latency)`),p.tenantCount!==void 0&&d(` Tenants: ${p.tenantCount} discovered`),d(` Pool: max=${i.isolation.maxPools??50}, ttl=${
|
|
1865
|
+
`),a.forEach((y,w)=>{d(` ${w+1}. ${y.message}`),y.action&&d(f(` \u2192 ${y.action}`));})),d(`
|
|
1866
|
+
`+T("\u{1F4CA} Summary:")),u.dbInfo&&d(` Database: PostgreSQL ${u.dbInfo.version} (${u.dbInfo.latencyMs}ms latency)`),p.tenantCount!==void 0&&d(` Tenants: ${p.tenantCount} discovered`),d(` Pool: max=${i.isolation.maxPools??50}, ttl=${ea(i.isolation.poolTtlMs??36e5)}`),d(` Isolation: ${i.isolation.strategy}-based`),d(""),x?(d(v(T("\u2717 Some checks failed. Please review the issues above."))),process.exit(1)):b?d(C(T("\u26A0 All checks passed with warnings."))):d(E(T("\u2713 All checks passed!")));}catch(s){t.fail(s.message),j(s);}});async function di(n){try{let e=await k(n);return {check:{name:"Configuration",status:"ok",message:"Configuration file found and valid",details:n??"tenant.config.ts"},config:e}}catch(e){return {check:{name:"Configuration",status:"error",message:e.message},recommendation:{priority:"high",message:"Configuration file not found or invalid",action:"Run `npx drizzle-multitenant init` to create a configuration file"}}}}async function ui(n){let e=new Pool({connectionString:n}),t=Date.now();try{let r=await e.query("SELECT version()"),a=Date.now()-t,o=r.rows[0].version.match(/PostgreSQL (\d+\.\d+)/),s=o?o[1]:"unknown";return await e.end(),{check:{name:"Database Connection",status:"ok",message:`PostgreSQL ${s} connected`,details:`Response time: ${a}ms`},dbInfo:{version:s,latencyMs:a}}}catch(r){return await e.end().catch(()=>{}),{check:{name:"Database Connection",status:"error",message:r.message},recommendation:{priority:"high",message:"Cannot connect to database",action:"Verify DATABASE_URL is correct and PostgreSQL is running"}}}}async function pi(n){if(!n)return {check:{name:"Tenant Discovery",status:"warn",message:"Not configured",details:"migrations.tenantDiscovery is not defined in config"},recommendation:{priority:"medium",message:"Tenant discovery is not configured",action:"Add tenantDiscovery function to your config for automatic tenant detection"}};try{let e=await n();return e.length===0?{check:{name:"Tenant Discovery",status:"warn",message:"No tenants found"},recommendation:{priority:"low",message:"No tenants discovered",action:"Create a tenant using `npx drizzle-multitenant tenant:create --id=<tenant-id>`"},tenantCount:0}:{check:{name:"Tenant Discovery",status:"ok",message:`Found ${e.length} tenant(s)`},tenantCount:e.length}}catch(e){return {check:{name:"Tenant Discovery",status:"error",message:e.message},recommendation:{priority:"high",message:"Tenant discovery function failed",action:"Check your tenantDiscovery implementation for errors"}}}}function Xr(n,e){let t=e==="tenant"?"Migrations Folder":"Shared Migrations Folder";if(!n)return e==="shared"?{check:{name:t,status:"warn",message:"Not configured",details:"sharedMigrationsFolder is not defined in config"},recommendation:{priority:"low",message:"Shared migrations folder not configured",action:"Add sharedFolder to your migrations config to enable shared schema migrations"}}:{check:{name:t,status:"warn",message:"Not configured"},recommendation:{priority:"medium",message:"Migrations folder not configured",action:"Add migrations.folder or migrations.tenantFolder to your config"}};let r=resolve(process.cwd(),n);if(!existsSync(r))return {check:{name:t,status:"warn",message:"Folder does not exist",details:r},recommendation:{priority:"medium",message:`${e==="tenant"?"Migrations":"Shared migrations"} folder not found`,action:`Create the folder: mkdir -p ${n}`},folderInfo:{exists:false,path:r,fileCount:0}};try{let a=readdirSync(r).filter(o=>o.endsWith(".sql")&&statSync(resolve(r,o)).isFile());return {check:{name:t,status:"ok",message:`${a.length} migration file(s)`,details:n},folderInfo:{exists:!0,path:r,fileCount:a.length}}}catch(a){return {check:{name:t,status:"error",message:a.message}}}}function hi(n){if(n.strategy!=="schema")return {check:{name:"Schema Isolation",status:"warn",message:`Strategy "${n.strategy}" is not yet supported`,details:'Only "schema" strategy is currently supported'}};try{let e=n.schemaNameTemplate("test-tenant");if(!e||typeof e!="string")throw new Error("schemaNameTemplate must return a string");return {check:{name:"Schema Isolation",status:"ok",message:"schema-based isolation",details:`Template: test-tenant \u2192 ${e}`}}}catch(e){return {check:{name:"Schema Isolation",status:"error",message:e.message}}}}function gi(n,e){let t=n.maxPools??50,r=n.poolTtlMs??36e5,a=`max=${t}, ttl=${ea(r)}`;if(e!==void 0&&e>t*.8){let o=Math.ceil(e*1.5);return {check:{name:"Pool Configuration",status:"warn",message:"Pool limit may be insufficient",details:a},recommendation:{priority:"medium",message:`Current maxPools (${t}) is close to tenant count (${e})`,action:`Consider increasing maxPools to ${o} to avoid LRU evictions`}}}return {check:{name:"Pool Configuration",status:"ok",message:"Configuration looks good",details:a}}}function fi(n){switch(n){case "ok":return Y("\u2713");case "warn":return Z("\u26A0");case "error":return te("\u2717")}}function yi(n){switch(n){case "ok":return e=>e;case "warn":return Z;case "error":return te}}function ea(n){return n<1e3?`${n}ms`:n<6e4?`${Math.round(n/1e3)}s`:n<36e5?`${Math.round(n/6e4)}min`:`${Math.round(n/36e5)}h`}function bn(n){let{tableName:e,tableNamePascal:t,tableNameCamel:r,type:a}=n,o=Si(n),s=bi(n),i=Ti(n),c=xi(n),l=wi(n);return `/**
|
|
1867
1867
|
* ${t} schema
|
|
1868
1868
|
* Type: ${a}
|
|
1869
1869
|
*
|
|
@@ -1882,17 +1882,17 @@ ${s}
|
|
|
1882
1882
|
${i}
|
|
1883
1883
|
${c}
|
|
1884
1884
|
${l}
|
|
1885
|
-
`}function
|
|
1885
|
+
`}function Si(n){let{includeTimestamps:e,includeSoftDelete:t,useUuid:r,includeExample:a}=n,o=["pgTable"];return r?o.push("uuid"):o.push("serial"),o.push("text"),a&&o.push("varchar","boolean"),(e||t)&&o.push("timestamp"),o.push("index"),`import { ${[...new Set(o)].sort().join(", ")} } from 'drizzle-orm/pg-core';
|
|
1886
1886
|
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
|
|
1887
|
-
import { z } from 'zod';`}function
|
|
1888
|
-
`)}function
|
|
1887
|
+
import { z } from 'zod';`}function bi(n){let{includeTimestamps:e,includeSoftDelete:t,useUuid:r,includeExample:a}=n,o=[];return r?o.push(" id: uuid('id').primaryKey().defaultRandom(),"):o.push(" id: serial('id').primaryKey(),"),a&&(o.push(" name: varchar('name', { length: 255 }).notNull(),"),o.push(" description: text('description'),"),o.push(" isActive: boolean('is_active').notNull().default(true),")),e&&(o.push(" createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),"),o.push(" updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),")),t&&o.push(" deletedAt: timestamp('deleted_at', { withTimezone: true }),"),o.join(`
|
|
1888
|
+
`)}function Ti(n){let{tableNameCamel:e,tableName:t,includeExample:r,includeTimestamps:a}=n,o=[];return r&&(o.push(` nameIdx: index('${t}_name_idx').on(${e}.name),`),o.push(` isActiveIdx: index('${t}_is_active_idx').on(${e}.isActive),`)),a&&o.push(` createdAtIdx: index('${t}_created_at_idx').on(${e}.createdAt),`),o.length===0?"":`/**
|
|
1889
1889
|
* ${n.tableNamePascal} table indexes
|
|
1890
1890
|
*/
|
|
1891
1891
|
export const ${e}Indexes = {
|
|
1892
1892
|
${o.join(`
|
|
1893
1893
|
`)}
|
|
1894
1894
|
};
|
|
1895
|
-
`}function
|
|
1895
|
+
`}function xi(n){return `// Uncomment and modify to add relations
|
|
1896
1896
|
// import { relations } from 'drizzle-orm';
|
|
1897
1897
|
//
|
|
1898
1898
|
// export const ${n.tableNameCamel}Relations = relations(${n.tableNameCamel}, ({ one, many }) => ({
|
|
@@ -1902,7 +1902,7 @@ ${o.join(`
|
|
|
1902
1902
|
// // references: [users.id],
|
|
1903
1903
|
// // }),
|
|
1904
1904
|
// }));
|
|
1905
|
-
`}function
|
|
1905
|
+
`}function wi(n){let{tableNameCamel:e,tableNamePascal:t}=n;return `/**
|
|
1906
1906
|
* Zod schemas for validation
|
|
1907
1907
|
*/
|
|
1908
1908
|
export const insert${t}Schema = createInsertSchema(${e});
|
|
@@ -1919,7 +1919,7 @@ export type New${t} = typeof ${e}.$inferInsert;
|
|
|
1919
1919
|
*/
|
|
1920
1920
|
export type Insert${t} = z.infer<typeof insert${t}Schema>;
|
|
1921
1921
|
export type Select${t} = z.infer<typeof select${t}Schema>;
|
|
1922
|
-
`}function Tn(n){let{seedName:e,type:t,tableName:r}=n;return t==="shared"?
|
|
1922
|
+
`}function Tn(n){let{seedName:e,type:t,tableName:r}=n;return t==="shared"?Ci(e,r):$i(e,r)}function $i(n,e){let t=e?`import { ${e} } from '../../src/db/schema/tenant/${e}.js';`:"// import { yourTable } from '../../src/db/schema/tenant/yourTable.js';",r=e?Mi(e):Ri();return `/**
|
|
1923
1923
|
* Tenant seed: ${n}
|
|
1924
1924
|
*
|
|
1925
1925
|
* This seed runs in the context of each tenant's schema.
|
|
@@ -1949,7 +1949,7 @@ ${r}
|
|
|
1949
1949
|
};
|
|
1950
1950
|
|
|
1951
1951
|
export default seed;
|
|
1952
|
-
`}function
|
|
1952
|
+
`}function Ci(n,e){let t=e?`import { ${e} } from '../../src/db/schema/shared/${e}.js';`:"// import { yourTable } from '../../src/db/schema/shared/yourTable.js';",r=e?Ei(e):Di();return `/**
|
|
1953
1953
|
* Shared seed: ${n}
|
|
1954
1954
|
*
|
|
1955
1955
|
* This seed runs against the shared/public schema.
|
|
@@ -1978,7 +1978,7 @@ ${r}
|
|
|
1978
1978
|
};
|
|
1979
1979
|
|
|
1980
1980
|
export default seed;
|
|
1981
|
-
`}function
|
|
1981
|
+
`}function Mi(n){return ` // Insert data with conflict handling (idempotent)
|
|
1982
1982
|
await db.insert(${n}).values([
|
|
1983
1983
|
{
|
|
1984
1984
|
name: 'Example Item 1',
|
|
@@ -1990,7 +1990,7 @@ export default seed;
|
|
|
1990
1990
|
},
|
|
1991
1991
|
]).onConflictDoNothing();
|
|
1992
1992
|
|
|
1993
|
-
console.log(\`Seeded ${n} for tenant: \${tenantId}\`);`}function
|
|
1993
|
+
console.log(\`Seeded ${n} for tenant: \${tenantId}\`);`}function Ei(n){return ` // Insert data with conflict handling (idempotent)
|
|
1994
1994
|
await db.insert(${n}).values([
|
|
1995
1995
|
{
|
|
1996
1996
|
name: 'Example Item 1',
|
|
@@ -2002,7 +2002,7 @@ export default seed;
|
|
|
2002
2002
|
},
|
|
2003
2003
|
]).onConflictDoNothing();
|
|
2004
2004
|
|
|
2005
|
-
console.log('Seeded ${n} in shared schema');`}function
|
|
2005
|
+
console.log('Seeded ${n} in shared schema');`}function Ri(){return ` // Example: Insert initial data
|
|
2006
2006
|
// await db.insert(yourTable).values([
|
|
2007
2007
|
// { name: 'Item 1', description: 'Description 1' },
|
|
2008
2008
|
// { name: 'Item 2', description: 'Description 2' },
|
|
@@ -2015,7 +2015,7 @@ export default seed;
|
|
|
2015
2015
|
// ]);
|
|
2016
2016
|
// }
|
|
2017
2017
|
|
|
2018
|
-
console.log(\`Seed completed for tenant: \${tenantId}\`);`}function
|
|
2018
|
+
console.log(\`Seed completed for tenant: \${tenantId}\`);`}function Di(){return ` // Example: Insert shared data like plans, roles, permissions
|
|
2019
2019
|
// await db.insert(plans).values([
|
|
2020
2020
|
// { id: 'free', name: 'Free', price: 0 },
|
|
2021
2021
|
// { id: 'pro', name: 'Pro', price: 29 },
|
|
@@ -2032,7 +2032,7 @@ export default seed;
|
|
|
2032
2032
|
-- Created at: ${o}
|
|
2033
2033
|
-- Template: ${r}
|
|
2034
2034
|
|
|
2035
|
-
`;switch(r){case "create-table":return s+
|
|
2035
|
+
`;switch(r){case "create-table":return s+vi(a);case "add-column":return s+_i(a);case "add-index":return s+Pi(a);case "add-foreign-key":return s+zi(a);default:return s+ki()}}function vi(n){let e=n||"table_name";return `-- Create table: ${e}
|
|
2036
2036
|
CREATE TABLE IF NOT EXISTS "${e}" (
|
|
2037
2037
|
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
2038
2038
|
"name" VARCHAR(255) NOT NULL,
|
|
@@ -2060,7 +2060,7 @@ CREATE TRIGGER update_${e}_updated_at
|
|
|
2060
2060
|
BEFORE UPDATE ON "${e}"
|
|
2061
2061
|
FOR EACH ROW
|
|
2062
2062
|
EXECUTE FUNCTION update_updated_at_column();
|
|
2063
|
-
`}function
|
|
2063
|
+
`}function _i(n){let e=n||"table_name";return `-- Add column to: ${e}
|
|
2064
2064
|
-- ALTER TABLE "${e}" ADD COLUMN "column_name" data_type [constraints];
|
|
2065
2065
|
|
|
2066
2066
|
-- Examples:
|
|
@@ -2079,7 +2079,7 @@ CREATE TRIGGER update_${e}_updated_at
|
|
|
2079
2079
|
|
|
2080
2080
|
-- Write your column additions below:
|
|
2081
2081
|
|
|
2082
|
-
`}function
|
|
2082
|
+
`}function Pi(n){let e=n||"table_name";return `-- Add index to: ${e}
|
|
2083
2083
|
-- CREATE INDEX [CONCURRENTLY] "index_name" ON "${e}" ("column_name");
|
|
2084
2084
|
|
|
2085
2085
|
-- Examples:
|
|
@@ -2104,7 +2104,7 @@ CREATE TRIGGER update_${e}_updated_at
|
|
|
2104
2104
|
|
|
2105
2105
|
-- Write your indexes below:
|
|
2106
2106
|
|
|
2107
|
-
`}function
|
|
2107
|
+
`}function zi(n){let e=n||"table_name";return `-- Add foreign key to: ${e}
|
|
2108
2108
|
-- ALTER TABLE "${e}" ADD CONSTRAINT "fk_name" FOREIGN KEY ("column") REFERENCES "other_table" ("id");
|
|
2109
2109
|
|
|
2110
2110
|
-- Examples:
|
|
@@ -2130,7 +2130,7 @@ CREATE TRIGGER update_${e}_updated_at
|
|
|
2130
2130
|
|
|
2131
2131
|
-- Write your foreign keys below:
|
|
2132
2132
|
|
|
2133
|
-
`}function
|
|
2133
|
+
`}function ki(){return `-- Write your SQL migration here
|
|
2134
2134
|
|
|
2135
2135
|
-- Up migration (apply changes)
|
|
2136
2136
|
|
|
@@ -2139,7 +2139,7 @@ CREATE TRIGGER update_${e}_updated_at
|
|
|
2139
2139
|
-- 1. Idempotency: Use IF NOT EXISTS / IF EXISTS where possible
|
|
2140
2140
|
-- 2. Transactions: This will run in a transaction
|
|
2141
2141
|
-- 3. Rollback: Plan how to undo these changes if needed
|
|
2142
|
-
`}function wn(n){let e=[/^create[-_](.+)$/i,/^add[-_](.+?)[-_]?(?:table)?$/i,/^(?:add|create)[-_](?:index|fk|constraint)[-_](?:to|on)[-_](.+)$/i,/^(.+?)[-_](?:table|schema)$/i];for(let t of e){let r=n.match(t);if(r?.[1])return r[1].toLowerCase().replace(/[-\s]+/g,"_").replace(/s$/,"")}}function $n(n){let e=n.toLowerCase();return e.includes("create")||e.includes("table")?"create-table":e.includes("index")||e.includes("idx")?"add-index":e.includes("fk")||e.includes("foreign")||e.includes("reference")?"add-foreign-key":e.includes("add")||e.includes("column")?"add-column":"blank"}function Dn(n){let e=n.replace(/([a-z])([A-Z])/g,"$1_$2").replace(/[-\s]+/g,"_").toLowerCase(),t=e.split("_").map(a=>a.charAt(0).toUpperCase()+a.slice(1)).join(""),r=t.charAt(0).toLowerCase()+t.slice(1);return {snake:e,pascal:t,camel:r}}var Oe={schemaDir:"src/db/schema",seedDir:"drizzle/seeds",tenantMigrationsDir:"drizzle/tenant-migrations",sharedMigrationsDir:"drizzle/shared-migrations"};async function vn(n){let{name:e,type:t,outputDir:r,includeExample:a=true,includeTimestamps:o=true,includeSoftDelete:s=false,useUuid:i=true}=n;try{let c=Dn(e),l={tableName:c.snake,tableNamePascal:c.pascal,tableNameCamel:c.camel,type:t,includeTimestamps:o,includeSoftDelete:s,useUuid:i,includeExample:a},m=bn(l),u=r||join(Oe.schemaDir,t),p=`${c.camel}.ts`,h=resolve(process.cwd(),u,p);return await mkdir(dirname(h),{recursive:!0}),existsSync(h)?{success:!1,filePath:h,fileName:p,kind:"schema",type:t,error:`File already exists: ${h}`}:(await writeFile(h,m,"utf-8"),{success:!0,filePath:h,fileName:p,kind:"schema",type:t})}catch(c){return {success:false,filePath:"",fileName:"",kind:"schema",type:t,error:c instanceof Error?c.message:String(c)}}}async function
|
|
2142
|
+
`}function wn(n){let e=[/^create[-_](.+)$/i,/^add[-_](.+?)[-_]?(?:table)?$/i,/^(?:add|create)[-_](?:index|fk|constraint)[-_](?:to|on)[-_](.+)$/i,/^(.+?)[-_](?:table|schema)$/i];for(let t of e){let r=n.match(t);if(r?.[1])return r[1].toLowerCase().replace(/[-\s]+/g,"_").replace(/s$/,"")}}function $n(n){let e=n.toLowerCase();return e.includes("create")||e.includes("table")?"create-table":e.includes("index")||e.includes("idx")?"add-index":e.includes("fk")||e.includes("foreign")||e.includes("reference")?"add-foreign-key":e.includes("add")||e.includes("column")?"add-column":"blank"}function Dn(n){let e=n.replace(/([a-z])([A-Z])/g,"$1_$2").replace(/[-\s]+/g,"_").toLowerCase(),t=e.split("_").map(a=>a.charAt(0).toUpperCase()+a.slice(1)).join(""),r=t.charAt(0).toLowerCase()+t.slice(1);return {snake:e,pascal:t,camel:r}}var Oe={schemaDir:"src/db/schema",seedDir:"drizzle/seeds",tenantMigrationsDir:"drizzle/tenant-migrations",sharedMigrationsDir:"drizzle/shared-migrations"};async function vn(n){let{name:e,type:t,outputDir:r,includeExample:a=true,includeTimestamps:o=true,includeSoftDelete:s=false,useUuid:i=true}=n;try{let c=Dn(e),l={tableName:c.snake,tableNamePascal:c.pascal,tableNameCamel:c.camel,type:t,includeTimestamps:o,includeSoftDelete:s,useUuid:i,includeExample:a},m=bn(l),u=r||join(Oe.schemaDir,t),p=`${c.camel}.ts`,h=resolve(process.cwd(),u,p);return await mkdir(dirname(h),{recursive:!0}),existsSync(h)?{success:!1,filePath:h,fileName:p,kind:"schema",type:t,error:`File already exists: ${h}`}:(await writeFile(h,m,"utf-8"),{success:!0,filePath:h,fileName:p,kind:"schema",type:t})}catch(c){return {success:false,filePath:"",fileName:"",kind:"schema",type:t,error:c instanceof Error?c.message:String(c)}}}async function _n(n){let{name:e,type:t,outputDir:r,tableName:a}=n;try{let o=Dn(e),s={seedName:o.camel,type:t,...a!==void 0&&{tableName:a}},i=Tn(s),c=r||join(Oe.seedDir,t),l=`${o.camel}.ts`,m=resolve(process.cwd(),c,l);return await mkdir(dirname(m),{recursive:!0}),existsSync(m)?{success:!1,filePath:m,fileName:l,kind:"seed",type:t,error:`File already exists: ${m}`}:(await writeFile(m,i,"utf-8"),{success:!0,filePath:m,fileName:l,kind:"seed",type:t})}catch(o){return {success:false,filePath:"",fileName:"",kind:"seed",type:t,error:o instanceof Error?o.message:String(o)}}}async function Pn(n){let{name:e,type:t,outputDir:r,template:a}=n;try{let o=t==="shared"?Oe.sharedMigrationsDir:Oe.tenantMigrationsDir,s=r||o,i=resolve(process.cwd(),s);await mkdir(i,{recursive:!0});let l=(existsSync(i)?await readdir(i):[]).filter(y=>y.endsWith(".sql")),m=0;for(let y of l){let w=y.match(/^(\d+)_/);w?.[1]&&(m=Math.max(m,parseInt(w[1],10)));}let u=(m+1).toString().padStart(4,"0"),p=e.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,""),h=`${u}_${p}.sql`,g=join(i,h),$=a||$n(e),S=wn(e),b={migrationName:e,type:t,template:$,...S!==void 0&&{tableName:S}},x=xn(b);return existsSync(g)?{success:!1,filePath:g,fileName:h,kind:"migration",type:t,error:`File already exists: ${g}`}:(await writeFile(g,x,"utf-8"),{success:!0,filePath:g,fileName:h,kind:"migration",type:t})}catch(o){return {success:false,filePath:"",fileName:"",kind:"migration",type:t,error:o instanceof Error?o.message:String(o)}}}function zn(){return [{value:"create-table",label:"Create Table",description:"Template for creating a new table with common columns"},{value:"add-column",label:"Add Column",description:"Template for adding columns to an existing table"},{value:"add-index",label:"Add Index",description:"Template for creating indexes"},{value:"add-foreign-key",label:"Add Foreign Key",description:"Template for adding foreign key constraints"},{value:"blank",label:"Blank",description:"Empty migration with basic comments"}]}var kn=new Command("scaffold:schema").description("Generate a new Drizzle schema file").argument("<name>","Schema/table name (e.g., orders, user-profiles)").option("-t, --type <type>","Schema type: tenant or shared","tenant").option("-o, --output <path>","Output directory").option("--no-timestamps","Do not include createdAt/updatedAt columns").option("--soft-delete","Include deletedAt column for soft delete").option("--no-uuid","Use serial instead of UUID for primary key").option("--no-example","Do not include example columns").option("-i, --interactive","Run in interactive mode").addHelpText("after",`
|
|
2143
2143
|
Examples:
|
|
2144
2144
|
$ drizzle-multitenant scaffold:schema orders --type=tenant
|
|
2145
2145
|
$ drizzle-multitenant scaffold:schema plans --type=shared
|
|
@@ -2152,7 +2152,7 @@ Examples:
|
|
|
2152
2152
|
$ drizzle-multitenant scaffold:seed initial --type=tenant
|
|
2153
2153
|
$ drizzle-multitenant scaffold:seed plans --type=shared --table=plans
|
|
2154
2154
|
$ drizzle-multitenant scaffold:seed demo-data -t tenant -i
|
|
2155
|
-
`).action(async(n,e)=>{let t=R("Generating seed...");try{let r=e.type,a=e.table;e.interactive&&(r=await select({message:"Seed type:",choices:[{value:"tenant",name:"Tenant - Runs per-tenant"},{value:"shared",name:"Shared - Runs once for shared schema"}]}),await confirm({message:"Include table import template?",default:!1})&&(a=await input({message:"Table name:",default:n}))),t.start();let o=await
|
|
2155
|
+
`).action(async(n,e)=>{let t=R("Generating seed...");try{let r=e.type,a=e.table;e.interactive&&(r=await select({message:"Seed type:",choices:[{value:"tenant",name:"Tenant - Runs per-tenant"},{value:"shared",name:"Shared - Runs once for shared schema"}]}),await confirm({message:"Include table import template?",default:!1})&&(a=await input({message:"Table name:",default:n}))),t.start();let o=await _n({name:n,type:r,outputDir:e.output,tableName:a});if(L().jsonMode){N({success:o.success,kind:"seed",type:o.type,filePath:o.filePath,fileName:o.fileName,error:o.error}),o.success||process.exit(1);return}o.success||(t.fail(o.error||"Failed to generate seed"),process.exit(1)),t.succeed("Seed generated"),console.log(`
|
|
2156
2156
|
`+E(`Created: ${f(o.filePath)}`)),console.log(f(`
|
|
2157
2157
|
Next steps:`)),console.log(f(" 1. Edit the seed file with your data")),console.log(r==="tenant"?f(" 2. Run: npx drizzle-multitenant seed --file="+o.filePath+" --all"):f(" 2. Run: npx drizzle-multitenant seed:shared --file="+o.filePath));}catch(r){t.fail("Failed to generate seed"),j(r);}}),In=new Command("scaffold:migration").description("Generate a new migration file with template").argument("<name>","Migration name (e.g., add-orders, create-users)").option("-t, --type <type>","Migration type: tenant or shared","tenant").option("-o, --output <path>","Output directory (overrides config)").option("--template <template>","Template: create-table, add-column, add-index, add-foreign-key, blank").option("-i, --interactive","Run in interactive mode").addHelpText("after",`
|
|
2158
2158
|
Examples:
|
|
@@ -2160,9 +2160,9 @@ Examples:
|
|
|
2160
2160
|
$ drizzle-multitenant scaffold:migration create-plans --type=shared --template=create-table
|
|
2161
2161
|
$ drizzle-multitenant scaffold:migration add-user-index -t tenant --template=add-index
|
|
2162
2162
|
$ drizzle-multitenant scaffold:migration my-migration -i # interactive mode
|
|
2163
|
-
`).action(async(n,e)=>{let t=R("Generating migration...");try{let r=e.type,a=e.template;if(e.interactive){r=await select({message:"Migration type:",choices:[{value:"tenant",name:"Tenant - Applied to all tenant schemas"},{value:"shared",name:"Shared - Applied to public/shared schema"}]});let i=
|
|
2163
|
+
`).action(async(n,e)=>{let t=R("Generating migration...");try{let r=e.type,a=e.template;if(e.interactive){r=await select({message:"Migration type:",choices:[{value:"tenant",name:"Tenant - Applied to all tenant schemas"},{value:"shared",name:"Shared - Applied to public/shared schema"}]});let i=zn();a=await select({message:"Template:",choices:i.map(c=>({value:c.value,name:`${c.label} - ${c.description}`}))});}t.start();let o=await Pn({name:n,type:r,outputDir:e.output,template:a});if(L().jsonMode){N({success:o.success,kind:"migration",type:o.type,filePath:o.filePath,fileName:o.fileName,error:o.error}),o.success||process.exit(1);return}o.success||(t.fail(o.error||"Failed to generate migration"),process.exit(1)),t.succeed("Migration generated"),console.log(`
|
|
2164
2164
|
`+E(`Created: ${f(o.filePath)}`)),console.log(f(`
|
|
2165
|
-
Next steps:`)),console.log(f(" 1. Edit the migration file with your SQL")),console.log(r==="tenant"?f(" 2. Run: npx drizzle-multitenant migrate --all"):f(" 2. Run: npx drizzle-multitenant migrate:shared"));}catch(r){t.fail("Failed to generate migration"),j(r);}}),
|
|
2165
|
+
Next steps:`)),console.log(f(" 1. Edit the migration file with your SQL")),console.log(r==="tenant"?f(" 2. Run: npx drizzle-multitenant migrate --all"):f(" 2. Run: npx drizzle-multitenant migrate:shared"));}catch(r){t.fail("Failed to generate migration"),j(r);}}),yt=new Command("scaffold").description("Scaffold boilerplate code for schemas, seeds, and migrations").addHelpText("after",`
|
|
2166
2166
|
Available scaffold commands:
|
|
2167
2167
|
scaffold:schema Generate a new Drizzle schema file
|
|
2168
2168
|
scaffold:seed Generate a new seed file
|
|
@@ -2174,7 +2174,7 @@ Examples:
|
|
|
2174
2174
|
$ drizzle-multitenant scaffold:migration add-orders --type=tenant
|
|
2175
2175
|
|
|
2176
2176
|
Use "drizzle-multitenant scaffold:<type> --help" for more information.
|
|
2177
|
-
`).action(()=>{
|
|
2177
|
+
`).action(()=>{yt.help();});var An=new Command("lint").description("Validate schemas against configurable rules").option("-c, --config <path>","Path to config file").option("--tenant-schema <path>","Path to tenant schema directory").option("--shared-schema <path>","Path to shared schema directory").option("--format <format>","Output format: console, json, github","console").option("--fix","Attempt to fix issues automatically (not implemented)").option("--rule <rules...>","Enable specific rules (e.g., --rule require-primary-key)").option("--ignore-rule <rules...>","Disable specific rules").addHelpText("after",`
|
|
2178
2178
|
Examples:
|
|
2179
2179
|
$ drizzle-multitenant lint
|
|
2180
2180
|
$ drizzle-multitenant lint --json
|
|
@@ -2197,7 +2197,7 @@ Available Rules:
|
|
|
2197
2197
|
Security:
|
|
2198
2198
|
no-cascade-delete Warn about CASCADE DELETE on foreign keys
|
|
2199
2199
|
require-soft-delete Require soft delete column on tables
|
|
2200
|
-
`).action(async n=>{let e=L(),t=R("Linting schemas...");try{let a,o,s;n.tenantSchema&&(a=resolve(process.cwd(),n.tenantSchema)),n.sharedSchema&&(o=resolve(process.cwd(),n.sharedSchema));try{let u=await
|
|
2200
|
+
`).action(async n=>{let e=L(),t=R("Linting schemas...");try{let a,o,s;n.tenantSchema&&(a=resolve(process.cwd(),n.tenantSchema)),n.sharedSchema&&(o=resolve(process.cwd(),n.sharedSchema));try{let u=await k(n.config);if(!a&&u.config.schemas.tenant){let p=["./src/db/schema/tenant","./src/schema/tenant","./drizzle/schema/tenant","./db/schema/tenant"];for(let h of p){let g=resolve(process.cwd(),h);if(existsSync(g)){a=g;break}}}if(!o&&u.config.schemas.shared){let p=["./src/db/schema/shared","./src/schema/shared","./drizzle/schema/shared","./db/schema/shared"];for(let h of p){let g=resolve(process.cwd(),h);if(existsSync(g)){o=g;break}}}u.config.lint&&(s=u.config.lint.rules);}catch{}let i={...s};if(n.rule)for(let u of n.rule)i[u]="error";if(n.ignoreRule)for(let u of n.ignoreRule)i[u]="off";!a&&!o&&(t.fail("No schema directories found"),d(""),d("Specify schema directories using:"),d(" --tenant-schema <path> Path to tenant schema directory"),d(" --shared-schema <path> Path to shared schema directory"),d(""),d("Or create schemas in one of these locations:"),d(" ./src/db/schema/tenant"),d(" ./src/db/schema/shared"),process.exit(1)),t.start();let l=await Pe({rules:i}).lintDirectories({tenant:a,shared:o});t.stop();let m=n.format??"console";if(e.jsonMode||m==="json"){let u={passed:l.passed,summary:l.summary,files:l.files,durationMs:l.durationMs};N(u);}else if(m==="github"){let u=dt(l,{format:"github",colors:!1});console.log(u);}else {let u=dt(l,{format:"console",colors:!n.noColor,verbose:n.verbose});console.log(u);}l.passed||process.exit(1);}catch(a){t.fail(a.message),j(a);}});var Fn=new Command("metrics").description("Display current pool and health metrics").option("-c, --config <path>","Path to config file").option("--health","Include health check (can be slow)",false).option("--prometheus","Output in Prometheus text format").option("--prefix <prefix>","Metric name prefix (default: drizzle_multitenant)").option("-w, --watch","Watch mode - refresh metrics periodically",false).option("-i, --interval <ms>","Refresh interval in ms (default: 5000)","5000").addHelpText("after",`
|
|
2201
2201
|
Examples:
|
|
2202
2202
|
$ drizzle-multitenant metrics
|
|
2203
2203
|
$ drizzle-multitenant metrics --health
|
|
@@ -2205,25 +2205,25 @@ Examples:
|
|
|
2205
2205
|
$ drizzle-multitenant metrics --json
|
|
2206
2206
|
$ drizzle-multitenant metrics --watch --interval 3000
|
|
2207
2207
|
$ drizzle-multitenant metrics --prometheus | curl -X POST --data-binary @- http://pushgateway:9091/metrics/job/drizzle
|
|
2208
|
-
`).action(async n=>{let e=L(),t=R("Loading configuration..."),r=null;try{t.start();let{config:a}=await
|
|
2208
|
+
`).action(async n=>{let e=L(),t=R("Loading configuration..."),r=null;try{t.start();let{config:a}=await k(n.config);r=new xe(a);let o=new we(r),s=n.prometheus?new $e({prefix:n.prefix}):null;if(t.succeed("Configuration loaded"),n.watch&&!e.jsonMode){await Oi(o,s,n,()=>r?.dispose());return}let i=Date.now();t.text="Collecting metrics...",t.start();let c=await o.collect({includeHealth:n.health}),l=o.calculateSummary(c),m=Date.now()-i;if(t.succeed("Metrics collected"),n.prometheus){let u=o.getRuntimeMetrics(),p=s.export(c,u);process.stdout.write(p);return}if(e.jsonMode){let u={pools:{total:c.pools.pools.total,maxPools:c.pools.pools.maxPools,tenants:c.pools.pools.tenants.map(p=>({tenantId:p.tenantId,schemaName:p.schemaName,connections:p.connections,lastAccessedAt:p.lastAccessedAt}))},shared:{initialized:c.pools.shared.initialized,connections:c.pools.shared.connections},health:c.health?{healthy:c.health.healthy,totalPools:c.health.totalPools,degradedPools:c.health.degradedPools,unhealthyPools:c.health.unhealthyPools,sharedDbStatus:c.health.sharedDb,pools:c.health.pools.map(p=>({tenantId:p.tenantId,status:p.status,responseTimeMs:p.responseTimeMs}))}:void 0,summary:l,timestamp:c.collectedAt,durationMs:m};N(u);return}ra(c,l,m,n.health??!1);}catch(a){t.fail(a.message),j(a);}finally{r&&!n.watch&&await r.dispose();}});function ra(n,e,t,r){if(d(`
|
|
2209
2209
|
`+T(G("\u{1F4CA} drizzle-multitenant Metrics"))+`
|
|
2210
2210
|
`),d(T("Pool Status:")),d(` Active Pools: ${G(String(n.pools.pools.total))} / ${n.pools.pools.maxPools}`),d(` Total Connections: ${e.totalConnections}`),d(` Idle Connections: ${Y(String(e.idleConnections))}`),d(` Waiting Requests: ${e.waitingRequests>0?Z(String(e.waitingRequests)):String(e.waitingRequests)}`),d(`
|
|
2211
2211
|
`+T("Shared Database:")),n.pools.shared.initialized){let o=n.pools.shared.connections;d(` Status: ${Y("Initialized")}`),d(` Connections: ${o.total} total, ${o.idle} idle, ${o.waiting} waiting`);}else d(` Status: ${f("Not initialized")}`);if(r&&n.health){d(`
|
|
2212
|
-
`+T("Health Status:"));let o=n.health.healthy?Y("\u2713"):te("\u2717");d(` Overall: ${o} ${n.health.healthy?"Healthy":"Unhealthy"}`),d(` Pools: ${Y(String(e.healthyPools))} healthy, ${Z(String(e.degradedPools))} degraded, ${te(String(e.unhealthyPools))} unhealthy`),d(` Shared DB: ${
|
|
2213
|
-
`+T("Active Tenants:"));for(let o of a){let s=r&&n.health?n.health.pools.find(l=>l.tenantId===o.tenantId):null,i=s?
|
|
2212
|
+
`+T("Health Status:"));let o=n.health.healthy?Y("\u2713"):te("\u2717");d(` Overall: ${o} ${n.health.healthy?"Healthy":"Unhealthy"}`),d(` Pools: ${Y(String(e.healthyPools))} healthy, ${Z(String(e.degradedPools))} degraded, ${te(String(e.unhealthyPools))} unhealthy`),d(` Shared DB: ${ji(n.health.sharedDb)(n.health.sharedDb)}`),n.health.sharedDbResponseTimeMs!==void 0&&d(` Shared DB Latency: ${n.health.sharedDbResponseTimeMs}ms`),d(` Check Duration: ${n.health.durationMs}ms`);}let a=n.pools.pools.tenants;if(a.length>0&&a.length<=20){d(`
|
|
2213
|
+
`+T("Active Tenants:"));for(let o of a){let s=r&&n.health?n.health.pools.find(l=>l.tenantId===o.tenantId):null,i=s?Li(s.status):f("\u25CB"),c=o.connections.waiting>0?Z:l=>l;d(` ${i} ${G(o.tenantId)}: ${o.connections.total} conn, ${o.connections.idle} idle, ${c(String(o.connections.waiting))} waiting`+(s?.responseTimeMs?f(` (${s.responseTimeMs}ms)`):""));}}else a.length>20&&d(`
|
|
2214
2214
|
`+f(`(${a.length} active tenants - use --json for full list)`));d(`
|
|
2215
|
-
`+f(`Collected at ${n.collectedAt} (${t}ms)`)),r||d(f("Tip: Use --health to include health checks"));}async function
|
|
2215
|
+
`+f(`Collected at ${n.collectedAt} (${t}ms)`)),r||d(f("Tip: Use --health to include health checks"));}async function Oi(n,e,t,r){let a=parseInt(t.interval??"5000",10);d(T(G(`
|
|
2216
2216
|
\u{1F4CA} Metrics Watch Mode`))+f(` (refresh every ${a}ms, Ctrl+C to exit)
|
|
2217
|
-
`));let o=async()=>{let c=Date.now(),l=await n.collect({includeHealth:t.health}),m=n.calculateSummary(l),u=Date.now()-c;if(process.stdout.write("\x1B[2J\x1B[0f"),e){let p=n.getRuntimeMetrics();process.stdout.write(e.export(l,p));}else
|
|
2218
|
-
`+f("Stopping watch mode...")),await r(),process.exit(0);};process.on("SIGINT",i),process.on("SIGTERM",i),await new Promise(()=>{});}function
|
|
2219
|
-
`)}function
|
|
2220
|
-
`)}function
|
|
2221
|
-
`)}function
|
|
2222
|
-
`)}function
|
|
2223
|
-
`)}};function
|
|
2224
|
-
`)}generateTableDefinition(e,t){let r=[],a=Ln(e.name);r.push(` ${a} {`);for(let o of e.columns){let s=[];if(t.includeDataTypes!==false&&s.push(
|
|
2225
|
-
`)}function
|
|
2226
|
-
`)}async function
|
|
2217
|
+
`));let o=async()=>{let c=Date.now(),l=await n.collect({includeHealth:t.health}),m=n.calculateSummary(l),u=Date.now()-c;if(process.stdout.write("\x1B[2J\x1B[0f"),e){let p=n.getRuntimeMetrics();process.stdout.write(e.export(l,p));}else ra(l,m,u,t.health??false);};await o();let s=setInterval(o,a),i=async()=>{clearInterval(s),d(`
|
|
2218
|
+
`+f("Stopping watch mode...")),await r(),process.exit(0);};process.on("SIGINT",i),process.on("SIGTERM",i),await new Promise(()=>{});}function Li(n){switch(n){case "ok":return Y("\u25CF");case "degraded":return Z("\u25D0");case "unhealthy":return te("\u25CB")}}function ji(n){switch(n){case "ok":return Y;case "degraded":return Z;case "unhealthy":return te}}function qi(n){let e=n.toLowerCase();if(e==="uuid")return {type:"string",format:"uuid"};if(e==="text"||e==="string")return {type:"string"};if(e.startsWith("varchar")||e.startsWith("character varying")){let t=e.match(/\((\d+)\)/),r=t?.[1]?parseInt(t[1],10):void 0;return {type:"string",...r&&{maxLength:r}}}if(e.startsWith("char")||e.startsWith("character")){let t=e.match(/\((\d+)\)/),r=t?.[1]?parseInt(t[1],10):void 0;return {type:"string",...r&&{maxLength:r}}}return e==="integer"||e==="int"||e==="int4"?{type:"integer",minimum:-2147483648,maximum:2147483647}:e==="smallint"||e==="int2"?{type:"integer",minimum:-32768,maximum:32767}:e==="bigint"||e==="int8"?{type:"integer"}:e==="serial"||e==="serial4"?{type:"integer",minimum:1,maximum:2147483647}:e==="bigserial"||e==="serial8"?{type:"integer",minimum:1}:e==="smallserial"||e==="serial2"?{type:"integer",minimum:1,maximum:32767}:e==="real"||e==="float4"||e==="float"?{type:"number"}:e==="double precision"||e==="float8"||e==="double"?{type:"number"}:e.startsWith("numeric")||e.startsWith("decimal")?{type:"number"}:e==="boolean"||e==="bool"?{type:"boolean"}:e==="date"?{type:"string",format:"date"}:e.startsWith("timestamp")?{type:"string",format:"date-time"}:e==="time"||e.startsWith("time ")?{type:"string",format:"time"}:e==="interval"?{type:"string"}:e==="json"||e==="jsonb"?{type:["object","array","string","number","boolean","null"]}:e.endsWith("[]")?{type:"array"}:e.startsWith("enum")?{type:"string"}:e==="bytea"?{type:"string",format:"byte"}:e==="inet"||e==="cidr"?{type:"string"}:e==="macaddr"||e==="macaddr8"?{type:"string"}:{type:"string"}}function aa(n){return n.split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}function Ui(n){let e={},t=[];for(let r of n.columns){let o={...qi(r.dataType)};if(r.isPrimaryKey?o.description="Primary key":r.references&&(o.description=`Foreign key to ${r.references.table}.${r.references.column}`,o.$ref=`#/definitions/${aa(r.references.table)}`),r.hasDefault&&r.defaultValue!=null&&r.defaultValue!==""){let s=r.defaultValue;s==="true"||s==="false"?o.default=s==="true":isNaN(Number(s))?s.startsWith("'")&&s.endsWith("'")&&(o.default=s.slice(1,-1)):o.default=Number(s);}r.isNullable&&typeof o.type=="string"&&(o.type=[o.type,"null"]),e[r.name]=o,!r.isNullable&&!r.hasDefault&&t.push(r.name);}return {type:"object",description:`Schema for ${n.name} table (${n.schemaType})`,properties:e,required:t,additionalProperties:false}}var je=class{export(e,t){let r={};for(let o of e){let s=aa(o.name);r[s]=Ui(o);}let a={$schema:"http://json-schema.org/draft-07/schema#",...t.projectName&&{$id:`${t.projectName}/schemas`},title:t.projectName?`${t.projectName} Database Schemas`:"Database Schemas",description:"Auto-generated JSON Schema from Drizzle ORM schema definitions",definitions:r};return JSON.stringify(a,null,2)}};function On(n,e){let t=n.toLowerCase(),r;if(t==="uuid")r="string";else if(t==="text"||t==="string")r="string";else if(t.startsWith("varchar")||t.startsWith("character varying")||t.startsWith("char")||t.startsWith("character"))r="string";else if(t==="integer"||t==="int"||t==="int4"||t==="smallint"||t==="int2"||t==="serial"||t==="serial4"||t==="smallserial"||t==="serial2")r="number";else if(t==="bigint"||t==="int8"||t==="bigserial"||t==="serial8")r="string";else if(t==="real"||t==="float4"||t==="float"||t==="double precision"||t==="float8"||t==="double"||t.startsWith("numeric")||t.startsWith("decimal"))r="number";else if(t==="boolean"||t==="bool")r="boolean";else if(t==="date")r="string";else if(t.startsWith("timestamp"))r="Date";else if(t==="time"||t.startsWith("time "))r="string";else if(t==="interval")r="string";else if(t==="json"||t==="jsonb")r="unknown";else if(t.endsWith("[]")){let a=t.slice(0,-2);r=`${On(a,false)}[]`;}else t.startsWith("enum")?r="string":t==="bytea"?r="Buffer":r="unknown";return e?`${r} | null`:r}function bt(n){return n.split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}function Wi(n){let e=bt(n);return e.charAt(0).toLowerCase()+e.slice(1)}function Hi(n){let e=[];return n.isPrimaryKey&&e.push("Primary key"),n.references&&e.push(`Foreign key to ${n.references.table}.${n.references.column}`),n.hasDefault&&n.defaultValue!==null&&e.push(`Default: ${n.defaultValue}`),e.length===0?null:e.length===1?` /** ${e[0]} */`:[" /**",...e.map(t=>` * ${t}`)," */"].join(`
|
|
2219
|
+
`)}function oa(n){let e=bt(n.name),t=[];t.push("/**"),t.push(` * ${n.name} table schema (${n.schemaType})`),t.push(" */"),t.push(`export interface ${e} {`);for(let r of n.columns){let a=Hi(r);a&&t.push(a);let o=On(r.dataType,r.isNullable),s=r.hasDefault||r.isNullable?"?":"";t.push(` ${r.name}${s}: ${o};`);}return t.push("}"),t.join(`
|
|
2220
|
+
`)}function sa(n){let t=`New${bt(n.name)}`,r=[];r.push("/**"),r.push(` * Insert type for ${n.name}`),r.push(" */"),r.push(`export interface ${t} {`);for(let a of n.columns){let o=On(a.dataType,a.isNullable),s=a.hasDefault||a.isNullable?"?":"";r.push(` ${a.name}${s}: ${o};`);}return r.push("}"),r.join(`
|
|
2221
|
+
`)}function ia(n){let e=`${Wi(n.name)}Schema`,t=[];t.push("/**"),t.push(` * Zod schema for ${n.name}`),t.push(" */"),t.push(`export const ${e} = z.object({`);for(let r of n.columns){let a=ca(r.dataType);r.isNullable&&(a=`${a}.nullable()`),r.hasDefault&&(a=`${a}.optional()`),t.push(` ${r.name}: ${a},`);}return t.push("});"),t.push(""),t.push(`export type ${bt(n.name)}Validated = z.infer<typeof ${e}>;`),t.join(`
|
|
2222
|
+
`)}function ca(n){let e=n.toLowerCase();if(e==="uuid")return "z.string().uuid()";if(e==="text"||e==="string")return "z.string()";if(e.startsWith("varchar")||e.startsWith("character varying")||e.startsWith("char")){let t=e.match(/\((\d+)\)/);return t?`z.string().max(${t[1]})`:"z.string()"}if(e==="integer"||e==="int"||e==="int4"||e==="smallint"||e==="serial")return "z.number().int()";if(e==="bigint"||e==="int8"||e==="bigserial")return "z.string()";if(e==="real"||e==="float4"||e==="float"||e==="double precision"||e.startsWith("numeric")||e.startsWith("decimal"))return "z.number()";if(e==="boolean"||e==="bool")return "z.boolean()";if(e==="date")return "z.string().date()";if(e.startsWith("timestamp"))return "z.date()";if(e==="json"||e==="jsonb")return "z.unknown()";if(e.endsWith("[]")){let t=e.slice(0,-2);return `z.array(${ca(t)})`}return "z.unknown()"}var qe=class{export(e,t){let r=t.typescript??{},a=[];a.push("/**"),a.push(" * Auto-generated TypeScript types from Drizzle ORM schemas"),a.push(` * Generated at: ${new Date().toISOString()}`),t.projectName&&a.push(` * Project: ${t.projectName}`),a.push(" */"),a.push(""),r.includeZod&&(a.push("import { z } from 'zod';"),a.push(""));let o=e.filter(i=>i.schemaType==="tenant"),s=e.filter(i=>i.schemaType==="shared");if(o.length>0){a.push("// ================================"),a.push("// Tenant Schema Types"),a.push("// ================================"),a.push("");for(let i of o)r.includeSelectTypes!==false&&(a.push(oa(i)),a.push("")),r.includeInsertTypes!==false&&(a.push(sa(i)),a.push("")),r.includeZod&&(a.push(ia(i)),a.push(""));}if(s.length>0){a.push("// ================================"),a.push("// Shared Schema Types"),a.push("// ================================"),a.push("");for(let i of s)r.includeSelectTypes!==false&&(a.push(oa(i)),a.push("")),r.includeInsertTypes!==false&&(a.push(sa(i)),a.push("")),r.includeZod&&(a.push(ia(i)),a.push(""));}return a.join(`
|
|
2223
|
+
`)}};function Bi(n){let e=n.toLowerCase();return e==="uuid"?"uuid":e==="text"||e==="string"?"text":e.startsWith("varchar")||e.startsWith("character varying")?"varchar":e.startsWith("char")||e.startsWith("character")?"char":e==="integer"||e==="int"||e==="int4"?"int":e==="smallint"||e==="int2"?"smallint":e==="bigint"||e==="int8"?"bigint":e==="serial"||e==="serial4"?"serial":e==="bigserial"||e==="serial8"?"bigserial":e==="real"||e==="float4"||e==="float"?"float":e==="double precision"||e==="float8"?"double":e.startsWith("numeric")||e.startsWith("decimal")?"decimal":e==="boolean"||e==="bool"?"bool":e==="date"?"date":e.startsWith("timestamp")?"timestamp":e==="time"||e.startsWith("time ")?"time":e==="json"||e==="jsonb"?"json":e==="bytea"?"binary":e}function Gi(n){return n.isPrimaryKey&&n.references?"PK,FK":n.isPrimaryKey?"PK":n.references?"FK":""}function Ln(n){return n.replace(/[^a-zA-Z0-9_]/g,"_")}function Ji(n,e){return "||--o{"}var Ue=class{export(e,t){let r=t.mermaid??{},a=[];a.push("```mermaid"),r.theme&&r.theme!=="default"&&a.push(`%%{init: {'theme': '${r.theme}'}}%%`),a.push("erDiagram"),a.push("");let o=e.filter(c=>c.schemaType==="tenant"),s=e.filter(c=>c.schemaType==="shared");if(o.length>0){a.push(" %% Tenant Schema Tables");for(let c of o)a.push(...this.generateTableDefinition(c,r));a.push("");}if(s.length>0){a.push(" %% Shared Schema Tables");for(let c of s)a.push(...this.generateTableDefinition(c,r));a.push("");}let i=this.generateRelationships(e);return i.length>0&&(a.push(" %% Relationships"),a.push(...i)),a.push("```"),a.join(`
|
|
2224
|
+
`)}generateTableDefinition(e,t){let r=[],a=Ln(e.name);r.push(` ${a} {`);for(let o of e.columns){let s=[];if(t.includeDataTypes!==false&&s.push(Bi(o.dataType)),s.push(o.name),t.showPrimaryKeys!==false||t.showForeignKeys!==false){let i=Gi(o);i&&s.push(i);}o.isNullable&&s.push('"nullable"'),r.push(` ${s.join(" ")}`);}if(r.push(" }"),t.includeIndexes&&e.indexes.length>0){let o=e.indexes.map(s=>{let i=s.isUnique?"(unique)":"";return `${s.name}${i}`});r.push(` %% Indexes: ${o.join(", ")}`);}return r}generateRelationships(e){let t=[],r=new Map(e.map(a=>[a.name,a]));for(let a of e)for(let o of a.columns){if(!o.references||!r.get(o.references.table))continue;let i=Ln(a.name),c=Ln(o.references.table),l=Ji(),m=`"${o.name} -> ${o.references.column}"`;t.push(` ${c} ${l} ${i} : ${m}`);}return t}};function Tt(n){return {name:n.name,schemaType:n.schemaType,columns:n.columns,indexes:n.indexes,filePath:n.filePath}}var xt=class{jsonExporter=new je;tsExporter=new qe;mermaidExporter=new Ue;async exportFromDirectories(e,t){let r=[];if(e.tenantDir){let a=await ue(e.tenantDir,"tenant");for(let o of a){let s=await pe(o.filePath,"tenant");r.push(...s.map(Tt));}}if(e.sharedDir){let a=await ue(e.sharedDir,"shared");for(let o of a){let s=await pe(o.filePath,"shared");r.push(...s.map(Tt));}}return this.export(r,t)}exportFromModules(e,t){let r=[];if(e.tenant){let a=Se(e.tenant,"tenant-schema","tenant");r.push(...a.map(Tt));}if(e.shared){let a=Se(e.shared,"shared-schema","shared");r.push(...a.map(Tt));}return this.export(r,t)}export(e,t){switch(t.format){case "json":return this.exportToJson(e,t);case "typescript":return this.tsExporter.export(e,t);case "mermaid":return this.mermaidExporter.export(e,t);default:throw new Error(`Unknown export format: ${t.format}`)}}exportToJson(e,t){let r={version:"1.0.0",exportedAt:new Date().toISOString(),tables:e,...t.projectName&&{projectName:t.projectName}};if(t.includeMetadata){let a=e.filter(l=>l.schemaType==="tenant").length,o=e.filter(l=>l.schemaType==="shared").length,s=e.reduce((l,m)=>l+m.columns.length,0),i=e.reduce((l,m)=>l+m.indexes.length,0),c=e.reduce((l,m)=>l+m.columns.filter(u=>u.references).length,0);r.metadata={tenantCount:a,sharedCount:o,totalColumns:s,totalIndexes:i,totalRelations:c};}return JSON.stringify(r,null,2)}exportToJsonSchema(e,t){return this.jsonExporter.export(e,t)}exportToTypeScript(e,t){return this.tsExporter.export(e,t)}exportToMermaid(e,t){return this.mermaidExporter.export(e,t)}getSupportedFormats(){return ["json","typescript","mermaid"]}};function jn(){return new xt}function ma(n){let e=n.toLowerCase();if(e==="uuid")return {import:"uuid",usage:"uuid('$name')"};if(e==="text"||e==="string")return {import:"text",usage:"text('$name')"};if(e.startsWith("varchar")||e.startsWith("character varying")){let t=e.match(/\((\d+)\)/);return {import:"varchar",usage:`varchar('$name', { length: ${t?t[1]:"255"} })`}}if(e.startsWith("char")||e.startsWith("character")){let t=e.match(/\((\d+)\)/);return {import:"char",usage:`char('$name', { length: ${t?t[1]:"1"} })`}}if(e==="integer"||e==="int"||e==="int4")return {import:"integer",usage:"integer('$name')"};if(e==="smallint"||e==="int2")return {import:"smallint",usage:"smallint('$name')"};if(e==="bigint"||e==="int8")return {import:"bigint",usage:"bigint('$name', { mode: 'number' })"};if(e==="serial"||e==="serial4")return {import:"serial",usage:"serial('$name')"};if(e==="bigserial"||e==="serial8")return {import:"bigserial",usage:"bigserial('$name', { mode: 'number' })"};if(e==="real"||e==="float4"||e==="float")return {import:"real",usage:"real('$name')"};if(e==="double precision"||e==="float8"||e==="double")return {import:"doublePrecision",usage:"doublePrecision('$name')"};if(e.startsWith("numeric")||e.startsWith("decimal")){let t=e.match(/\((\d+)(?:,\s*(\d+))?\)/),r=t?.[1]??"10",a=t?.[2]??"2";return {import:"numeric",usage:`numeric('$name', { precision: ${r}, scale: ${a} })`}}return e==="boolean"||e==="bool"?{import:"boolean",usage:"boolean('$name')"}:e==="date"?{import:"date",usage:"date('$name')"}:e.startsWith("timestamp")?{import:"timestamp",usage:e.includes("with time zone")||e.includes("timestamptz")?"timestamp('$name', { withTimezone: true })":"timestamp('$name')"}:e==="time"||e.startsWith("time ")?{import:"time",usage:e.includes("with time zone")?"time('$name', { withTimezone: true })":"time('$name')"}:e==="json"?{import:"json",usage:"json('$name')"}:e==="jsonb"?{import:"jsonb",usage:"jsonb('$name')"}:e==="bytea"?{import:"customType",usage:"bytea('$name')"}:{import:"text",usage:"text('$name')"}}function We(n){return n.split("_").map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}function $t(n){let e=We(n);return e.charAt(0).toLowerCase()+e.slice(1)}function da(n,e){let t=[],r=new Set;r.add("pgTable");for(let i of n.columns){let{import:c}=ma(i.dataType);r.add(c);}n.indexes.length>0&&(r.add("index"),n.indexes.some(c=>c.isUnique)&&r.add("uniqueIndex")),t.push("/**"),t.push(` * ${n.name} schema`),t.push(" * Auto-generated from schema import"),t.push(` * Schema type: ${n.schemaType}`),t.push(" */"),t.push(""),t.push(`import { ${Array.from(r).sort().join(", ")} } from 'drizzle-orm/pg-core';`),e.includeZod&&(t.push("import { createInsertSchema, createSelectSchema } from 'drizzle-zod';"),t.push("import { z } from 'zod';")),t.push("");let a=n.columns.filter(i=>i.references).map(i=>i.references);if(a.length>0){let i=[...new Set(a.map(c=>c.table))];for(let c of i)c!==n.name&&(t.push(`// TODO: Adjust import path for ${c}`),t.push(`// import { ${$t(c)} } from './${c}';`));t.push("");}let o=$t(n.name);t.push(`export const ${o} = pgTable('${n.name}', {`);let s=n.columns.length;if(n.columns.forEach((i,c)=>{let{usage:l}=ma(i.dataType),m=c===s-1,u=l.replace("$name",i.name),p=[];if(i.isPrimaryKey&&(p.push(".primaryKey()"),i.dataType.toLowerCase()==="uuid"&&p.push(".defaultRandom()")),!i.isNullable&&!i.isPrimaryKey&&p.push(".notNull()"),i.hasDefault&&i.defaultValue!=null&&i.defaultValue!==""&&!i.isPrimaryKey){let h=i.defaultValue;h==="now()"||h==="CURRENT_TIMESTAMP"?p.push(".defaultNow()"):h==="true"||h==="false"?p.push(`.default(${h})`):isNaN(Number(h))?h.startsWith("'")&&h.endsWith("'")&&p.push(`.default(${h})`):p.push(`.default(${h})`);}i.references&&p.push(`// .references(() => ${$t(i.references.table)}.${i.references.column})`),u+=p.join(""),t.push(` ${i.name}: ${u}${m?"":","}`);}),t.push("});"),t.push(""),n.indexes.length>0){t.push(`export const ${o}Indexes = {`);let i=n.indexes.length;n.indexes.forEach((c,l)=>{let m=l===i-1,u=c.isUnique?"uniqueIndex":"index",p=c.columns.map(h=>`${o}.${h}`).join(", ");t.push(` ${$t(c.name)}: ${u}('${c.name}').on(${p})${m?"":","}`);}),t.push("};"),t.push("");}return e.includeZod&&(t.push(`export const insert${We(n.name)}Schema = createInsertSchema(${o});`),t.push(`export const select${We(n.name)}Schema = createSelectSchema(${o});`),t.push("")),e.includeTypes!==false&&(t.push(`export type ${We(n.name)} = typeof ${o}.$inferSelect;`),t.push(`export type New${We(n.name)} = typeof ${o}.$inferInsert;`)),t.join(`
|
|
2225
|
+
`)}function ua(n){let e=[];e.push("/**"),e.push(" * Barrel export for all schema files"),e.push(" * Auto-generated from schema import"),e.push(" */"),e.push("");for(let t of n)e.push(`export * from './${t.name}';`);return e.join(`
|
|
2226
|
+
`)}async function Ct(n){try{return await access(n),!0}catch{return false}}var Mt=class{async import(e,t){let r={success:true,filesCreated:[],filesSkipped:[],errors:[]},a=resolve(t.outputDir),o=join(a,"tenant"),s=join(a,"shared");t.dryRun||(await mkdir(o,{recursive:true}),await mkdir(s,{recursive:true}));let i=e.tables.filter(l=>l.schemaType==="tenant"),c=e.tables.filter(l=>l.schemaType==="shared");if(t.generateTenant!==false){for(let l of i){let m=join(o,`${l.name}.ts`);try{if(!t.overwrite&&await Ct(m)){r.filesSkipped.push(m);continue}let u=da(l,t);t.dryRun||await writeFile(m,u,"utf-8"),r.filesCreated.push(m);}catch(u){r.success=false,r.errors.push({file:m,error:u.message});}}if(i.length>0){let l=join(o,"index.ts");try{if(t.overwrite||!await Ct(l)){let m=ua(i);t.dryRun||await writeFile(l,m,"utf-8"),r.filesCreated.push(l);}else r.filesSkipped.push(l);}catch(m){r.errors.push({file:l,error:m.message});}}}if(t.generateShared!==false){for(let l of c){let m=join(s,`${l.name}.ts`);try{if(!t.overwrite&&await Ct(m)){r.filesSkipped.push(m);continue}let u=da(l,t);t.dryRun||await writeFile(m,u,"utf-8"),r.filesCreated.push(m);}catch(u){r.success=false,r.errors.push({file:m,error:u.message});}}if(c.length>0){let l=join(s,"index.ts");try{if(t.overwrite||!await Ct(l)){let m=ua(c);t.dryRun||await writeFile(l,m,"utf-8"),r.filesCreated.push(l);}else r.filesSkipped.push(l);}catch(m){r.errors.push({file:l,error:m.message});}}}return r}};function qn(){return new Mt}async function Un(n){let e=await readFile(resolve(n),"utf-8");return JSON.parse(e)}var Wn=new Command("export").description("Export schemas to JSON, TypeScript, or Mermaid ERD format").option("-c, --config <path>","Path to config file").option("--tenant-schema <path>","Path to tenant schema directory").option("--shared-schema <path>","Path to shared schema directory").option("-f, --format <format>","Output format: json, typescript, mermaid","json").option("-o, --output <path>","Output file path (defaults to stdout)").option("--project-name <name>","Project name for the export").option("--include-metadata","Include metadata in JSON export").option("--include-zod","Include Zod schemas in TypeScript export").option("--no-insert-types","Exclude insert types from TypeScript export").option("--no-select-types","Exclude select types from TypeScript export").option("--mermaid-theme <theme>","Mermaid theme: default, dark, forest, neutral","default").option("--include-indexes","Include indexes in Mermaid ERD").option("--json-schema","Export as JSON Schema format instead of raw JSON").addHelpText("after",`
|
|
2227
2227
|
Examples:
|
|
2228
2228
|
# Export to JSON (stdout)
|
|
2229
2229
|
$ drizzle-multitenant export
|
|
@@ -2247,7 +2247,7 @@ Output Formats:
|
|
|
2247
2247
|
json Raw JSON with table definitions (default)
|
|
2248
2248
|
typescript TypeScript type definitions
|
|
2249
2249
|
mermaid Mermaid ERD diagram
|
|
2250
|
-
`).action(async n=>{let e=L(),t=R("Exporting schemas...");try{let r,a;n.tenantSchema&&(r=resolve(process.cwd(),n.tenantSchema)),n.sharedSchema&&(a=resolve(process.cwd(),n.sharedSchema));try{let l=await
|
|
2250
|
+
`).action(async n=>{let e=L(),t=R("Exporting schemas...");try{let r,a;n.tenantSchema&&(r=resolve(process.cwd(),n.tenantSchema)),n.sharedSchema&&(a=resolve(process.cwd(),n.sharedSchema));try{let l=await k(n.config);if(!r&&l.config.schemas.tenant){let m=["./src/db/schema/tenant","./src/schema/tenant","./drizzle/schema/tenant","./db/schema/tenant"];for(let u of m){let p=resolve(process.cwd(),u);if(existsSync(p)){r=p;break}}}if(!a&&l.config.schemas.shared){let m=["./src/db/schema/shared","./src/schema/shared","./drizzle/schema/shared","./db/schema/shared"];for(let u of m){let p=resolve(process.cwd(),u);if(existsSync(p)){a=p;break}}}}catch{}!r&&!a&&(e.jsonMode?N({success:!1,error:"No schema directories found"}):(t.fail("No schema directories found"),d(""),d("Specify schema directories using:"),d(" --tenant-schema <path> Path to tenant schema directory"),d(" --shared-schema <path> Path to shared schema directory"),d(""),d("Or create schemas in one of these locations:"),d(" ./src/db/schema/tenant"),d(" ./src/db/schema/shared")),process.exit(1)),e.jsonMode||t.start();let o=jn(),s=n.format??"json",i={format:s,projectName:n.projectName,includeMetadata:n.includeMetadata,typescript:{includeZod:n.includeZod,includeInsertTypes:n.insertTypes!==!1,includeSelectTypes:n.selectTypes!==!1},mermaid:{theme:n.mermaidTheme,includeIndexes:n.includeIndexes,includeDataTypes:!0,showPrimaryKeys:!0,showForeignKeys:!0}},c;if(n.jsonSchema&&s==="json"){let l=await o.exportFromDirectories({tenantDir:r,sharedDir:a},{...i,format:"json"}),m=JSON.parse(l).tables;c=o.exportToJsonSchema(m,i);}else c=await o.exportFromDirectories({tenantDir:r,sharedDir:a},i);if(e.jsonMode||t.stop(),n.output){let l=resolve(process.cwd(),n.output);await writeFile(l,c,"utf-8"),e.jsonMode?N({success:!0,outputFile:l,format:s}):E(`Exported schemas to ${G(l)}`);}else e.jsonMode&&s!=="json"?N({success:!0,format:s,content:c}):console.log(c);}catch(r){e.jsonMode||t.fail(r.message),j(r);}});var Hn=new Command("import").description("Import schemas from JSON and generate Drizzle schema files").argument("<file>","Path to JSON schema file").option("-o, --output <path>","Output directory for generated files","./src/db/schema").option("--overwrite","Overwrite existing files").option("--no-tenant","Skip tenant schema generation").option("--no-shared","Skip shared schema generation").option("--include-zod","Include Zod validation schemas").option("--no-types","Skip TypeScript type generation").option("--dry-run","Show what would be generated without writing files").addHelpText("after",`
|
|
2251
2251
|
Examples:
|
|
2252
2252
|
# Import from JSON file
|
|
2253
2253
|
$ drizzle-multitenant import schemas.json
|
|
@@ -2283,14 +2283,14 @@ JSON File Format:
|
|
|
2283
2283
|
|
|
2284
2284
|
You can generate this file using:
|
|
2285
2285
|
$ drizzle-multitenant export > schemas.json
|
|
2286
|
-
`).action(async(n,e)=>{let t=L(),r=R("Importing schemas...");try{let a=resolve(process.cwd(),n);t.jsonMode||(d(`Reading schema file: ${G(relative(process.cwd(),a))}`),r.start());let o=await Un(a);if(!o.tables||!Array.isArray(o.tables))throw new Error("Invalid schema file: missing tables array");if(o.tables.length===0)throw new Error("Schema file contains no tables");let s={outputDir:resolve(process.cwd(),e.output??"./src/db/schema"),overwrite:e.overwrite,generateTenant:e.tenant!==!1,generateShared:e.shared!==!1,includeZod:e.includeZod,includeTypes:e.types!==!1,dryRun:e.dryRun},c=await qn().import(o,s);if(t.jsonMode||r.stop(),t.jsonMode)N({success:c.success,dryRun:e.dryRun,filesCreated:c.filesCreated,filesSkipped:c.filesSkipped,errors:c.errors,summary:{created:c.filesCreated.length,skipped:c.filesSkipped.length,errors:c.errors.length}});else {if(d(""),e.dryRun&&(d(T(Z("Dry run - no files were written"))),d("")),c.filesCreated.length>0){d(T(Y(`${e.dryRun?"Would create":"Created"} ${c.filesCreated.length} file(s):`)));for(let l of c.filesCreated)d(` ${Y("+")} ${relative(process.cwd(),l)}`);d("");}if(c.filesSkipped.length>0){d(T(Z(`Skipped ${c.filesSkipped.length} existing file(s):`)));for(let l of c.filesSkipped)d(` ${Z("-")} ${relative(process.cwd(),l)}`);d(""),d(f("Use --overwrite to replace existing files")),d("");}if(c.errors.length>0){d(T(v(`Errors (${c.errors.length}):`)));for(let l of c.errors)d(` ${relative(process.cwd(),l.file)}: ${l.error}`);d("");}c.success?E("Import completed successfully"):v("Import completed with errors"),c.filesCreated.length>0&&!e.dryRun&&(d(""),d(T("Next steps:")),d(" 1. Review the generated schema files"),d(" 2. Uncomment and adjust foreign key references"),d(" 3. Add relations if needed"),d(" 4. Generate migrations: npx drizzle-multitenant generate"));}c.success||process.exit(1);}catch(a){t.jsonMode||r.fail(a.message),j(a);}});process.on("SIGINT",()=>{console.log(
|
|
2286
|
+
`).action(async(n,e)=>{let t=L(),r=R("Importing schemas...");try{let a=resolve(process.cwd(),n);t.jsonMode||(d(`Reading schema file: ${G(relative(process.cwd(),a))}`),r.start());let o=await Un(a);if(!o.tables||!Array.isArray(o.tables))throw new Error("Invalid schema file: missing tables array");if(o.tables.length===0)throw new Error("Schema file contains no tables");let s={outputDir:resolve(process.cwd(),e.output??"./src/db/schema"),overwrite:e.overwrite,generateTenant:e.tenant!==!1,generateShared:e.shared!==!1,includeZod:e.includeZod,includeTypes:e.types!==!1,dryRun:e.dryRun},c=await qn().import(o,s);if(t.jsonMode||r.stop(),t.jsonMode)N({success:c.success,dryRun:e.dryRun,filesCreated:c.filesCreated,filesSkipped:c.filesSkipped,errors:c.errors,summary:{created:c.filesCreated.length,skipped:c.filesSkipped.length,errors:c.errors.length}});else {if(d(""),e.dryRun&&(d(T(Z("Dry run - no files were written"))),d("")),c.filesCreated.length>0){d(T(Y(`${e.dryRun?"Would create":"Created"} ${c.filesCreated.length} file(s):`)));for(let l of c.filesCreated)d(` ${Y("+")} ${relative(process.cwd(),l)}`);d("");}if(c.filesSkipped.length>0){d(T(Z(`Skipped ${c.filesSkipped.length} existing file(s):`)));for(let l of c.filesSkipped)d(` ${Z("-")} ${relative(process.cwd(),l)}`);d(""),d(f("Use --overwrite to replace existing files")),d("");}if(c.errors.length>0){d(T(v(`Errors (${c.errors.length}):`)));for(let l of c.errors)d(` ${relative(process.cwd(),l.file)}: ${l.error}`);d("");}c.success?E("Import completed successfully"):v("Import completed with errors"),c.filesCreated.length>0&&!e.dryRun&&(d(""),d(T("Next steps:")),d(" 1. Review the generated schema files"),d(" 2. Uncomment and adjust foreign key references"),d(" 3. Add relations if needed"),d(" 4. Generate migrations: npx drizzle-multitenant generate"));}c.success||process.exit(1);}catch(a){t.jsonMode||r.fail(a.message),j(a);}});process.on("SIGINT",()=>{console.log(M.cyan(`
|
|
2287
2287
|
|
|
2288
2288
|
Goodbye!
|
|
2289
|
-
`)),process.exit(0);});process.on("uncaughtException",n=>{n.name==="ExitPromptError"&&(console.log(
|
|
2289
|
+
`)),process.exit(0);});process.on("uncaughtException",n=>{n.name==="ExitPromptError"&&(console.log(M.cyan(`
|
|
2290
2290
|
|
|
2291
2291
|
Goodbye!
|
|
2292
|
-
`)),process.exit(0)),console.error(
|
|
2293
|
-
Unexpected error:`),n.message),process.exit(1);});var U=new Command;U.name("drizzle-multitenant").description("Multi-tenancy toolkit for Drizzle ORM").version("1.1.0").option("--json","Output as JSON (machine-readable)").option("-v, --verbose","Show verbose output").option("-q, --quiet","Only show errors").option("--no-color","Disable colored output").hook("preAction",n=>{let e=n.opts();
|
|
2292
|
+
`)),process.exit(0)),console.error(M.red(`
|
|
2293
|
+
Unexpected error:`),n.message),process.exit(1);});var U=new Command;U.name("drizzle-multitenant").description("Multi-tenancy toolkit for Drizzle ORM").version("1.1.0").option("--json","Output as JSON (machine-readable)").option("-v, --verbose","Show verbose output").option("-q, --quiet","Only show errors").option("--no-color","Disable colored output").hook("preAction",n=>{let e=n.opts();yr({json:e.json,verbose:e.verbose,quiet:e.quiet,noColor:e.color===false});});U.addHelpText("after",`
|
|
2294
2294
|
Examples:
|
|
2295
2295
|
$ drizzle-multitenant # Launch interactive mode
|
|
2296
2296
|
$ drizzle-multitenant interactive # Launch interactive mode
|
|
@@ -2313,4 +2313,4 @@ Examples:
|
|
|
2313
2313
|
|
|
2314
2314
|
Documentation:
|
|
2315
2315
|
https://github.com/mateusflorez/drizzle-multitenant
|
|
2316
|
-
`);U.addCommand(vt);U.addCommand(
|
|
2316
|
+
`);U.addCommand(vt);U.addCommand(_t);U.addCommand(Pt);U.addCommand(zt);U.addCommand(kt);U.addCommand(It);U.addCommand(Ft);U.addCommand(Ot);U.addCommand(Lt);U.addCommand(jt);U.addCommand(qt);U.addCommand(Wt);U.addCommand(Ht);U.addCommand(hn);U.addCommand(gn);U.addCommand(fn);U.addCommand(yn);U.addCommand(Sn);U.addCommand(yt);U.addCommand(kn);U.addCommand(Nn);U.addCommand(In);U.addCommand(An);U.addCommand(Fn);U.addCommand(Wn);U.addCommand(Hn);U.action(async()=>{await pt();});U.parse();
|