drizzle-multitenant 1.0.7 → 1.0.9

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Mateus Florez
3
+ Copyright (c) 2024 Mateus Flores
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,30 +1,20 @@
1
1
  # drizzle-multitenant
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/drizzle-multitenant.svg)](https://www.npmjs.com/package/drizzle-multitenant)
4
- [![GitHub](https://img.shields.io/github/stars/mateusflorez/drizzle-multitenant?style=social)](https://github.com/mateusflorez/drizzle-multitenant)
5
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Documentation](https://img.shields.io/badge/docs-online-blue.svg)](https://mateusflorez.github.io/drizzle-multitenant/)
6
6
 
7
7
  Multi-tenancy toolkit for Drizzle ORM with schema isolation, tenant context, and parallel migrations.
8
8
 
9
- ```
10
- ┌─────────────────────────────────────────────────────────────────────┐
11
- │ ╔═══════════════════════════════════════════════════════════════╗ │
12
- │ ║ drizzle-multitenant ║ │
13
- │ ║ ━━━━━━━━━━━━━━━━━━━━ ║ │
14
- │ ║ ║ │
15
- │ ║ Schema isolation • Context propagation • Migrations ║ │
16
- │ ╚═══════════════════════════════════════════════════════════════╝ │
17
- └─────────────────────────────────────────────────────────────────────┘
18
- ```
19
-
20
9
  ## Features
21
10
 
22
- - **Schema Isolation** - Automatic PostgreSQL schema-per-tenant with LRU pool management
23
- - **Context Propagation** - AsyncLocalStorage-based tenant context across your entire stack
24
- - **Parallel Migrations** - Apply migrations to all tenants concurrently with progress tracking
25
- - **Cross-Schema Queries** - Type-safe queries joining tenant and shared schemas
26
- - **Framework Integrations** - Express, Fastify, NestJS, and Hono middleware/plugins
27
- - **CLI Tools** - Generate migrations, manage tenants, check status
11
+ - **Schema Isolation** - PostgreSQL schema-per-tenant with LRU pool management
12
+ - **Context Propagation** - AsyncLocalStorage-based tenant context
13
+ - **Parallel Migrations** - Concurrent migrations with progress tracking
14
+ - **Cross-Schema Queries** - Type-safe joins between tenant and shared tables
15
+ - **Connection Retry** - Automatic retry with exponential backoff
16
+ - **Framework Support** - Express, Fastify, NestJS middleware/plugins
17
+ - **CLI Tools** - Generate, migrate, status, tenant management
28
18
 
29
19
  ## Installation
30
20
 
@@ -34,43 +24,23 @@ npm install drizzle-multitenant drizzle-orm pg
34
24
 
35
25
  ## Quick Start
36
26
 
37
- ### 1. Define your configuration
38
-
39
27
  ```typescript
40
28
  // tenant.config.ts
41
29
  import { defineConfig } from 'drizzle-multitenant';
42
30
  import * as tenantSchema from './schemas/tenant';
43
- import * as sharedSchema from './schemas/shared';
44
31
 
45
32
  export default defineConfig({
46
- connection: {
47
- url: process.env.DATABASE_URL!,
48
- },
33
+ connection: { url: process.env.DATABASE_URL! },
49
34
  isolation: {
50
35
  strategy: 'schema',
51
- schemaNameTemplate: (tenantId) => `tenant_${tenantId}`,
52
- maxPools: 50,
53
- poolTtlMs: 60 * 60 * 1000,
54
- },
55
- schemas: {
56
- tenant: tenantSchema,
57
- shared: sharedSchema,
58
- },
59
- // Optional: CLI migrations config
60
- migrations: {
61
- tenantFolder: './drizzle/tenant',
62
- migrationsTable: '__drizzle_migrations', // Custom table name
63
- tenantDiscovery: async () => {
64
- // Return list of tenant IDs for migrations
65
- return ['tenant-1', 'tenant-2'];
66
- },
36
+ schemaNameTemplate: (id) => `tenant_${id}`,
67
37
  },
38
+ schemas: { tenant: tenantSchema },
68
39
  });
69
40
  ```
70
41
 
71
- ### 2. Create the tenant manager
72
-
73
42
  ```typescript
43
+ // app.ts
74
44
  import { createTenantManager } from 'drizzle-multitenant';
75
45
  import config from './tenant.config';
76
46
 
@@ -80,329 +50,47 @@ const tenants = createTenantManager(config);
80
50
  const db = tenants.getDb('tenant-123');
81
51
  const users = await db.select().from(schema.users);
82
52
 
83
- // Get shared DB (public schema)
84
- const shared = tenants.getSharedDb();
85
- const plans = await shared.select().from(sharedSchema.plans);
53
+ // With retry and validation
54
+ const db = await tenants.getDbAsync('tenant-123');
86
55
  ```
87
56
 
88
- ### 3. Use context propagation
57
+ ## CLI
89
58
 
90
- ```typescript
91
- import { createTenantContext } from 'drizzle-multitenant';
92
-
93
- const ctx = createTenantContext(tenants);
94
-
95
- // Run code within tenant context
96
- await ctx.runWithTenant({ tenantId: 'tenant-123' }, async () => {
97
- const db = ctx.getTenantDb();
98
- // All queries automatically scoped to tenant
99
- const users = await db.select().from(schema.users);
100
- });
59
+ ```bash
60
+ npx drizzle-multitenant init # Setup wizard
61
+ npx drizzle-multitenant generate --name=users # Create migration
62
+ npx drizzle-multitenant migrate --all # Apply to all tenants
63
+ npx drizzle-multitenant status # Check status
101
64
  ```
102
65
 
103
66
  ## Framework Integrations
104
67
 
105
- ### Express
106
-
107
68
  ```typescript
69
+ // Express
108
70
  import { createExpressMiddleware } from 'drizzle-multitenant/express';
71
+ app.use(createExpressMiddleware({ manager: tenants, extractTenantId: (req) => req.headers['x-tenant-id'] }));
109
72
 
110
- const middleware = createExpressMiddleware({
111
- manager: tenants,
112
- extractTenantId: (req) => req.headers['x-tenant-id'] as string,
113
- validateTenant: async (id) => checkTenantExists(id),
114
- });
115
-
116
- app.use('/api/:tenantId/*', middleware);
117
- ```
118
-
119
- ### Fastify
120
-
121
- ```typescript
73
+ // Fastify
122
74
  import { fastifyTenantPlugin } from 'drizzle-multitenant/fastify';
75
+ fastify.register(fastifyTenantPlugin, { manager: tenants, extractTenantId: (req) => req.headers['x-tenant-id'] });
123
76
 
124
- await fastify.register(fastifyTenantPlugin, {
125
- manager: tenants,
126
- extractTenantId: (req) => req.headers['x-tenant-id'] as string,
127
- });
128
- ```
129
-
130
- ### NestJS
131
-
132
- ```typescript
77
+ // NestJS
133
78
  import { TenantModule, InjectTenantDb } from 'drizzle-multitenant/nestjs';
134
-
135
- @Module({
136
- imports: [
137
- TenantModule.forRoot({
138
- config: tenantConfig,
139
- extractTenantId: (req) => req.headers['x-tenant-id'],
140
- isGlobal: true,
141
- }),
142
- ],
143
- })
144
- export class AppModule {}
145
-
146
- @Injectable({ scope: Scope.REQUEST })
147
- export class UserService {
148
- constructor(@InjectTenantDb() private readonly db: TenantDb) {}
149
-
150
- async findAll() {
151
- return this.db.select().from(users);
152
- }
153
- }
154
- ```
155
-
156
- #### Singleton Services (Cron Jobs, Event Handlers)
157
-
158
- Use `TenantDbFactory` when you need to access tenant databases from singleton services:
159
-
160
- ```typescript
161
- import { TenantDbFactory, InjectTenantDbFactory } from 'drizzle-multitenant/nestjs';
162
-
163
- @Injectable() // Singleton - no scope needed
164
- export class ReportService {
165
- constructor(@InjectTenantDbFactory() private dbFactory: TenantDbFactory) {}
166
-
167
- async generateReport(tenantId: string) {
168
- const db = this.dbFactory.getDb(tenantId);
169
- return db.select().from(reports);
170
- }
171
- }
172
-
173
- // Cron job example
174
- @Injectable()
175
- export class DailyReportCron {
176
- constructor(@InjectTenantDbFactory() private dbFactory: TenantDbFactory) {}
177
-
178
- @Cron('0 8 * * *')
179
- async run() {
180
- const tenants = await this.getTenantIds();
181
- for (const tenantId of tenants) {
182
- const db = this.dbFactory.getDb(tenantId);
183
- await this.processReports(db);
184
- }
185
- }
186
- }
79
+ @Module({ imports: [TenantModule.forRoot({ config, extractTenantId: (req) => req.headers['x-tenant-id'] })] })
187
80
  ```
188
81
 
189
- #### Debugging
190
-
191
- The injected `TenantDb` provides debug utilities:
192
-
193
- ```typescript
194
- // Console output shows useful info
195
- console.log(tenantDb);
196
- // [TenantDb] tenant=123 schema=empresa_123
197
-
198
- // Access debug information
199
- console.log(tenantDb.__debug);
200
- // { tenantId: '123', schemaName: 'empresa_123', isProxy: true, poolCount: 5 }
201
-
202
- // Quick access
203
- console.log(tenantDb.__tenantId); // '123'
204
-
205
- // In tests
206
- expect(tenantDb.__tenantId).toBe('expected-tenant');
207
- ```
82
+ ## Documentation
208
83
 
209
- ## CLI Commands
84
+ **[Read the full documentation →](https://mateusflorez.github.io/drizzle-multitenant/)**
210
85
 
211
- ```bash
212
- # Initialize configuration (interactive wizard)
213
- npx drizzle-multitenant init
214
-
215
- # Generate a new migration
216
- npx drizzle-multitenant generate --name=add-users-table
217
-
218
- # Apply migrations to all tenants (with progress bar)
219
- npx drizzle-multitenant migrate --all --concurrency=10
220
-
221
- # Interactive tenant selection
222
- npx drizzle-multitenant migrate # Shows checkbox to select tenants
223
-
224
- # Check migration status
225
- npx drizzle-multitenant status
226
-
227
- # Create a new tenant schema
228
- npx drizzle-multitenant tenant:create --id=new-tenant
229
-
230
- # Drop a tenant schema
231
- npx drizzle-multitenant tenant:drop --id=old-tenant --force
232
-
233
- # Convert migration table format
234
- npx drizzle-multitenant convert-format --to=name --dry-run
235
-
236
- # Generate shell completions
237
- npx drizzle-multitenant completion bash >> ~/.bashrc
238
- npx drizzle-multitenant completion zsh >> ~/.zshrc
239
- ```
240
-
241
- ### Global Options
242
-
243
- ```bash
244
- --json # Output as JSON (for scripts/CI)
245
- --verbose # Show detailed output
246
- --quiet # Only show errors
247
- --no-color # Disable colored output
248
- ```
249
-
250
- ### JSON Output
251
-
252
- ```bash
253
- # Get status as JSON for scripting
254
- npx drizzle-multitenant status --json | jq '.tenants[] | select(.pending > 0)'
255
-
256
- # Parse migration results
257
- npx drizzle-multitenant migrate --all --json | jq '.summary'
258
- ```
259
-
260
- ### Status Output
261
-
262
- ```
263
- ┌──────────────────┬──────────────┬────────────┬─────────┬─────────┬──────────┐
264
- │ Tenant │ Schema │ Format │ Applied │ Pending │ Status │
265
- ├──────────────────┼──────────────┼────────────┼─────────┼─────────┼──────────┤
266
- │ abc-123 │ tenant_abc │ drizzle-kit│ 45 │ 3 │ Behind │
267
- │ def-456 │ tenant_def │ name │ 48 │ 0 │ OK │
268
- │ ghi-789 │ tenant_ghi │ (new) │ 0 │ 48 │ Behind │
269
- └──────────────────┴──────────────┴────────────┴─────────┴─────────┴──────────┘
270
- ```
271
-
272
- ## Migration Table Formats
273
-
274
- `drizzle-multitenant` supports multiple migration table formats for compatibility with existing databases:
275
-
276
- ### Supported Formats
277
-
278
- | Format | Identifier | Timestamp | Compatible With |
279
- |--------|------------|-----------|-----------------|
280
- | `name` | Filename | `applied_at` (timestamp) | drizzle-multitenant native |
281
- | `hash` | SHA-256 | `created_at` (timestamp) | Custom scripts |
282
- | `drizzle-kit` | SHA-256 | `created_at` (bigint) | drizzle-kit migrate |
283
-
284
- ### Configuration
285
-
286
- ```typescript
287
- // tenant.config.ts
288
- export default defineConfig({
289
- // ...
290
- migrations: {
291
- tenantFolder: './drizzle/tenant',
292
- tenantDiscovery: async () => getTenantIds(),
293
-
294
- /**
295
- * Table format for tracking migrations
296
- * - "auto": Auto-detect existing format (default)
297
- * - "name": Filename-based (drizzle-multitenant native)
298
- * - "hash": SHA-256 hash
299
- * - "drizzle-kit": Exact drizzle-kit format
300
- */
301
- tableFormat: 'auto',
302
-
303
- /**
304
- * Format to use when creating new tables (only for "auto" mode)
305
- * @default "name"
306
- */
307
- defaultFormat: 'name',
308
- },
309
- });
310
- ```
311
-
312
- ### Migrating from drizzle-kit
313
-
314
- If you have existing databases with migrations applied via `drizzle-kit migrate`, the CLI will automatically detect the format:
315
-
316
- ```bash
317
- # Check current format for all tenants
318
- npx drizzle-multitenant status
319
-
320
- # Apply new migrations (works with any format)
321
- npx drizzle-multitenant migrate --all
322
- ```
323
-
324
- ### Converting Between Formats
325
-
326
- Use the `convert-format` command to standardize all tenants to a single format:
327
-
328
- ```bash
329
- # Preview conversion (dry-run)
330
- npx drizzle-multitenant convert-format --to=name --dry-run
331
-
332
- # Convert all tenants to name format
333
- npx drizzle-multitenant convert-format --to=name
334
-
335
- # Convert specific tenant
336
- npx drizzle-multitenant convert-format --to=name --tenant=abc-123
337
-
338
- # Convert to drizzle-kit format (for compatibility)
339
- npx drizzle-multitenant convert-format --to=drizzle-kit
340
- ```
341
-
342
- ## Cross-Schema Queries
343
-
344
- ```typescript
345
- import { createCrossSchemaQuery } from 'drizzle-multitenant/cross-schema';
346
-
347
- const query = createCrossSchemaQuery({
348
- tenantDb: tenants.getDb('tenant-123'),
349
- sharedDb: tenants.getSharedDb(),
350
- tenantSchema: 'tenant_123',
351
- sharedSchema: 'public',
352
- });
353
-
354
- // Type-safe join between tenant and shared tables
355
- const result = await query
356
- .select({
357
- orderId: orders.id,
358
- planName: subscriptionPlans.name,
359
- })
360
- .from(orders)
361
- .leftJoin(subscriptionPlans, eq(orders.planId, subscriptionPlans.id));
362
- ```
363
-
364
- ## API Reference
365
-
366
- ### Core
367
-
368
- | Export | Description |
369
- |--------|-------------|
370
- | `defineConfig()` | Create typed configuration |
371
- | `createTenantManager()` | Create pool manager instance |
372
- | `createTenantContext()` | Create AsyncLocalStorage context |
373
-
374
- ### Manager Methods
375
-
376
- | Method | Description |
377
- |--------|-------------|
378
- | `getDb(tenantId)` | Get Drizzle instance for tenant |
379
- | `getSharedDb()` | Get Drizzle instance for shared schema |
380
- | `getSchemaName(tenantId)` | Get schema name for tenant |
381
- | `hasPool(tenantId)` | Check if pool exists |
382
- | `evictPool(tenantId)` | Force evict a pool |
383
- | `dispose()` | Cleanup all pools |
384
-
385
- ### NestJS Decorators
386
-
387
- | Decorator | Description |
388
- |-----------|-------------|
389
- | `@InjectTenantDb()` | Inject tenant database (request-scoped) |
390
- | `@InjectTenantDbFactory()` | Inject factory for singleton services |
391
- | `@InjectSharedDb()` | Inject shared database |
392
- | `@InjectTenantContext()` | Inject tenant context |
393
- | `@InjectTenantManager()` | Inject tenant manager |
394
- | `@RequiresTenant()` | Mark route as requiring tenant |
395
- | `@PublicRoute()` | Mark route as public |
396
-
397
- ### TenantDbFactory Methods
398
-
399
- | Method | Description |
400
- |--------|-------------|
401
- | `getDb(tenantId)` | Get Drizzle instance for tenant |
402
- | `getSharedDb()` | Get shared database instance |
403
- | `getSchemaName(tenantId)` | Get schema name for tenant |
404
- | `getDebugInfo(tenantId)` | Get debug info (tenantId, schema, pool stats) |
405
- | `getManager()` | Get underlying TenantManager |
86
+ - [Getting Started](https://mateusflorez.github.io/drizzle-multitenant/guide/getting-started)
87
+ - [Configuration](https://mateusflorez.github.io/drizzle-multitenant/guide/configuration)
88
+ - [Framework Integrations](https://mateusflorez.github.io/drizzle-multitenant/guide/frameworks/express)
89
+ - [CLI Commands](https://mateusflorez.github.io/drizzle-multitenant/guide/cli)
90
+ - [Cross-Schema Queries](https://mateusflorez.github.io/drizzle-multitenant/guide/cross-schema)
91
+ - [Advanced Features](https://mateusflorez.github.io/drizzle-multitenant/guide/advanced)
92
+ - [API Reference](https://mateusflorez.github.io/drizzle-multitenant/api/reference)
93
+ - [Examples](https://mateusflorez.github.io/drizzle-multitenant/examples/)
406
94
 
407
95
  ## Requirements
408
96
 
@@ -410,30 +98,6 @@ const result = await query
410
98
  - PostgreSQL 12+
411
99
  - Drizzle ORM 0.29+
412
100
 
413
- ## Tech Stack
414
-
415
- | Package | Purpose |
416
- |---------|---------|
417
- | `drizzle-orm` | Type-safe ORM |
418
- | `pg` | PostgreSQL driver |
419
- | `lru-cache` | Pool management |
420
- | `commander` | CLI framework |
421
- | `chalk` | Terminal styling |
422
- | `ora` | Loading spinners |
423
- | `cli-table3` | Table formatting |
424
- | `cli-progress` | Progress bars |
425
- | `@inquirer/prompts` | Interactive prompts |
426
-
427
- ## Comparison
428
-
429
- | Feature | drizzle-multitenant | Manual Implementation |
430
- |---------|---------------------|----------------------|
431
- | Pool management | Automatic LRU | Manual |
432
- | Context propagation | AsyncLocalStorage | Pass through params |
433
- | Parallel migrations | Built-in CLI | Custom scripts |
434
- | Cross-schema queries | Type-safe builder | Raw SQL |
435
- | Framework support | Express/Fastify/NestJS/Hono | DIY |
436
-
437
101
  ## License
438
102
 
439
103
  MIT