@nebutra/tenant 0.1.0 → 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.
@@ -0,0 +1,211 @@
1
+ import React from "react";
2
+ import type { TenantContext } from "./types";
3
+ /**
4
+ * Provider component that wraps your app with tenant context.
5
+ *
6
+ * @param children Child components to wrap
7
+ * @param value The tenant context value (usually from server)
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * // layout.tsx
12
+ * import { TenantProvider } from "@nebutra/tenant/react";
13
+ * import { getTenantContext } from "@/lib/tenant-server";
14
+ *
15
+ * export default async function RootLayout() {
16
+ * const tenant = await getTenantContext();
17
+ *
18
+ * return (
19
+ * <TenantProvider value={tenant}>
20
+ * <MainApp />
21
+ * </TenantProvider>
22
+ * );
23
+ * }
24
+ * ```
25
+ */
26
+ export declare function TenantProvider({ children, value, }: {
27
+ children: React.ReactNode;
28
+ value: TenantContext;
29
+ }): React.FunctionComponentElement<React.ProviderProps<{
30
+ id: string;
31
+ slug?: string | undefined;
32
+ plan?: "free" | "pro" | "enterprise" | undefined;
33
+ features?: string[] | undefined;
34
+ limits?: Record<string, number> | undefined;
35
+ metadata?: Record<string, unknown> | undefined;
36
+ } | null>>;
37
+ /**
38
+ * Hook to access the current tenant context from client-side components.
39
+ *
40
+ * Throws an error if no tenant context is available.
41
+ * Use `useTenantOrNull()` for optional tenant contexts.
42
+ *
43
+ * @returns The current tenant context
44
+ * @throws TenantRequiredError if tenant context is not available
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * function MyComponent() {
49
+ * const tenant = useTenant();
50
+ *
51
+ * return <div>Current tenant: {tenant.id}</div>;
52
+ * }
53
+ * ```
54
+ */
55
+ export declare function useTenant(): TenantContext;
56
+ /**
57
+ * Hook to access the current tenant context or null if not available.
58
+ *
59
+ * Useful for components that work in both tenant and non-tenant contexts
60
+ * (e.g., public pages, webhooks, etc).
61
+ *
62
+ * @returns The current tenant context, or null
63
+ *
64
+ * @example
65
+ * ```tsx
66
+ * function MyComponent() {
67
+ * const tenant = useTenantOrNull();
68
+ *
69
+ * if (!tenant) {
70
+ * return <div>Public content</div>;
71
+ * }
72
+ *
73
+ * return <div>Tenant-specific content for {tenant.id}</div>;
74
+ * }
75
+ * ```
76
+ */
77
+ export declare function useTenantOrNull(): TenantContext | null;
78
+ /**
79
+ * Hook to get just the tenant ID from context.
80
+ *
81
+ * Shorthand for `useTenant().id`.
82
+ *
83
+ * @returns The current tenant's ID
84
+ * @throws TenantRequiredError if tenant context is not available
85
+ *
86
+ * @example
87
+ * ```tsx
88
+ * function MyComponent() {
89
+ * const tenantId = useTenantId();
90
+ *
91
+ * return <div>Tenant: {tenantId}</div>;
92
+ * }
93
+ * ```
94
+ */
95
+ export declare function useTenantId(): string;
96
+ /**
97
+ * Hook to get just the tenant ID, or null if not available.
98
+ *
99
+ * @returns The current tenant's ID, or null
100
+ *
101
+ * @example
102
+ * ```tsx
103
+ * function MyComponent() {
104
+ * const tenantId = useTenantIdOrNull();
105
+ * return <div>{tenantId ? `Tenant: ${tenantId}` : "Public"}</div>;
106
+ * }
107
+ * ```
108
+ */
109
+ export declare function useTenantIdOrNull(): string | null;
110
+ /**
111
+ * Hook to get the tenant's plan tier.
112
+ *
113
+ * @returns The current tenant's plan ("free", "pro", "enterprise"), or undefined
114
+ *
115
+ * @example
116
+ * ```tsx
117
+ * function PremiumFeature() {
118
+ * const plan = useTenantPlan();
119
+ *
120
+ * if (plan === "free") {
121
+ * return <UpgradePrompt />;
122
+ * }
123
+ *
124
+ * return <Feature />;
125
+ * }
126
+ * ```
127
+ */
128
+ export declare function useTenantPlan(): "free" | "pro" | "enterprise" | undefined;
129
+ /**
130
+ * Hook to check if a specific feature is enabled for the tenant.
131
+ *
132
+ * @param feature The feature flag name
133
+ * @returns Whether the feature is enabled
134
+ *
135
+ * @example
136
+ * ```tsx
137
+ * function DashboardPage() {
138
+ * const hasAdvancedAnalytics = useTenantFeature("advanced_analytics");
139
+ *
140
+ * return (
141
+ * <>
142
+ * {hasAdvancedAnalytics && <AdvancedAnalytics />}
143
+ * </>
144
+ * );
145
+ * }
146
+ * ```
147
+ */
148
+ export declare function useTenantFeature(feature: string): boolean;
149
+ /**
150
+ * Hook to get a rate limit or quota for the tenant.
151
+ *
152
+ * @param limitName The limit name (e.g., "requests_per_minute", "storage_gb")
153
+ * @param defaultValue Default value if limit is not set
154
+ * @returns The limit value, or default if not set
155
+ *
156
+ * @example
157
+ * ```tsx
158
+ * function ApiDashboard() {
159
+ * const rateLimit = useTenantLimit("requests_per_minute", 100);
160
+ *
161
+ * return <div>Rate limit: {rateLimit} req/min</div>;
162
+ * }
163
+ * ```
164
+ */
165
+ export declare function useTenantLimit(limitName: string, defaultValue?: number): number | undefined;
166
+ /**
167
+ * Higher-order component that requires tenant context.
168
+ *
169
+ * Wraps a component and throws an error if no tenant context is available.
170
+ * Useful as a safety check for tenant-specific features.
171
+ *
172
+ * @param Component The component to wrap
173
+ * @param errorFallback Optional fallback component if tenant is missing
174
+ * @returns A wrapped component
175
+ *
176
+ * @example
177
+ * ```tsx
178
+ * function DashboardContent() {
179
+ * const tenantId = useTenantId();
180
+ * return <div>Dashboard for {tenantId}</div>;
181
+ * }
182
+ *
183
+ * export default withTenantGuard(DashboardContent);
184
+ * ```
185
+ */
186
+ export declare function withTenantGuard<P extends object>(Component: React.ComponentType<P>, errorFallback?: React.ComponentType<{
187
+ error: Error;
188
+ }>): {
189
+ (props: P): React.ReactElement<P, string | React.JSXElementConstructor<any>> | React.ReactElement<{
190
+ error: Error;
191
+ }, string | React.JSXElementConstructor<any>>;
192
+ displayName: string;
193
+ };
194
+ /**
195
+ * Component that renders children only if tenant context is available.
196
+ *
197
+ * @param children Children to render if tenant is available
198
+ * @param fallback Optional fallback if tenant is missing
199
+ *
200
+ * @example
201
+ * ```tsx
202
+ * <TenantBoundary fallback={<PublicPage />}>
203
+ * <DashboardPage />
204
+ * </TenantBoundary>
205
+ * ```
206
+ */
207
+ export declare function TenantBoundary({ children, fallback, }: {
208
+ children: React.ReactNode;
209
+ fallback?: React.ReactNode;
210
+ }): React.FunctionComponentElement<React.FragmentProps>;
211
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAEA,OAAO,KAAoC,MAAM,OAAO,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAe7C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,EAAE,aAAa,CAAC;CACtB;;;;;;;WAMA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,IAAI,aAAa,CAUzC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,IAAI,aAAa,GAAG,IAAI,CAEtD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAGpC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAEjD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,8CAG5B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG3F;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAC9C,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,EACjC,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;YAEpB,CAAC;eAFW,KAAK;;;EAqBnD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,uDAYA"}
package/dist/react.js ADDED
@@ -0,0 +1,252 @@
1
+ "use client";
2
+ import React, { createContext, useContext } from "react";
3
+ import { TenantRequiredError } from "./types";
4
+ // =============================================================================
5
+ // React Context for Tenant — Client-side multi-tenancy
6
+ // =============================================================================
7
+ /**
8
+ * React context that provides tenant information to client-side components.
9
+ *
10
+ * Usually populated by middleware that reads the tenant from request headers
11
+ * or URL and passes it as a prop to the Next.js layout or page component.
12
+ */
13
+ const TenantContextValue = createContext(null);
14
+ /**
15
+ * Provider component that wraps your app with tenant context.
16
+ *
17
+ * @param children Child components to wrap
18
+ * @param value The tenant context value (usually from server)
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * // layout.tsx
23
+ * import { TenantProvider } from "@nebutra/tenant/react";
24
+ * import { getTenantContext } from "@/lib/tenant-server";
25
+ *
26
+ * export default async function RootLayout() {
27
+ * const tenant = await getTenantContext();
28
+ *
29
+ * return (
30
+ * <TenantProvider value={tenant}>
31
+ * <MainApp />
32
+ * </TenantProvider>
33
+ * );
34
+ * }
35
+ * ```
36
+ */
37
+ export function TenantProvider({ children, value, }) {
38
+ if (!value || !value.id) {
39
+ console.warn("TenantProvider: No tenant value provided");
40
+ }
41
+ return React.createElement(TenantContextValue.Provider, { value: value ?? null }, children);
42
+ }
43
+ /**
44
+ * Hook to access the current tenant context from client-side components.
45
+ *
46
+ * Throws an error if no tenant context is available.
47
+ * Use `useTenantOrNull()` for optional tenant contexts.
48
+ *
49
+ * @returns The current tenant context
50
+ * @throws TenantRequiredError if tenant context is not available
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * function MyComponent() {
55
+ * const tenant = useTenant();
56
+ *
57
+ * return <div>Current tenant: {tenant.id}</div>;
58
+ * }
59
+ * ```
60
+ */
61
+ export function useTenant() {
62
+ const context = useContext(TenantContextValue);
63
+ if (!context) {
64
+ throw new TenantRequiredError("useTenant must be used within a TenantProvider that has a tenant value");
65
+ }
66
+ return context;
67
+ }
68
+ /**
69
+ * Hook to access the current tenant context or null if not available.
70
+ *
71
+ * Useful for components that work in both tenant and non-tenant contexts
72
+ * (e.g., public pages, webhooks, etc).
73
+ *
74
+ * @returns The current tenant context, or null
75
+ *
76
+ * @example
77
+ * ```tsx
78
+ * function MyComponent() {
79
+ * const tenant = useTenantOrNull();
80
+ *
81
+ * if (!tenant) {
82
+ * return <div>Public content</div>;
83
+ * }
84
+ *
85
+ * return <div>Tenant-specific content for {tenant.id}</div>;
86
+ * }
87
+ * ```
88
+ */
89
+ export function useTenantOrNull() {
90
+ return useContext(TenantContextValue);
91
+ }
92
+ /**
93
+ * Hook to get just the tenant ID from context.
94
+ *
95
+ * Shorthand for `useTenant().id`.
96
+ *
97
+ * @returns The current tenant's ID
98
+ * @throws TenantRequiredError if tenant context is not available
99
+ *
100
+ * @example
101
+ * ```tsx
102
+ * function MyComponent() {
103
+ * const tenantId = useTenantId();
104
+ *
105
+ * return <div>Tenant: {tenantId}</div>;
106
+ * }
107
+ * ```
108
+ */
109
+ export function useTenantId() {
110
+ const tenant = useTenant();
111
+ return tenant.id;
112
+ }
113
+ /**
114
+ * Hook to get just the tenant ID, or null if not available.
115
+ *
116
+ * @returns The current tenant's ID, or null
117
+ *
118
+ * @example
119
+ * ```tsx
120
+ * function MyComponent() {
121
+ * const tenantId = useTenantIdOrNull();
122
+ * return <div>{tenantId ? `Tenant: ${tenantId}` : "Public"}</div>;
123
+ * }
124
+ * ```
125
+ */
126
+ export function useTenantIdOrNull() {
127
+ return useTenantOrNull()?.id ?? null;
128
+ }
129
+ /**
130
+ * Hook to get the tenant's plan tier.
131
+ *
132
+ * @returns The current tenant's plan ("free", "pro", "enterprise"), or undefined
133
+ *
134
+ * @example
135
+ * ```tsx
136
+ * function PremiumFeature() {
137
+ * const plan = useTenantPlan();
138
+ *
139
+ * if (plan === "free") {
140
+ * return <UpgradePrompt />;
141
+ * }
142
+ *
143
+ * return <Feature />;
144
+ * }
145
+ * ```
146
+ */
147
+ export function useTenantPlan() {
148
+ const tenant = useTenant();
149
+ return tenant.plan;
150
+ }
151
+ /**
152
+ * Hook to check if a specific feature is enabled for the tenant.
153
+ *
154
+ * @param feature The feature flag name
155
+ * @returns Whether the feature is enabled
156
+ *
157
+ * @example
158
+ * ```tsx
159
+ * function DashboardPage() {
160
+ * const hasAdvancedAnalytics = useTenantFeature("advanced_analytics");
161
+ *
162
+ * return (
163
+ * <>
164
+ * {hasAdvancedAnalytics && <AdvancedAnalytics />}
165
+ * </>
166
+ * );
167
+ * }
168
+ * ```
169
+ */
170
+ export function useTenantFeature(feature) {
171
+ const tenant = useTenant();
172
+ return tenant.features?.includes(feature) ?? false;
173
+ }
174
+ /**
175
+ * Hook to get a rate limit or quota for the tenant.
176
+ *
177
+ * @param limitName The limit name (e.g., "requests_per_minute", "storage_gb")
178
+ * @param defaultValue Default value if limit is not set
179
+ * @returns The limit value, or default if not set
180
+ *
181
+ * @example
182
+ * ```tsx
183
+ * function ApiDashboard() {
184
+ * const rateLimit = useTenantLimit("requests_per_minute", 100);
185
+ *
186
+ * return <div>Rate limit: {rateLimit} req/min</div>;
187
+ * }
188
+ * ```
189
+ */
190
+ export function useTenantLimit(limitName, defaultValue) {
191
+ const tenant = useTenant();
192
+ return tenant.limits?.[limitName] ?? defaultValue;
193
+ }
194
+ /**
195
+ * Higher-order component that requires tenant context.
196
+ *
197
+ * Wraps a component and throws an error if no tenant context is available.
198
+ * Useful as a safety check for tenant-specific features.
199
+ *
200
+ * @param Component The component to wrap
201
+ * @param errorFallback Optional fallback component if tenant is missing
202
+ * @returns A wrapped component
203
+ *
204
+ * @example
205
+ * ```tsx
206
+ * function DashboardContent() {
207
+ * const tenantId = useTenantId();
208
+ * return <div>Dashboard for {tenantId}</div>;
209
+ * }
210
+ *
211
+ * export default withTenantGuard(DashboardContent);
212
+ * ```
213
+ */
214
+ export function withTenantGuard(Component, errorFallback) {
215
+ const WrappedComponent = (props) => {
216
+ try {
217
+ // Verify tenant context is available by calling hook
218
+ useTenant();
219
+ return React.createElement(Component, props);
220
+ }
221
+ catch (err) {
222
+ if (errorFallback) {
223
+ const ErrorComponent = errorFallback;
224
+ return React.createElement(ErrorComponent, { error: err });
225
+ }
226
+ // Re-throw if no fallback provided
227
+ throw err;
228
+ }
229
+ };
230
+ WrappedComponent.displayName = `withTenantGuard(${Component.displayName || Component.name})`;
231
+ return WrappedComponent;
232
+ }
233
+ /**
234
+ * Component that renders children only if tenant context is available.
235
+ *
236
+ * @param children Children to render if tenant is available
237
+ * @param fallback Optional fallback if tenant is missing
238
+ *
239
+ * @example
240
+ * ```tsx
241
+ * <TenantBoundary fallback={<PublicPage />}>
242
+ * <DashboardPage />
243
+ * </TenantBoundary>
244
+ * ```
245
+ */
246
+ export function TenantBoundary({ children, fallback, }) {
247
+ const tenant = useTenantOrNull();
248
+ if (!tenant) {
249
+ return React.createElement(React.Fragment, null, fallback ?? React.createElement("div", null, "No tenant context available"));
250
+ }
251
+ return React.createElement(React.Fragment, null, children);
252
+ }
@@ -0,0 +1,48 @@
1
+ import type { TenantResolver } from "../types";
2
+ /**
3
+ * Minimal shape this resolver needs from a session object.
4
+ *
5
+ * Structurally compatible with `Session` from `@nebutra/auth` but defined
6
+ * locally so `@nebutra/tenant` stays IAM-neutral — there is no hard dep on
7
+ * `@nebutra/auth`. Callers pass in their own session getter (typically
8
+ * `(req) => auth.getSession(req)`), keeping the dependency direction one-way:
9
+ * apps depend on both packages; neither package depends on the other.
10
+ */
11
+ export interface AuthSessionLike {
12
+ organizationId?: string;
13
+ }
14
+ /**
15
+ * Function that retrieves an auth session from a resolver-input request.
16
+ *
17
+ * Receives the same request envelope as any other {@link TenantResolver}
18
+ * (`{ headers?, url?, token?, apiKey? }`). Returns either an `AuthSessionLike`
19
+ * or `null` when no session exists. May be sync or async.
20
+ */
21
+ export type SessionGetter = (req: Parameters<TenantResolver>[0]) => Promise<AuthSessionLike | null> | AuthSessionLike | null;
22
+ /**
23
+ * Resolve `tenantId` from an auth session's `organizationId`.
24
+ *
25
+ * This is the canonical bridge between `Session.organizationId` (from
26
+ * `@nebutra/auth`) and the tenant context. It is the standard second link in
27
+ * the resolver chain — after `fromHeader` (service-to-service) and before any
28
+ * fallback strategy.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { createAuth } from "@nebutra/auth/server";
33
+ * import { tenantMiddleware, compose, fromHeader, fromAuthSession } from "@nebutra/tenant";
34
+ *
35
+ * const auth = await createAuth({ provider: "better-auth" });
36
+ *
37
+ * app.use(
38
+ * tenantMiddleware({
39
+ * resolver: compose(
40
+ * fromHeader("x-tenant-id"), // service-to-service wins
41
+ * fromAuthSession(() => auth.getSession()), // then user-session
42
+ * ),
43
+ * }),
44
+ * );
45
+ * ```
46
+ */
47
+ export declare function fromAuthSession(getSession: SessionGetter): TenantResolver;
48
+ //# sourceMappingURL=from-auth-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"from-auth-session.d.ts","sourceRoot":"","sources":["../../src/resolvers/from-auth-session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAM/C;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,KAC/B,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,eAAe,GAAG,IAAI,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,aAAa,GAAG,cAAc,CAgBzE"}
@@ -0,0 +1,41 @@
1
+ import { logger } from "@nebutra/logger";
2
+ /**
3
+ * Resolve `tenantId` from an auth session's `organizationId`.
4
+ *
5
+ * This is the canonical bridge between `Session.organizationId` (from
6
+ * `@nebutra/auth`) and the tenant context. It is the standard second link in
7
+ * the resolver chain — after `fromHeader` (service-to-service) and before any
8
+ * fallback strategy.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { createAuth } from "@nebutra/auth/server";
13
+ * import { tenantMiddleware, compose, fromHeader, fromAuthSession } from "@nebutra/tenant";
14
+ *
15
+ * const auth = await createAuth({ provider: "better-auth" });
16
+ *
17
+ * app.use(
18
+ * tenantMiddleware({
19
+ * resolver: compose(
20
+ * fromHeader("x-tenant-id"), // service-to-service wins
21
+ * fromAuthSession(() => auth.getSession()), // then user-session
22
+ * ),
23
+ * }),
24
+ * );
25
+ * ```
26
+ */
27
+ export function fromAuthSession(getSession) {
28
+ return async (req) => {
29
+ // Intentionally NOT wrapped in try/catch — errors propagate to the
30
+ // caller (typically the `compose` chain, which logs + continues to the
31
+ // next resolver). Silently swallowing here would mask real auth bugs.
32
+ const session = await Promise.resolve(getSession(req));
33
+ if (session?.organizationId) {
34
+ logger.debug("Tenant resolved from auth session", {
35
+ tenantId: session.organizationId,
36
+ });
37
+ return session.organizationId;
38
+ }
39
+ return null;
40
+ };
41
+ }
@@ -0,0 +1,110 @@
1
+ import type { TenantResolver } from "./types";
2
+ /**
3
+ * Extract tenant ID from an HTTP header.
4
+ *
5
+ * Default strategy for API gateways. Header name is configurable.
6
+ *
7
+ * @param headerName The header name to look for (default: "x-tenant-id")
8
+ * @returns A resolver function
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const resolver = fromHeader("x-tenant-id");
13
+ * const tenantId = await resolver({ headers: { "x-tenant-id": "org_123" } });
14
+ * ```
15
+ */
16
+ export declare function fromHeader(headerName?: string): TenantResolver;
17
+ /**
18
+ * Extract tenant ID from a subdomain using a regex pattern.
19
+ *
20
+ * Useful for SaaS products with tenant subdomains (e.g., `acme.app.nebutra.com`).
21
+ *
22
+ * @param pattern A regex pattern with a capture group for tenant ID
23
+ * @returns A resolver function
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * // Extract "acme" from "acme.app.nebutra.com"
28
+ * const resolver = fromSubdomain("^([a-z0-9-]+)\\.app\\.nebutra\\.com$");
29
+ * const tenantId = await resolver({ url: "https://acme.app.nebutra.com/api" });
30
+ * ```
31
+ */
32
+ export declare function fromSubdomain(pattern: string): TenantResolver;
33
+ /**
34
+ * Extract tenant ID from a URL path prefix.
35
+ *
36
+ * Useful for multi-tenant apps with path-based routing (e.g., `/org/acme/...`).
37
+ *
38
+ * @param prefix The path prefix pattern (e.g., "/org/:tenantId" or "/org/([^/]+)")
39
+ * @returns A resolver function
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * // Extract tenant ID from "/org/acme/..."
44
+ * const resolver = fromPath("/org/:tenantId");
45
+ * const tenantId = await resolver({ url: "https://app.nebutra.com/org/acme/dashboard" });
46
+ * ```
47
+ */
48
+ export declare function fromPath(prefix: string): TenantResolver;
49
+ /**
50
+ * Extract tenant ID from a JWT token claim.
51
+ *
52
+ * Useful for authentication services that embed tenant info in the token.
53
+ *
54
+ * @param claimName The JWT claim name (e.g., "tenant_id", "org_id")
55
+ * @returns A resolver function
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * // Extract from decoded JWT { sub: "user_123", tenant_id: "org_456" }
60
+ * const resolver = fromJwtClaim("tenant_id");
61
+ * const tenantId = await resolver({ token: "eyJhbGc..." });
62
+ * ```
63
+ */
64
+ export declare function fromJwtClaim(claimName: string): TenantResolver;
65
+ /**
66
+ * Resolve tenant ID from an API key by looking it up in a function (e.g., database).
67
+ *
68
+ * Useful for service-to-service authentication or API clients.
69
+ *
70
+ * @param lookupFn Async function that takes an API key and returns the tenant ID
71
+ * @returns A resolver function
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * // Look up API key in database
76
+ * const resolver = fromApiKey(async (apiKey) => {
77
+ * const key = await db.apiKey.findUnique({ where: { key: apiKey } });
78
+ * return key?.tenantId ?? null;
79
+ * });
80
+ * const tenantId = await resolver({ apiKey: "sk_123" });
81
+ * ```
82
+ */
83
+ export declare function fromApiKey(lookupFn: (apiKey: string) => Promise<string | null>): TenantResolver;
84
+ /**
85
+ * Compose multiple tenant resolvers with fallback behavior.
86
+ *
87
+ * Tries each resolver in order; returns the first successful match.
88
+ * If all resolvers return null, returns null.
89
+ *
90
+ * @param resolvers Array of resolver functions
91
+ * @returns A composite resolver function
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * // Try header first, then subdomain, then path
96
+ * const resolver = compose(
97
+ * fromHeader("x-tenant-id"),
98
+ * fromSubdomain("^([a-z0-9-]+)\\.app\\.nebutra\\.com$"),
99
+ * fromPath("/org/:tenantId")
100
+ * );
101
+ *
102
+ * const tenantId = await resolver({
103
+ * headers: { "x-tenant-id": "org_123" },
104
+ * url: "https://acme.app.nebutra.com/org/xyz/dashboard"
105
+ * });
106
+ * // Returns "org_123" (first match)
107
+ * ```
108
+ */
109
+ export declare function compose(...resolvers: TenantResolver[]): TenantResolver;
110
+ //# sourceMappingURL=resolvers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAsD9C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,UAAU,GAAE,MAAsB,GAAG,cAAc,CAW7E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAsB7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAwBvD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAsB9D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,cAAc,CAiB/F;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,OAAO,CAAC,GAAG,SAAS,EAAE,cAAc,EAAE,GAAG,cAAc,CAiBtE"}