@saga-bus/middleware-tenant 0.1.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Dean Foran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # @saga-bus/middleware-tenant
2
+
3
+ Multi-tenant isolation middleware for saga-bus.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @saga-bus/middleware-tenant
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { createTenantMiddleware, getTenantId } from "@saga-bus/middleware-tenant";
15
+ import { createBus } from "@saga-bus/core";
16
+
17
+ const tenantMiddleware = createTenantMiddleware({
18
+ strategy: "header",
19
+ headerName: "x-tenant-id",
20
+ required: true,
21
+ });
22
+
23
+ const bus = createBus({
24
+ middleware: [tenantMiddleware],
25
+ sagas: [...],
26
+ transport,
27
+ });
28
+
29
+ // Access tenant in handlers
30
+ const tenantId = getTenantId(); // from AsyncLocalStorage context
31
+ ```
32
+
33
+ ## Tenant Resolution Strategies
34
+
35
+ ### Header Strategy (default)
36
+ Extracts tenant ID from message headers:
37
+
38
+ ```typescript
39
+ createTenantMiddleware({
40
+ strategy: "header",
41
+ headerName: "x-tenant-id",
42
+ });
43
+ ```
44
+
45
+ ### Correlation Prefix Strategy
46
+ Extracts tenant ID from correlation ID prefix:
47
+
48
+ ```typescript
49
+ createTenantMiddleware({
50
+ strategy: "correlation-prefix",
51
+ correlationSeparator: ":",
52
+ });
53
+ // correlationId "tenant123:order-456" → tenantId "tenant123"
54
+ ```
55
+
56
+ ### Custom Strategy
57
+ Provide your own resolver function:
58
+
59
+ ```typescript
60
+ createTenantMiddleware({
61
+ strategy: "custom",
62
+ resolver: (envelope) => envelope.payload?.tenantId,
63
+ });
64
+ ```
65
+
66
+ ## Tenant-Aware Publishing
67
+
68
+ ```typescript
69
+ import { createTenantPublisher } from "@saga-bus/middleware-tenant";
70
+
71
+ const publish = createTenantPublisher(bus, {
72
+ headerName: "x-tenant-id",
73
+ });
74
+
75
+ // Automatically adds tenant header from context
76
+ await publish({ type: "OrderCreated", payload: { orderId: "123" } });
77
+ ```
78
+
79
+ ## Configuration
80
+
81
+ | Option | Type | Default | Description |
82
+ |--------|------|---------|-------------|
83
+ | `strategy` | `string` | `"header"` | Resolution strategy |
84
+ | `headerName` | `string` | `"x-tenant-id"` | Header name for header strategy |
85
+ | `correlationSeparator` | `string` | `":"` | Separator for correlation prefix |
86
+ | `resolver` | `function` | - | Custom resolver function |
87
+ | `required` | `boolean` | `true` | Require tenant on all messages |
88
+ | `defaultTenantId` | `string` | - | Default when not required |
89
+ | `allowedTenants` | `string[]` | - | Allowlist of valid tenants |
90
+ | `prefixSagaId` | `boolean` | `true` | Prefix saga IDs with tenant |
91
+
92
+ ## Context API
93
+
94
+ ```typescript
95
+ import {
96
+ getTenantId,
97
+ getTenantInfo,
98
+ requireTenantId,
99
+ runWithTenant,
100
+ } from "@saga-bus/middleware-tenant";
101
+
102
+ // Get current tenant (undefined if not set)
103
+ const tenantId = getTenantId();
104
+
105
+ // Get full tenant info
106
+ const info = getTenantInfo(); // { tenantId, originalSagaId }
107
+
108
+ // Throw if no tenant
109
+ const tenantId = requireTenantId();
110
+
111
+ // Run code with explicit tenant context
112
+ await runWithTenant("tenant-123", async () => {
113
+ // getTenantId() returns "tenant-123" here
114
+ });
115
+ ```
116
+
117
+ ## License
118
+
119
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,158 @@
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
+ TenantNotAllowedError: () => TenantNotAllowedError,
24
+ TenantResolutionError: () => TenantResolutionError,
25
+ createTenantMiddleware: () => createTenantMiddleware,
26
+ createTenantPublisher: () => createTenantPublisher,
27
+ getTenantId: () => getTenantId,
28
+ getTenantInfo: () => getTenantInfo,
29
+ requireTenantId: () => requireTenantId,
30
+ runWithTenant: () => runWithTenant
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/types.ts
35
+ var TenantResolutionError = class extends Error {
36
+ constructor(message, envelope) {
37
+ super(message);
38
+ this.envelope = envelope;
39
+ this.name = "TenantResolutionError";
40
+ }
41
+ };
42
+ var TenantNotAllowedError = class extends Error {
43
+ constructor(tenantId, envelope) {
44
+ super(`Tenant "${tenantId}" is not allowed`);
45
+ this.tenantId = tenantId;
46
+ this.envelope = envelope;
47
+ this.name = "TenantNotAllowedError";
48
+ }
49
+ };
50
+
51
+ // src/TenantContext.ts
52
+ var import_node_async_hooks = require("async_hooks");
53
+ var tenantStorage = new import_node_async_hooks.AsyncLocalStorage();
54
+ function runWithTenant(tenantInfo, fn) {
55
+ return tenantStorage.run(tenantInfo, fn);
56
+ }
57
+ function getTenantId() {
58
+ return tenantStorage.getStore()?.tenantId;
59
+ }
60
+ function getTenantInfo() {
61
+ return tenantStorage.getStore();
62
+ }
63
+ function requireTenantId() {
64
+ const tenantId = getTenantId();
65
+ if (!tenantId) {
66
+ throw new Error("No tenant context available");
67
+ }
68
+ return tenantId;
69
+ }
70
+
71
+ // src/TenantMiddleware.ts
72
+ function createTenantMiddleware(options = {}) {
73
+ const strategy = options.strategy ?? "header";
74
+ const headerName = options.headerName ?? "x-tenant-id";
75
+ const correlationSeparator = options.correlationSeparator ?? ":";
76
+ const required = options.required ?? true;
77
+ const defaultTenantId = options.defaultTenantId;
78
+ const allowedTenants = options.allowedTenants ? new Set(options.allowedTenants) : void 0;
79
+ const prefixSagaId = options.prefixSagaId ?? true;
80
+ const resolver = options.resolver ?? createResolver(strategy, {
81
+ headerName,
82
+ correlationSeparator
83
+ });
84
+ return async (ctx, next) => {
85
+ const { envelope } = ctx;
86
+ let tenantId = resolver(envelope);
87
+ if (!tenantId) {
88
+ if (required) {
89
+ throw new TenantResolutionError(
90
+ `Could not resolve tenant ID from message using strategy "${strategy}"`,
91
+ envelope
92
+ );
93
+ }
94
+ tenantId = defaultTenantId;
95
+ }
96
+ if (!tenantId) {
97
+ await next();
98
+ return;
99
+ }
100
+ if (allowedTenants && !allowedTenants.has(tenantId)) {
101
+ throw new TenantNotAllowedError(tenantId, envelope);
102
+ }
103
+ let originalSagaId;
104
+ if (prefixSagaId && ctx.sagaId) {
105
+ originalSagaId = ctx.sagaId;
106
+ ctx.sagaId = `${tenantId}:${ctx.sagaId}`;
107
+ }
108
+ await runWithTenant({ tenantId, originalSagaId }, async () => {
109
+ await next();
110
+ });
111
+ };
112
+ }
113
+ function createTenantPublisher(options = {}) {
114
+ const headerName = options.headerName ?? "x-tenant-id";
115
+ return (envelope, tenantId) => {
116
+ return {
117
+ ...envelope,
118
+ headers: {
119
+ ...envelope.headers,
120
+ [headerName]: tenantId
121
+ }
122
+ };
123
+ };
124
+ }
125
+ function createResolver(strategy, options) {
126
+ switch (strategy) {
127
+ case "header":
128
+ return (envelope) => {
129
+ return envelope.headers[options.headerName];
130
+ };
131
+ case "correlation-prefix":
132
+ return (envelope) => {
133
+ const partitionKey = envelope.partitionKey;
134
+ if (!partitionKey) {
135
+ return void 0;
136
+ }
137
+ const separatorIndex = partitionKey.indexOf(options.correlationSeparator);
138
+ if (separatorIndex === -1) {
139
+ return void 0;
140
+ }
141
+ return partitionKey.slice(0, separatorIndex);
142
+ };
143
+ case "custom":
144
+ throw new Error("Custom strategy requires a resolver function");
145
+ }
146
+ }
147
+ // Annotate the CommonJS export names for ESM import in node:
148
+ 0 && (module.exports = {
149
+ TenantNotAllowedError,
150
+ TenantResolutionError,
151
+ createTenantMiddleware,
152
+ createTenantPublisher,
153
+ getTenantId,
154
+ getTenantInfo,
155
+ requireTenantId,
156
+ runWithTenant
157
+ });
158
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/TenantContext.ts","../src/TenantMiddleware.ts"],"sourcesContent":["export {\n createTenantMiddleware,\n createTenantPublisher,\n} from \"./TenantMiddleware.js\";\nexport {\n getTenantId,\n getTenantInfo,\n requireTenantId,\n runWithTenant,\n} from \"./TenantContext.js\";\nexport type {\n TenantMiddlewareOptions,\n TenantResolver,\n TenantInfo,\n} from \"./types.js\";\nexport { TenantResolutionError, TenantNotAllowedError } from \"./types.js\";\n","import type { MessageEnvelope } from \"@saga-bus/core\";\n\n/**\n * Tenant middleware configuration options.\n */\nexport interface TenantMiddlewareOptions {\n /**\n * Strategy for resolving tenant ID from messages.\n * @default \"header\"\n */\n strategy?: \"header\" | \"correlation-prefix\" | \"custom\";\n\n /**\n * Custom tenant resolver function.\n * Required when strategy is \"custom\".\n */\n resolver?: TenantResolver;\n\n /**\n * Header name for tenant ID when using \"header\" strategy.\n * @default \"x-tenant-id\"\n */\n headerName?: string;\n\n /**\n * Separator for correlation prefix strategy.\n * @default \":\"\n */\n correlationSeparator?: string;\n\n /**\n * Whether to require tenant ID on all messages.\n * @default true\n */\n required?: boolean;\n\n /**\n * Default tenant ID when not required and not found.\n */\n defaultTenantId?: string;\n\n /**\n * Allowlist of valid tenant IDs.\n * If provided, messages from unknown tenants are rejected.\n */\n allowedTenants?: string[];\n\n /**\n * Whether to prefix saga IDs with tenant ID for isolation.\n * @default true\n */\n prefixSagaId?: boolean;\n}\n\n/**\n * Function to resolve tenant ID from a message.\n */\nexport type TenantResolver = (envelope: MessageEnvelope) => string | undefined;\n\n/**\n * Tenant context for the current request.\n */\nexport interface TenantInfo {\n /**\n * Resolved tenant ID.\n */\n tenantId: string;\n\n /**\n * Original saga ID before tenant prefix.\n */\n originalSagaId?: string;\n}\n\n/**\n * Error thrown when tenant resolution fails.\n */\nexport class TenantResolutionError extends Error {\n constructor(\n message: string,\n public readonly envelope: MessageEnvelope\n ) {\n super(message);\n this.name = \"TenantResolutionError\";\n }\n}\n\n/**\n * Error thrown when tenant is not in allowlist.\n */\nexport class TenantNotAllowedError extends Error {\n constructor(\n public readonly tenantId: string,\n public readonly envelope: MessageEnvelope\n ) {\n super(`Tenant \"${tenantId}\" is not allowed`);\n this.name = \"TenantNotAllowedError\";\n }\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { TenantInfo } from \"./types.js\";\n\nconst tenantStorage = new AsyncLocalStorage<TenantInfo>();\n\n/**\n * Run a function within a tenant context.\n */\nexport function runWithTenant<T>(\n tenantInfo: TenantInfo,\n fn: () => T | Promise<T>\n): T | Promise<T> {\n return tenantStorage.run(tenantInfo, fn);\n}\n\n/**\n * Get the current tenant ID.\n * Returns undefined if not in a tenant context.\n */\nexport function getTenantId(): string | undefined {\n return tenantStorage.getStore()?.tenantId;\n}\n\n/**\n * Get the full tenant info for the current context.\n */\nexport function getTenantInfo(): TenantInfo | undefined {\n return tenantStorage.getStore();\n}\n\n/**\n * Get the current tenant ID or throw if not available.\n */\nexport function requireTenantId(): string {\n const tenantId = getTenantId();\n if (!tenantId) {\n throw new Error(\"No tenant context available\");\n }\n return tenantId;\n}\n","import type {\n SagaMiddleware,\n SagaPipelineContext,\n MessageEnvelope,\n} from \"@saga-bus/core\";\nimport type { TenantMiddlewareOptions, TenantResolver } from \"./types.js\";\nimport { TenantResolutionError, TenantNotAllowedError } from \"./types.js\";\nimport { runWithTenant } from \"./TenantContext.js\";\n\n/**\n * Create multi-tenant middleware for saga-bus.\n *\n * Resolves tenant ID from messages and provides tenant context\n * via AsyncLocalStorage for use within handlers.\n *\n * @example\n * ```typescript\n * const middleware = createTenantMiddleware({\n * strategy: \"header\",\n * headerName: \"x-tenant-id\",\n * allowedTenants: [\"tenant1\", \"tenant2\"],\n * });\n *\n * const bus = createMessageBus({\n * middleware: [middleware],\n * });\n * ```\n */\nexport function createTenantMiddleware(\n options: TenantMiddlewareOptions = {}\n): SagaMiddleware {\n const strategy = options.strategy ?? \"header\";\n const headerName = options.headerName ?? \"x-tenant-id\";\n const correlationSeparator = options.correlationSeparator ?? \":\";\n const required = options.required ?? true;\n const defaultTenantId = options.defaultTenantId;\n const allowedTenants = options.allowedTenants\n ? new Set(options.allowedTenants)\n : undefined;\n const prefixSagaId = options.prefixSagaId ?? true;\n\n const resolver =\n options.resolver ??\n createResolver(strategy, {\n headerName,\n correlationSeparator,\n });\n\n return async (ctx: SagaPipelineContext, next: () => Promise<void>) => {\n const { envelope } = ctx;\n\n // Resolve tenant ID\n let tenantId = resolver(envelope);\n\n if (!tenantId) {\n if (required) {\n throw new TenantResolutionError(\n `Could not resolve tenant ID from message using strategy \"${strategy}\"`,\n envelope\n );\n }\n tenantId = defaultTenantId;\n }\n\n if (!tenantId) {\n // No tenant and not required - proceed without tenant context\n await next();\n return;\n }\n\n // Validate against allowlist\n if (allowedTenants && !allowedTenants.has(tenantId)) {\n throw new TenantNotAllowedError(tenantId, envelope);\n }\n\n // Modify saga ID if needed\n let originalSagaId: string | undefined;\n if (prefixSagaId && ctx.sagaId) {\n originalSagaId = ctx.sagaId;\n ctx.sagaId = `${tenantId}:${ctx.sagaId}`;\n }\n\n // Run handler within tenant context\n await runWithTenant({ tenantId, originalSagaId }, async () => {\n await next();\n });\n };\n}\n\n/**\n * Create a publish interceptor that adds tenant ID to outbound messages.\n *\n * @example\n * ```typescript\n * const publisher = createTenantPublisher();\n * const envelope = publisher(originalEnvelope, \"tenant1\");\n * ```\n */\nexport function createTenantPublisher(\n options: Pick<TenantMiddlewareOptions, \"headerName\"> = {}\n): (envelope: MessageEnvelope, tenantId: string) => MessageEnvelope {\n const headerName = options.headerName ?? \"x-tenant-id\";\n\n return (envelope: MessageEnvelope, tenantId: string): MessageEnvelope => {\n return {\n ...envelope,\n headers: {\n ...envelope.headers,\n [headerName]: tenantId,\n },\n };\n };\n}\n\nfunction createResolver(\n strategy: \"header\" | \"correlation-prefix\" | \"custom\",\n options: {\n headerName: string;\n correlationSeparator: string;\n }\n): TenantResolver {\n switch (strategy) {\n case \"header\":\n return (envelope: MessageEnvelope) => {\n return envelope.headers[options.headerName];\n };\n\n case \"correlation-prefix\":\n return (envelope: MessageEnvelope) => {\n const partitionKey = envelope.partitionKey;\n if (!partitionKey) {\n return undefined;\n }\n const separatorIndex = partitionKey.indexOf(options.correlationSeparator);\n if (separatorIndex === -1) {\n return undefined;\n }\n return partitionKey.slice(0, separatorIndex);\n };\n\n case \"custom\":\n throw new Error(\"Custom strategy requires a resolver function\");\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,129 @@
1
+ import { MessageEnvelope, SagaMiddleware } from '@saga-bus/core';
2
+
3
+ /**
4
+ * Tenant middleware configuration options.
5
+ */
6
+ interface TenantMiddlewareOptions {
7
+ /**
8
+ * Strategy for resolving tenant ID from messages.
9
+ * @default "header"
10
+ */
11
+ strategy?: "header" | "correlation-prefix" | "custom";
12
+ /**
13
+ * Custom tenant resolver function.
14
+ * Required when strategy is "custom".
15
+ */
16
+ resolver?: TenantResolver;
17
+ /**
18
+ * Header name for tenant ID when using "header" strategy.
19
+ * @default "x-tenant-id"
20
+ */
21
+ headerName?: string;
22
+ /**
23
+ * Separator for correlation prefix strategy.
24
+ * @default ":"
25
+ */
26
+ correlationSeparator?: string;
27
+ /**
28
+ * Whether to require tenant ID on all messages.
29
+ * @default true
30
+ */
31
+ required?: boolean;
32
+ /**
33
+ * Default tenant ID when not required and not found.
34
+ */
35
+ defaultTenantId?: string;
36
+ /**
37
+ * Allowlist of valid tenant IDs.
38
+ * If provided, messages from unknown tenants are rejected.
39
+ */
40
+ allowedTenants?: string[];
41
+ /**
42
+ * Whether to prefix saga IDs with tenant ID for isolation.
43
+ * @default true
44
+ */
45
+ prefixSagaId?: boolean;
46
+ }
47
+ /**
48
+ * Function to resolve tenant ID from a message.
49
+ */
50
+ type TenantResolver = (envelope: MessageEnvelope) => string | undefined;
51
+ /**
52
+ * Tenant context for the current request.
53
+ */
54
+ interface TenantInfo {
55
+ /**
56
+ * Resolved tenant ID.
57
+ */
58
+ tenantId: string;
59
+ /**
60
+ * Original saga ID before tenant prefix.
61
+ */
62
+ originalSagaId?: string;
63
+ }
64
+ /**
65
+ * Error thrown when tenant resolution fails.
66
+ */
67
+ declare class TenantResolutionError extends Error {
68
+ readonly envelope: MessageEnvelope;
69
+ constructor(message: string, envelope: MessageEnvelope);
70
+ }
71
+ /**
72
+ * Error thrown when tenant is not in allowlist.
73
+ */
74
+ declare class TenantNotAllowedError extends Error {
75
+ readonly tenantId: string;
76
+ readonly envelope: MessageEnvelope;
77
+ constructor(tenantId: string, envelope: MessageEnvelope);
78
+ }
79
+
80
+ /**
81
+ * Create multi-tenant middleware for saga-bus.
82
+ *
83
+ * Resolves tenant ID from messages and provides tenant context
84
+ * via AsyncLocalStorage for use within handlers.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * const middleware = createTenantMiddleware({
89
+ * strategy: "header",
90
+ * headerName: "x-tenant-id",
91
+ * allowedTenants: ["tenant1", "tenant2"],
92
+ * });
93
+ *
94
+ * const bus = createMessageBus({
95
+ * middleware: [middleware],
96
+ * });
97
+ * ```
98
+ */
99
+ declare function createTenantMiddleware(options?: TenantMiddlewareOptions): SagaMiddleware;
100
+ /**
101
+ * Create a publish interceptor that adds tenant ID to outbound messages.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const publisher = createTenantPublisher();
106
+ * const envelope = publisher(originalEnvelope, "tenant1");
107
+ * ```
108
+ */
109
+ declare function createTenantPublisher(options?: Pick<TenantMiddlewareOptions, "headerName">): (envelope: MessageEnvelope, tenantId: string) => MessageEnvelope;
110
+
111
+ /**
112
+ * Run a function within a tenant context.
113
+ */
114
+ declare function runWithTenant<T>(tenantInfo: TenantInfo, fn: () => T | Promise<T>): T | Promise<T>;
115
+ /**
116
+ * Get the current tenant ID.
117
+ * Returns undefined if not in a tenant context.
118
+ */
119
+ declare function getTenantId(): string | undefined;
120
+ /**
121
+ * Get the full tenant info for the current context.
122
+ */
123
+ declare function getTenantInfo(): TenantInfo | undefined;
124
+ /**
125
+ * Get the current tenant ID or throw if not available.
126
+ */
127
+ declare function requireTenantId(): string;
128
+
129
+ export { type TenantInfo, type TenantMiddlewareOptions, TenantNotAllowedError, TenantResolutionError, type TenantResolver, createTenantMiddleware, createTenantPublisher, getTenantId, getTenantInfo, requireTenantId, runWithTenant };
@@ -0,0 +1,129 @@
1
+ import { MessageEnvelope, SagaMiddleware } from '@saga-bus/core';
2
+
3
+ /**
4
+ * Tenant middleware configuration options.
5
+ */
6
+ interface TenantMiddlewareOptions {
7
+ /**
8
+ * Strategy for resolving tenant ID from messages.
9
+ * @default "header"
10
+ */
11
+ strategy?: "header" | "correlation-prefix" | "custom";
12
+ /**
13
+ * Custom tenant resolver function.
14
+ * Required when strategy is "custom".
15
+ */
16
+ resolver?: TenantResolver;
17
+ /**
18
+ * Header name for tenant ID when using "header" strategy.
19
+ * @default "x-tenant-id"
20
+ */
21
+ headerName?: string;
22
+ /**
23
+ * Separator for correlation prefix strategy.
24
+ * @default ":"
25
+ */
26
+ correlationSeparator?: string;
27
+ /**
28
+ * Whether to require tenant ID on all messages.
29
+ * @default true
30
+ */
31
+ required?: boolean;
32
+ /**
33
+ * Default tenant ID when not required and not found.
34
+ */
35
+ defaultTenantId?: string;
36
+ /**
37
+ * Allowlist of valid tenant IDs.
38
+ * If provided, messages from unknown tenants are rejected.
39
+ */
40
+ allowedTenants?: string[];
41
+ /**
42
+ * Whether to prefix saga IDs with tenant ID for isolation.
43
+ * @default true
44
+ */
45
+ prefixSagaId?: boolean;
46
+ }
47
+ /**
48
+ * Function to resolve tenant ID from a message.
49
+ */
50
+ type TenantResolver = (envelope: MessageEnvelope) => string | undefined;
51
+ /**
52
+ * Tenant context for the current request.
53
+ */
54
+ interface TenantInfo {
55
+ /**
56
+ * Resolved tenant ID.
57
+ */
58
+ tenantId: string;
59
+ /**
60
+ * Original saga ID before tenant prefix.
61
+ */
62
+ originalSagaId?: string;
63
+ }
64
+ /**
65
+ * Error thrown when tenant resolution fails.
66
+ */
67
+ declare class TenantResolutionError extends Error {
68
+ readonly envelope: MessageEnvelope;
69
+ constructor(message: string, envelope: MessageEnvelope);
70
+ }
71
+ /**
72
+ * Error thrown when tenant is not in allowlist.
73
+ */
74
+ declare class TenantNotAllowedError extends Error {
75
+ readonly tenantId: string;
76
+ readonly envelope: MessageEnvelope;
77
+ constructor(tenantId: string, envelope: MessageEnvelope);
78
+ }
79
+
80
+ /**
81
+ * Create multi-tenant middleware for saga-bus.
82
+ *
83
+ * Resolves tenant ID from messages and provides tenant context
84
+ * via AsyncLocalStorage for use within handlers.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * const middleware = createTenantMiddleware({
89
+ * strategy: "header",
90
+ * headerName: "x-tenant-id",
91
+ * allowedTenants: ["tenant1", "tenant2"],
92
+ * });
93
+ *
94
+ * const bus = createMessageBus({
95
+ * middleware: [middleware],
96
+ * });
97
+ * ```
98
+ */
99
+ declare function createTenantMiddleware(options?: TenantMiddlewareOptions): SagaMiddleware;
100
+ /**
101
+ * Create a publish interceptor that adds tenant ID to outbound messages.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const publisher = createTenantPublisher();
106
+ * const envelope = publisher(originalEnvelope, "tenant1");
107
+ * ```
108
+ */
109
+ declare function createTenantPublisher(options?: Pick<TenantMiddlewareOptions, "headerName">): (envelope: MessageEnvelope, tenantId: string) => MessageEnvelope;
110
+
111
+ /**
112
+ * Run a function within a tenant context.
113
+ */
114
+ declare function runWithTenant<T>(tenantInfo: TenantInfo, fn: () => T | Promise<T>): T | Promise<T>;
115
+ /**
116
+ * Get the current tenant ID.
117
+ * Returns undefined if not in a tenant context.
118
+ */
119
+ declare function getTenantId(): string | undefined;
120
+ /**
121
+ * Get the full tenant info for the current context.
122
+ */
123
+ declare function getTenantInfo(): TenantInfo | undefined;
124
+ /**
125
+ * Get the current tenant ID or throw if not available.
126
+ */
127
+ declare function requireTenantId(): string;
128
+
129
+ export { type TenantInfo, type TenantMiddlewareOptions, TenantNotAllowedError, TenantResolutionError, type TenantResolver, createTenantMiddleware, createTenantPublisher, getTenantId, getTenantInfo, requireTenantId, runWithTenant };
package/dist/index.js ADDED
@@ -0,0 +1,124 @@
1
+ // src/types.ts
2
+ var TenantResolutionError = class extends Error {
3
+ constructor(message, envelope) {
4
+ super(message);
5
+ this.envelope = envelope;
6
+ this.name = "TenantResolutionError";
7
+ }
8
+ };
9
+ var TenantNotAllowedError = class extends Error {
10
+ constructor(tenantId, envelope) {
11
+ super(`Tenant "${tenantId}" is not allowed`);
12
+ this.tenantId = tenantId;
13
+ this.envelope = envelope;
14
+ this.name = "TenantNotAllowedError";
15
+ }
16
+ };
17
+
18
+ // src/TenantContext.ts
19
+ import { AsyncLocalStorage } from "async_hooks";
20
+ var tenantStorage = new AsyncLocalStorage();
21
+ function runWithTenant(tenantInfo, fn) {
22
+ return tenantStorage.run(tenantInfo, fn);
23
+ }
24
+ function getTenantId() {
25
+ return tenantStorage.getStore()?.tenantId;
26
+ }
27
+ function getTenantInfo() {
28
+ return tenantStorage.getStore();
29
+ }
30
+ function requireTenantId() {
31
+ const tenantId = getTenantId();
32
+ if (!tenantId) {
33
+ throw new Error("No tenant context available");
34
+ }
35
+ return tenantId;
36
+ }
37
+
38
+ // src/TenantMiddleware.ts
39
+ function createTenantMiddleware(options = {}) {
40
+ const strategy = options.strategy ?? "header";
41
+ const headerName = options.headerName ?? "x-tenant-id";
42
+ const correlationSeparator = options.correlationSeparator ?? ":";
43
+ const required = options.required ?? true;
44
+ const defaultTenantId = options.defaultTenantId;
45
+ const allowedTenants = options.allowedTenants ? new Set(options.allowedTenants) : void 0;
46
+ const prefixSagaId = options.prefixSagaId ?? true;
47
+ const resolver = options.resolver ?? createResolver(strategy, {
48
+ headerName,
49
+ correlationSeparator
50
+ });
51
+ return async (ctx, next) => {
52
+ const { envelope } = ctx;
53
+ let tenantId = resolver(envelope);
54
+ if (!tenantId) {
55
+ if (required) {
56
+ throw new TenantResolutionError(
57
+ `Could not resolve tenant ID from message using strategy "${strategy}"`,
58
+ envelope
59
+ );
60
+ }
61
+ tenantId = defaultTenantId;
62
+ }
63
+ if (!tenantId) {
64
+ await next();
65
+ return;
66
+ }
67
+ if (allowedTenants && !allowedTenants.has(tenantId)) {
68
+ throw new TenantNotAllowedError(tenantId, envelope);
69
+ }
70
+ let originalSagaId;
71
+ if (prefixSagaId && ctx.sagaId) {
72
+ originalSagaId = ctx.sagaId;
73
+ ctx.sagaId = `${tenantId}:${ctx.sagaId}`;
74
+ }
75
+ await runWithTenant({ tenantId, originalSagaId }, async () => {
76
+ await next();
77
+ });
78
+ };
79
+ }
80
+ function createTenantPublisher(options = {}) {
81
+ const headerName = options.headerName ?? "x-tenant-id";
82
+ return (envelope, tenantId) => {
83
+ return {
84
+ ...envelope,
85
+ headers: {
86
+ ...envelope.headers,
87
+ [headerName]: tenantId
88
+ }
89
+ };
90
+ };
91
+ }
92
+ function createResolver(strategy, options) {
93
+ switch (strategy) {
94
+ case "header":
95
+ return (envelope) => {
96
+ return envelope.headers[options.headerName];
97
+ };
98
+ case "correlation-prefix":
99
+ return (envelope) => {
100
+ const partitionKey = envelope.partitionKey;
101
+ if (!partitionKey) {
102
+ return void 0;
103
+ }
104
+ const separatorIndex = partitionKey.indexOf(options.correlationSeparator);
105
+ if (separatorIndex === -1) {
106
+ return void 0;
107
+ }
108
+ return partitionKey.slice(0, separatorIndex);
109
+ };
110
+ case "custom":
111
+ throw new Error("Custom strategy requires a resolver function");
112
+ }
113
+ }
114
+ export {
115
+ TenantNotAllowedError,
116
+ TenantResolutionError,
117
+ createTenantMiddleware,
118
+ createTenantPublisher,
119
+ getTenantId,
120
+ getTenantInfo,
121
+ requireTenantId,
122
+ runWithTenant
123
+ };
124
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/TenantContext.ts","../src/TenantMiddleware.ts"],"sourcesContent":["import type { MessageEnvelope } from \"@saga-bus/core\";\n\n/**\n * Tenant middleware configuration options.\n */\nexport interface TenantMiddlewareOptions {\n /**\n * Strategy for resolving tenant ID from messages.\n * @default \"header\"\n */\n strategy?: \"header\" | \"correlation-prefix\" | \"custom\";\n\n /**\n * Custom tenant resolver function.\n * Required when strategy is \"custom\".\n */\n resolver?: TenantResolver;\n\n /**\n * Header name for tenant ID when using \"header\" strategy.\n * @default \"x-tenant-id\"\n */\n headerName?: string;\n\n /**\n * Separator for correlation prefix strategy.\n * @default \":\"\n */\n correlationSeparator?: string;\n\n /**\n * Whether to require tenant ID on all messages.\n * @default true\n */\n required?: boolean;\n\n /**\n * Default tenant ID when not required and not found.\n */\n defaultTenantId?: string;\n\n /**\n * Allowlist of valid tenant IDs.\n * If provided, messages from unknown tenants are rejected.\n */\n allowedTenants?: string[];\n\n /**\n * Whether to prefix saga IDs with tenant ID for isolation.\n * @default true\n */\n prefixSagaId?: boolean;\n}\n\n/**\n * Function to resolve tenant ID from a message.\n */\nexport type TenantResolver = (envelope: MessageEnvelope) => string | undefined;\n\n/**\n * Tenant context for the current request.\n */\nexport interface TenantInfo {\n /**\n * Resolved tenant ID.\n */\n tenantId: string;\n\n /**\n * Original saga ID before tenant prefix.\n */\n originalSagaId?: string;\n}\n\n/**\n * Error thrown when tenant resolution fails.\n */\nexport class TenantResolutionError extends Error {\n constructor(\n message: string,\n public readonly envelope: MessageEnvelope\n ) {\n super(message);\n this.name = \"TenantResolutionError\";\n }\n}\n\n/**\n * Error thrown when tenant is not in allowlist.\n */\nexport class TenantNotAllowedError extends Error {\n constructor(\n public readonly tenantId: string,\n public readonly envelope: MessageEnvelope\n ) {\n super(`Tenant \"${tenantId}\" is not allowed`);\n this.name = \"TenantNotAllowedError\";\n }\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { TenantInfo } from \"./types.js\";\n\nconst tenantStorage = new AsyncLocalStorage<TenantInfo>();\n\n/**\n * Run a function within a tenant context.\n */\nexport function runWithTenant<T>(\n tenantInfo: TenantInfo,\n fn: () => T | Promise<T>\n): T | Promise<T> {\n return tenantStorage.run(tenantInfo, fn);\n}\n\n/**\n * Get the current tenant ID.\n * Returns undefined if not in a tenant context.\n */\nexport function getTenantId(): string | undefined {\n return tenantStorage.getStore()?.tenantId;\n}\n\n/**\n * Get the full tenant info for the current context.\n */\nexport function getTenantInfo(): TenantInfo | undefined {\n return tenantStorage.getStore();\n}\n\n/**\n * Get the current tenant ID or throw if not available.\n */\nexport function requireTenantId(): string {\n const tenantId = getTenantId();\n if (!tenantId) {\n throw new Error(\"No tenant context available\");\n }\n return tenantId;\n}\n","import type {\n SagaMiddleware,\n SagaPipelineContext,\n MessageEnvelope,\n} from \"@saga-bus/core\";\nimport type { TenantMiddlewareOptions, TenantResolver } from \"./types.js\";\nimport { TenantResolutionError, TenantNotAllowedError } from \"./types.js\";\nimport { runWithTenant } from \"./TenantContext.js\";\n\n/**\n * Create multi-tenant middleware for saga-bus.\n *\n * Resolves tenant ID from messages and provides tenant context\n * via AsyncLocalStorage for use within handlers.\n *\n * @example\n * ```typescript\n * const middleware = createTenantMiddleware({\n * strategy: \"header\",\n * headerName: \"x-tenant-id\",\n * allowedTenants: [\"tenant1\", \"tenant2\"],\n * });\n *\n * const bus = createMessageBus({\n * middleware: [middleware],\n * });\n * ```\n */\nexport function createTenantMiddleware(\n options: TenantMiddlewareOptions = {}\n): SagaMiddleware {\n const strategy = options.strategy ?? \"header\";\n const headerName = options.headerName ?? \"x-tenant-id\";\n const correlationSeparator = options.correlationSeparator ?? \":\";\n const required = options.required ?? true;\n const defaultTenantId = options.defaultTenantId;\n const allowedTenants = options.allowedTenants\n ? new Set(options.allowedTenants)\n : undefined;\n const prefixSagaId = options.prefixSagaId ?? true;\n\n const resolver =\n options.resolver ??\n createResolver(strategy, {\n headerName,\n correlationSeparator,\n });\n\n return async (ctx: SagaPipelineContext, next: () => Promise<void>) => {\n const { envelope } = ctx;\n\n // Resolve tenant ID\n let tenantId = resolver(envelope);\n\n if (!tenantId) {\n if (required) {\n throw new TenantResolutionError(\n `Could not resolve tenant ID from message using strategy \"${strategy}\"`,\n envelope\n );\n }\n tenantId = defaultTenantId;\n }\n\n if (!tenantId) {\n // No tenant and not required - proceed without tenant context\n await next();\n return;\n }\n\n // Validate against allowlist\n if (allowedTenants && !allowedTenants.has(tenantId)) {\n throw new TenantNotAllowedError(tenantId, envelope);\n }\n\n // Modify saga ID if needed\n let originalSagaId: string | undefined;\n if (prefixSagaId && ctx.sagaId) {\n originalSagaId = ctx.sagaId;\n ctx.sagaId = `${tenantId}:${ctx.sagaId}`;\n }\n\n // Run handler within tenant context\n await runWithTenant({ tenantId, originalSagaId }, async () => {\n await next();\n });\n };\n}\n\n/**\n * Create a publish interceptor that adds tenant ID to outbound messages.\n *\n * @example\n * ```typescript\n * const publisher = createTenantPublisher();\n * const envelope = publisher(originalEnvelope, \"tenant1\");\n * ```\n */\nexport function createTenantPublisher(\n options: Pick<TenantMiddlewareOptions, \"headerName\"> = {}\n): (envelope: MessageEnvelope, tenantId: string) => MessageEnvelope {\n const headerName = options.headerName ?? \"x-tenant-id\";\n\n return (envelope: MessageEnvelope, tenantId: string): MessageEnvelope => {\n return {\n ...envelope,\n headers: {\n ...envelope.headers,\n [headerName]: tenantId,\n },\n };\n };\n}\n\nfunction createResolver(\n strategy: \"header\" | \"correlation-prefix\" | \"custom\",\n options: {\n headerName: string;\n correlationSeparator: string;\n }\n): TenantResolver {\n switch (strategy) {\n case \"header\":\n return (envelope: MessageEnvelope) => {\n return envelope.headers[options.headerName];\n };\n\n case \"correlation-prefix\":\n return (envelope: MessageEnvelope) => {\n const partitionKey = envelope.partitionKey;\n if (!partitionKey) {\n return undefined;\n }\n const separatorIndex = partitionKey.indexOf(options.correlationSeparator);\n if (separatorIndex === -1) {\n return undefined;\n }\n return partitionKey.slice(0, separatorIndex);\n };\n\n case \"custom\":\n throw new Error(\"Custom strategy requires a resolver function\");\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@saga-bus/middleware-tenant",
3
+ "version": "0.1.1",
4
+ "description": "Multi-tenant middleware for saga-bus",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/d-e-a-n-f/saga-bus.git",
26
+ "directory": "packages/middleware-tenant"
27
+ },
28
+ "keywords": [
29
+ "saga",
30
+ "message-bus",
31
+ "middleware",
32
+ "multi-tenant",
33
+ "tenant"
34
+ ],
35
+ "dependencies": {
36
+ "@saga-bus/core": "0.1.1"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^20.0.0",
40
+ "tsup": "^8.0.0",
41
+ "typescript": "^5.9.2",
42
+ "vitest": "^3.0.0",
43
+ "@repo/eslint-config": "0.0.0",
44
+ "@repo/typescript-config": "0.0.0"
45
+ },
46
+ "peerDependencies": {
47
+ "@saga-bus/core": ">=0.1.1"
48
+ },
49
+ "scripts": {
50
+ "build": "tsup",
51
+ "dev": "tsup --watch",
52
+ "lint": "eslint src/",
53
+ "check-types": "tsc --noEmit",
54
+ "test": "vitest run",
55
+ "test:watch": "vitest"
56
+ }
57
+ }