@forinda/kickjs-multi-tenant 2.0.0 → 2.1.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.
@@ -0,0 +1,85 @@
1
+
2
+ import { AdapterContext, AdapterMiddleware, AppAdapter } from "@forinda/kickjs";
3
+
4
+ //#region src/types.d.ts
5
+ /** DI token for the current tenant context */
6
+ declare const TENANT_CONTEXT: unique symbol;
7
+ /** Tenant information resolved from the request */
8
+ interface TenantInfo {
9
+ /** Unique tenant identifier */
10
+ id: string;
11
+ /** Optional tenant name */
12
+ name?: string;
13
+ /** Optional tenant-specific config/metadata */
14
+ metadata?: Record<string, any>;
15
+ }
16
+ /** Strategy for resolving the tenant from a request */
17
+ type TenantResolutionStrategy = 'header' | 'subdomain' | 'path' | 'query' | ((req: any) => TenantInfo | null | Promise<TenantInfo | null>);
18
+ interface MultiTenantOptions {
19
+ /**
20
+ * How to resolve the tenant from the request.
21
+ * - 'header' — reads X-Tenant-ID header (default)
22
+ * - 'subdomain' — extracts from subdomain (tenant.example.com)
23
+ * - 'path' — extracts from first path segment (/tenant-id/...)
24
+ * - 'query' — reads ?tenantId= query param
25
+ * - function — custom resolver
26
+ */
27
+ strategy?: TenantResolutionStrategy;
28
+ /** Header name when strategy is 'header' (default: 'x-tenant-id') */
29
+ headerName?: string;
30
+ /** Query param name when strategy is 'query' (default: 'tenantId') */
31
+ queryParam?: string;
32
+ /**
33
+ * Called after tenant is resolved. Use for validation, loading tenant
34
+ * config from DB, or rejecting unknown tenants.
35
+ */
36
+ onTenantResolved?: (tenant: TenantInfo, req: any) => void | Promise<void>;
37
+ /** Return a 403 if no tenant can be resolved (default: true) */
38
+ required?: boolean;
39
+ /** Routes to skip tenant resolution (e.g., health checks) */
40
+ excludeRoutes?: string[];
41
+ }
42
+ //#endregion
43
+ //#region src/tenant.adapter.d.ts
44
+ /**
45
+ * Multi-tenancy adapter for KickJS.
46
+ *
47
+ * Resolves the tenant from each request and makes it available
48
+ * via DI (`@Inject(TENANT_CONTEXT)`) and `req.tenant`.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { TenantAdapter, TENANT_CONTEXT } from '@forinda/kickjs-multi-tenant'
53
+ *
54
+ * bootstrap({
55
+ * modules,
56
+ * adapters: [
57
+ * new TenantAdapter({
58
+ * strategy: 'header',
59
+ * onTenantResolved: async (tenant) => {
60
+ * // Load tenant config from DB, validate, etc.
61
+ * },
62
+ * }),
63
+ * ],
64
+ * })
65
+ *
66
+ * // In a service:
67
+ * @Service()
68
+ * class UserService {
69
+ * constructor(@Inject(TENANT_CONTEXT) private tenant: TenantInfo) {}
70
+ * }
71
+ * ```
72
+ */
73
+ declare class TenantAdapter implements AppAdapter {
74
+ name: string;
75
+ private options;
76
+ constructor(options?: MultiTenantOptions);
77
+ middleware(): AdapterMiddleware[];
78
+ beforeStart({
79
+ container
80
+ }: AdapterContext): void;
81
+ private resolveTenant;
82
+ }
83
+ //#endregion
84
+ export { type MultiTenantOptions, TENANT_CONTEXT, TenantAdapter, type TenantInfo, type TenantResolutionStrategy };
85
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/tenant.adapter.ts"],"mappings":";;;;;cACa,cAAA;;UAGI,UAAA;EAHoC;EAKnD,EAAA;EALmD;EAOnD,IAAA;EAJe;EAMf,QAAA,GAAW,MAAA;AAAA;;KAID,wBAAA,iDAKN,GAAA,UAAa,UAAA,UAAoB,OAAA,CAAQ,UAAA;AAAA,UAE9B,kBAAA;EAXf;;;;AAIF;;;;EAgBE,QAAA,GAAW,wBAAA;EAX0B;EAcrC,UAAA;EAd4C;EAiB5C,UAAA;EAjBiB;;;;EAuBjB,gBAAA,IAAoB,MAAA,EAAQ,UAAA,EAAY,GAAA,iBAAoB,OAAA;EArB7C;EAwBf,QAAA;;EAGA,aAAA;AAAA;;;;AA/CF;;;;;AAGA;;;;;;;;;;AAUA;;;;;;;;;;;;;cCgCa,aAAA,YAAyB,UAAA;EACpC,IAAA;EAAA,QACQ,OAAA;cAKI,OAAA,GAAS,kBAAA;EAUrB,UAAA,CAAA,GAAc,iBAAA;EAoCd,WAAA,CAAA;IAAc;EAAA,GAAa,cAAA;EAAA,QAab,aAAA;AAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @forinda/kickjs-multi-tenant v2.1.0
3
+ *
4
+ * Copyright (c) Felix Orinda
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ import { Logger, Scope } from "@forinda/kickjs";
12
+ //#region src/types.ts
13
+ /** DI token for the current tenant context */
14
+ const TENANT_CONTEXT = Symbol("TenantContext");
15
+ //#endregion
16
+ //#region src/tenant.adapter.ts
17
+ const log = Logger.for("MultiTenant");
18
+ /**
19
+ * Multi-tenancy adapter for KickJS.
20
+ *
21
+ * Resolves the tenant from each request and makes it available
22
+ * via DI (`@Inject(TENANT_CONTEXT)`) and `req.tenant`.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { TenantAdapter, TENANT_CONTEXT } from '@forinda/kickjs-multi-tenant'
27
+ *
28
+ * bootstrap({
29
+ * modules,
30
+ * adapters: [
31
+ * new TenantAdapter({
32
+ * strategy: 'header',
33
+ * onTenantResolved: async (tenant) => {
34
+ * // Load tenant config from DB, validate, etc.
35
+ * },
36
+ * }),
37
+ * ],
38
+ * })
39
+ *
40
+ * // In a service:
41
+ * @Service()
42
+ * class UserService {
43
+ * constructor(@Inject(TENANT_CONTEXT) private tenant: TenantInfo) {}
44
+ * }
45
+ * ```
46
+ */
47
+ var TenantAdapter = class {
48
+ name = "TenantAdapter";
49
+ options;
50
+ constructor(options = {}) {
51
+ this.options = {
52
+ strategy: options.strategy ?? "header",
53
+ required: options.required ?? true,
54
+ headerName: options.headerName ?? "x-tenant-id",
55
+ queryParam: options.queryParam ?? "tenantId",
56
+ ...options
57
+ };
58
+ }
59
+ middleware() {
60
+ return [{
61
+ handler: async (req, res, next) => {
62
+ if (this.options.excludeRoutes?.some((r) => req.path.startsWith(r))) return next();
63
+ const tenant = await this.resolveTenant(req);
64
+ if (!tenant) {
65
+ if (this.options.required) {
66
+ res.status(403).json({ message: "Tenant not found. Provide a valid tenant identifier." });
67
+ return;
68
+ }
69
+ return next();
70
+ }
71
+ req.tenant = tenant;
72
+ if (this.options.onTenantResolved) await this.options.onTenantResolved(tenant, req);
73
+ next();
74
+ },
75
+ phase: "beforeGlobal"
76
+ }];
77
+ }
78
+ beforeStart({ container }) {
79
+ container.registerFactory(TENANT_CONTEXT, () => ({
80
+ id: "default",
81
+ name: "Default Tenant"
82
+ }), Scope.SINGLETON);
83
+ log.info(`Tenant resolution: ${typeof this.options.strategy === "function" ? "custom" : this.options.strategy}`);
84
+ }
85
+ async resolveTenant(req) {
86
+ const strategy = this.options.strategy;
87
+ if (typeof strategy === "function") return strategy(req);
88
+ switch (strategy) {
89
+ case "header": {
90
+ const tenantId = req.get(this.options.headerName);
91
+ return tenantId ? { id: tenantId } : null;
92
+ }
93
+ case "subdomain": {
94
+ const parts = req.hostname.split(".");
95
+ if (parts.length >= 3) return { id: parts[0] };
96
+ return null;
97
+ }
98
+ case "path": {
99
+ const segments = req.path.split("/").filter(Boolean);
100
+ if (segments.length > 0) return { id: segments[0] };
101
+ return null;
102
+ }
103
+ case "query": {
104
+ const tenantId = req.query[this.options.queryParam];
105
+ return tenantId ? { id: tenantId } : null;
106
+ }
107
+ default: return null;
108
+ }
109
+ }
110
+ };
111
+ //#endregion
112
+ export { TENANT_CONTEXT, TenantAdapter };
113
+
114
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/types.ts","../src/tenant.adapter.ts"],"sourcesContent":["/** DI token for the current tenant context */\nexport const TENANT_CONTEXT = Symbol('TenantContext')\n\n/** Tenant information resolved from the request */\nexport interface TenantInfo {\n /** Unique tenant identifier */\n id: string\n /** Optional tenant name */\n name?: string\n /** Optional tenant-specific config/metadata */\n metadata?: Record<string, any>\n}\n\n/** Strategy for resolving the tenant from a request */\nexport type TenantResolutionStrategy =\n | 'header'\n | 'subdomain'\n | 'path'\n | 'query'\n | ((req: any) => TenantInfo | null | Promise<TenantInfo | null>)\n\nexport interface MultiTenantOptions {\n /**\n * How to resolve the tenant from the request.\n * - 'header' — reads X-Tenant-ID header (default)\n * - 'subdomain' — extracts from subdomain (tenant.example.com)\n * - 'path' — extracts from first path segment (/tenant-id/...)\n * - 'query' — reads ?tenantId= query param\n * - function — custom resolver\n */\n strategy?: TenantResolutionStrategy\n\n /** Header name when strategy is 'header' (default: 'x-tenant-id') */\n headerName?: string\n\n /** Query param name when strategy is 'query' (default: 'tenantId') */\n queryParam?: string\n\n /**\n * Called after tenant is resolved. Use for validation, loading tenant\n * config from DB, or rejecting unknown tenants.\n */\n onTenantResolved?: (tenant: TenantInfo, req: any) => void | Promise<void>\n\n /** Return a 403 if no tenant can be resolved (default: true) */\n required?: boolean\n\n /** Routes to skip tenant resolution (e.g., health checks) */\n excludeRoutes?: string[]\n}\n","import {\n Logger,\n type AppAdapter,\n type AdapterContext,\n type AdapterMiddleware,\n Scope,\n} from '@forinda/kickjs'\nimport type { Request, Response, NextFunction } from 'express'\nimport {\n TENANT_CONTEXT,\n type TenantInfo,\n type MultiTenantOptions,\n type TenantResolutionStrategy,\n} from './types'\n\nconst log = Logger.for('MultiTenant')\n\n/**\n * Multi-tenancy adapter for KickJS.\n *\n * Resolves the tenant from each request and makes it available\n * via DI (`@Inject(TENANT_CONTEXT)`) and `req.tenant`.\n *\n * @example\n * ```ts\n * import { TenantAdapter, TENANT_CONTEXT } from '@forinda/kickjs-multi-tenant'\n *\n * bootstrap({\n * modules,\n * adapters: [\n * new TenantAdapter({\n * strategy: 'header',\n * onTenantResolved: async (tenant) => {\n * // Load tenant config from DB, validate, etc.\n * },\n * }),\n * ],\n * })\n *\n * // In a service:\n * @Service()\n * class UserService {\n * constructor(@Inject(TENANT_CONTEXT) private tenant: TenantInfo) {}\n * }\n * ```\n */\nexport class TenantAdapter implements AppAdapter {\n name = 'TenantAdapter'\n private options: Required<\n Pick<MultiTenantOptions, 'strategy' | 'required' | 'headerName' | 'queryParam'>\n > &\n MultiTenantOptions\n\n constructor(options: MultiTenantOptions = {}) {\n this.options = {\n strategy: options.strategy ?? 'header',\n required: options.required ?? true,\n headerName: options.headerName ?? 'x-tenant-id',\n queryParam: options.queryParam ?? 'tenantId',\n ...options,\n }\n }\n\n middleware(): AdapterMiddleware[] {\n return [\n {\n handler: async (req: Request, res: Response, next: NextFunction) => {\n // Skip excluded routes\n if (this.options.excludeRoutes?.some((r) => req.path.startsWith(r))) {\n return next()\n }\n\n const tenant = await this.resolveTenant(req)\n\n if (!tenant) {\n if (this.options.required) {\n res\n .status(403)\n .json({ message: 'Tenant not found. Provide a valid tenant identifier.' })\n return\n }\n return next()\n }\n\n // Attach to request\n ;(req as any).tenant = tenant\n\n // Call hook\n if (this.options.onTenantResolved) {\n await this.options.onTenantResolved(tenant, req)\n }\n\n next()\n },\n phase: 'beforeGlobal',\n },\n ]\n }\n\n beforeStart({ container }: AdapterContext): void {\n // Register a factory that reads the tenant from the current request context\n // This requires request-scoped resolution — for now, register as a placeholder\n container.registerFactory(\n TENANT_CONTEXT,\n () => ({ id: 'default', name: 'Default Tenant' }) as TenantInfo,\n Scope.SINGLETON,\n )\n log.info(\n `Tenant resolution: ${typeof this.options.strategy === 'function' ? 'custom' : this.options.strategy}`,\n )\n }\n\n private async resolveTenant(req: Request): Promise<TenantInfo | null> {\n const strategy = this.options.strategy\n\n if (typeof strategy === 'function') {\n return strategy(req)\n }\n\n switch (strategy) {\n case 'header': {\n const tenantId = req.get(this.options.headerName)\n return tenantId ? { id: tenantId } : null\n }\n case 'subdomain': {\n const host = req.hostname\n const parts = host.split('.')\n if (parts.length >= 3) {\n return { id: parts[0] }\n }\n return null\n }\n case 'path': {\n const segments = req.path.split('/').filter(Boolean)\n if (segments.length > 0) {\n return { id: segments[0] }\n }\n return null\n }\n case 'query': {\n const tenantId = req.query[this.options.queryParam] as string\n return tenantId ? { id: tenantId } : null\n }\n default:\n return null\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AACA,MAAa,iBAAiB,OAAO,gBAAgB;;;ACcrD,MAAM,MAAM,OAAO,IAAI,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BrC,IAAa,gBAAb,MAAiD;CAC/C,OAAO;CACP;CAKA,YAAY,UAA8B,EAAE,EAAE;AAC5C,OAAK,UAAU;GACb,UAAU,QAAQ,YAAY;GAC9B,UAAU,QAAQ,YAAY;GAC9B,YAAY,QAAQ,cAAc;GAClC,YAAY,QAAQ,cAAc;GAClC,GAAG;GACJ;;CAGH,aAAkC;AAChC,SAAO,CACL;GACE,SAAS,OAAO,KAAc,KAAe,SAAuB;AAElE,QAAI,KAAK,QAAQ,eAAe,MAAM,MAAM,IAAI,KAAK,WAAW,EAAE,CAAC,CACjE,QAAO,MAAM;IAGf,MAAM,SAAS,MAAM,KAAK,cAAc,IAAI;AAE5C,QAAI,CAAC,QAAQ;AACX,SAAI,KAAK,QAAQ,UAAU;AACzB,UACG,OAAO,IAAI,CACX,KAAK,EAAE,SAAS,wDAAwD,CAAC;AAC5E;;AAEF,YAAO,MAAM;;AAIb,QAAY,SAAS;AAGvB,QAAI,KAAK,QAAQ,iBACf,OAAM,KAAK,QAAQ,iBAAiB,QAAQ,IAAI;AAGlD,UAAM;;GAER,OAAO;GACR,CACF;;CAGH,YAAY,EAAE,aAAmC;AAG/C,YAAU,gBACR,uBACO;GAAE,IAAI;GAAW,MAAM;GAAkB,GAChD,MAAM,UACP;AACD,MAAI,KACF,sBAAsB,OAAO,KAAK,QAAQ,aAAa,aAAa,WAAW,KAAK,QAAQ,WAC7F;;CAGH,MAAc,cAAc,KAA0C;EACpE,MAAM,WAAW,KAAK,QAAQ;AAE9B,MAAI,OAAO,aAAa,WACtB,QAAO,SAAS,IAAI;AAGtB,UAAQ,UAAR;GACE,KAAK,UAAU;IACb,MAAM,WAAW,IAAI,IAAI,KAAK,QAAQ,WAAW;AACjD,WAAO,WAAW,EAAE,IAAI,UAAU,GAAG;;GAEvC,KAAK,aAAa;IAEhB,MAAM,QADO,IAAI,SACE,MAAM,IAAI;AAC7B,QAAI,MAAM,UAAU,EAClB,QAAO,EAAE,IAAI,MAAM,IAAI;AAEzB,WAAO;;GAET,KAAK,QAAQ;IACX,MAAM,WAAW,IAAI,KAAK,MAAM,IAAI,CAAC,OAAO,QAAQ;AACpD,QAAI,SAAS,SAAS,EACpB,QAAO,EAAE,IAAI,SAAS,IAAI;AAE5B,WAAO;;GAET,KAAK,SAAS;IACZ,MAAM,WAAW,IAAI,MAAM,KAAK,QAAQ;AACxC,WAAO,WAAW,EAAE,IAAI,UAAU,GAAG;;GAEvC,QACE,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-multi-tenant",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Multi-tenancy helpers for KickJS — tenant resolution, scoped DI, and database routing",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -37,20 +37,37 @@
37
37
  "vite"
38
38
  ],
39
39
  "type": "module",
40
- "main": "dist/index.js",
41
- "types": "dist/index.d.ts",
40
+ "main": "dist/index.mjs",
41
+ "types": "dist/index.d.mts",
42
42
  "exports": {
43
43
  ".": {
44
- "import": "./dist/index.js",
45
- "types": "./dist/index.d.ts"
44
+ "import": "./dist/index.mjs",
45
+ "types": "./dist/index.d.mts"
46
46
  }
47
47
  },
48
48
  "files": [
49
49
  "dist"
50
50
  ],
51
+ "wireit": {
52
+ "build": {
53
+ "command": "tsdown",
54
+ "files": [
55
+ "src/**/*.ts",
56
+ "tsdown.config.ts",
57
+ "tsconfig.json",
58
+ "package.json"
59
+ ],
60
+ "output": [
61
+ "dist/**"
62
+ ],
63
+ "dependencies": [
64
+ "../core:build"
65
+ ]
66
+ }
67
+ },
51
68
  "dependencies": {
52
69
  "reflect-metadata": "^0.2.2",
53
- "@forinda/kickjs": "2.0.0"
70
+ "@forinda/kickjs": "2.1.0"
54
71
  },
55
72
  "devDependencies": {
56
73
  "@types/node": "^25.0.0",
@@ -74,10 +91,9 @@
74
91
  "url": "https://github.com/forinda/kick-js/issues"
75
92
  },
76
93
  "scripts": {
77
- "build": "vite build && pnpm build:types",
78
- "build:types": "tsc -p tsconfig.build.json",
79
- "dev": "vite build --watch",
94
+ "build": "wireit",
95
+ "dev": "tsdown --watch",
80
96
  "typecheck": "tsc --noEmit",
81
- "clean": "rm -rf dist .turbo"
97
+ "clean": "rm -rf dist .wireit"
82
98
  }
83
99
  }
package/dist/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export { TenantAdapter } from './tenant.adapter';
2
- export { TENANT_CONTEXT } from './types';
3
- export type { TenantInfo, MultiTenantOptions, TenantResolutionStrategy } from './types';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxC,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA"}
package/dist/index.js DELETED
@@ -1,65 +0,0 @@
1
- import { Logger as s, Scope as o } from "@forinda/kickjs";
2
- var i = /* @__PURE__ */ Symbol("TenantContext"), u = s.for("MultiTenant"), d = class {
3
- name = "TenantAdapter";
4
- options;
5
- constructor(t = {}) {
6
- this.options = {
7
- strategy: t.strategy ?? "header",
8
- required: t.required ?? !0,
9
- headerName: t.headerName ?? "x-tenant-id",
10
- queryParam: t.queryParam ?? "tenantId",
11
- ...t
12
- };
13
- }
14
- middleware() {
15
- return [{
16
- handler: async (t, n, e) => {
17
- if (this.options.excludeRoutes?.some((r) => t.path.startsWith(r))) return e();
18
- const a = await this.resolveTenant(t);
19
- if (!a) {
20
- if (this.options.required) {
21
- n.status(403).json({ message: "Tenant not found. Provide a valid tenant identifier." });
22
- return;
23
- }
24
- return e();
25
- }
26
- t.tenant = a, this.options.onTenantResolved && await this.options.onTenantResolved(a, t), e();
27
- },
28
- phase: "beforeGlobal"
29
- }];
30
- }
31
- beforeStart({ container: t }) {
32
- t.registerFactory(i, () => ({
33
- id: "default",
34
- name: "Default Tenant"
35
- }), o.SINGLETON), u.info(`Tenant resolution: ${typeof this.options.strategy == "function" ? "custom" : this.options.strategy}`);
36
- }
37
- async resolveTenant(t) {
38
- const n = this.options.strategy;
39
- if (typeof n == "function") return n(t);
40
- switch (n) {
41
- case "header": {
42
- const e = t.get(this.options.headerName);
43
- return e ? { id: e } : null;
44
- }
45
- case "subdomain": {
46
- const e = t.hostname.split(".");
47
- return e.length >= 3 ? { id: e[0] } : null;
48
- }
49
- case "path": {
50
- const e = t.path.split("/").filter(Boolean);
51
- return e.length > 0 ? { id: e[0] } : null;
52
- }
53
- case "query": {
54
- const e = t.query[this.options.queryParam];
55
- return e ? { id: e } : null;
56
- }
57
- default:
58
- return null;
59
- }
60
- }
61
- };
62
- export {
63
- i as TENANT_CONTEXT,
64
- d as TenantAdapter
65
- };
@@ -1,40 +0,0 @@
1
- import { type AppAdapter, type AdapterContext, type AdapterMiddleware } from '@forinda/kickjs';
2
- import { type MultiTenantOptions } from './types';
3
- /**
4
- * Multi-tenancy adapter for KickJS.
5
- *
6
- * Resolves the tenant from each request and makes it available
7
- * via DI (`@Inject(TENANT_CONTEXT)`) and `req.tenant`.
8
- *
9
- * @example
10
- * ```ts
11
- * import { TenantAdapter, TENANT_CONTEXT } from '@forinda/kickjs-multi-tenant'
12
- *
13
- * bootstrap({
14
- * modules,
15
- * adapters: [
16
- * new TenantAdapter({
17
- * strategy: 'header',
18
- * onTenantResolved: async (tenant) => {
19
- * // Load tenant config from DB, validate, etc.
20
- * },
21
- * }),
22
- * ],
23
- * })
24
- *
25
- * // In a service:
26
- * @Service()
27
- * class UserService {
28
- * constructor(@Inject(TENANT_CONTEXT) private tenant: TenantInfo) {}
29
- * }
30
- * ```
31
- */
32
- export declare class TenantAdapter implements AppAdapter {
33
- name: string;
34
- private options;
35
- constructor(options?: MultiTenantOptions);
36
- middleware(): AdapterMiddleware[];
37
- beforeStart({ container }: AdapterContext): void;
38
- private resolveTenant;
39
- }
40
- //# sourceMappingURL=tenant.adapter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tenant.adapter.d.ts","sourceRoot":"","sources":["../src/tenant.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,iBAAiB,EAEvB,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAGL,KAAK,kBAAkB,EAExB,MAAM,SAAS,CAAA;AAIhB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,aAAc,YAAW,UAAU;IAC9C,IAAI,SAAkB;IACtB,OAAO,CAAC,OAAO,CAGK;gBAER,OAAO,GAAE,kBAAuB;IAU5C,UAAU,IAAI,iBAAiB,EAAE;IAoCjC,WAAW,CAAC,EAAE,SAAS,EAAE,EAAE,cAAc,GAAG,IAAI;YAalC,aAAa;CAmC5B"}
package/dist/types.d.ts DELETED
@@ -1,38 +0,0 @@
1
- /** DI token for the current tenant context */
2
- export declare const TENANT_CONTEXT: unique symbol;
3
- /** Tenant information resolved from the request */
4
- export interface TenantInfo {
5
- /** Unique tenant identifier */
6
- id: string;
7
- /** Optional tenant name */
8
- name?: string;
9
- /** Optional tenant-specific config/metadata */
10
- metadata?: Record<string, any>;
11
- }
12
- /** Strategy for resolving the tenant from a request */
13
- export type TenantResolutionStrategy = 'header' | 'subdomain' | 'path' | 'query' | ((req: any) => TenantInfo | null | Promise<TenantInfo | null>);
14
- export interface MultiTenantOptions {
15
- /**
16
- * How to resolve the tenant from the request.
17
- * - 'header' — reads X-Tenant-ID header (default)
18
- * - 'subdomain' — extracts from subdomain (tenant.example.com)
19
- * - 'path' — extracts from first path segment (/tenant-id/...)
20
- * - 'query' — reads ?tenantId= query param
21
- * - function — custom resolver
22
- */
23
- strategy?: TenantResolutionStrategy;
24
- /** Header name when strategy is 'header' (default: 'x-tenant-id') */
25
- headerName?: string;
26
- /** Query param name when strategy is 'query' (default: 'tenantId') */
27
- queryParam?: string;
28
- /**
29
- * Called after tenant is resolved. Use for validation, loading tenant
30
- * config from DB, or rejecting unknown tenants.
31
- */
32
- onTenantResolved?: (tenant: TenantInfo, req: any) => void | Promise<void>;
33
- /** Return a 403 if no tenant can be resolved (default: true) */
34
- required?: boolean;
35
- /** Routes to skip tenant resolution (e.g., health checks) */
36
- excludeRoutes?: string[];
37
- }
38
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,eAAO,MAAM,cAAc,eAA0B,CAAA;AAErD,mDAAmD;AACnD,MAAM,WAAW,UAAU;IACzB,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC/B;AAED,uDAAuD;AACvD,MAAM,MAAM,wBAAwB,GAChC,QAAQ,GACR,WAAW,GACX,MAAM,GACN,OAAO,GACP,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAA;AAElE,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,wBAAwB,CAAA;IAEnC,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEzE,gEAAgE;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;CACzB"}