drizzle-multitenant 1.0.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/.claude/settings.local.json +14 -0
- package/LICENSE +21 -0
- package/README.md +262 -0
- package/bin/drizzle-multitenant.js +2 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +866 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-DBerWr50.d.ts +76 -0
- package/dist/cross-schema/index.d.ts +262 -0
- package/dist/cross-schema/index.js +219 -0
- package/dist/cross-schema/index.js.map +1 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.js +935 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/express.d.ts +93 -0
- package/dist/integrations/express.js +110 -0
- package/dist/integrations/express.js.map +1 -0
- package/dist/integrations/fastify.d.ts +92 -0
- package/dist/integrations/fastify.js +236 -0
- package/dist/integrations/fastify.js.map +1 -0
- package/dist/integrations/hono.d.ts +2 -0
- package/dist/integrations/hono.js +3 -0
- package/dist/integrations/hono.js.map +1 -0
- package/dist/integrations/nestjs/index.d.ts +386 -0
- package/dist/integrations/nestjs/index.js +9828 -0
- package/dist/integrations/nestjs/index.js.map +1 -0
- package/dist/migrator/index.d.ts +208 -0
- package/dist/migrator/index.js +390 -0
- package/dist/migrator/index.js.map +1 -0
- package/dist/types-DKVaTaIb.d.ts +130 -0
- package/package.json +102 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
+
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
__defProp(target, "default", { value: mod, enumerable: true }) ,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
|
|
29
|
+
// node_modules/fastify-plugin/lib/getPluginName.js
|
|
30
|
+
var require_getPluginName = __commonJS({
|
|
31
|
+
"node_modules/fastify-plugin/lib/getPluginName.js"(exports$1, module) {
|
|
32
|
+
var fpStackTracePattern = /at\s(?:.*\.)?plugin\s.*\n\s*(.*)/;
|
|
33
|
+
var fileNamePattern = /(\w*(\.\w*)*)\..*/;
|
|
34
|
+
module.exports = function getPluginName(fn) {
|
|
35
|
+
if (fn.name.length > 0) return fn.name;
|
|
36
|
+
const stackTraceLimit = Error.stackTraceLimit;
|
|
37
|
+
Error.stackTraceLimit = 10;
|
|
38
|
+
try {
|
|
39
|
+
throw new Error("anonymous function");
|
|
40
|
+
} catch (e) {
|
|
41
|
+
Error.stackTraceLimit = stackTraceLimit;
|
|
42
|
+
return extractPluginName(e.stack);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
function extractPluginName(stack) {
|
|
46
|
+
const m = stack.match(fpStackTracePattern);
|
|
47
|
+
return m ? m[1].split(/[/\\]/).slice(-1)[0].match(fileNamePattern)[1] : "anonymous";
|
|
48
|
+
}
|
|
49
|
+
module.exports.extractPluginName = extractPluginName;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// node_modules/fastify-plugin/lib/toCamelCase.js
|
|
54
|
+
var require_toCamelCase = __commonJS({
|
|
55
|
+
"node_modules/fastify-plugin/lib/toCamelCase.js"(exports$1, module) {
|
|
56
|
+
module.exports = function toCamelCase(name) {
|
|
57
|
+
if (name[0] === "@") {
|
|
58
|
+
name = name.slice(1).replace("/", "-");
|
|
59
|
+
}
|
|
60
|
+
return name.replace(/-(.)/g, function(match, g1) {
|
|
61
|
+
return g1.toUpperCase();
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// node_modules/fastify-plugin/plugin.js
|
|
68
|
+
var require_plugin = __commonJS({
|
|
69
|
+
"node_modules/fastify-plugin/plugin.js"(exports$1, module) {
|
|
70
|
+
var getPluginName = require_getPluginName();
|
|
71
|
+
var toCamelCase = require_toCamelCase();
|
|
72
|
+
var count = 0;
|
|
73
|
+
function plugin(fn, options = {}) {
|
|
74
|
+
let autoName = false;
|
|
75
|
+
if (fn.default !== void 0) {
|
|
76
|
+
fn = fn.default;
|
|
77
|
+
}
|
|
78
|
+
if (typeof fn !== "function") {
|
|
79
|
+
throw new TypeError(
|
|
80
|
+
`fastify-plugin expects a function, instead got a '${typeof fn}'`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (typeof options === "string") {
|
|
84
|
+
options = {
|
|
85
|
+
fastify: options
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (typeof options !== "object" || Array.isArray(options) || options === null) {
|
|
89
|
+
throw new TypeError("The options object should be an object");
|
|
90
|
+
}
|
|
91
|
+
if (options.fastify !== void 0 && typeof options.fastify !== "string") {
|
|
92
|
+
throw new TypeError(`fastify-plugin expects a version string, instead got '${typeof options.fastify}'`);
|
|
93
|
+
}
|
|
94
|
+
if (!options.name) {
|
|
95
|
+
autoName = true;
|
|
96
|
+
options.name = getPluginName(fn) + "-auto-" + count++;
|
|
97
|
+
}
|
|
98
|
+
fn[/* @__PURE__ */ Symbol.for("skip-override")] = options.encapsulate !== true;
|
|
99
|
+
fn[/* @__PURE__ */ Symbol.for("fastify.display-name")] = options.name;
|
|
100
|
+
fn[/* @__PURE__ */ Symbol.for("plugin-meta")] = options;
|
|
101
|
+
if (!fn.default) {
|
|
102
|
+
fn.default = fn;
|
|
103
|
+
}
|
|
104
|
+
const camelCase = toCamelCase(options.name);
|
|
105
|
+
if (!autoName && !fn[camelCase]) {
|
|
106
|
+
fn[camelCase] = fn;
|
|
107
|
+
}
|
|
108
|
+
return fn;
|
|
109
|
+
}
|
|
110
|
+
module.exports = plugin;
|
|
111
|
+
module.exports.default = plugin;
|
|
112
|
+
module.exports.fastifyPlugin = plugin;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// src/integrations/fastify.ts
|
|
117
|
+
var import_fastify_plugin = __toESM(require_plugin());
|
|
118
|
+
function createTenantContext(manager) {
|
|
119
|
+
const storage = new AsyncLocalStorage();
|
|
120
|
+
function getTenantOrNull() {
|
|
121
|
+
return storage.getStore();
|
|
122
|
+
}
|
|
123
|
+
function getTenant() {
|
|
124
|
+
const context = getTenantOrNull();
|
|
125
|
+
if (!context) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
"[drizzle-multitenant] No tenant context found. Make sure you are calling this within runWithTenant()."
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return context;
|
|
131
|
+
}
|
|
132
|
+
function getTenantId() {
|
|
133
|
+
return getTenant().tenantId;
|
|
134
|
+
}
|
|
135
|
+
function getTenantDb() {
|
|
136
|
+
const tenantId = getTenantId();
|
|
137
|
+
return manager.getDb(tenantId);
|
|
138
|
+
}
|
|
139
|
+
function getSharedDb() {
|
|
140
|
+
return manager.getSharedDb();
|
|
141
|
+
}
|
|
142
|
+
function isInTenantContext() {
|
|
143
|
+
return getTenantOrNull() !== void 0;
|
|
144
|
+
}
|
|
145
|
+
function runWithTenant(context, callback) {
|
|
146
|
+
if (!context.tenantId) {
|
|
147
|
+
throw new Error("[drizzle-multitenant] tenantId is required in context");
|
|
148
|
+
}
|
|
149
|
+
return storage.run(context, callback);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
runWithTenant,
|
|
153
|
+
getTenant,
|
|
154
|
+
getTenantOrNull,
|
|
155
|
+
getTenantId,
|
|
156
|
+
getTenantDb,
|
|
157
|
+
getSharedDb,
|
|
158
|
+
isInTenantContext
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/integrations/fastify.ts
|
|
163
|
+
var TenantNotFoundError = class extends Error {
|
|
164
|
+
constructor(message = "Tenant not found") {
|
|
165
|
+
super(message);
|
|
166
|
+
this.name = "TenantNotFoundError";
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var TenantValidationError = class extends Error {
|
|
170
|
+
constructor(message = "Tenant validation failed") {
|
|
171
|
+
super(message);
|
|
172
|
+
this.name = "TenantValidationError";
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
function createFastifyPlugin(options) {
|
|
176
|
+
const { manager, extractTenantId, validateTenant, enrichContext, onError } = options;
|
|
177
|
+
const tenantContext = createTenantContext(manager);
|
|
178
|
+
const defaultErrorHandler = async (error, _req, reply) => {
|
|
179
|
+
if (error instanceof TenantNotFoundError) {
|
|
180
|
+
reply.status(400).send({ error: "Tenant ID is required" });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (error instanceof TenantValidationError) {
|
|
184
|
+
reply.status(403).send({ error: "Invalid tenant" });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
reply.status(500).send({ error: "Internal server error" });
|
|
188
|
+
};
|
|
189
|
+
const errorHandler = onError ?? defaultErrorHandler;
|
|
190
|
+
const pluginImpl = async (fastify) => {
|
|
191
|
+
fastify.decorateRequest("tenantContext", void 0);
|
|
192
|
+
fastify.addHook("preHandler", async (req, reply) => {
|
|
193
|
+
let tenantId;
|
|
194
|
+
try {
|
|
195
|
+
tenantId = await extractTenantId(req);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
await errorHandler(error, req, reply);
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
if (!tenantId) {
|
|
201
|
+
const error = new TenantNotFoundError("Tenant ID not found in request");
|
|
202
|
+
await errorHandler(error, req, reply);
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
if (validateTenant) {
|
|
206
|
+
try {
|
|
207
|
+
const isValid = await validateTenant(tenantId, req);
|
|
208
|
+
if (!isValid) {
|
|
209
|
+
const error = new TenantValidationError(`Tenant ${tenantId} validation failed`);
|
|
210
|
+
await errorHandler(error, req, reply);
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (!(error instanceof TenantValidationError)) {
|
|
215
|
+
await errorHandler(error, req, reply);
|
|
216
|
+
}
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const customContext = enrichContext ? await enrichContext(tenantId, req) : {};
|
|
221
|
+
const context = {
|
|
222
|
+
tenantId,
|
|
223
|
+
...customContext
|
|
224
|
+
};
|
|
225
|
+
req.tenantContext = context;
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
const plugin = (0, import_fastify_plugin.default)(pluginImpl, {
|
|
229
|
+
name: "drizzle-multitenant"
|
|
230
|
+
});
|
|
231
|
+
return { plugin, context: tenantContext };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export { TenantNotFoundError, TenantValidationError, createFastifyPlugin, createTenantContext };
|
|
235
|
+
//# sourceMappingURL=fastify.js.map
|
|
236
|
+
//# sourceMappingURL=fastify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../node_modules/fastify-plugin/lib/getPluginName.js","../../node_modules/fastify-plugin/lib/toCamelCase.js","../../node_modules/fastify-plugin/plugin.js","../../src/integrations/fastify.ts","../../src/context.ts"],"names":["exports","fp"],"mappings":"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","file":"fastify.js","sourcesContent":["'use strict'\n\nconst fpStackTracePattern = /at\\s(?:.*\\.)?plugin\\s.*\\n\\s*(.*)/\nconst fileNamePattern = /(\\w*(\\.\\w*)*)\\..*/\n\nmodule.exports = function getPluginName (fn) {\n if (fn.name.length > 0) return fn.name\n\n const stackTraceLimit = Error.stackTraceLimit\n Error.stackTraceLimit = 10\n try {\n throw new Error('anonymous function')\n } catch (e) {\n Error.stackTraceLimit = stackTraceLimit\n return extractPluginName(e.stack)\n }\n}\n\nfunction extractPluginName (stack) {\n const m = stack.match(fpStackTracePattern)\n\n // get last section of path and match for filename\n return m ? m[1].split(/[/\\\\]/).slice(-1)[0].match(fileNamePattern)[1] : 'anonymous'\n}\nmodule.exports.extractPluginName = extractPluginName\n","'use strict'\n\nmodule.exports = function toCamelCase (name) {\n if (name[0] === '@') {\n name = name.slice(1).replace('/', '-')\n }\n return name.replace(/-(.)/g, function (match, g1) {\n return g1.toUpperCase()\n })\n}\n","'use strict'\n\nconst getPluginName = require('./lib/getPluginName')\nconst toCamelCase = require('./lib/toCamelCase')\n\nlet count = 0\n\nfunction plugin (fn, options = {}) {\n let autoName = false\n\n if (fn.default !== undefined) {\n // Support for 'export default' behaviour in transpiled ECMAScript module\n fn = fn.default\n }\n\n if (typeof fn !== 'function') {\n throw new TypeError(\n `fastify-plugin expects a function, instead got a '${typeof fn}'`\n )\n }\n\n if (typeof options === 'string') {\n options = {\n fastify: options\n }\n }\n\n if (\n typeof options !== 'object' ||\n Array.isArray(options) ||\n options === null\n ) {\n throw new TypeError('The options object should be an object')\n }\n\n if (options.fastify !== undefined && typeof options.fastify !== 'string') {\n throw new TypeError(`fastify-plugin expects a version string, instead got '${typeof options.fastify}'`)\n }\n\n if (!options.name) {\n autoName = true\n options.name = getPluginName(fn) + '-auto-' + count++\n }\n\n fn[Symbol.for('skip-override')] = options.encapsulate !== true\n fn[Symbol.for('fastify.display-name')] = options.name\n fn[Symbol.for('plugin-meta')] = options\n\n // Faux modules support\n if (!fn.default) {\n fn.default = fn\n }\n\n // TypeScript support for named imports\n // See https://github.com/fastify/fastify/issues/2404 for more details\n // The type definitions would have to be update to match this.\n const camelCase = toCamelCase(options.name)\n if (!autoName && !fn[camelCase]) {\n fn[camelCase] = fn\n }\n\n return fn\n}\n\nmodule.exports = plugin\nmodule.exports.default = plugin\nmodule.exports.fastifyPlugin = plugin\n","import type {\n FastifyPluginAsync,\n FastifyRequest,\n FastifyReply,\n FastifyInstance,\n} from 'fastify';\nimport fp from 'fastify-plugin';\nimport type { TenantManager } from '../types.js';\nimport { createTenantContext, type TenantContext, type TenantContextData } from '../context.js';\n\n/**\n * Fastify plugin options\n */\nexport interface FastifyPluginOptions<\n TTenantSchema extends Record<string, unknown>,\n TSharedSchema extends Record<string, unknown>,\n TCustom extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Tenant manager instance */\n manager: TenantManager<TTenantSchema, TSharedSchema>;\n\n /**\n * Extract tenant ID from request\n * @example\n * // From header\n * extractTenantId: (req) => req.headers['x-tenant-id'] as string\n *\n * // From path param\n * extractTenantId: (req) => (req.params as any).tenantId\n *\n * // From subdomain\n * extractTenantId: (req) => req.hostname.split('.')[0]\n */\n extractTenantId: (req: FastifyRequest) => string | undefined | Promise<string | undefined>;\n\n /**\n * Optional tenant validation\n * Throw an error or return false to reject the request\n */\n validateTenant?: (tenantId: string, req: FastifyRequest) => boolean | Promise<boolean>;\n\n /**\n * Enrich context with additional data\n */\n enrichContext?: (tenantId: string, req: FastifyRequest) => TCustom | Promise<TCustom>;\n\n /**\n * Custom error handler\n */\n onError?: (error: Error, req: FastifyRequest, reply: FastifyReply) => void | Promise<void>;\n}\n\n/**\n * Tenant not found error\n */\nexport class TenantNotFoundError extends Error {\n constructor(message = 'Tenant not found') {\n super(message);\n this.name = 'TenantNotFoundError';\n }\n}\n\n/**\n * Tenant validation error\n */\nexport class TenantValidationError extends Error {\n constructor(message = 'Tenant validation failed') {\n super(message);\n this.name = 'TenantValidationError';\n }\n}\n\n/**\n * Fastify request decorator\n */\ndeclare module 'fastify' {\n interface FastifyRequest {\n tenantContext?: TenantContextData<Record<string, unknown>>;\n }\n}\n\n/**\n * Create Fastify plugin for tenant context\n *\n * @example\n * ```typescript\n * import Fastify from 'fastify';\n * import { createTenantManager } from 'drizzle-multitenant';\n * import { createFastifyPlugin } from 'drizzle-multitenant/fastify';\n *\n * const fastify = Fastify();\n * const manager = createTenantManager(config);\n *\n * const { plugin, context } = createFastifyPlugin({\n * manager,\n * extractTenantId: (req) => req.headers['x-tenant-id'] as string,\n * });\n *\n * await fastify.register(plugin);\n *\n * fastify.get('/users', async (req, reply) => {\n * const db = context.getTenantDb();\n * const users = await db.select().from(schema.users);\n * return users;\n * });\n * ```\n */\nexport function createFastifyPlugin<\n TTenantSchema extends Record<string, unknown>,\n TSharedSchema extends Record<string, unknown>,\n TCustom extends Record<string, unknown> = Record<string, unknown>,\n>(\n options: FastifyPluginOptions<TTenantSchema, TSharedSchema, TCustom>\n): {\n plugin: FastifyPluginAsync;\n context: TenantContext<TTenantSchema, TSharedSchema, TCustom>;\n} {\n const { manager, extractTenantId, validateTenant, enrichContext, onError } = options;\n\n const tenantContext = createTenantContext<TTenantSchema, TSharedSchema, TCustom>(manager);\n\n const defaultErrorHandler = async (\n error: Error,\n _req: FastifyRequest,\n reply: FastifyReply\n ): Promise<void> => {\n if (error instanceof TenantNotFoundError) {\n reply.status(400).send({ error: 'Tenant ID is required' });\n return;\n }\n if (error instanceof TenantValidationError) {\n reply.status(403).send({ error: 'Invalid tenant' });\n return;\n }\n reply.status(500).send({ error: 'Internal server error' });\n };\n\n const errorHandler = onError ?? defaultErrorHandler;\n\n const pluginImpl: FastifyPluginAsync = async (fastify: FastifyInstance): Promise<void> => {\n // Decorate request with tenantContext\n fastify.decorateRequest('tenantContext', undefined);\n\n // Add preHandler hook to set tenant context\n fastify.addHook('preHandler', async (req: FastifyRequest, reply: FastifyReply) => {\n let tenantId: string | undefined;\n\n try {\n tenantId = await extractTenantId(req);\n } catch (error) {\n await errorHandler(error as Error, req, reply);\n throw error; // Re-throw to stop processing\n }\n\n if (!tenantId) {\n const error = new TenantNotFoundError('Tenant ID not found in request');\n await errorHandler(error, req, reply);\n throw error;\n }\n\n if (validateTenant) {\n try {\n const isValid = await validateTenant(tenantId, req);\n if (!isValid) {\n const error = new TenantValidationError(`Tenant ${tenantId} validation failed`);\n await errorHandler(error, req, reply);\n throw error;\n }\n } catch (error) {\n if (!(error instanceof TenantValidationError)) {\n await errorHandler(error as Error, req, reply);\n }\n throw error;\n }\n }\n\n const customContext = enrichContext ? await enrichContext(tenantId, req) : ({} as TCustom);\n\n const context: TenantContextData<TCustom> = {\n tenantId,\n ...customContext,\n };\n\n req.tenantContext = context;\n });\n };\n\n // Use fastify-plugin to make the plugin global (not encapsulated)\n const plugin = fp(pluginImpl, {\n name: 'drizzle-multitenant',\n });\n\n return { plugin, context: tenantContext };\n}\n\n/**\n * Re-export context utilities for convenience\n */\nexport { createTenantContext, type TenantContext, type TenantContextData } from '../context.js';\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { TenantManager, TenantDb, SharedDb } from './types.js';\n\n/**\n * Base tenant context data\n */\nexport interface BaseTenantContext {\n tenantId: string;\n}\n\n/**\n * Tenant context with optional custom data\n */\nexport type TenantContextData<TCustom extends Record<string, unknown> = Record<string, unknown>> =\n BaseTenantContext & TCustom;\n\n/**\n * Tenant context API\n */\nexport interface TenantContext<\n TTenantSchema extends Record<string, unknown>,\n TSharedSchema extends Record<string, unknown>,\n TCustom extends Record<string, unknown> = Record<string, unknown>,\n> {\n /**\n * Run a callback with tenant context\n */\n runWithTenant<T>(\n context: TenantContextData<TCustom>,\n callback: () => T | Promise<T>\n ): T | Promise<T>;\n\n /**\n * Get current tenant context (throws if not in context)\n */\n getTenant(): TenantContextData<TCustom>;\n\n /**\n * Get current tenant context or undefined\n */\n getTenantOrNull(): TenantContextData<TCustom> | undefined;\n\n /**\n * Get current tenant ID (throws if not in context)\n */\n getTenantId(): string;\n\n /**\n * Get database for current tenant (throws if not in context)\n */\n getTenantDb(): TenantDb<TTenantSchema>;\n\n /**\n * Get shared database\n */\n getSharedDb(): SharedDb<TSharedSchema>;\n\n /**\n * Check if currently running within a tenant context\n */\n isInTenantContext(): boolean;\n}\n\n/**\n * Create a tenant context with AsyncLocalStorage\n *\n * @example\n * ```typescript\n * import { createTenantContext, createTenantManager } from 'drizzle-multitenant';\n *\n * const manager = createTenantManager(config);\n *\n * const {\n * runWithTenant,\n * getTenant,\n * getTenantDb,\n * getSharedDb,\n * } = createTenantContext(manager);\n *\n * // Use in request handler\n * app.get('/users', async (req, res) => {\n * const tenantId = req.headers['x-tenant-id'];\n *\n * await runWithTenant({ tenantId }, async () => {\n * const db = getTenantDb();\n * const users = await db.select().from(schema.users);\n * res.json(users);\n * });\n * });\n * ```\n */\nexport function createTenantContext<\n TTenantSchema extends Record<string, unknown>,\n TSharedSchema extends Record<string, unknown> = Record<string, unknown>,\n TCustom extends Record<string, unknown> = Record<string, unknown>,\n>(\n manager: TenantManager<TTenantSchema, TSharedSchema>\n): TenantContext<TTenantSchema, TSharedSchema, TCustom> {\n const storage = new AsyncLocalStorage<TenantContextData<TCustom>>();\n\n function getTenantOrNull(): TenantContextData<TCustom> | undefined {\n return storage.getStore();\n }\n\n function getTenant(): TenantContextData<TCustom> {\n const context = getTenantOrNull();\n if (!context) {\n throw new Error(\n '[drizzle-multitenant] No tenant context found. ' +\n 'Make sure you are calling this within runWithTenant().'\n );\n }\n return context;\n }\n\n function getTenantId(): string {\n return getTenant().tenantId;\n }\n\n function getTenantDb(): TenantDb<TTenantSchema> {\n const tenantId = getTenantId();\n return manager.getDb(tenantId);\n }\n\n function getSharedDb(): SharedDb<TSharedSchema> {\n return manager.getSharedDb();\n }\n\n function isInTenantContext(): boolean {\n return getTenantOrNull() !== undefined;\n }\n\n function runWithTenant<T>(\n context: TenantContextData<TCustom>,\n callback: () => T | Promise<T>\n ): T | Promise<T> {\n if (!context.tenantId) {\n throw new Error('[drizzle-multitenant] tenantId is required in context');\n }\n return storage.run(context, callback);\n }\n\n return {\n runWithTenant,\n getTenant,\n getTenantOrNull,\n getTenantId,\n getTenantDb,\n getSharedDb,\n isInTenantContext,\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"hono.js"}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import * as _nestjs_common from '@nestjs/common';
|
|
2
|
+
import { ModuleMetadata, Type, InjectionToken, DynamicModule, CanActivate, ExecutionContext, NestInterceptor, CallHandler, Provider } from '@nestjs/common';
|
|
3
|
+
import { Request } from 'express';
|
|
4
|
+
import { C as Config, T as TenantManager } from '../../types-DKVaTaIb.js';
|
|
5
|
+
export { S as SharedDb, a as TenantDb } from '../../types-DKVaTaIb.js';
|
|
6
|
+
import { Reflector } from '@nestjs/core';
|
|
7
|
+
import { Observable } from 'rxjs';
|
|
8
|
+
import 'pg';
|
|
9
|
+
import 'drizzle-orm/node-postgres';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Function to extract tenant ID from request
|
|
13
|
+
*/
|
|
14
|
+
type TenantIdExtractor = (request: Request) => string | undefined | Promise<string | undefined>;
|
|
15
|
+
/**
|
|
16
|
+
* Function to validate tenant ID
|
|
17
|
+
*/
|
|
18
|
+
type TenantValidator = (tenantId: string) => boolean | Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* Tenant context data available in requests
|
|
21
|
+
*/
|
|
22
|
+
interface NestTenantContext {
|
|
23
|
+
/** Current tenant ID */
|
|
24
|
+
tenantId: string;
|
|
25
|
+
/** Schema name for the tenant */
|
|
26
|
+
schemaName: string;
|
|
27
|
+
/** Additional custom data */
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Options for TenantModule.forRoot()
|
|
32
|
+
*/
|
|
33
|
+
interface TenantModuleOptions<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>> {
|
|
34
|
+
/** Drizzle multitenant configuration */
|
|
35
|
+
config: Config<TTenantSchema, TSharedSchema>;
|
|
36
|
+
/** Function to extract tenant ID from request */
|
|
37
|
+
extractTenantId: TenantIdExtractor;
|
|
38
|
+
/** Optional function to validate tenant ID */
|
|
39
|
+
validateTenant?: TenantValidator;
|
|
40
|
+
/** Whether to make the module global */
|
|
41
|
+
isGlobal?: boolean;
|
|
42
|
+
/** Custom context enrichment */
|
|
43
|
+
enrichContext?: (tenantId: string, request: Request) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Factory for async options
|
|
47
|
+
*/
|
|
48
|
+
interface TenantModuleOptionsFactory<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>> {
|
|
49
|
+
createTenantModuleOptions(): Promise<TenantModuleOptions<TTenantSchema, TSharedSchema>> | TenantModuleOptions<TTenantSchema, TSharedSchema>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Options for TenantModule.forRootAsync()
|
|
53
|
+
*/
|
|
54
|
+
interface TenantModuleAsyncOptions<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>> extends Pick<ModuleMetadata, 'imports'> {
|
|
55
|
+
/** Whether to make the module global */
|
|
56
|
+
isGlobal?: boolean;
|
|
57
|
+
/** Use existing provider */
|
|
58
|
+
useExisting?: Type<TenantModuleOptionsFactory<TTenantSchema, TSharedSchema>>;
|
|
59
|
+
/** Use class as factory */
|
|
60
|
+
useClass?: Type<TenantModuleOptionsFactory<TTenantSchema, TSharedSchema>>;
|
|
61
|
+
/** Use factory function */
|
|
62
|
+
useFactory?: (...args: unknown[]) => Promise<TenantModuleOptions<TTenantSchema, TSharedSchema>> | TenantModuleOptions<TTenantSchema, TSharedSchema>;
|
|
63
|
+
/** Dependencies to inject into factory */
|
|
64
|
+
inject?: InjectionToken[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extended request with tenant context
|
|
68
|
+
*/
|
|
69
|
+
interface TenantRequest extends Request {
|
|
70
|
+
tenantContext?: NestTenantContext;
|
|
71
|
+
tenantId?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* NestJS module for multi-tenant support
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* // Basic usage
|
|
80
|
+
* @Module({
|
|
81
|
+
* imports: [
|
|
82
|
+
* TenantModule.forRoot({
|
|
83
|
+
* config: tenantConfig,
|
|
84
|
+
* extractTenantId: (req) => req.headers['x-tenant-id'] as string,
|
|
85
|
+
* isGlobal: true,
|
|
86
|
+
* }),
|
|
87
|
+
* ],
|
|
88
|
+
* })
|
|
89
|
+
* export class AppModule {}
|
|
90
|
+
*
|
|
91
|
+
* // Async usage with ConfigService
|
|
92
|
+
* @Module({
|
|
93
|
+
* imports: [
|
|
94
|
+
* TenantModule.forRootAsync({
|
|
95
|
+
* imports: [ConfigModule],
|
|
96
|
+
* useFactory: (configService: ConfigService) => ({
|
|
97
|
+
* config: defineConfig({
|
|
98
|
+
* connection: { url: configService.get('DATABASE_URL') },
|
|
99
|
+
* isolation: {
|
|
100
|
+
* strategy: 'schema',
|
|
101
|
+
* schemaNameTemplate: (id) => `tenant_${id}`,
|
|
102
|
+
* },
|
|
103
|
+
* schemas: { tenant: tenantSchema },
|
|
104
|
+
* }),
|
|
105
|
+
* extractTenantId: (req) => req.params.tenantId,
|
|
106
|
+
* }),
|
|
107
|
+
* inject: [ConfigService],
|
|
108
|
+
* isGlobal: true,
|
|
109
|
+
* }),
|
|
110
|
+
* ],
|
|
111
|
+
* })
|
|
112
|
+
* export class AppModule {}
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
declare class TenantModule {
|
|
116
|
+
/**
|
|
117
|
+
* Register the module with static configuration
|
|
118
|
+
*/
|
|
119
|
+
static forRoot<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>>(options: TenantModuleOptions<TTenantSchema, TSharedSchema>): DynamicModule;
|
|
120
|
+
/**
|
|
121
|
+
* Register the module with async configuration
|
|
122
|
+
*/
|
|
123
|
+
static forRootAsync<TTenantSchema extends Record<string, unknown> = Record<string, unknown>, TSharedSchema extends Record<string, unknown> = Record<string, unknown>>(options: TenantModuleAsyncOptions<TTenantSchema, TSharedSchema>): DynamicModule;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Alias for TenantModule for those who prefer this naming
|
|
127
|
+
*/
|
|
128
|
+
declare const DrizzleMultitenantModule: typeof TenantModule;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Inject the tenant database for the current request
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* @Injectable()
|
|
136
|
+
* export class UserService {
|
|
137
|
+
* constructor(@InjectTenantDb() private readonly db: TenantDb) {}
|
|
138
|
+
*
|
|
139
|
+
* async findAll() {
|
|
140
|
+
* return this.db.select().from(users);
|
|
141
|
+
* }
|
|
142
|
+
* }
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
declare const InjectTenantDb: () => ParameterDecorator;
|
|
146
|
+
/**
|
|
147
|
+
* Inject the shared database
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* @Injectable()
|
|
152
|
+
* export class PlanService {
|
|
153
|
+
* constructor(@InjectSharedDb() private readonly db: SharedDb) {}
|
|
154
|
+
*
|
|
155
|
+
* async findAll() {
|
|
156
|
+
* return this.db.select().from(plans);
|
|
157
|
+
* }
|
|
158
|
+
* }
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
declare const InjectSharedDb: () => ParameterDecorator;
|
|
162
|
+
/**
|
|
163
|
+
* Inject the tenant context
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* @Injectable()
|
|
168
|
+
* export class AuditService {
|
|
169
|
+
* constructor(@InjectTenantContext() private readonly ctx: NestTenantContext) {}
|
|
170
|
+
*
|
|
171
|
+
* log(action: string) {
|
|
172
|
+
* console.log(`[${this.ctx.tenantId}] ${action}`);
|
|
173
|
+
* }
|
|
174
|
+
* }
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
declare const InjectTenantContext: () => ParameterDecorator;
|
|
178
|
+
/**
|
|
179
|
+
* Inject the tenant manager
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* @Injectable()
|
|
184
|
+
* export class TenantService {
|
|
185
|
+
* constructor(@InjectTenantManager() private readonly manager: TenantManager) {}
|
|
186
|
+
*
|
|
187
|
+
* getPoolCount() {
|
|
188
|
+
* return this.manager.getPoolCount();
|
|
189
|
+
* }
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
declare const InjectTenantManager: () => ParameterDecorator;
|
|
194
|
+
/**
|
|
195
|
+
* Get the current tenant context from the request
|
|
196
|
+
* Use this in controllers to access tenant information
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* @Controller('users')
|
|
201
|
+
* export class UserController {
|
|
202
|
+
* @Get()
|
|
203
|
+
* findAll(@TenantCtx() ctx: NestTenantContext) {
|
|
204
|
+
* console.log(`Fetching users for tenant: ${ctx.tenantId}`);
|
|
205
|
+
* return this.userService.findAll();
|
|
206
|
+
* }
|
|
207
|
+
* }
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
declare const TenantCtx: (...dataOrPipes: (keyof NestTenantContext | _nestjs_common.PipeTransform<any, any> | _nestjs_common.Type<_nestjs_common.PipeTransform<any, any>> | undefined)[]) => ParameterDecorator;
|
|
211
|
+
/**
|
|
212
|
+
* Get the current tenant ID from the request
|
|
213
|
+
* Shorthand for @TenantCtx('tenantId')
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```typescript
|
|
217
|
+
* @Controller('users')
|
|
218
|
+
* export class UserController {
|
|
219
|
+
* @Get()
|
|
220
|
+
* findAll(@TenantId() tenantId: string) {
|
|
221
|
+
* console.log(`Fetching users for tenant: ${tenantId}`);
|
|
222
|
+
* return this.userService.findAll();
|
|
223
|
+
* }
|
|
224
|
+
* }
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
declare const TenantId: (...dataOrPipes: unknown[]) => ParameterDecorator;
|
|
228
|
+
/**
|
|
229
|
+
* Mark a route as requiring a tenant context
|
|
230
|
+
* Used with TenantGuard to enforce tenant requirement
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```typescript
|
|
234
|
+
* @Controller('users')
|
|
235
|
+
* @RequiresTenant()
|
|
236
|
+
* export class UserController {
|
|
237
|
+
* // All routes in this controller require a tenant
|
|
238
|
+
* }
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
declare const RequiresTenant: () => ClassDecorator & MethodDecorator;
|
|
242
|
+
/**
|
|
243
|
+
* Mark a route as public (no tenant required)
|
|
244
|
+
* Use this to exclude specific routes from tenant requirement
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```typescript
|
|
248
|
+
* @Controller('health')
|
|
249
|
+
* export class HealthController {
|
|
250
|
+
* @Get()
|
|
251
|
+
* @PublicRoute()
|
|
252
|
+
* check() {
|
|
253
|
+
* return { status: 'ok' };
|
|
254
|
+
* }
|
|
255
|
+
* }
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
declare const PublicRoute: () => MethodDecorator;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Guard that extracts and validates tenant ID from requests
|
|
262
|
+
*
|
|
263
|
+
* This guard should be applied globally or to specific controllers/routes
|
|
264
|
+
* that require tenant context.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```typescript
|
|
268
|
+
* // Apply globally in main.ts
|
|
269
|
+
* const app = await NestFactory.create(AppModule);
|
|
270
|
+
* app.useGlobalGuards(app.get(TenantGuard));
|
|
271
|
+
*
|
|
272
|
+
* // Or apply to specific controllers
|
|
273
|
+
* @Controller('users')
|
|
274
|
+
* @UseGuards(TenantGuard)
|
|
275
|
+
* export class UserController {}
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
declare class TenantGuard implements CanActivate {
|
|
279
|
+
private readonly reflector;
|
|
280
|
+
private readonly options;
|
|
281
|
+
private readonly tenantManager;
|
|
282
|
+
constructor(reflector: Reflector, options: TenantModuleOptions, tenantManager: TenantManager);
|
|
283
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Guard that requires a valid tenant context
|
|
287
|
+
* Throws error if tenant is not present
|
|
288
|
+
*
|
|
289
|
+
* Use this when you want to ensure a route always has a tenant,
|
|
290
|
+
* regardless of the @RequiresTenant decorator.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```typescript
|
|
294
|
+
* @Controller('users')
|
|
295
|
+
* @UseGuards(RequireTenantGuard)
|
|
296
|
+
* export class UserController {
|
|
297
|
+
* // All routes require tenant
|
|
298
|
+
* }
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
declare class RequireTenantGuard implements CanActivate {
|
|
302
|
+
private readonly options;
|
|
303
|
+
constructor(options: TenantModuleOptions);
|
|
304
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Interceptor that sets up AsyncLocalStorage context for tenant
|
|
309
|
+
*
|
|
310
|
+
* This interceptor ensures that tenant context is available via
|
|
311
|
+
* AsyncLocalStorage throughout the request lifecycle, enabling
|
|
312
|
+
* access to tenant info in services that don't have request scope.
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* // Apply globally
|
|
317
|
+
* const app = await NestFactory.create(AppModule);
|
|
318
|
+
* app.useGlobalInterceptors(app.get(TenantContextInterceptor));
|
|
319
|
+
*
|
|
320
|
+
* // Or apply to specific controllers
|
|
321
|
+
* @Controller('users')
|
|
322
|
+
* @UseInterceptors(TenantContextInterceptor)
|
|
323
|
+
* export class UserController {}
|
|
324
|
+
* ```
|
|
325
|
+
*/
|
|
326
|
+
declare class TenantContextInterceptor implements NestInterceptor {
|
|
327
|
+
private readonly tenantManager;
|
|
328
|
+
private readonly tenantContext;
|
|
329
|
+
constructor(tenantManager: TenantManager);
|
|
330
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Interceptor that logs tenant information for each request
|
|
334
|
+
*
|
|
335
|
+
* Useful for debugging and audit purposes.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```typescript
|
|
339
|
+
* @Controller('users')
|
|
340
|
+
* @UseInterceptors(TenantLoggingInterceptor)
|
|
341
|
+
* export class UserController {}
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
declare class TenantLoggingInterceptor implements NestInterceptor {
|
|
345
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Create providers for the tenant module
|
|
350
|
+
*/
|
|
351
|
+
declare function createTenantProviders<TTenantSchema extends Record<string, unknown>, TSharedSchema extends Record<string, unknown>>(): Provider[];
|
|
352
|
+
/**
|
|
353
|
+
* Create async providers for dynamic module configuration
|
|
354
|
+
*/
|
|
355
|
+
declare function createAsyncProviders<TTenantSchema extends Record<string, unknown>, TSharedSchema extends Record<string, unknown>>(options: {
|
|
356
|
+
useFactory?: (...args: unknown[]) => Promise<TenantModuleOptions<TTenantSchema, TSharedSchema>> | TenantModuleOptions<TTenantSchema, TSharedSchema>;
|
|
357
|
+
inject?: InjectionToken[];
|
|
358
|
+
useClass?: new (...args: unknown[]) => {
|
|
359
|
+
createTenantModuleOptions(): Promise<TenantModuleOptions<TTenantSchema, TSharedSchema>> | TenantModuleOptions<TTenantSchema, TSharedSchema>;
|
|
360
|
+
};
|
|
361
|
+
useExisting?: new (...args: unknown[]) => {
|
|
362
|
+
createTenantModuleOptions(): Promise<TenantModuleOptions<TTenantSchema, TSharedSchema>> | TenantModuleOptions<TTenantSchema, TSharedSchema>;
|
|
363
|
+
};
|
|
364
|
+
}): Provider[];
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Injection tokens for NestJS
|
|
368
|
+
*/
|
|
369
|
+
/** Token for injecting the TenantManager */
|
|
370
|
+
declare const TENANT_MANAGER: unique symbol;
|
|
371
|
+
/** Token for injecting the tenant database */
|
|
372
|
+
declare const TENANT_DB: unique symbol;
|
|
373
|
+
/** Token for injecting the shared database */
|
|
374
|
+
declare const SHARED_DB: unique symbol;
|
|
375
|
+
/** Token for injecting the tenant context */
|
|
376
|
+
declare const TENANT_CONTEXT: unique symbol;
|
|
377
|
+
/** Token for the module configuration */
|
|
378
|
+
declare const TENANT_MODULE_OPTIONS: unique symbol;
|
|
379
|
+
/** Token for the tenant ID extractor function */
|
|
380
|
+
declare const TENANT_ID_EXTRACTOR: unique symbol;
|
|
381
|
+
/** Metadata key for tenant requirement */
|
|
382
|
+
declare const REQUIRES_TENANT_KEY = "requires_tenant";
|
|
383
|
+
/** Metadata key for public routes (no tenant required) */
|
|
384
|
+
declare const IS_PUBLIC_KEY = "is_public_tenant";
|
|
385
|
+
|
|
386
|
+
export { Config, DrizzleMultitenantModule, IS_PUBLIC_KEY, InjectSharedDb, InjectTenantContext, InjectTenantDb, InjectTenantManager, type NestTenantContext, PublicRoute, REQUIRES_TENANT_KEY, RequireTenantGuard, RequiresTenant, SHARED_DB, TENANT_CONTEXT, TENANT_DB, TENANT_ID_EXTRACTOR, TENANT_MANAGER, TENANT_MODULE_OPTIONS, TenantContextInterceptor, TenantCtx, TenantGuard, TenantId, type TenantIdExtractor, TenantLoggingInterceptor, TenantManager, TenantModule, type TenantModuleAsyncOptions, type TenantModuleOptions, type TenantModuleOptionsFactory, type TenantRequest, type TenantValidator, createAsyncProviders, createTenantProviders };
|