@happyvertical/smrt-tenancy 0.30.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/AGENTS.md +71 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +122 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/adapters/cli.d.ts +178 -0
- package/dist/adapters/cli.d.ts.map +1 -0
- package/dist/adapters/express.d.ts +115 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/index.d.ts +22 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +7 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/sveltekit.d.ts +123 -0
- package/dist/adapters/sveltekit.d.ts.map +1 -0
- package/dist/chunks/context-B5CKsmMi.js +190 -0
- package/dist/chunks/context-B5CKsmMi.js.map +1 -0
- package/dist/chunks/sveltekit-9eRH1RLw.js +153 -0
- package/dist/chunks/sveltekit-9eRH1RLw.js.map +1 -0
- package/dist/chunks/testing-C_tV23JW.js +487 -0
- package/dist/chunks/testing-C_tV23JW.js.map +1 -0
- package/dist/context.d.ts +435 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/decorators.d.ts +126 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/enabled-state.d.ts +25 -0
- package/dist/enabled-state.d.ts.map +1 -0
- package/dist/entry-point.d.ts +83 -0
- package/dist/entry-point.d.ts.map +1 -0
- package/dist/fields.d.ts +104 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/dist/interceptor.d.ts +156 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/manifest.json +11 -0
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +80 -0
- package/dist/playground.js.map +1 -0
- package/dist/registry.d.ts +145 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +65 -0
- package/dist/svelte/components/TenantCard.svelte +272 -0
- package/dist/svelte/components/TenantCard.svelte.d.ts +18 -0
- package/dist/svelte/components/TenantCard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/TenantSwitcher.svelte +68 -0
- package/dist/svelte/components/TenantSwitcher.svelte.d.ts +11 -0
- package/dist/svelte/components/TenantSwitcher.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.d.ts +5 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +9 -0
- package/dist/svelte/index.d.ts +15 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +19 -0
- package/dist/svelte/playground.d.ts +70 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +75 -0
- package/dist/testing.d.ts +145 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +11 -0
- package/dist/testing.js.map +1 -0
- package/dist/ui.d.ts +21 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +33 -0
- package/dist/ui.js.map +1 -0
- package/package.json +99 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @happyvertical/smrt-tenancy
|
|
2
|
+
|
|
3
|
+
Multi-tenancy via AsyncLocalStorage context propagation with automatic query filtering and tenant ID population.
|
|
4
|
+
|
|
5
|
+
## Context Propagation
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { withTenant, getTenantId, withSystemContext } from '@happyvertical/smrt-tenancy';
|
|
9
|
+
|
|
10
|
+
await withTenant({ tenantId: 'tenant-123' }, async () => {
|
|
11
|
+
// All SmrtCollection queries auto-filtered by tenantId
|
|
12
|
+
// All creates auto-populate tenantId
|
|
13
|
+
const docs = await collection.list({}); // WHERE tenant_id = 'tenant-123'
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
await withSystemContext(async () => { /* bypasses all tenant checks */ });
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Critical distinction**: `withSystemContext()` sets a SYSTEM_CONTEXT_MARKER sentinel — different from "no context" (undefined). Interceptor can distinguish intentional bypass from missing context.
|
|
20
|
+
|
|
21
|
+
## Interceptor System
|
|
22
|
+
|
|
23
|
+
Hooks into SmrtCollection via `GlobalInterceptors.register()` (priority 100, runs first):
|
|
24
|
+
|
|
25
|
+
| Hook | Behavior |
|
|
26
|
+
|------|----------|
|
|
27
|
+
| `beforeList` | Injects `tenantId` into WHERE clause; validates existing filters match context |
|
|
28
|
+
| `beforeGet` | Same — converts ID lookup to `{ id, tenantId }` |
|
|
29
|
+
| `beforeSave` | Auto-populates tenantId if empty + `autoPopulate: true`; validates if already set |
|
|
30
|
+
| `beforeDelete` | Validates instance.tenantId matches context |
|
|
31
|
+
| `beforeQuery` | Enforces raw SQL policy on tenant-scoped classes (`throw`/`warn`/`allow`) |
|
|
32
|
+
| `afterSave` | Emits `directory.<class>.created`/`updated` via `dispatchBus` for configured `directoryClasses` |
|
|
33
|
+
| `afterDelete` | Emits `directory.<class>.deleted` via `dispatchBus` for configured `directoryClasses` |
|
|
34
|
+
|
|
35
|
+
Mismatches throw `TenantIsolationError`. Missing required context throws `TenantContextError`.
|
|
36
|
+
|
|
37
|
+
## Registration — Two Patterns
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// Pattern 1: Tenancy decorator
|
|
41
|
+
@TenantScoped({ mode: 'optional' })
|
|
42
|
+
class Doc extends SmrtObject { @tenantId({ nullable: true }) tenantId: string | null = null; }
|
|
43
|
+
|
|
44
|
+
// Pattern 2: Core decorator (tenancy package reads this too)
|
|
45
|
+
@smrt({ tenantScoped: { mode: 'optional' } })
|
|
46
|
+
class Doc extends SmrtObject { tenantId: string | null = null; }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Modes: `'required'` (default — throws without context) or `'optional'` (passes through if no context).
|
|
50
|
+
|
|
51
|
+
## Adapters
|
|
52
|
+
|
|
53
|
+
- **Express**: `createExpressMiddleware()` — uses `enterTenantContext()` (not withTenant, because middleware returns before handlers run)
|
|
54
|
+
- **SvelteKit**: `createSvelteKitHandle()` — stores context in `event.locals`
|
|
55
|
+
- **CLI**: `createCliContext()` — `run()`, `runWithTenant()`, `runAsSystem()`, `runAsSuperAdmin()`
|
|
56
|
+
|
|
57
|
+
## Super Admin Bypass
|
|
58
|
+
|
|
59
|
+
`withSuperAdminBypass()` keeps tenant context but disables auto-filtering. Different from `withSystemContext()` which removes context entirely.
|
|
60
|
+
|
|
61
|
+
## Gotchas
|
|
62
|
+
|
|
63
|
+
- **Context lost in callbacks**: `setTimeout(() => getTenantId(), 100)` → undefined. Fix: `TenantContext.bind(fn)`
|
|
64
|
+
- **Nested contexts override**: inner `withTenant()` overrides outer; restores on exit
|
|
65
|
+
- **Auto-populate only if empty**: if tenantId already set, interceptor validates (not overwrites)
|
|
66
|
+
- **Isolation checked at query time**: `list({ where: { tenantId: 'other' } })` throws immediately
|
|
67
|
+
- **Testing**: `resetTenancy()` + `setupTestTenancy()` in beforeEach; `testTenantIsolation()` helper
|
|
68
|
+
|
|
69
|
+
## Known exceptions to monorepo standards
|
|
70
|
+
|
|
71
|
+
- **`serializeInstance()` in `src/interceptor.ts` calls `instance.toJSON()` directly** (standards.md §7 forbids this in favor of `transformJSON()`). The interceptor must serialize arbitrary instances handed to it — including workspace stubs and plain-object test doubles whose classes may not extend `SmrtObject` and therefore have no `transformJSON()` hook. The call is duck-typed and falls back to manual key iteration when `toJSON` is absent. See the inline comment at the call site for the full rationale.
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright <2025> <Happy Vertical Corporation>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# @happyvertical/smrt-tenancy
|
|
2
|
+
|
|
3
|
+
Multi-tenancy for SMRT with AsyncLocalStorage context propagation, automatic query filtering, and tenant ID population.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install @happyvertical/smrt-tenancy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { enableTenancy, TenantScoped, tenantId, withTenant } from '@happyvertical/smrt-tenancy';
|
|
15
|
+
import { smrt, SmrtObject } from '@happyvertical/smrt-core';
|
|
16
|
+
|
|
17
|
+
// 1. Enable tenancy globally (once at app startup)
|
|
18
|
+
enableTenancy();
|
|
19
|
+
|
|
20
|
+
// 2. Mark classes as tenant-scoped
|
|
21
|
+
@smrt()
|
|
22
|
+
@TenantScoped({ mode: 'optional' })
|
|
23
|
+
class Document extends SmrtObject {
|
|
24
|
+
@tenantId({ nullable: true })
|
|
25
|
+
tenantId: string | null = null;
|
|
26
|
+
|
|
27
|
+
title: string = '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 3. Wrap operations in tenant context
|
|
31
|
+
await withTenant({ tenantId: 'tenant-123' }, async () => {
|
|
32
|
+
const docs = await collection.list({ where: { status: 'active' } });
|
|
33
|
+
// Executes: WHERE tenant_id = 'tenant-123' AND status = 'active'
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API
|
|
38
|
+
|
|
39
|
+
### Decorators
|
|
40
|
+
|
|
41
|
+
| Export | Description |
|
|
42
|
+
|--------|-------------|
|
|
43
|
+
| `TenantScoped(options?)` | Class decorator. Modes: `'required'` (default) or `'optional'` |
|
|
44
|
+
| `tenantId(options?)` | Property decorator for the tenant ID field |
|
|
45
|
+
|
|
46
|
+
### Context Runners
|
|
47
|
+
|
|
48
|
+
| Export | Description |
|
|
49
|
+
|--------|-------------|
|
|
50
|
+
| `withTenant(ctx, fn)` | Run code scoped to a tenant |
|
|
51
|
+
| `withTenantSync(ctx, fn)` | Synchronous variant |
|
|
52
|
+
| `withSystemContext(fn)` | Bypass all tenant checks (admin/migrations) |
|
|
53
|
+
| `withSuperAdminBypass(fn)` | Keep tenant context but disable auto-filtering |
|
|
54
|
+
| `enterTenantContext(ctx)` | Enter context without callback (for middleware) |
|
|
55
|
+
|
|
56
|
+
### Context Accessors
|
|
57
|
+
|
|
58
|
+
| Export | Description |
|
|
59
|
+
|--------|-------------|
|
|
60
|
+
| `getCurrentTenant()` | Get current tenant context (may be undefined) |
|
|
61
|
+
| `getTenantId()` | Get tenant ID string (may be undefined) |
|
|
62
|
+
| `requireTenant()` | Get tenant context or throw |
|
|
63
|
+
| `requireTenantId()` | Get tenant ID or throw |
|
|
64
|
+
| `hasTenantContext()` | Check if in tenant context |
|
|
65
|
+
| `isSystemContext()` | Check if in system context |
|
|
66
|
+
| `isSuperAdminBypass()` | Check if super admin bypass is active |
|
|
67
|
+
| `TenantContext` | AsyncLocalStorage instance (advanced use) |
|
|
68
|
+
|
|
69
|
+
### Errors
|
|
70
|
+
|
|
71
|
+
`TenantContextError` (missing required context), `TenantIsolationError` (tenant mismatch).
|
|
72
|
+
|
|
73
|
+
### Interceptor
|
|
74
|
+
|
|
75
|
+
| Export | Description |
|
|
76
|
+
|--------|-------------|
|
|
77
|
+
| `enableTenancy()` | Register tenant interceptor globally |
|
|
78
|
+
| `disableTenancy()` | Remove tenant interceptor |
|
|
79
|
+
| `isTenancyEnabled()` | Check if tenancy is active |
|
|
80
|
+
| `createTenantInterceptor(options?)` | Create interceptor manually |
|
|
81
|
+
|
|
82
|
+
### Framework Adapters
|
|
83
|
+
|
|
84
|
+
| Export | Description |
|
|
85
|
+
|--------|-------------|
|
|
86
|
+
| `createSvelteKitHandle(options)` | SvelteKit hooks.server.ts handler |
|
|
87
|
+
| `createExpressMiddleware(options)` | Express middleware |
|
|
88
|
+
| `createCliContext(options)` | CLI context with `run()`, `runWithTenant()`, `runAsSystem()` |
|
|
89
|
+
|
|
90
|
+
### Registry (Advanced)
|
|
91
|
+
|
|
92
|
+
| Export | Description |
|
|
93
|
+
|--------|-------------|
|
|
94
|
+
| `isTenantScopedClass(name)` | Check if a class is tenant-scoped |
|
|
95
|
+
| `getTenantScopedConfig(name)` | Get tenant config for a class |
|
|
96
|
+
| `getAllTenantScopedClasses()` | List all registered tenant-scoped classes |
|
|
97
|
+
| `registerTenantScopedClass()` | Register a class programmatically |
|
|
98
|
+
| `unregisterTenantScopedClass()` | Remove a class from registry |
|
|
99
|
+
| `clearTenantScopedRegistry()` | Clear all registrations |
|
|
100
|
+
|
|
101
|
+
### Testing
|
|
102
|
+
|
|
103
|
+
| Export | Description |
|
|
104
|
+
|--------|-------------|
|
|
105
|
+
| `setupTestTenancy(options?)` | Enable tenancy for tests |
|
|
106
|
+
| `resetTenancy()` | Clean up tenancy state between tests |
|
|
107
|
+
| `createTestTenantContext(ctx, fn)` | Run test code in tenant context |
|
|
108
|
+
| `testTenantIsolation(tenantIds, fn)` | Verify isolation between tenants |
|
|
109
|
+
| `assertTenantContextRequired(fn)` | Assert operation requires context |
|
|
110
|
+
| `assertTenantIsolationViolation(fn)` | Assert operation violates isolation |
|
|
111
|
+
|
|
112
|
+
## Dependencies
|
|
113
|
+
|
|
114
|
+
- `@happyvertical/smrt-core` -- SmrtObject, SmrtCollection, GlobalInterceptors
|
|
115
|
+
- `@happyvertical/sql` -- database operations
|
|
116
|
+
- `@happyvertical/utils` -- utility functions
|
|
117
|
+
|
|
118
|
+
Optional peers: `svelte`, `@happyvertical/smrt-users`, `@happyvertical/smrt-svelte`
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"__smrt-register__.d.ts","sourceRoot":"","sources":["../src/__smrt-register__.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Adapter for smrt-tenancy
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for setting up tenant context in CLI tools.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { createCliContext } from '@happyvertical/smrt-tenancy/adapters';
|
|
9
|
+
*
|
|
10
|
+
* // Create context helper for CLI
|
|
11
|
+
* const cliContext = createCliContext({
|
|
12
|
+
* resolveTenantId: () => process.env.TENANT_ID,
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* // Run command in tenant context
|
|
16
|
+
* await cliContext.run(async () => {
|
|
17
|
+
* await documentCollection.list({});
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Configuration for the CLI context runner created by `createCliContext()`.
|
|
23
|
+
*
|
|
24
|
+
* `resolveTenantId` is optional — when omitted (or when it returns
|
|
25
|
+
* `null`/`undefined`) the `run()` method falls back to `withSystemContext()`.
|
|
26
|
+
*
|
|
27
|
+
* @see createCliContext
|
|
28
|
+
* @see CliContextRunner
|
|
29
|
+
*/
|
|
30
|
+
export interface CliContextOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Resolve tenant ID
|
|
33
|
+
*
|
|
34
|
+
* Common sources:
|
|
35
|
+
* - Environment variable: () => process.env.TENANT_ID
|
|
36
|
+
* - Command line argument: () => argv.tenant
|
|
37
|
+
* - Config file: () => config.tenantId
|
|
38
|
+
*/
|
|
39
|
+
resolveTenantId?: () => Promise<string | null | undefined> | string | null | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Resolve user ID (optional)
|
|
42
|
+
*/
|
|
43
|
+
resolveUserId?: () => Promise<string | null | undefined> | string | null | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Default permissions for CLI operations
|
|
46
|
+
*/
|
|
47
|
+
defaultPermissions?: Set<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Run CLI as super admin by default
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
superAdminByDefault?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Context runner returned by `createCliContext()`.
|
|
56
|
+
*
|
|
57
|
+
* Provides four execution modes suitable for different CLI scenarios:
|
|
58
|
+
* - `run()` — resolves the tenant from the configured options and runs code in
|
|
59
|
+
* that context; falls back to system context if no tenant is available.
|
|
60
|
+
* - `runWithTenant()` — explicitly specify a tenant ID for this invocation.
|
|
61
|
+
* - `runAsSystem()` — bypass all tenant checks (migration scripts, admin tools).
|
|
62
|
+
* - `runAsSuperAdmin()` — tenant context with bypass flag enabled.
|
|
63
|
+
*
|
|
64
|
+
* @see createCliContext
|
|
65
|
+
*/
|
|
66
|
+
export interface CliContextRunner {
|
|
67
|
+
/**
|
|
68
|
+
* Run `fn` inside the tenant context resolved from the `CliContextOptions`.
|
|
69
|
+
*
|
|
70
|
+
* Falls back to `withSystemContext()` when no tenant ID is available.
|
|
71
|
+
*
|
|
72
|
+
* @param fn - Async function to execute.
|
|
73
|
+
* @returns Promise resolving to the return value of `fn`.
|
|
74
|
+
*/
|
|
75
|
+
run<T>(fn: () => Promise<T>): Promise<T>;
|
|
76
|
+
/**
|
|
77
|
+
* Run `fn` inside the context of the specified tenant.
|
|
78
|
+
*
|
|
79
|
+
* @param tenantId - Tenant ID to set as context.
|
|
80
|
+
* @param fn - Async function to execute.
|
|
81
|
+
* @returns Promise resolving to the return value of `fn`.
|
|
82
|
+
*/
|
|
83
|
+
runWithTenant<T>(tenantId: string, fn: () => Promise<T>): Promise<T>;
|
|
84
|
+
/**
|
|
85
|
+
* Run `fn` in system context, bypassing all tenant checks.
|
|
86
|
+
*
|
|
87
|
+
* @param fn - Async function to execute.
|
|
88
|
+
* @returns Promise resolving to the return value of `fn`.
|
|
89
|
+
*/
|
|
90
|
+
runAsSystem<T>(fn: () => Promise<T>): Promise<T>;
|
|
91
|
+
/**
|
|
92
|
+
* Run `fn` with a tenant context and super admin bypass enabled.
|
|
93
|
+
*
|
|
94
|
+
* @param tenantId - Tenant ID to set as context.
|
|
95
|
+
* @param fn - Async function to execute.
|
|
96
|
+
* @returns Promise resolving to the return value of `fn`.
|
|
97
|
+
*/
|
|
98
|
+
runAsSuperAdmin<T>(tenantId: string, fn: () => Promise<T>): Promise<T>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Create a CLI context runner
|
|
102
|
+
*
|
|
103
|
+
* @param options - Configuration options
|
|
104
|
+
* @returns CLI context runner with various execution modes
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const cli = createCliContext({
|
|
109
|
+
* resolveTenantId: () => process.env.TENANT_ID,
|
|
110
|
+
* superAdminByDefault: true,
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* // Use resolved tenant
|
|
114
|
+
* await cli.run(async () => {
|
|
115
|
+
* const docs = await collection.list({});
|
|
116
|
+
* console.log(`Found ${docs.length} documents`);
|
|
117
|
+
* });
|
|
118
|
+
*
|
|
119
|
+
* // Override tenant
|
|
120
|
+
* await cli.runWithTenant('other-tenant', async () => {
|
|
121
|
+
* // Operations in other-tenant context
|
|
122
|
+
* });
|
|
123
|
+
*
|
|
124
|
+
* // System operations (no tenant)
|
|
125
|
+
* await cli.runAsSystem(async () => {
|
|
126
|
+
* // Can access all data
|
|
127
|
+
* const allDocs = await collection.list({});
|
|
128
|
+
* });
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export declare function createCliContext(options?: CliContextOptions): CliContextRunner;
|
|
132
|
+
/**
|
|
133
|
+
* Run an async function with a specific tenant ID set as context.
|
|
134
|
+
*
|
|
135
|
+
* Convenience wrapper around `withTenant()` for one-off CLI operations where
|
|
136
|
+
* a full `CliContextRunner` is not needed.
|
|
137
|
+
*
|
|
138
|
+
* @param tenantId - Tenant ID to set as the active context.
|
|
139
|
+
* @param fn - Async function to execute in the tenant context.
|
|
140
|
+
* @returns Promise resolving to the return value of `fn`.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* import { runWithTenant } from '@happyvertical/smrt-tenancy/adapters';
|
|
145
|
+
*
|
|
146
|
+
* await runWithTenant('tenant-123', async () => {
|
|
147
|
+
* await collection.list({});
|
|
148
|
+
* });
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* @see createCliContext
|
|
152
|
+
* @see runAsSystem
|
|
153
|
+
*/
|
|
154
|
+
export declare function runWithTenant<T>(tenantId: string, fn: () => Promise<T>): Promise<T>;
|
|
155
|
+
/**
|
|
156
|
+
* Run an async function in system context, bypassing all tenant checks.
|
|
157
|
+
*
|
|
158
|
+
* Convenience wrapper around `withSystemContext()` for one-off CLI operations
|
|
159
|
+
* such as migration scripts or admin tooling that needs cross-tenant access.
|
|
160
|
+
*
|
|
161
|
+
* @param fn - Async function to execute in system context.
|
|
162
|
+
* @returns Promise resolving to the return value of `fn`.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* import { runAsSystem } from '@happyvertical/smrt-tenancy/adapters';
|
|
167
|
+
*
|
|
168
|
+
* await runAsSystem(async () => {
|
|
169
|
+
* const all = await collection.list({});
|
|
170
|
+
* console.log(`Total records: ${all.length}`);
|
|
171
|
+
* });
|
|
172
|
+
* ```
|
|
173
|
+
*
|
|
174
|
+
* @see runWithTenant
|
|
175
|
+
* @see createCliContext
|
|
176
|
+
*/
|
|
177
|
+
export declare function runAsSystem<T>(fn: () => Promise<T>): Promise<T>;
|
|
178
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/adapters/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MACd,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAClC,MAAM,GACN,IAAI,GACJ,SAAS,CAAC;IAEd;;OAEG;IACH,aAAa,CAAC,EAAE,MACZ,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAClC,MAAM,GACN,IAAI,GACJ,SAAS,CAAC;IAEd;;OAEG;IACH,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAEjC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;OAOG;IACH,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAEzC;;;;;;OAMG;IACH,aAAa,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAErE;;;;;OAKG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAEjD;;;;;;OAMG;IACH,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACxE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CA8DlB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAEZ;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAErE"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express Adapter for smrt-tenancy
|
|
3
|
+
*
|
|
4
|
+
* Provides Express middleware that sets up tenant context for each request.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import express from 'express';
|
|
9
|
+
* import { createExpressMiddleware } from '@happyvertical/smrt-tenancy/adapters';
|
|
10
|
+
*
|
|
11
|
+
* const app = express();
|
|
12
|
+
*
|
|
13
|
+
* app.use(createExpressMiddleware({
|
|
14
|
+
* resolveTenantId: (req) => req.headers['x-tenant-id'] as string,
|
|
15
|
+
* }));
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Express Request interface (minimal to avoid direct dependency)
|
|
20
|
+
*/
|
|
21
|
+
interface ExpressRequest {
|
|
22
|
+
headers: Record<string, string | string[] | undefined>;
|
|
23
|
+
url: string;
|
|
24
|
+
path: string;
|
|
25
|
+
query: Record<string, unknown>;
|
|
26
|
+
cookies?: Record<string, string>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Express Response interface
|
|
30
|
+
*/
|
|
31
|
+
interface ExpressResponse {
|
|
32
|
+
status(code: number): ExpressResponse;
|
|
33
|
+
json(data: unknown): ExpressResponse;
|
|
34
|
+
send(data: unknown): ExpressResponse;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Express NextFunction
|
|
38
|
+
*/
|
|
39
|
+
type ExpressNext = (error?: unknown) => void;
|
|
40
|
+
/**
|
|
41
|
+
* Configuration options for the Express tenancy middleware created by
|
|
42
|
+
* `createExpressMiddleware()`.
|
|
43
|
+
*
|
|
44
|
+
* Only `resolveTenantId` is required. All callback options receive the raw
|
|
45
|
+
* Express `Request` object so you can extract tenant information from headers,
|
|
46
|
+
* subdomains, cookies, or any other request property.
|
|
47
|
+
*
|
|
48
|
+
* @see createExpressMiddleware
|
|
49
|
+
*/
|
|
50
|
+
export interface ExpressMiddlewareOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Resolve tenant ID from the request
|
|
53
|
+
*/
|
|
54
|
+
resolveTenantId: (req: ExpressRequest) => Promise<string | null | undefined> | string | null | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Resolve user ID from the request (optional)
|
|
57
|
+
*/
|
|
58
|
+
resolveUserId?: (req: ExpressRequest) => Promise<string | null | undefined> | string | null | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Resolve permissions (optional)
|
|
61
|
+
*/
|
|
62
|
+
resolvePermissions?: (req: ExpressRequest, tenantId: string, userId?: string) => Promise<Set<string>> | Set<string>;
|
|
63
|
+
/**
|
|
64
|
+
* Check if user is super admin (optional)
|
|
65
|
+
*/
|
|
66
|
+
isSuperAdmin?: (req: ExpressRequest, tenantId: string, userId?: string) => Promise<boolean> | boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Called when no tenant ID could be resolved
|
|
69
|
+
* Return true to continue, false to stop with 400 error.
|
|
70
|
+
*/
|
|
71
|
+
onNoTenant?: (req: ExpressRequest, res: ExpressResponse) => Promise<boolean> | boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Paths to exclude from tenant context
|
|
74
|
+
*/
|
|
75
|
+
excludePaths?: string[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Create an Express middleware function that establishes tenant context for
|
|
79
|
+
* every incoming request.
|
|
80
|
+
*
|
|
81
|
+
* Uses `enterTenantContext()` (rather than `withTenant()`) because Express
|
|
82
|
+
* middleware returns before route handlers execute. `enterWith()` sets the
|
|
83
|
+
* context on the current async resource so it propagates to handlers that run
|
|
84
|
+
* after `next()` is called.
|
|
85
|
+
*
|
|
86
|
+
* The resolved context is also attached directly to the request object for
|
|
87
|
+
* convenience:
|
|
88
|
+
* - `req.tenantContext` — full `TenantContextData`
|
|
89
|
+
* - `req.tenantId` — string tenant ID shortcut
|
|
90
|
+
*
|
|
91
|
+
* When no tenant ID can be resolved, the default behaviour returns a `400`
|
|
92
|
+
* JSON response. Customise this with the `onNoTenant` option.
|
|
93
|
+
*
|
|
94
|
+
* @param options - Middleware configuration including the required
|
|
95
|
+
* `resolveTenantId` callback.
|
|
96
|
+
* @returns An Express-compatible middleware function `(req, res, next) => void`.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* import express from 'express';
|
|
101
|
+
* import { createExpressMiddleware } from '@happyvertical/smrt-tenancy/adapters';
|
|
102
|
+
*
|
|
103
|
+
* const app = express();
|
|
104
|
+
* app.use(createExpressMiddleware({
|
|
105
|
+
* resolveTenantId: (req) => req.headers['x-tenant-id'] as string,
|
|
106
|
+
* excludePaths: ['/health', '/public/*'],
|
|
107
|
+
* }));
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @see ExpressMiddlewareOptions
|
|
111
|
+
* @see createSvelteKitHandle
|
|
112
|
+
*/
|
|
113
|
+
export declare function createExpressMiddleware(options: ExpressMiddlewareOptions): (req: ExpressRequest, res: ExpressResponse, next: ExpressNext) => Promise<void>;
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH;;GAEG;AACH,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAAC;IACtC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,CAAC;IACrC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,CAAC;CACtC;AAED;;GAEG;AACH,KAAK,WAAW,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAE7C;;;;;;;;;GASG;AACH,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,eAAe,EAAE,CACf,GAAG,EAAE,cAAc,KAChB,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAEpE;;OAEG;IACH,aAAa,CAAC,EAAE,CACd,GAAG,EAAE,cAAc,KAChB,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAEpE;;OAEG;IACH,kBAAkB,CAAC,EAAE,CACnB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,KACZ,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IAExC;;OAEG;IACH,YAAY,CAAC,EAAE,CACb,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,KACZ,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAEhC;;;OAGG;IACH,UAAU,CAAC,EAAE,CACX,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,KACjB,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAEhC;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,IAWrE,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,WAAW,KAChB,OAAO,CAAC,IAAI,CAAC,CA2DjB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework Adapters for smrt-tenancy
|
|
3
|
+
*
|
|
4
|
+
* Provides middleware/hooks for popular frameworks to set up tenant context.
|
|
5
|
+
*
|
|
6
|
+
* @example SvelteKit
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // hooks.server.ts
|
|
9
|
+
* import { createSvelteKitHandle } from '@happyvertical/smrt-tenancy/adapters';
|
|
10
|
+
*
|
|
11
|
+
* export const handle = createSvelteKitHandle({
|
|
12
|
+
* resolveTenantId: async (event) => {
|
|
13
|
+
* // Get from subdomain, header, cookie, etc.
|
|
14
|
+
* return event.request.headers.get('x-tenant-id');
|
|
15
|
+
* }
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export { type CliContextOptions, createCliContext } from './cli.js';
|
|
20
|
+
export { createExpressMiddleware, type ExpressMiddlewareOptions, } from './express.js';
|
|
21
|
+
export { createSvelteKitHandle, type SvelteKitHandleOptions, } from './sveltekit.js';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EACL,uBAAuB,EACvB,KAAK,wBAAwB,GAC9B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvelteKit Adapter for smrt-tenancy
|
|
3
|
+
*
|
|
4
|
+
* Provides a SvelteKit Handle that sets up tenant context for each request.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // hooks.server.ts
|
|
9
|
+
* import { createSvelteKitHandle } from '@happyvertical/smrt-tenancy/adapters';
|
|
10
|
+
*
|
|
11
|
+
* export const handle = createSvelteKitHandle({
|
|
12
|
+
* resolveTenantId: async (event) => {
|
|
13
|
+
* // From subdomain
|
|
14
|
+
* const host = event.request.headers.get('host');
|
|
15
|
+
* const subdomain = host?.split('.')[0];
|
|
16
|
+
* return subdomain;
|
|
17
|
+
*
|
|
18
|
+
* // Or from header
|
|
19
|
+
* // return event.request.headers.get('x-tenant-id');
|
|
20
|
+
*
|
|
21
|
+
* // Or from cookie
|
|
22
|
+
* // return event.cookies.get('tenant_id');
|
|
23
|
+
* }
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* SvelteKit RequestEvent (minimal interface to avoid direct dependency)
|
|
29
|
+
*/
|
|
30
|
+
interface SvelteKitEvent {
|
|
31
|
+
request: Request;
|
|
32
|
+
url: URL;
|
|
33
|
+
cookies: {
|
|
34
|
+
get(name: string): string | undefined;
|
|
35
|
+
set(name: string, value: string, opts?: unknown): void;
|
|
36
|
+
};
|
|
37
|
+
locals: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* SvelteKit resolve function
|
|
41
|
+
*/
|
|
42
|
+
type SvelteKitResolve = (event: SvelteKitEvent) => Promise<Response>;
|
|
43
|
+
/**
|
|
44
|
+
* Configuration options for the SvelteKit tenancy handle created by
|
|
45
|
+
* `createSvelteKitHandle()`.
|
|
46
|
+
*
|
|
47
|
+
* Only `resolveTenantId` is required; all other fields are optional callbacks
|
|
48
|
+
* used to enrich the context or customise missing-tenant behaviour.
|
|
49
|
+
*
|
|
50
|
+
* @see createSvelteKitHandle
|
|
51
|
+
*/
|
|
52
|
+
export interface SvelteKitHandleOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Resolve tenant ID from the request
|
|
55
|
+
*
|
|
56
|
+
* Return the tenant ID string, or null/undefined if no tenant context should be set.
|
|
57
|
+
*/
|
|
58
|
+
resolveTenantId: (event: SvelteKitEvent) => Promise<string | null | undefined> | string | null | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Resolve user ID from the request (optional)
|
|
61
|
+
*/
|
|
62
|
+
resolveUserId?: (event: SvelteKitEvent) => Promise<string | null | undefined> | string | null | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Resolve permissions for the user in this tenant (optional)
|
|
65
|
+
*/
|
|
66
|
+
resolvePermissions?: (event: SvelteKitEvent, tenantId: string, userId?: string) => Promise<Set<string>> | Set<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Check if user is a super admin (optional)
|
|
69
|
+
* If true and super admin bypass is enabled on the class, tenant filtering is skipped.
|
|
70
|
+
*/
|
|
71
|
+
isSuperAdmin?: (event: SvelteKitEvent, tenantId: string, userId?: string) => Promise<boolean> | boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Called when no tenant ID could be resolved
|
|
74
|
+
* Return a Response to short-circuit, or undefined to continue without tenant context.
|
|
75
|
+
*/
|
|
76
|
+
onNoTenant?: (event: SvelteKitEvent) => Promise<Response | undefined> | Response | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Paths to exclude from tenant context (e.g., public APIs, health checks)
|
|
79
|
+
* Supports glob patterns.
|
|
80
|
+
*/
|
|
81
|
+
excludePaths?: string[];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Create a SvelteKit `Handle` function that establishes tenant context for
|
|
85
|
+
* every server-side request.
|
|
86
|
+
*
|
|
87
|
+
* The returned handle wraps the `resolve` call in `withTenant()` so that all
|
|
88
|
+
* server-side load functions, API routes, and `+server.ts` handlers within the
|
|
89
|
+
* request share the same tenant context via `AsyncLocalStorage`.
|
|
90
|
+
*
|
|
91
|
+
* The resolved context is also stored in `event.locals` under two keys:
|
|
92
|
+
* - `event.locals.tenantContext` — full `TenantContextData`
|
|
93
|
+
* - `event.locals.tenantId` — string tenant ID shortcut
|
|
94
|
+
*
|
|
95
|
+
* When no tenant ID can be resolved (and no custom `onNoTenant` handler
|
|
96
|
+
* returns a `Response`), the request continues without any tenant context.
|
|
97
|
+
*
|
|
98
|
+
* @param options - Configuration options including the required
|
|
99
|
+
* `resolveTenantId` callback.
|
|
100
|
+
* @returns A SvelteKit `Handle` function suitable for use in `hooks.server.ts`.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* // src/hooks.server.ts
|
|
105
|
+
* import { createSvelteKitHandle } from '@happyvertical/smrt-tenancy/adapters';
|
|
106
|
+
*
|
|
107
|
+
* export const handle = createSvelteKitHandle({
|
|
108
|
+
* resolveTenantId: (event) =>
|
|
109
|
+
* event.request.headers.get('x-tenant-id'),
|
|
110
|
+
* onNoTenant: () =>
|
|
111
|
+
* new Response('Tenant required', { status: 400 }),
|
|
112
|
+
* });
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* @see SvelteKitHandleOptions
|
|
116
|
+
* @see createExpressMiddleware
|
|
117
|
+
*/
|
|
118
|
+
export declare function createSvelteKitHandle(options: SvelteKitHandleOptions): ({ event, resolve, }: {
|
|
119
|
+
event: SvelteKitEvent;
|
|
120
|
+
resolve: SvelteKitResolve;
|
|
121
|
+
}) => Promise<Response>;
|
|
122
|
+
export {};
|
|
123
|
+
//# sourceMappingURL=sveltekit.d.ts.map
|