@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.
- package/dist/context.d.ts +87 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +125 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/isolation.d.ts +157 -0
- package/dist/isolation.d.ts.map +1 -0
- package/dist/isolation.js +319 -0
- package/dist/middleware.d.ts +106 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +247 -0
- package/dist/react.d.ts +211 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +252 -0
- package/dist/resolvers/from-auth-session.d.ts +48 -0
- package/dist/resolvers/from-auth-session.d.ts.map +1 -0
- package/dist/resolvers/from-auth-session.js +41 -0
- package/dist/resolvers.d.ts +110 -0
- package/dist/resolvers.d.ts.map +1 -0
- package/dist/resolvers.js +253 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +78 -0
- package/package.json +2 -2
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { TenantContext } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Execute a function with a tenant context set.
|
|
4
|
+
*
|
|
5
|
+
* All async operations within `fn` will have access to the same tenant context
|
|
6
|
+
* via `getCurrentTenant()` without passing it as a parameter.
|
|
7
|
+
*
|
|
8
|
+
* @param context The tenant context to set
|
|
9
|
+
* @param fn The function to execute
|
|
10
|
+
* @returns The return value of `fn`
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const result = await runWithTenant({ id: "org_123" }, async () => {
|
|
15
|
+
* const tenant = getCurrentTenant();
|
|
16
|
+
* await doSomething(tenant.id);
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function runWithTenant<T>(context: TenantContext, fn: () => Promise<T> | T): Promise<T>;
|
|
21
|
+
/**
|
|
22
|
+
* Get the current tenant context from AsyncLocalStorage.
|
|
23
|
+
*
|
|
24
|
+
* Throws `TenantRequiredError` if no tenant context is set.
|
|
25
|
+
*
|
|
26
|
+
* @throws TenantRequiredError if tenant context is not available
|
|
27
|
+
* @returns The current tenant context
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const tenant = getCurrentTenant();
|
|
32
|
+
* console.log(tenant.id); // "org_123"
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function getCurrentTenant(): TenantContext;
|
|
36
|
+
/**
|
|
37
|
+
* Get the current tenant context, or null if not set.
|
|
38
|
+
*
|
|
39
|
+
* Useful for optional tenant contexts (public routes, webhooks, etc).
|
|
40
|
+
*
|
|
41
|
+
* @returns The current tenant context, or null if not set
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* const tenant = getTenantOrNull();
|
|
46
|
+
* if (tenant) {
|
|
47
|
+
* console.log(`Processing for tenant: ${tenant.id}`);
|
|
48
|
+
* } else {
|
|
49
|
+
* console.log("Public route, no tenant");
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function getTenantOrNull(): TenantContext | null;
|
|
54
|
+
/**
|
|
55
|
+
* Assertion helper that ensures a tenant context is present.
|
|
56
|
+
*
|
|
57
|
+
* Throws a structured error if tenant is missing. Useful as a safety
|
|
58
|
+
* check at the start of tenant-specific handlers.
|
|
59
|
+
*
|
|
60
|
+
* @param tenant The tenant context (or null)
|
|
61
|
+
* @param context Optional context message for the error
|
|
62
|
+
* @throws TenantRequiredError if tenant is null or undefined
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const tenant = getTenantOrNull();
|
|
67
|
+
* requireTenant(tenant, "DELETE /api/documents/:id");
|
|
68
|
+
* // Safe to use tenant.id from here on
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare function requireTenant(tenant: TenantContext | null | undefined, context?: string): asserts tenant is TenantContext;
|
|
72
|
+
/**
|
|
73
|
+
* Get the current tenant ID as a shorthand.
|
|
74
|
+
*
|
|
75
|
+
* Throws if tenant context is not set.
|
|
76
|
+
*
|
|
77
|
+
* @returns The current tenant's ID
|
|
78
|
+
* @throws TenantRequiredError if tenant context is not available
|
|
79
|
+
*/
|
|
80
|
+
export declare function getCurrentTenantId(): string;
|
|
81
|
+
/**
|
|
82
|
+
* Get the current tenant ID, or null if not set.
|
|
83
|
+
*
|
|
84
|
+
* @returns The current tenant's ID, or null
|
|
85
|
+
*/
|
|
86
|
+
export declare function getTenantIdOrNull(): string | null;
|
|
87
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAW7C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAS7F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAWhD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,IAAI,aAAa,GAAG,IAAI,CAEtD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,EACxC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,IAAI,aAAa,CASjC;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAEjD"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { logger } from "@nebutra/logger";
|
|
3
|
+
import { TenantRequiredError } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* AsyncLocalStorage-based tenant context — request-scoped, zero-copy across async boundaries.
|
|
6
|
+
*
|
|
7
|
+
* Each incoming request gets its own isolated context, even across await boundaries.
|
|
8
|
+
* No risk of tenant data leaking between requests or threads.
|
|
9
|
+
*/
|
|
10
|
+
const tenantStorage = new AsyncLocalStorage();
|
|
11
|
+
/**
|
|
12
|
+
* Execute a function with a tenant context set.
|
|
13
|
+
*
|
|
14
|
+
* All async operations within `fn` will have access to the same tenant context
|
|
15
|
+
* via `getCurrentTenant()` without passing it as a parameter.
|
|
16
|
+
*
|
|
17
|
+
* @param context The tenant context to set
|
|
18
|
+
* @param fn The function to execute
|
|
19
|
+
* @returns The return value of `fn`
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const result = await runWithTenant({ id: "org_123" }, async () => {
|
|
24
|
+
* const tenant = getCurrentTenant();
|
|
25
|
+
* await doSomething(tenant.id);
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function runWithTenant(context, fn) {
|
|
30
|
+
return tenantStorage.run(context, () => {
|
|
31
|
+
// Support both sync and async functions
|
|
32
|
+
const result = fn();
|
|
33
|
+
if (result instanceof Promise) {
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
return Promise.resolve(result);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the current tenant context from AsyncLocalStorage.
|
|
41
|
+
*
|
|
42
|
+
* Throws `TenantRequiredError` if no tenant context is set.
|
|
43
|
+
*
|
|
44
|
+
* @throws TenantRequiredError if tenant context is not available
|
|
45
|
+
* @returns The current tenant context
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const tenant = getCurrentTenant();
|
|
50
|
+
* console.log(tenant.id); // "org_123"
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function getCurrentTenant() {
|
|
54
|
+
const context = tenantStorage.getStore();
|
|
55
|
+
if (!context) {
|
|
56
|
+
logger.warn("getCurrentTenant() called without active tenant context");
|
|
57
|
+
throw new TenantRequiredError("No tenant context found. Ensure middleware called runWithTenant().");
|
|
58
|
+
}
|
|
59
|
+
return context;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get the current tenant context, or null if not set.
|
|
63
|
+
*
|
|
64
|
+
* Useful for optional tenant contexts (public routes, webhooks, etc).
|
|
65
|
+
*
|
|
66
|
+
* @returns The current tenant context, or null if not set
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const tenant = getTenantOrNull();
|
|
71
|
+
* if (tenant) {
|
|
72
|
+
* console.log(`Processing for tenant: ${tenant.id}`);
|
|
73
|
+
* } else {
|
|
74
|
+
* console.log("Public route, no tenant");
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function getTenantOrNull() {
|
|
79
|
+
return tenantStorage.getStore() ?? null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Assertion helper that ensures a tenant context is present.
|
|
83
|
+
*
|
|
84
|
+
* Throws a structured error if tenant is missing. Useful as a safety
|
|
85
|
+
* check at the start of tenant-specific handlers.
|
|
86
|
+
*
|
|
87
|
+
* @param tenant The tenant context (or null)
|
|
88
|
+
* @param context Optional context message for the error
|
|
89
|
+
* @throws TenantRequiredError if tenant is null or undefined
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* const tenant = getTenantOrNull();
|
|
94
|
+
* requireTenant(tenant, "DELETE /api/documents/:id");
|
|
95
|
+
* // Safe to use tenant.id from here on
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export function requireTenant(tenant, context) {
|
|
99
|
+
if (!tenant) {
|
|
100
|
+
const message = context
|
|
101
|
+
? `Tenant context required for: ${context}`
|
|
102
|
+
: "Tenant context is required";
|
|
103
|
+
logger.warn(message);
|
|
104
|
+
throw new TenantRequiredError(message);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get the current tenant ID as a shorthand.
|
|
109
|
+
*
|
|
110
|
+
* Throws if tenant context is not set.
|
|
111
|
+
*
|
|
112
|
+
* @returns The current tenant's ID
|
|
113
|
+
* @throws TenantRequiredError if tenant context is not available
|
|
114
|
+
*/
|
|
115
|
+
export function getCurrentTenantId() {
|
|
116
|
+
return getCurrentTenant().id;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get the current tenant ID, or null if not set.
|
|
120
|
+
*
|
|
121
|
+
* @returns The current tenant's ID, or null
|
|
122
|
+
*/
|
|
123
|
+
export function getTenantIdOrNull() {
|
|
124
|
+
return getTenantOrNull()?.id ?? null;
|
|
125
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { getCurrentTenant, getCurrentTenantId, getTenantIdOrNull, getTenantOrNull, requireTenant, runWithTenant, } from "./context";
|
|
2
|
+
export { compose, fromApiKey, fromHeader, fromJwtClaim, fromPath, fromSubdomain, } from "./resolvers";
|
|
3
|
+
export type { AuthSessionLike, SessionGetter } from "./resolvers/from-auth-session";
|
|
4
|
+
export { fromAuthSession } from "./resolvers/from-auth-session";
|
|
5
|
+
export type { IsolationStrategy, PlanTier, TenantConfig, TenantContext, TenantInfo, TenantResolver, } from "./types";
|
|
6
|
+
export { TenantConfigSchema, TenantContextSchema, TenantInfoSchema, TenantIsolationError, TenantRequiredError, } from "./types";
|
|
7
|
+
export type { RlsPolicyCommand, RlsPolicySqlOptions } from "./isolation";
|
|
8
|
+
export { createTenantPrismaProxy, generateRlsPolicySql, getTenantDatabaseUrl, getTenantSchema, TenantAwarePrismaClient, withRls, } from "./isolation";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,OAAO,EACP,UAAU,EACV,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEhE,YAAY,EACV,iBAAiB,EACjB,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,UAAU,EACV,cAAc,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,SAAS,CAAC;AAKjB,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEzE,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,uBAAuB,EACvB,OAAO,GACR,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// @nebutra/tenant — Multi-tenancy context and isolation
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Re-export context functions
|
|
5
|
+
export { getCurrentTenant, getCurrentTenantId, getTenantIdOrNull, getTenantOrNull, requireTenant, runWithTenant, } from "./context";
|
|
6
|
+
// Re-export resolvers
|
|
7
|
+
export { compose, fromApiKey, fromHeader, fromJwtClaim, fromPath, fromSubdomain, } from "./resolvers";
|
|
8
|
+
export { fromAuthSession } from "./resolvers/from-auth-session";
|
|
9
|
+
export { TenantConfigSchema, TenantContextSchema, TenantInfoSchema, TenantIsolationError, TenantRequiredError, } from "./types";
|
|
10
|
+
// Re-export isolation helpers
|
|
11
|
+
export { createTenantPrismaProxy, generateRlsPolicySql, getTenantDatabaseUrl, getTenantSchema, TenantAwarePrismaClient, withRls, } from "./isolation";
|
|
12
|
+
// Re-export React hooks (as subpath export ./react)
|
|
13
|
+
// These are exported via package.json "exports" for tree-shaking
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { IsolationStrategy } from "./types";
|
|
2
|
+
/** Minimal Prisma-like client interface for RLS extension support. */
|
|
3
|
+
interface PrismaLikeClient {
|
|
4
|
+
$extends?: (extension: unknown) => unknown;
|
|
5
|
+
$executeRaw?: (query: TemplateStringsArray, ...values: unknown[]) => Promise<number>;
|
|
6
|
+
$queryRaw?: (query: TemplateStringsArray, ...values: unknown[]) => Promise<unknown[]>;
|
|
7
|
+
}
|
|
8
|
+
export type RlsPolicyCommand = "ALL" | "SELECT" | "INSERT" | "UPDATE" | "DELETE";
|
|
9
|
+
export interface RlsPolicySqlOptions {
|
|
10
|
+
/** Tables that contain the tenant discriminator column. Sorted for deterministic output. */
|
|
11
|
+
tables: string[];
|
|
12
|
+
/** Optional schema qualifier. Defaults to unqualified table names. */
|
|
13
|
+
schema?: string;
|
|
14
|
+
/** Tenant discriminator column name. */
|
|
15
|
+
tenantColumn?: string;
|
|
16
|
+
/** PostgreSQL policy name prefix. Policy names are generated as `${prefix}_${table}`. */
|
|
17
|
+
policyPrefix?: string;
|
|
18
|
+
/** Policy command. Defaults to ALL for read/write tenant isolation. */
|
|
19
|
+
command?: RlsPolicyCommand;
|
|
20
|
+
/** Tenant expression used by generated policies. */
|
|
21
|
+
tenantExpression?: string;
|
|
22
|
+
/** Emit FORCE ROW LEVEL SECURITY after enabling RLS. Defaults to true. */
|
|
23
|
+
forceRls?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generate deterministic PostgreSQL Row-Level Security DDL for shared-schema tenancy.
|
|
27
|
+
*
|
|
28
|
+
* The generated SQL is intentionally migration-tool friendly: stable table sorting,
|
|
29
|
+
* explicit policy replacement, and no database connection side effects.
|
|
30
|
+
*/
|
|
31
|
+
export declare function generateRlsPolicySql(options: RlsPolicySqlOptions): string;
|
|
32
|
+
/**
|
|
33
|
+
* Apply Prisma client extension that sets RLS (Row-Level Security) context.
|
|
34
|
+
*
|
|
35
|
+
* Works with PostgreSQL RLS policies that check `app.current_tenant_id`.
|
|
36
|
+
* This is the standard pattern for shared-schema multi-tenancy.
|
|
37
|
+
*
|
|
38
|
+
* The Prisma client middleware intercepts all queries and sets the
|
|
39
|
+
* application-level variable before executing.
|
|
40
|
+
*
|
|
41
|
+
* @param prisma The Prisma client to extend
|
|
42
|
+
* @param tenantId The tenant ID to set in RLS context
|
|
43
|
+
* @returns The extended Prisma client
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* import { PrismaClient } from "@prisma/client";
|
|
48
|
+
* import { withRls } from "@nebutra/tenant/isolation";
|
|
49
|
+
* import { getCurrentTenant } from "@nebutra/tenant";
|
|
50
|
+
*
|
|
51
|
+
* const prisma = new PrismaClient();
|
|
52
|
+
*
|
|
53
|
+
* // In a request handler:
|
|
54
|
+
* const tenant = getCurrentTenant();
|
|
55
|
+
* const client = withRls(prisma, tenant.id);
|
|
56
|
+
*
|
|
57
|
+
* // All queries now include RLS enforcement:
|
|
58
|
+
* const users = await client.user.findMany();
|
|
59
|
+
* // SQL: SELECT * FROM users WHERE current_setting('app.current_tenant_id') = user.tenant_id
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function withRls<P extends PrismaLikeClient>(prisma: P, tenantId: string): P;
|
|
63
|
+
/**
|
|
64
|
+
* Get the PostgreSQL schema name for schema-per-tenant strategy.
|
|
65
|
+
*
|
|
66
|
+
* Converts a tenant ID to a safe PostgreSQL schema name.
|
|
67
|
+
* Schema names must start with a letter and contain only alphanumerics and underscores.
|
|
68
|
+
*
|
|
69
|
+
* @param tenantId The tenant ID
|
|
70
|
+
* @returns The schema name (e.g., "org_acme_corp_public")
|
|
71
|
+
* @throws TenantIsolationError if tenant ID is invalid
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const schemaName = getTenantSchema("acme-corp");
|
|
76
|
+
* // Returns: "org_acme_corp_public"
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function getTenantSchema(tenantId: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* Get the PostgreSQL connection string for database-per-tenant strategy.
|
|
82
|
+
*
|
|
83
|
+
* Constructs a connection URL with the tenant-specific database name.
|
|
84
|
+
* Assumes the base connection URL is available and tenant databases follow
|
|
85
|
+
* a naming pattern (e.g., `nebutra_acme_corp`).
|
|
86
|
+
*
|
|
87
|
+
* @param tenantId The tenant ID
|
|
88
|
+
* @param baseUrl Optional base connection URL (defaults to process.env.DATABASE_URL)
|
|
89
|
+
* @returns The tenant-specific database URL
|
|
90
|
+
* @throws TenantIsolationError if base URL is invalid
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* const dbUrl = getTenantDatabaseUrl("acme-corp");
|
|
95
|
+
* // Returns: "postgresql://user:pass@localhost/nebutra_acme_corp"
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export declare function getTenantDatabaseUrl(tenantId: string, baseUrl?: string): string;
|
|
99
|
+
/**
|
|
100
|
+
* Wrapper for a Prisma client that automatically applies tenant filtering.
|
|
101
|
+
*
|
|
102
|
+
* This is useful for schema-per-tenant or database-per-tenant strategies
|
|
103
|
+
* where you want to ensure tenant isolation at the client level.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* import { TenantAwarePrismaClient } from "@nebutra/tenant/isolation";
|
|
108
|
+
* import { getCurrentTenant } from "@nebutra/tenant";
|
|
109
|
+
*
|
|
110
|
+
* export function getTenantPrisma() {
|
|
111
|
+
* const tenant = getCurrentTenant();
|
|
112
|
+
* return new TenantAwarePrismaClient(prisma, tenant.id);
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export declare class TenantAwarePrismaClient {
|
|
117
|
+
private prisma;
|
|
118
|
+
private tenantId;
|
|
119
|
+
constructor(prisma: PrismaLikeClient, tenantId: string);
|
|
120
|
+
/**
|
|
121
|
+
* Get the underlying Prisma client with RLS context applied.
|
|
122
|
+
*/
|
|
123
|
+
get client(): PrismaLikeClient;
|
|
124
|
+
/**
|
|
125
|
+
* Get the schema name for this tenant (schema-per-tenant strategy).
|
|
126
|
+
*/
|
|
127
|
+
getSchema(): string;
|
|
128
|
+
/**
|
|
129
|
+
* Get the database URL for this tenant (database-per-tenant strategy).
|
|
130
|
+
*/
|
|
131
|
+
getDatabaseUrl(baseUrl?: string): string;
|
|
132
|
+
/**
|
|
133
|
+
* Execute a raw SQL query with tenant context.
|
|
134
|
+
*/
|
|
135
|
+
executeRaw(query: string): Promise<number>;
|
|
136
|
+
/**
|
|
137
|
+
* Execute a raw query and return results.
|
|
138
|
+
*/
|
|
139
|
+
queryRaw(query: string): Promise<unknown[]>;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Create a tenant-aware Prisma proxy that applies isolation based on strategy.
|
|
143
|
+
*
|
|
144
|
+
* @param prisma The base Prisma client
|
|
145
|
+
* @param tenantId The tenant ID
|
|
146
|
+
* @param strategy The isolation strategy (default: "shared_schema")
|
|
147
|
+
* @returns A proxy or wrapper for the Prisma client
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* const prisma = createTenantPrismaProxy(client, "acme-corp", "shared_schema");
|
|
152
|
+
* const users = await prisma.user.findMany(); // Filtered by RLS
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export declare function createTenantPrismaProxy(prisma: PrismaLikeClient, tenantId: string, strategy?: IsolationStrategy): PrismaLikeClient;
|
|
156
|
+
export {};
|
|
157
|
+
//# sourceMappingURL=isolation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isolation.d.ts","sourceRoot":"","sources":["../src/isolation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAOjD,sEAAsE;AACtE,UAAU,gBAAgB;IACxB,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,OAAO,CAAC;IAC3C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrF,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CACvF;AAED,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjF,MAAM,WAAW,mBAAmB;IAClC,4FAA4F;IAC5F,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAyCD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CA2DzE;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,gBAAgB,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,CAiClF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAmBxD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CA6B/E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,QAAQ;gBADR,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,MAAM;IAK1B;;OAEG;IACH,IAAI,MAAM,qBAET;IAED;;OAEG;IACH,SAAS,IAAI,MAAM;IAInB;;OAEG;IACH,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IAIxC;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwBhD;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;CAoBlD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,iBAAmC,GAC5C,gBAAgB,CAmBlB"}
|