drizzle-multitenant 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -8
- package/dist/cli/index.js +2001 -2442
- package/dist/{context-Vki959ri.d.ts → context-BBLPNjmk.d.ts} +1 -1
- package/dist/cross-schema/index.js +1 -426
- package/dist/export/index.d.ts +395 -0
- package/dist/export/index.js +9 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +149 -2437
- package/dist/integrations/express.d.ts +3 -3
- package/dist/integrations/express.js +1 -110
- package/dist/integrations/fastify.d.ts +3 -3
- package/dist/integrations/fastify.js +1 -236
- package/dist/integrations/hono.js +0 -3
- package/dist/integrations/nestjs/index.d.ts +1 -1
- package/dist/integrations/nestjs/index.js +3 -10759
- package/dist/lint/index.d.ts +475 -0
- package/dist/lint/index.js +5 -0
- package/dist/metrics/index.d.ts +530 -0
- package/dist/metrics/index.js +3 -0
- package/dist/migrator/index.d.ts +1087 -270
- package/dist/migrator/index.js +149 -970
- package/dist/migrator-B7oPKe73.d.ts +1067 -0
- package/dist/scaffold/index.d.ts +330 -0
- package/dist/scaffold/index.js +277 -0
- package/dist/{types-BhK96FPC.d.ts → types-CGqsPe2Q.d.ts} +49 -1
- package/package.json +18 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cross-schema/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/express.js.map +0 -1
- package/dist/integrations/fastify.js.map +0 -1
- package/dist/integrations/hono.js.map +0 -1
- package/dist/integrations/nestjs/index.js.map +0 -1
- package/dist/migrator/index.js.map +0 -1
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold module types
|
|
3
|
+
*
|
|
4
|
+
* @module scaffold/types
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Type of scaffold to generate
|
|
8
|
+
*/
|
|
9
|
+
type ScaffoldType = 'tenant' | 'shared';
|
|
10
|
+
/**
|
|
11
|
+
* Kind of scaffold template
|
|
12
|
+
*/
|
|
13
|
+
type ScaffoldKind = 'schema' | 'seed' | 'migration';
|
|
14
|
+
/**
|
|
15
|
+
* Options for scaffolding a schema
|
|
16
|
+
*/
|
|
17
|
+
interface ScaffoldSchemaOptions {
|
|
18
|
+
/** Name of the schema/table (e.g., "orders", "products") */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Type of schema (tenant or shared) */
|
|
21
|
+
type: ScaffoldType;
|
|
22
|
+
/** Output directory for the schema file */
|
|
23
|
+
outputDir?: string;
|
|
24
|
+
/** Whether to include example columns */
|
|
25
|
+
includeExample?: boolean;
|
|
26
|
+
/** Whether to include timestamps (createdAt, updatedAt) */
|
|
27
|
+
includeTimestamps?: boolean;
|
|
28
|
+
/** Whether to include soft delete (deletedAt) */
|
|
29
|
+
includeSoftDelete?: boolean;
|
|
30
|
+
/** Whether to use UUID for primary key (default: true) */
|
|
31
|
+
useUuid?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Options for scaffolding a seed
|
|
35
|
+
*/
|
|
36
|
+
interface ScaffoldSeedOptions {
|
|
37
|
+
/** Name of the seed file (e.g., "initial", "demo-data") */
|
|
38
|
+
name: string;
|
|
39
|
+
/** Type of seed (tenant or shared) */
|
|
40
|
+
type: ScaffoldType;
|
|
41
|
+
/** Output directory for the seed file */
|
|
42
|
+
outputDir?: string;
|
|
43
|
+
/** Optional table name to seed */
|
|
44
|
+
tableName?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Options for scaffolding a migration
|
|
48
|
+
*/
|
|
49
|
+
interface ScaffoldMigrationOptions {
|
|
50
|
+
/** Name of the migration (e.g., "add-orders", "create-users") */
|
|
51
|
+
name: string;
|
|
52
|
+
/** Type of migration (tenant or shared) */
|
|
53
|
+
type: ScaffoldType;
|
|
54
|
+
/** Output directory for the migration file */
|
|
55
|
+
outputDir?: string;
|
|
56
|
+
/** Migration template type */
|
|
57
|
+
template?: MigrationTemplate;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Available migration templates
|
|
61
|
+
*/
|
|
62
|
+
type MigrationTemplate = 'create-table' | 'add-column' | 'add-index' | 'add-foreign-key' | 'blank';
|
|
63
|
+
/**
|
|
64
|
+
* Result of a scaffold operation
|
|
65
|
+
*/
|
|
66
|
+
interface ScaffoldResult {
|
|
67
|
+
/** Whether the operation was successful */
|
|
68
|
+
success: boolean;
|
|
69
|
+
/** Path to the generated file */
|
|
70
|
+
filePath: string;
|
|
71
|
+
/** Name of the generated file */
|
|
72
|
+
fileName: string;
|
|
73
|
+
/** Type of scaffold generated */
|
|
74
|
+
kind: ScaffoldKind;
|
|
75
|
+
/** Tenant or shared */
|
|
76
|
+
type: ScaffoldType;
|
|
77
|
+
/** Optional error message if failed */
|
|
78
|
+
error?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Template context for schema generation
|
|
82
|
+
*/
|
|
83
|
+
interface SchemaTemplateContext {
|
|
84
|
+
/** Table name in snake_case */
|
|
85
|
+
tableName: string;
|
|
86
|
+
/** Table name in PascalCase */
|
|
87
|
+
tableNamePascal: string;
|
|
88
|
+
/** Table name in camelCase */
|
|
89
|
+
tableNameCamel: string;
|
|
90
|
+
/** Schema type (tenant or shared) */
|
|
91
|
+
type: ScaffoldType;
|
|
92
|
+
/** Whether to include timestamps */
|
|
93
|
+
includeTimestamps: boolean;
|
|
94
|
+
/** Whether to include soft delete */
|
|
95
|
+
includeSoftDelete: boolean;
|
|
96
|
+
/** Whether to use UUID for primary key */
|
|
97
|
+
useUuid: boolean;
|
|
98
|
+
/** Whether to include example columns */
|
|
99
|
+
includeExample: boolean;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Template context for seed generation
|
|
103
|
+
*/
|
|
104
|
+
interface SeedTemplateContext {
|
|
105
|
+
/** Seed function name in camelCase */
|
|
106
|
+
seedName: string;
|
|
107
|
+
/** Table name (optional) */
|
|
108
|
+
tableName?: string;
|
|
109
|
+
/** Schema type (tenant or shared) */
|
|
110
|
+
type: ScaffoldType;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Template context for migration generation
|
|
114
|
+
*/
|
|
115
|
+
interface MigrationTemplateContext {
|
|
116
|
+
/** Migration name for comments */
|
|
117
|
+
migrationName: string;
|
|
118
|
+
/** Schema type (tenant or shared) */
|
|
119
|
+
type: ScaffoldType;
|
|
120
|
+
/** Template type */
|
|
121
|
+
template: MigrationTemplate;
|
|
122
|
+
/** Table name (derived from migration name) */
|
|
123
|
+
tableName?: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Generated file output
|
|
127
|
+
*/
|
|
128
|
+
interface GeneratedFile {
|
|
129
|
+
/** File path relative to project root */
|
|
130
|
+
path: string;
|
|
131
|
+
/** File content */
|
|
132
|
+
content: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Scaffold configuration from tenant.config.ts
|
|
136
|
+
*/
|
|
137
|
+
interface ScaffoldConfig {
|
|
138
|
+
/** Schema output directory */
|
|
139
|
+
schemaDir?: string;
|
|
140
|
+
/** Seed output directory */
|
|
141
|
+
seedDir?: string;
|
|
142
|
+
/** Migrations folder */
|
|
143
|
+
migrationsFolder?: string;
|
|
144
|
+
/** Shared migrations folder */
|
|
145
|
+
sharedMigrationsFolder?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Scaffold generator
|
|
150
|
+
*
|
|
151
|
+
* Orchestrates the generation of schema, seed, and migration files.
|
|
152
|
+
*
|
|
153
|
+
* @module scaffold/generator
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Convert a name to various case formats
|
|
158
|
+
*/
|
|
159
|
+
declare function toCase(name: string): {
|
|
160
|
+
snake: string;
|
|
161
|
+
pascal: string;
|
|
162
|
+
camel: string;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* Default output directories
|
|
166
|
+
*/
|
|
167
|
+
declare const DEFAULT_DIRS: {
|
|
168
|
+
readonly schemaDir: "src/db/schema";
|
|
169
|
+
readonly seedDir: "drizzle/seeds";
|
|
170
|
+
readonly tenantMigrationsDir: "drizzle/tenant-migrations";
|
|
171
|
+
readonly sharedMigrationsDir: "drizzle/shared-migrations";
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Scaffold a new Drizzle schema file
|
|
175
|
+
*
|
|
176
|
+
* @param options - Scaffold options
|
|
177
|
+
* @returns Result of the scaffold operation
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* const result = await scaffoldSchema({
|
|
182
|
+
* name: 'orders',
|
|
183
|
+
* type: 'tenant',
|
|
184
|
+
* includeTimestamps: true,
|
|
185
|
+
* });
|
|
186
|
+
*
|
|
187
|
+
* if (result.success) {
|
|
188
|
+
* console.log(`Created: ${result.filePath}`);
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
declare function scaffoldSchema(options: ScaffoldSchemaOptions): Promise<ScaffoldResult>;
|
|
193
|
+
/**
|
|
194
|
+
* Scaffold a new seed file
|
|
195
|
+
*
|
|
196
|
+
* @param options - Scaffold options
|
|
197
|
+
* @returns Result of the scaffold operation
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* const result = await scaffoldSeed({
|
|
202
|
+
* name: 'initial',
|
|
203
|
+
* type: 'tenant',
|
|
204
|
+
* tableName: 'users',
|
|
205
|
+
* });
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
declare function scaffoldSeed(options: ScaffoldSeedOptions): Promise<ScaffoldResult>;
|
|
209
|
+
/**
|
|
210
|
+
* Scaffold a new migration file
|
|
211
|
+
*
|
|
212
|
+
* @param options - Scaffold options
|
|
213
|
+
* @returns Result of the scaffold operation
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* const result = await scaffoldMigration({
|
|
218
|
+
* name: 'add-orders',
|
|
219
|
+
* type: 'tenant',
|
|
220
|
+
* template: 'create-table',
|
|
221
|
+
* });
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
declare function scaffoldMigration(options: ScaffoldMigrationOptions): Promise<ScaffoldResult>;
|
|
225
|
+
/**
|
|
226
|
+
* Get available migration templates
|
|
227
|
+
*/
|
|
228
|
+
declare function getMigrationTemplates(): Array<{
|
|
229
|
+
value: MigrationTemplate;
|
|
230
|
+
label: string;
|
|
231
|
+
description: string;
|
|
232
|
+
}>;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Schema template generator
|
|
236
|
+
*
|
|
237
|
+
* @module scaffold/templates/schema-template
|
|
238
|
+
*/
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Generate a Drizzle schema file content
|
|
242
|
+
*
|
|
243
|
+
* @param context - Template context
|
|
244
|
+
* @returns Generated TypeScript code for the schema
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```typescript
|
|
248
|
+
* const content = generateSchemaTemplate({
|
|
249
|
+
* tableName: 'orders',
|
|
250
|
+
* tableNamePascal: 'Orders',
|
|
251
|
+
* tableNameCamel: 'orders',
|
|
252
|
+
* type: 'tenant',
|
|
253
|
+
* includeTimestamps: true,
|
|
254
|
+
* includeSoftDelete: false,
|
|
255
|
+
* useUuid: true,
|
|
256
|
+
* includeExample: true,
|
|
257
|
+
* });
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
declare function generateSchemaTemplate(context: SchemaTemplateContext): string;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Seed template generator
|
|
264
|
+
*
|
|
265
|
+
* @module scaffold/templates/seed-template
|
|
266
|
+
*/
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Generate a seed file content
|
|
270
|
+
*
|
|
271
|
+
* @param context - Template context
|
|
272
|
+
* @returns Generated TypeScript code for the seed
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```typescript
|
|
276
|
+
* const content = generateSeedTemplate({
|
|
277
|
+
* seedName: 'initialData',
|
|
278
|
+
* type: 'tenant',
|
|
279
|
+
* tableName: 'users',
|
|
280
|
+
* });
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
declare function generateSeedTemplate(context: SeedTemplateContext): string;
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Migration template generator
|
|
287
|
+
*
|
|
288
|
+
* @module scaffold/templates/migration-template
|
|
289
|
+
*/
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Generate a SQL migration file content
|
|
293
|
+
*
|
|
294
|
+
* @param context - Template context
|
|
295
|
+
* @returns Generated SQL migration content
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* ```typescript
|
|
299
|
+
* const content = generateMigrationTemplate({
|
|
300
|
+
* migrationName: 'add-orders',
|
|
301
|
+
* type: 'tenant',
|
|
302
|
+
* template: 'create-table',
|
|
303
|
+
* tableName: 'orders',
|
|
304
|
+
* });
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
declare function generateMigrationTemplate(context: MigrationTemplateContext): string;
|
|
308
|
+
/**
|
|
309
|
+
* Infer table name from migration name
|
|
310
|
+
*
|
|
311
|
+
* @param migrationName - Name of the migration
|
|
312
|
+
* @returns Inferred table name or undefined
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* inferTableName('add-orders') // 'orders'
|
|
317
|
+
* inferTableName('create-user-profiles') // 'user_profiles'
|
|
318
|
+
* inferTableName('add-index-to-users') // 'users'
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
declare function inferTableName(migrationName: string): string | undefined;
|
|
322
|
+
/**
|
|
323
|
+
* Determine the best template based on migration name
|
|
324
|
+
*
|
|
325
|
+
* @param migrationName - Name of the migration
|
|
326
|
+
* @returns Suggested template type
|
|
327
|
+
*/
|
|
328
|
+
declare function inferMigrationTemplate(migrationName: string): MigrationTemplate;
|
|
329
|
+
|
|
330
|
+
export { DEFAULT_DIRS, type GeneratedFile, type MigrationTemplate, type MigrationTemplateContext, type ScaffoldConfig, type ScaffoldKind, type ScaffoldMigrationOptions, type ScaffoldResult, type ScaffoldSchemaOptions, type ScaffoldSeedOptions, type ScaffoldType, type SchemaTemplateContext, type SeedTemplateContext, generateMigrationTemplate, generateSchemaTemplate, generateSeedTemplate, getMigrationTemplates, inferMigrationTemplate, inferTableName, scaffoldMigration, scaffoldSchema, scaffoldSeed, toCase };
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import {existsSync}from'fs';import {mkdir,writeFile,readdir}from'fs/promises';import {join,resolve,dirname}from'path';function h(t){let{tableName:e,tableNamePascal:n,tableNameCamel:r,type:i}=t,a=w(t),o=F(t),d=k(t),s=z(t),m=M(t);return `/**
|
|
2
|
+
* ${n} schema
|
|
3
|
+
* Type: ${i}
|
|
4
|
+
*
|
|
5
|
+
* @module schema/${i}/${r}
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
${a}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ${n} table definition
|
|
12
|
+
*/
|
|
13
|
+
export const ${r} = pgTable('${e}', {
|
|
14
|
+
${o}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
${d}
|
|
18
|
+
${s}
|
|
19
|
+
${m}
|
|
20
|
+
`}function w(t){let{includeTimestamps:e,includeSoftDelete:n,useUuid:r,includeExample:i}=t,a=["pgTable"];return r?a.push("uuid"):a.push("serial"),a.push("text"),i&&a.push("varchar","boolean"),(e||n)&&a.push("timestamp"),a.push("index"),`import { ${[...new Set(a)].sort().join(", ")} } from 'drizzle-orm/pg-core';
|
|
21
|
+
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
|
|
22
|
+
import { z } from 'zod';`}function F(t){let{includeTimestamps:e,includeSoftDelete:n,useUuid:r,includeExample:i}=t,a=[];return r?a.push(" id: uuid('id').primaryKey().defaultRandom(),"):a.push(" id: serial('id').primaryKey(),"),i&&(a.push(" name: varchar('name', { length: 255 }).notNull(),"),a.push(" description: text('description'),"),a.push(" isActive: boolean('is_active').notNull().default(true),")),e&&(a.push(" createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),"),a.push(" updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),")),n&&a.push(" deletedAt: timestamp('deleted_at', { withTimezone: true }),"),a.join(`
|
|
23
|
+
`)}function k(t){let{tableNameCamel:e,tableName:n,includeExample:r,includeTimestamps:i}=t,a=[];return r&&(a.push(` nameIdx: index('${n}_name_idx').on(${e}.name),`),a.push(` isActiveIdx: index('${n}_is_active_idx').on(${e}.isActive),`)),i&&a.push(` createdAtIdx: index('${n}_created_at_idx').on(${e}.createdAt),`),a.length===0?"":`/**
|
|
24
|
+
* ${t.tableNamePascal} table indexes
|
|
25
|
+
*/
|
|
26
|
+
export const ${e}Indexes = {
|
|
27
|
+
${a.join(`
|
|
28
|
+
`)}
|
|
29
|
+
};
|
|
30
|
+
`}function z(t){return `// Uncomment and modify to add relations
|
|
31
|
+
// import { relations } from 'drizzle-orm';
|
|
32
|
+
//
|
|
33
|
+
// export const ${t.tableNameCamel}Relations = relations(${t.tableNameCamel}, ({ one, many }) => ({
|
|
34
|
+
// // Add your relations here
|
|
35
|
+
// // user: one(users, {
|
|
36
|
+
// // fields: [${t.tableNameCamel}.userId],
|
|
37
|
+
// // references: [users.id],
|
|
38
|
+
// // }),
|
|
39
|
+
// }));
|
|
40
|
+
`}function M(t){let{tableNameCamel:e,tableNamePascal:n}=t;return `/**
|
|
41
|
+
* Zod schemas for validation
|
|
42
|
+
*/
|
|
43
|
+
export const insert${n}Schema = createInsertSchema(${e});
|
|
44
|
+
export const select${n}Schema = createSelectSchema(${e});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* TypeScript types inferred from schema
|
|
48
|
+
*/
|
|
49
|
+
export type ${n} = typeof ${e}.$inferSelect;
|
|
50
|
+
export type New${n} = typeof ${e}.$inferInsert;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Zod types
|
|
54
|
+
*/
|
|
55
|
+
export type Insert${n} = z.infer<typeof insert${n}Schema>;
|
|
56
|
+
export type Select${n} = z.infer<typeof select${n}Schema>;
|
|
57
|
+
`}function S(t){let{seedName:e,type:n,tableName:r}=t;return n==="shared"?P(e,r):v(e,r)}function v(t,e){let n=e?`import { ${e} } from '../../src/db/schema/tenant/${e}.js';`:"// import { yourTable } from '../../src/db/schema/tenant/yourTable.js';",r=e?G(e):X();return `/**
|
|
58
|
+
* Tenant seed: ${t}
|
|
59
|
+
*
|
|
60
|
+
* This seed runs in the context of each tenant's schema.
|
|
61
|
+
* Use this to populate initial data for each tenant.
|
|
62
|
+
*
|
|
63
|
+
* @module seeds/tenant/${t}
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
import type { SeedFunction } from 'drizzle-multitenant';
|
|
67
|
+
${n}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Seed function that populates tenant data
|
|
71
|
+
*
|
|
72
|
+
* @param db - Drizzle database instance scoped to the tenant
|
|
73
|
+
* @param tenantId - The tenant ID being seeded
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* \`\`\`bash
|
|
77
|
+
* npx drizzle-multitenant seed --file=./drizzle/seeds/tenant/${t}.ts --tenant=my-tenant
|
|
78
|
+
* \`\`\`
|
|
79
|
+
*/
|
|
80
|
+
export const seed: SeedFunction = async (db, tenantId) => {
|
|
81
|
+
console.log(\`Seeding tenant: \${tenantId}\`);
|
|
82
|
+
|
|
83
|
+
${r}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export default seed;
|
|
87
|
+
`}function P(t,e){let n=e?`import { ${e} } from '../../src/db/schema/shared/${e}.js';`:"// import { yourTable } from '../../src/db/schema/shared/yourTable.js';",r=e?B(e):j();return `/**
|
|
88
|
+
* Shared seed: ${t}
|
|
89
|
+
*
|
|
90
|
+
* This seed runs against the shared/public schema.
|
|
91
|
+
* Use this to populate global data like plans, roles, permissions, etc.
|
|
92
|
+
*
|
|
93
|
+
* @module seeds/shared/${t}
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
import type { SharedSeedFunction } from 'drizzle-multitenant';
|
|
97
|
+
${n}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Seed function that populates shared data
|
|
101
|
+
*
|
|
102
|
+
* @param db - Drizzle database instance for the shared schema
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* \`\`\`bash
|
|
106
|
+
* npx drizzle-multitenant seed:shared --file=./drizzle/seeds/shared/${t}.ts
|
|
107
|
+
* \`\`\`
|
|
108
|
+
*/
|
|
109
|
+
export const seed: SharedSeedFunction = async (db) => {
|
|
110
|
+
console.log('Seeding shared schema...');
|
|
111
|
+
|
|
112
|
+
${r}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export default seed;
|
|
116
|
+
`}function G(t){return ` // Insert data with conflict handling (idempotent)
|
|
117
|
+
await db.insert(${t}).values([
|
|
118
|
+
{
|
|
119
|
+
name: 'Example Item 1',
|
|
120
|
+
// Add your columns here
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'Example Item 2',
|
|
124
|
+
// Add your columns here
|
|
125
|
+
},
|
|
126
|
+
]).onConflictDoNothing();
|
|
127
|
+
|
|
128
|
+
console.log(\`Seeded ${t} for tenant: \${tenantId}\`);`}function B(t){return ` // Insert data with conflict handling (idempotent)
|
|
129
|
+
await db.insert(${t}).values([
|
|
130
|
+
{
|
|
131
|
+
name: 'Example Item 1',
|
|
132
|
+
// Add your columns here
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'Example Item 2',
|
|
136
|
+
// Add your columns here
|
|
137
|
+
},
|
|
138
|
+
]).onConflictDoNothing();
|
|
139
|
+
|
|
140
|
+
console.log('Seeded ${t} in shared schema');`}function X(){return ` // Example: Insert initial data
|
|
141
|
+
// await db.insert(yourTable).values([
|
|
142
|
+
// { name: 'Item 1', description: 'Description 1' },
|
|
143
|
+
// { name: 'Item 2', description: 'Description 2' },
|
|
144
|
+
// ]).onConflictDoNothing();
|
|
145
|
+
|
|
146
|
+
// You can use tenantId to customize data per tenant
|
|
147
|
+
// if (tenantId === 'demo-tenant') {
|
|
148
|
+
// await db.insert(yourTable).values([
|
|
149
|
+
// { name: 'Demo Item', description: 'Only for demo tenant' },
|
|
150
|
+
// ]);
|
|
151
|
+
// }
|
|
152
|
+
|
|
153
|
+
console.log(\`Seed completed for tenant: \${tenantId}\`);`}function j(){return ` // Example: Insert shared data like plans, roles, permissions
|
|
154
|
+
// await db.insert(plans).values([
|
|
155
|
+
// { id: 'free', name: 'Free', price: 0 },
|
|
156
|
+
// { id: 'pro', name: 'Pro', price: 29 },
|
|
157
|
+
// { id: 'enterprise', name: 'Enterprise', price: 99 },
|
|
158
|
+
// ]).onConflictDoNothing();
|
|
159
|
+
|
|
160
|
+
// await db.insert(roles).values([
|
|
161
|
+
// { id: 'admin', name: 'Administrator', permissions: ['*'] },
|
|
162
|
+
// { id: 'user', name: 'User', permissions: ['read'] },
|
|
163
|
+
// ]).onConflictDoNothing();
|
|
164
|
+
|
|
165
|
+
console.log('Shared seed completed');`}function N(t){let{migrationName:e,type:n,template:r,tableName:i}=t,a=new Date().toISOString(),o=`-- Migration: ${e}
|
|
166
|
+
-- Type: ${n}
|
|
167
|
+
-- Created at: ${a}
|
|
168
|
+
-- Template: ${r}
|
|
169
|
+
|
|
170
|
+
`;switch(r){case "create-table":return o+K(i);case "add-column":return o+W(i);case "add-index":return o+q(i);case "add-foreign-key":return o+Y(i);default:return o+H()}}function K(t){let e=t||"table_name";return `-- Create table: ${e}
|
|
171
|
+
CREATE TABLE IF NOT EXISTS "${e}" (
|
|
172
|
+
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
173
|
+
"name" VARCHAR(255) NOT NULL,
|
|
174
|
+
"description" TEXT,
|
|
175
|
+
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
|
176
|
+
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
177
|
+
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
-- Create indexes
|
|
181
|
+
CREATE INDEX IF NOT EXISTS "${e}_name_idx" ON "${e}" ("name");
|
|
182
|
+
CREATE INDEX IF NOT EXISTS "${e}_created_at_idx" ON "${e}" ("created_at");
|
|
183
|
+
|
|
184
|
+
-- Add updated_at trigger (optional but recommended)
|
|
185
|
+
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
186
|
+
RETURNS TRIGGER AS $$
|
|
187
|
+
BEGIN
|
|
188
|
+
NEW.updated_at = NOW();
|
|
189
|
+
RETURN NEW;
|
|
190
|
+
END;
|
|
191
|
+
$$ LANGUAGE plpgsql;
|
|
192
|
+
|
|
193
|
+
DROP TRIGGER IF EXISTS update_${e}_updated_at ON "${e}";
|
|
194
|
+
CREATE TRIGGER update_${e}_updated_at
|
|
195
|
+
BEFORE UPDATE ON "${e}"
|
|
196
|
+
FOR EACH ROW
|
|
197
|
+
EXECUTE FUNCTION update_updated_at_column();
|
|
198
|
+
`}function W(t){let e=t||"table_name";return `-- Add column to: ${e}
|
|
199
|
+
-- ALTER TABLE "${e}" ADD COLUMN "column_name" data_type [constraints];
|
|
200
|
+
|
|
201
|
+
-- Examples:
|
|
202
|
+
|
|
203
|
+
-- Add a nullable text column
|
|
204
|
+
-- ALTER TABLE "${e}" ADD COLUMN "notes" TEXT;
|
|
205
|
+
|
|
206
|
+
-- Add a non-null column with default
|
|
207
|
+
-- ALTER TABLE "${e}" ADD COLUMN "status" VARCHAR(50) NOT NULL DEFAULT 'pending';
|
|
208
|
+
|
|
209
|
+
-- Add a column with foreign key reference
|
|
210
|
+
-- ALTER TABLE "${e}" ADD COLUMN "user_id" UUID REFERENCES "users" ("id");
|
|
211
|
+
|
|
212
|
+
-- Add a column with check constraint
|
|
213
|
+
-- ALTER TABLE "${e}" ADD COLUMN "priority" INTEGER CHECK (priority >= 1 AND priority <= 5);
|
|
214
|
+
|
|
215
|
+
-- Write your column additions below:
|
|
216
|
+
|
|
217
|
+
`}function q(t){let e=t||"table_name";return `-- Add index to: ${e}
|
|
218
|
+
-- CREATE INDEX [CONCURRENTLY] "index_name" ON "${e}" ("column_name");
|
|
219
|
+
|
|
220
|
+
-- Examples:
|
|
221
|
+
|
|
222
|
+
-- Simple index on single column
|
|
223
|
+
-- CREATE INDEX "${e}_column_idx" ON "${e}" ("column_name");
|
|
224
|
+
|
|
225
|
+
-- Composite index on multiple columns
|
|
226
|
+
-- CREATE INDEX "${e}_col1_col2_idx" ON "${e}" ("column1", "column2");
|
|
227
|
+
|
|
228
|
+
-- Partial index (filtered)
|
|
229
|
+
-- CREATE INDEX "${e}_active_idx" ON "${e}" ("created_at") WHERE "is_active" = true;
|
|
230
|
+
|
|
231
|
+
-- Unique index
|
|
232
|
+
-- CREATE UNIQUE INDEX "${e}_email_unique_idx" ON "${e}" ("email");
|
|
233
|
+
|
|
234
|
+
-- GIN index for full-text search or JSONB
|
|
235
|
+
-- CREATE INDEX "${e}_data_gin_idx" ON "${e}" USING GIN ("data");
|
|
236
|
+
|
|
237
|
+
-- CONCURRENT index (doesn't lock table, recommended for production)
|
|
238
|
+
-- CREATE INDEX CONCURRENTLY "${e}_column_idx" ON "${e}" ("column_name");
|
|
239
|
+
|
|
240
|
+
-- Write your indexes below:
|
|
241
|
+
|
|
242
|
+
`}function Y(t){let e=t||"table_name";return `-- Add foreign key to: ${e}
|
|
243
|
+
-- ALTER TABLE "${e}" ADD CONSTRAINT "fk_name" FOREIGN KEY ("column") REFERENCES "other_table" ("id");
|
|
244
|
+
|
|
245
|
+
-- Examples:
|
|
246
|
+
|
|
247
|
+
-- Basic foreign key
|
|
248
|
+
-- ALTER TABLE "${e}" ADD CONSTRAINT "${e}_user_id_fk"
|
|
249
|
+
-- FOREIGN KEY ("user_id") REFERENCES "users" ("id");
|
|
250
|
+
|
|
251
|
+
-- Foreign key with ON DELETE action
|
|
252
|
+
-- ALTER TABLE "${e}" ADD CONSTRAINT "${e}_user_id_fk"
|
|
253
|
+
-- FOREIGN KEY ("user_id") REFERENCES "users" ("id")
|
|
254
|
+
-- ON DELETE CASCADE;
|
|
255
|
+
|
|
256
|
+
-- Foreign key with ON UPDATE action
|
|
257
|
+
-- ALTER TABLE "${e}" ADD CONSTRAINT "${e}_category_id_fk"
|
|
258
|
+
-- FOREIGN KEY ("category_id") REFERENCES "categories" ("id")
|
|
259
|
+
-- ON DELETE SET NULL
|
|
260
|
+
-- ON UPDATE CASCADE;
|
|
261
|
+
|
|
262
|
+
-- Cross-schema foreign key (to shared/public schema)
|
|
263
|
+
-- ALTER TABLE "${e}" ADD CONSTRAINT "${e}_plan_id_fk"
|
|
264
|
+
-- FOREIGN KEY ("plan_id") REFERENCES "public"."plans" ("id");
|
|
265
|
+
|
|
266
|
+
-- Write your foreign keys below:
|
|
267
|
+
|
|
268
|
+
`}function H(){return `-- Write your SQL migration here
|
|
269
|
+
|
|
270
|
+
-- Up migration (apply changes)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
-- Remember to consider:
|
|
274
|
+
-- 1. Idempotency: Use IF NOT EXISTS / IF EXISTS where possible
|
|
275
|
+
-- 2. Transactions: This will run in a transaction
|
|
276
|
+
-- 3. Rollback: Plan how to undo these changes if needed
|
|
277
|
+
`}function x(t){let e=[/^create[-_](.+)$/i,/^add[-_](.+?)[-_]?(?:table)?$/i,/^(?:add|create)[-_](?:index|fk|constraint)[-_](?:to|on)[-_](.+)$/i,/^(.+?)[-_](?:table|schema)$/i];for(let n of e){let r=t.match(n);if(r?.[1])return r[1].toLowerCase().replace(/[-\s]+/g,"_").replace(/s$/,"")}}function $(t){let e=t.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 y(t){let e=t.replace(/([a-z])([A-Z])/g,"$1_$2").replace(/[-\s]+/g,"_").toLowerCase(),n=e.split("_").map(i=>i.charAt(0).toUpperCase()+i.slice(1)).join(""),r=n.charAt(0).toLowerCase()+n.slice(1);return {snake:e,pascal:n,camel:r}}var f={schemaDir:"src/db/schema",seedDir:"drizzle/seeds",tenantMigrationsDir:"drizzle/tenant-migrations",sharedMigrationsDir:"drizzle/shared-migrations"};async function Q(t){let{name:e,type:n,outputDir:r,includeExample:i=true,includeTimestamps:a=true,includeSoftDelete:o=false,useUuid:d=true}=t;try{let s=y(e),m={tableName:s.snake,tableNamePascal:s.pascal,tableNameCamel:s.camel,type:n,includeTimestamps:a,includeSoftDelete:o,useUuid:d,includeExample:i},l=h(m),E=r||join(f.schemaDir,n),p=`${s.camel}.ts`,c=resolve(process.cwd(),E,p);return await mkdir(dirname(c),{recursive:!0}),existsSync(c)?{success:!1,filePath:c,fileName:p,kind:"schema",type:n,error:`File already exists: ${c}`}:(await writeFile(c,l,"utf-8"),{success:!0,filePath:c,fileName:p,kind:"schema",type:n})}catch(s){return {success:false,filePath:"",fileName:"",kind:"schema",type:n,error:s instanceof Error?s.message:String(s)}}}async function V(t){let{name:e,type:n,outputDir:r,tableName:i}=t;try{let a=y(e),o={seedName:a.camel,type:n,...i!==void 0&&{tableName:i}},d=S(o),s=r||join(f.seedDir,n),m=`${a.camel}.ts`,l=resolve(process.cwd(),s,m);return await mkdir(dirname(l),{recursive:!0}),existsSync(l)?{success:!1,filePath:l,fileName:m,kind:"seed",type:n,error:`File already exists: ${l}`}:(await writeFile(l,d,"utf-8"),{success:!0,filePath:l,fileName:m,kind:"seed",type:n})}catch(a){return {success:false,filePath:"",fileName:"",kind:"seed",type:n,error:a instanceof Error?a.message:String(a)}}}async function J(t){let{name:e,type:n,outputDir:r,template:i}=t;try{let a=n==="shared"?f.sharedMigrationsDir:f.tenantMigrationsDir,o=r||a,d=resolve(process.cwd(),o);await mkdir(d,{recursive:!0});let m=(existsSync(d)?await readdir(d):[]).filter(g=>g.endsWith(".sql")),l=0;for(let g of m){let R=g.match(/^(\d+)_/);R?.[1]&&(l=Math.max(l,parseInt(R[1],10)));}let E=(l+1).toString().padStart(4,"0"),p=e.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,""),c=`${E}_${p}.sql`,u=join(d,c),O=i||$(e),I=x(e),L={migrationName:e,type:n,template:O,...I!==void 0&&{tableName:I}},U=N(L);return existsSync(u)?{success:!1,filePath:u,fileName:c,kind:"migration",type:n,error:`File already exists: ${u}`}:(await writeFile(u,U,"utf-8"),{success:!0,filePath:u,fileName:c,kind:"migration",type:n})}catch(a){return {success:false,filePath:"",fileName:"",kind:"migration",type:n,error:a instanceof Error?a.message:String(a)}}}function ee(){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"}]}export{f as DEFAULT_DIRS,N as generateMigrationTemplate,h as generateSchemaTemplate,S as generateSeedTemplate,ee as getMigrationTemplates,$ as inferMigrationTemplate,x as inferTableName,J as scaffoldMigration,Q as scaffoldSchema,V as scaffoldSeed,y as toCase};
|
|
@@ -111,6 +111,52 @@ interface DebugContext {
|
|
|
111
111
|
/** Additional metadata */
|
|
112
112
|
metadata?: Record<string, unknown>;
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Lint rule configuration - can be severity only or [severity, options]
|
|
116
|
+
*/
|
|
117
|
+
type LintRuleConfig<TOptions = Record<string, unknown>> = 'off' | 'warn' | 'error' | ['off' | 'warn' | 'error', TOptions];
|
|
118
|
+
/**
|
|
119
|
+
* All available lint rules for configuration
|
|
120
|
+
*/
|
|
121
|
+
interface LintRulesConfig {
|
|
122
|
+
/** Enforce table naming convention (snake_case by default) */
|
|
123
|
+
'table-naming'?: LintRuleConfig<{
|
|
124
|
+
style?: 'snake_case' | 'camelCase' | 'PascalCase' | 'kebab-case';
|
|
125
|
+
exceptions?: string[];
|
|
126
|
+
}>;
|
|
127
|
+
/** Enforce column naming convention (snake_case by default) */
|
|
128
|
+
'column-naming'?: LintRuleConfig<{
|
|
129
|
+
style?: 'snake_case' | 'camelCase' | 'PascalCase' | 'kebab-case';
|
|
130
|
+
exceptions?: string[];
|
|
131
|
+
}>;
|
|
132
|
+
/** Require every table to have a primary key */
|
|
133
|
+
'require-primary-key'?: LintRuleConfig;
|
|
134
|
+
/** Prefer UUID over serial/integer for primary keys */
|
|
135
|
+
'prefer-uuid-pk'?: LintRuleConfig;
|
|
136
|
+
/** Require created_at/updated_at columns */
|
|
137
|
+
'require-timestamps'?: LintRuleConfig<{
|
|
138
|
+
columns?: string[];
|
|
139
|
+
}>;
|
|
140
|
+
/** Require indexes on foreign key columns */
|
|
141
|
+
'index-foreign-keys'?: LintRuleConfig;
|
|
142
|
+
/** Warn about CASCADE DELETE on foreign keys */
|
|
143
|
+
'no-cascade-delete'?: LintRuleConfig;
|
|
144
|
+
/** Require soft delete column on tables */
|
|
145
|
+
'require-soft-delete'?: LintRuleConfig<{
|
|
146
|
+
column?: string;
|
|
147
|
+
}>;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Lint configuration
|
|
151
|
+
*/
|
|
152
|
+
interface LintConfig {
|
|
153
|
+
/** Lint rules configuration */
|
|
154
|
+
rules?: LintRulesConfig;
|
|
155
|
+
/** Glob patterns for schema files to include */
|
|
156
|
+
include?: string[];
|
|
157
|
+
/** Glob patterns for schema files to exclude */
|
|
158
|
+
exclude?: string[];
|
|
159
|
+
}
|
|
114
160
|
/**
|
|
115
161
|
* Main configuration interface
|
|
116
162
|
*/
|
|
@@ -127,6 +173,8 @@ interface Config<TTenantSchema extends Record<string, unknown> = Record<string,
|
|
|
127
173
|
metrics?: MetricsConfig;
|
|
128
174
|
/** Debug configuration */
|
|
129
175
|
debug?: DebugConfig;
|
|
176
|
+
/** Schema linting configuration */
|
|
177
|
+
lint?: LintConfig;
|
|
130
178
|
}
|
|
131
179
|
/**
|
|
132
180
|
* Internal pool entry for LRU cache
|
|
@@ -346,4 +394,4 @@ declare const DEFAULT_CONFIG: {
|
|
|
346
394
|
};
|
|
347
395
|
};
|
|
348
396
|
|
|
349
|
-
export { type Config as C, type DebugConfig as D, type Hooks as H, type IsolationConfig as I, type MetricsConfig as M, type PoolEntry as P, type RetryConfig as R, type SharedDb as S, type TenantManager as T, type WarmupOptions as W, type TenantDb as a, type ConnectionConfig as b, type IsolationStrategy as c, type SchemasConfig as d, type DebugContext as e, type WarmupResult as f, type TenantWarmupResult as g, type HealthCheckOptions as h, type HealthCheckResult as i, type PoolHealth as j, type PoolHealthStatus as k, type MetricsResult as l, type TenantPoolMetrics as m, type ConnectionMetrics as n,
|
|
397
|
+
export { type Config as C, type DebugConfig as D, type Hooks as H, type IsolationConfig as I, type LintRuleConfig as L, type MetricsConfig as M, type PoolEntry as P, type RetryConfig as R, type SharedDb as S, type TenantManager as T, type WarmupOptions as W, type TenantDb as a, type ConnectionConfig as b, type IsolationStrategy as c, type SchemasConfig as d, type DebugContext as e, type WarmupResult as f, type TenantWarmupResult as g, type HealthCheckOptions as h, type HealthCheckResult as i, type PoolHealth as j, type PoolHealthStatus as k, type MetricsResult as l, type TenantPoolMetrics as m, type ConnectionMetrics as n, type LintRulesConfig as o, type LintConfig as p, DEFAULT_CONFIG as q };
|