@node-tenant/tenant-express 0.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.
- package/README.md +136 -0
- package/dist/index.cjs +91 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# @node-tenant/tenant-express
|
|
2
|
+
|
|
3
|
+
> **Express.js middleware** for the `@node-tenant` ecosystem — effortlessly resolves tenant context from HTTP requests.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@node-tenant/tenant-express)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
`@node-tenant/tenant-express` provides middleware and resolvers to automatically identify which tenant is making an HTTP request in your Express application. It integrates perfectly with `@node-tenant/tenant-core` to establish the tenant context using Node.js `AsyncLocalStorage`.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Table of Contents
|
|
17
|
+
|
|
18
|
+
- [Installation](#installation)
|
|
19
|
+
- [Quick Start](#quick-start)
|
|
20
|
+
- [Resolvers](#resolvers)
|
|
21
|
+
- [Header Resolver](#header-resolver)
|
|
22
|
+
- [Subdomain Resolver](#subdomain-resolver)
|
|
23
|
+
- [Custom Resolver](#custom-resolver)
|
|
24
|
+
- [API Reference](#api-reference)
|
|
25
|
+
- [License](#license)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @node-tenant/tenant-core @node-tenant/tenant-express
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
> **Peer dependency**: `express >= 4.0.0` must be installed in your project.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import express from 'express';
|
|
43
|
+
import { TenantManager, TenantContextManager } from '@node-tenant/tenant-core';
|
|
44
|
+
import { tenantMiddleware, headerResolver } from '@node-tenant/tenant-express';
|
|
45
|
+
|
|
46
|
+
const manager = new TenantManager({ /* storage, etc */ });
|
|
47
|
+
const app = express();
|
|
48
|
+
|
|
49
|
+
// Use the middleware globally (or on specific routes)
|
|
50
|
+
// This will look for a tenant slug in the 'x-tenant-id' header
|
|
51
|
+
app.use(tenantMiddleware({
|
|
52
|
+
manager,
|
|
53
|
+
resolver: headerResolver('x-tenant-id')
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
app.get('/api/me', (req, res) => {
|
|
57
|
+
// Access the resolved tenant context safely!
|
|
58
|
+
const tenant = TenantContextManager.currentOrThrow();
|
|
59
|
+
|
|
60
|
+
res.json({
|
|
61
|
+
message: `Hello from tenant: ${tenant.name}`,
|
|
62
|
+
tenantId: tenant.id
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
app.listen(3000);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Resolvers
|
|
72
|
+
|
|
73
|
+
Resolvers are strategies for extracting the tenant identifier (slug or ID) from an incoming Express request.
|
|
74
|
+
|
|
75
|
+
### Header Resolver
|
|
76
|
+
|
|
77
|
+
Extracts the tenant identifier from an HTTP header. Common in API-first applications.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { headerResolver } from '@node-tenant/tenant-express';
|
|
81
|
+
|
|
82
|
+
// Looks for 'x-tenant-id' in headers
|
|
83
|
+
const resolver = headerResolver('x-tenant-id');
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Subdomain Resolver
|
|
87
|
+
|
|
88
|
+
Extracts the tenant identifier from the subdomain (e.g., `acme` in `acme.myapp.com`). You **must** provide the `storage` adapter so the resolver can lookup the tenant by its slug.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { subdomainResolver } from '@node-tenant/tenant-express';
|
|
92
|
+
|
|
93
|
+
// Extracts subdomain and looks up tenant via storage.getTenantBySlug(subdomain)
|
|
94
|
+
const resolver = subdomainResolver(manager.storage);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Custom Resolver
|
|
98
|
+
|
|
99
|
+
You can easily write your own resolver by implementing the `TenantResolver` signature: `(req: Request) => Promise<string | undefined>`
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const jwtResolver = async (req) => {
|
|
103
|
+
// Extract tenant ID from a decoded JWT token
|
|
104
|
+
return req.user?.tenantId;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
app.use(tenantMiddleware({ manager, resolver: jwtResolver }));
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## API Reference
|
|
113
|
+
|
|
114
|
+
### `tenantMiddleware(options)`
|
|
115
|
+
|
|
116
|
+
Creates the Express middleware.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const middleware = tenantMiddleware({
|
|
120
|
+
manager: managerInstance,
|
|
121
|
+
resolver: myResolver,
|
|
122
|
+
requireTenant: true // default: false
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
| Option | Type | Required | Description |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| `manager` | `TenantManager` | ✅ | Instance of `TenantManager` |
|
|
129
|
+
| `resolver` | `TenantResolver` | ✅ | Function to extract tenant ID/slug from request |
|
|
130
|
+
| `requireTenant` | `boolean` | ❌ | If true, returns 401 if no tenant is found. Default: `false`. |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT © AdPulseFlow
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
chainResolvers: () => import_tenant_core2.chainResolvers,
|
|
24
|
+
domainResolver: () => import_tenant_core2.domainResolver,
|
|
25
|
+
getCurrentTenant: () => getCurrentTenant,
|
|
26
|
+
headerResolver: () => import_tenant_core2.headerResolver,
|
|
27
|
+
pathResolver: () => import_tenant_core2.pathResolver,
|
|
28
|
+
subdomainResolver: () => import_tenant_core2.subdomainResolver,
|
|
29
|
+
tenantMiddleware: () => tenantMiddleware
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
var import_tenant_core = require("@node-tenant/tenant-core");
|
|
33
|
+
var import_tenant_core2 = require("@node-tenant/tenant-core");
|
|
34
|
+
function tenantMiddleware(options) {
|
|
35
|
+
const { manager, resolver, required = true } = options;
|
|
36
|
+
return async (req, res, next) => {
|
|
37
|
+
try {
|
|
38
|
+
const result = await resolver(req);
|
|
39
|
+
if (!result) {
|
|
40
|
+
if (required) {
|
|
41
|
+
if (options.onNotFound) {
|
|
42
|
+
return options.onNotFound(req, res, next);
|
|
43
|
+
}
|
|
44
|
+
res.status(401).json({ error: "Tenant not found" });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
}
|
|
49
|
+
const tenant = await manager.getTenant(result.tenantId);
|
|
50
|
+
if (!tenant) {
|
|
51
|
+
if (required) {
|
|
52
|
+
res.status(401).json({ error: "Tenant not found" });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
return next();
|
|
56
|
+
}
|
|
57
|
+
if (tenant.status !== "active") {
|
|
58
|
+
res.status(403).json({ error: `Tenant is ${tenant.status}` });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const ctx = {
|
|
62
|
+
id: tenant.id,
|
|
63
|
+
slug: tenant.slug,
|
|
64
|
+
strategy: tenant.strategy,
|
|
65
|
+
organizationId: tenant.organizationId,
|
|
66
|
+
config: tenant.config
|
|
67
|
+
};
|
|
68
|
+
for (const plugin of manager.plugins) {
|
|
69
|
+
await plugin.onTenantResolved?.(ctx);
|
|
70
|
+
}
|
|
71
|
+
manager.emit("tenant.resolved", ctx);
|
|
72
|
+
import_tenant_core.TenantContextManager.run(ctx, () => next());
|
|
73
|
+
} catch (err) {
|
|
74
|
+
next(err);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function getCurrentTenant() {
|
|
79
|
+
return import_tenant_core.TenantContextManager.currentOrThrow();
|
|
80
|
+
}
|
|
81
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
82
|
+
0 && (module.exports = {
|
|
83
|
+
chainResolvers,
|
|
84
|
+
domainResolver,
|
|
85
|
+
getCurrentTenant,
|
|
86
|
+
headerResolver,
|
|
87
|
+
pathResolver,
|
|
88
|
+
subdomainResolver,
|
|
89
|
+
tenantMiddleware
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Request, Response, NextFunction, RequestHandler } from 'express';\nimport {\n TenantContextManager,\n TenantManager,\n type TenantResolverFn,\n type TenantPlugin,\n} from '@node-tenant/tenant-core';\n\nexport interface TenantMiddlewareOptions {\n manager: TenantManager;\n resolver: TenantResolverFn<Request>;\n /** If true, 401 is returned when tenant cannot be resolved. Default: true */\n required?: boolean;\n /** Called when tenant resolution fails */\n onNotFound?: (req: Request, res: Response, next: NextFunction) => void;\n}\n\n/**\n * Express middleware that resolves the tenant for each request and\n * runs the rest of the middleware chain inside that tenant's context.\n *\n * Usage:\n * app.use(tenantMiddleware({ manager, resolver: subdomainResolver(manager.storage) }))\n */\nexport function tenantMiddleware(options: TenantMiddlewareOptions): RequestHandler {\n const { manager, resolver, required = true } = options;\n\n return async (req, res, next) => {\n try {\n const result = await resolver(req);\n\n if (!result) {\n if (required) {\n if (options.onNotFound) {\n return options.onNotFound(req, res, next);\n }\n res.status(401).json({ error: 'Tenant not found' });\n return;\n }\n return next();\n }\n\n const tenant = await manager.getTenant(result.tenantId);\n if (!tenant) {\n if (required) {\n res.status(401).json({ error: 'Tenant not found' });\n return;\n }\n return next();\n }\n\n if (tenant.status !== 'active') {\n res.status(403).json({ error: `Tenant is ${tenant.status}` });\n return;\n }\n\n const ctx = {\n id: tenant.id,\n slug: tenant.slug,\n strategy: tenant.strategy,\n organizationId: tenant.organizationId,\n config: tenant.config,\n };\n\n for (const plugin of (manager as any).plugins as TenantPlugin[]) {\n await plugin.onTenantResolved?.(ctx);\n }\n\n manager.emit('tenant.resolved', ctx);\n\n TenantContextManager.run(ctx, () => next());\n } catch (err) {\n next(err);\n }\n };\n}\n\n/**\n * Get the current tenant context inside an Express handler.\n * Must be used after tenantMiddleware().\n */\nexport function getCurrentTenant() {\n return TenantContextManager.currentOrThrow();\n}\n\nexport { subdomainResolver, domainResolver, pathResolver, headerResolver, chainResolvers } from '@node-tenant/tenant-core';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,yBAKO;AA+EP,IAAAA,sBAAgG;AA7DzF,SAAS,iBAAiB,SAAkD;AACjF,QAAM,EAAE,SAAS,UAAU,WAAW,KAAK,IAAI;AAE/C,SAAO,OAAO,KAAK,KAAK,SAAS;AAC/B,QAAI;AACF,YAAM,SAAS,MAAM,SAAS,GAAG;AAEjC,UAAI,CAAC,QAAQ;AACX,YAAI,UAAU;AACZ,cAAI,QAAQ,YAAY;AACtB,mBAAO,QAAQ,WAAW,KAAK,KAAK,IAAI;AAAA,UAC1C;AACA,cAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAClD;AAAA,QACF;AACA,eAAO,KAAK;AAAA,MACd;AAEA,YAAM,SAAS,MAAM,QAAQ,UAAU,OAAO,QAAQ;AACtD,UAAI,CAAC,QAAQ;AACX,YAAI,UAAU;AACZ,cAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAClD;AAAA,QACF;AACA,eAAO,KAAK;AAAA,MACd;AAEA,UAAI,OAAO,WAAW,UAAU;AAC9B,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,aAAa,OAAO,MAAM,GAAG,CAAC;AAC5D;AAAA,MACF;AAEA,YAAM,MAAM;AAAA,QACV,IAAI,OAAO;AAAA,QACX,MAAM,OAAO;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,gBAAgB,OAAO;AAAA,QACvB,QAAQ,OAAO;AAAA,MACjB;AAEA,iBAAW,UAAW,QAAgB,SAA2B;AAC/D,cAAM,OAAO,mBAAmB,GAAG;AAAA,MACrC;AAEA,cAAQ,KAAK,mBAAmB,GAAG;AAEnC,8CAAqB,IAAI,KAAK,MAAM,KAAK,CAAC;AAAA,IAC5C,SAAS,KAAK;AACZ,WAAK,GAAG;AAAA,IACV;AAAA,EACF;AACF;AAMO,SAAS,mBAAmB;AACjC,SAAO,wCAAqB,eAAe;AAC7C;","names":["import_tenant_core"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as _node_tenant_tenant_core from '@node-tenant/tenant-core';
|
|
2
|
+
import { TenantManager, TenantResolverFn } from '@node-tenant/tenant-core';
|
|
3
|
+
export { chainResolvers, domainResolver, headerResolver, pathResolver, subdomainResolver } from '@node-tenant/tenant-core';
|
|
4
|
+
import { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
5
|
+
|
|
6
|
+
interface TenantMiddlewareOptions {
|
|
7
|
+
manager: TenantManager;
|
|
8
|
+
resolver: TenantResolverFn<Request>;
|
|
9
|
+
/** If true, 401 is returned when tenant cannot be resolved. Default: true */
|
|
10
|
+
required?: boolean;
|
|
11
|
+
/** Called when tenant resolution fails */
|
|
12
|
+
onNotFound?: (req: Request, res: Response, next: NextFunction) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Express middleware that resolves the tenant for each request and
|
|
16
|
+
* runs the rest of the middleware chain inside that tenant's context.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* app.use(tenantMiddleware({ manager, resolver: subdomainResolver(manager.storage) }))
|
|
20
|
+
*/
|
|
21
|
+
declare function tenantMiddleware(options: TenantMiddlewareOptions): RequestHandler;
|
|
22
|
+
/**
|
|
23
|
+
* Get the current tenant context inside an Express handler.
|
|
24
|
+
* Must be used after tenantMiddleware().
|
|
25
|
+
*/
|
|
26
|
+
declare function getCurrentTenant(): _node_tenant_tenant_core.TenantContext;
|
|
27
|
+
|
|
28
|
+
export { type TenantMiddlewareOptions, getCurrentTenant, tenantMiddleware };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as _node_tenant_tenant_core from '@node-tenant/tenant-core';
|
|
2
|
+
import { TenantManager, TenantResolverFn } from '@node-tenant/tenant-core';
|
|
3
|
+
export { chainResolvers, domainResolver, headerResolver, pathResolver, subdomainResolver } from '@node-tenant/tenant-core';
|
|
4
|
+
import { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
5
|
+
|
|
6
|
+
interface TenantMiddlewareOptions {
|
|
7
|
+
manager: TenantManager;
|
|
8
|
+
resolver: TenantResolverFn<Request>;
|
|
9
|
+
/** If true, 401 is returned when tenant cannot be resolved. Default: true */
|
|
10
|
+
required?: boolean;
|
|
11
|
+
/** Called when tenant resolution fails */
|
|
12
|
+
onNotFound?: (req: Request, res: Response, next: NextFunction) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Express middleware that resolves the tenant for each request and
|
|
16
|
+
* runs the rest of the middleware chain inside that tenant's context.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* app.use(tenantMiddleware({ manager, resolver: subdomainResolver(manager.storage) }))
|
|
20
|
+
*/
|
|
21
|
+
declare function tenantMiddleware(options: TenantMiddlewareOptions): RequestHandler;
|
|
22
|
+
/**
|
|
23
|
+
* Get the current tenant context inside an Express handler.
|
|
24
|
+
* Must be used after tenantMiddleware().
|
|
25
|
+
*/
|
|
26
|
+
declare function getCurrentTenant(): _node_tenant_tenant_core.TenantContext;
|
|
27
|
+
|
|
28
|
+
export { type TenantMiddlewareOptions, getCurrentTenant, tenantMiddleware };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
TenantContextManager
|
|
4
|
+
} from "@node-tenant/tenant-core";
|
|
5
|
+
import { subdomainResolver, domainResolver, pathResolver, headerResolver, chainResolvers } from "@node-tenant/tenant-core";
|
|
6
|
+
function tenantMiddleware(options) {
|
|
7
|
+
const { manager, resolver, required = true } = options;
|
|
8
|
+
return async (req, res, next) => {
|
|
9
|
+
try {
|
|
10
|
+
const result = await resolver(req);
|
|
11
|
+
if (!result) {
|
|
12
|
+
if (required) {
|
|
13
|
+
if (options.onNotFound) {
|
|
14
|
+
return options.onNotFound(req, res, next);
|
|
15
|
+
}
|
|
16
|
+
res.status(401).json({ error: "Tenant not found" });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
return next();
|
|
20
|
+
}
|
|
21
|
+
const tenant = await manager.getTenant(result.tenantId);
|
|
22
|
+
if (!tenant) {
|
|
23
|
+
if (required) {
|
|
24
|
+
res.status(401).json({ error: "Tenant not found" });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
return next();
|
|
28
|
+
}
|
|
29
|
+
if (tenant.status !== "active") {
|
|
30
|
+
res.status(403).json({ error: `Tenant is ${tenant.status}` });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const ctx = {
|
|
34
|
+
id: tenant.id,
|
|
35
|
+
slug: tenant.slug,
|
|
36
|
+
strategy: tenant.strategy,
|
|
37
|
+
organizationId: tenant.organizationId,
|
|
38
|
+
config: tenant.config
|
|
39
|
+
};
|
|
40
|
+
for (const plugin of manager.plugins) {
|
|
41
|
+
await plugin.onTenantResolved?.(ctx);
|
|
42
|
+
}
|
|
43
|
+
manager.emit("tenant.resolved", ctx);
|
|
44
|
+
TenantContextManager.run(ctx, () => next());
|
|
45
|
+
} catch (err) {
|
|
46
|
+
next(err);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function getCurrentTenant() {
|
|
51
|
+
return TenantContextManager.currentOrThrow();
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
chainResolvers,
|
|
55
|
+
domainResolver,
|
|
56
|
+
getCurrentTenant,
|
|
57
|
+
headerResolver,
|
|
58
|
+
pathResolver,
|
|
59
|
+
subdomainResolver,
|
|
60
|
+
tenantMiddleware
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Request, Response, NextFunction, RequestHandler } from 'express';\nimport {\n TenantContextManager,\n TenantManager,\n type TenantResolverFn,\n type TenantPlugin,\n} from '@node-tenant/tenant-core';\n\nexport interface TenantMiddlewareOptions {\n manager: TenantManager;\n resolver: TenantResolverFn<Request>;\n /** If true, 401 is returned when tenant cannot be resolved. Default: true */\n required?: boolean;\n /** Called when tenant resolution fails */\n onNotFound?: (req: Request, res: Response, next: NextFunction) => void;\n}\n\n/**\n * Express middleware that resolves the tenant for each request and\n * runs the rest of the middleware chain inside that tenant's context.\n *\n * Usage:\n * app.use(tenantMiddleware({ manager, resolver: subdomainResolver(manager.storage) }))\n */\nexport function tenantMiddleware(options: TenantMiddlewareOptions): RequestHandler {\n const { manager, resolver, required = true } = options;\n\n return async (req, res, next) => {\n try {\n const result = await resolver(req);\n\n if (!result) {\n if (required) {\n if (options.onNotFound) {\n return options.onNotFound(req, res, next);\n }\n res.status(401).json({ error: 'Tenant not found' });\n return;\n }\n return next();\n }\n\n const tenant = await manager.getTenant(result.tenantId);\n if (!tenant) {\n if (required) {\n res.status(401).json({ error: 'Tenant not found' });\n return;\n }\n return next();\n }\n\n if (tenant.status !== 'active') {\n res.status(403).json({ error: `Tenant is ${tenant.status}` });\n return;\n }\n\n const ctx = {\n id: tenant.id,\n slug: tenant.slug,\n strategy: tenant.strategy,\n organizationId: tenant.organizationId,\n config: tenant.config,\n };\n\n for (const plugin of (manager as any).plugins as TenantPlugin[]) {\n await plugin.onTenantResolved?.(ctx);\n }\n\n manager.emit('tenant.resolved', ctx);\n\n TenantContextManager.run(ctx, () => next());\n } catch (err) {\n next(err);\n }\n };\n}\n\n/**\n * Get the current tenant context inside an Express handler.\n * Must be used after tenantMiddleware().\n */\nexport function getCurrentTenant() {\n return TenantContextManager.currentOrThrow();\n}\n\nexport { subdomainResolver, domainResolver, pathResolver, headerResolver, chainResolvers } from '@node-tenant/tenant-core';\n"],"mappings":";AACA;AAAA,EACE;AAAA,OAIK;AA+EP,SAAS,mBAAmB,gBAAgB,cAAc,gBAAgB,sBAAsB;AA7DzF,SAAS,iBAAiB,SAAkD;AACjF,QAAM,EAAE,SAAS,UAAU,WAAW,KAAK,IAAI;AAE/C,SAAO,OAAO,KAAK,KAAK,SAAS;AAC/B,QAAI;AACF,YAAM,SAAS,MAAM,SAAS,GAAG;AAEjC,UAAI,CAAC,QAAQ;AACX,YAAI,UAAU;AACZ,cAAI,QAAQ,YAAY;AACtB,mBAAO,QAAQ,WAAW,KAAK,KAAK,IAAI;AAAA,UAC1C;AACA,cAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAClD;AAAA,QACF;AACA,eAAO,KAAK;AAAA,MACd;AAEA,YAAM,SAAS,MAAM,QAAQ,UAAU,OAAO,QAAQ;AACtD,UAAI,CAAC,QAAQ;AACX,YAAI,UAAU;AACZ,cAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAClD;AAAA,QACF;AACA,eAAO,KAAK;AAAA,MACd;AAEA,UAAI,OAAO,WAAW,UAAU;AAC9B,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,aAAa,OAAO,MAAM,GAAG,CAAC;AAC5D;AAAA,MACF;AAEA,YAAM,MAAM;AAAA,QACV,IAAI,OAAO;AAAA,QACX,MAAM,OAAO;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,gBAAgB,OAAO;AAAA,QACvB,QAAQ,OAAO;AAAA,MACjB;AAEA,iBAAW,UAAW,QAAgB,SAA2B;AAC/D,cAAM,OAAO,mBAAmB,GAAG;AAAA,MACrC;AAEA,cAAQ,KAAK,mBAAmB,GAAG;AAEnC,2BAAqB,IAAI,KAAK,MAAM,KAAK,CAAC;AAAA,IAC5C,SAAS,KAAK;AACZ,WAAK,GAAG;AAAA,IACV;AAAA,EACF;AACF;AAMO,SAAS,mBAAmB;AACjC,SAAO,qBAAqB,eAAe;AAC7C;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@node-tenant/tenant-express",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Express.js middleware for @node-tenant/tenant",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".":{
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": ["dist"],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"lint": "eslint src --ext .ts",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"clean": "rimraf dist"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@node-tenant/tenant-core": "^0.1.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"express": ">=4.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/express": "^4.17.21",
|
|
34
|
+
"express": "^4.18.3",
|
|
35
|
+
"rimraf": "^5.0.5",
|
|
36
|
+
"tsup": "^8.0.0",
|
|
37
|
+
"typescript": "^5.4.0",
|
|
38
|
+
"vitest": "^1.4.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|