@happyvertical/smrt-tenancy 0.51.31 → 0.51.33

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.
@@ -1,4 +1,5 @@
1
- import { SmrtClassOptions, SmrtCollection, SmrtObject } from '@happyvertical/smrt-core';
1
+ /** Reseller parentage and billing ownership, independent of user permissions. */
2
+ import { type SmrtClassOptions, SmrtCollection, SmrtObject } from '@happyvertical/smrt-core';
2
3
  export type BillingOwnerMode = 'self' | 'reseller';
3
4
  export interface BillingRelationshipView {
4
5
  childTenantId: string;
package/dist/context.d.ts CHANGED
@@ -1,4 +1,34 @@
1
- import { DatabaseInterface } from '@happyvertical/sql';
1
+ /**
2
+ * TenantContext - AsyncLocalStorage-based tenant context propagation
3
+ *
4
+ * Provides request-scoped tenant context that flows through async operations.
5
+ * This is the core of the tenancy system - all tenant isolation depends on
6
+ * having a valid context.
7
+ *
8
+ * @example Basic usage with middleware
9
+ * ```typescript
10
+ * import { withTenant, requireTenantId } from '@happyvertical/smrt-tenancy';
11
+ *
12
+ * // In middleware (SvelteKit, Express, etc.)
13
+ * await withTenant({ tenantId: 'tenant-123' }, async () => {
14
+ * // All code in this async tree has access to tenant context
15
+ * const id = requireTenantId(); // 'tenant-123'
16
+ * });
17
+ * ```
18
+ *
19
+ * @example Background job binding
20
+ * ```typescript
21
+ * import { TenantContext } from '@happyvertical/smrt-tenancy';
22
+ *
23
+ * // Bind a callback to preserve context across async boundaries
24
+ * setTimeout(TenantContext.bind(() => {
25
+ * console.log(requireTenantId()); // Works!
26
+ * }), 1000);
27
+ * ```
28
+ *
29
+ * @see https://github.com/happyvertical/smrt/issues/675
30
+ */
31
+ import type { DatabaseInterface } from '@happyvertical/sql';
2
32
  /**
3
33
  * Full data stored in tenant context for the current async execution scope.
4
34
  *
@@ -1,5 +1,28 @@
1
- import { CompatiblePropertyDecorator } from '@happyvertical/smrt-core';
2
- import { TenantIdFieldOptions } from './fields.js';
1
+ /**
2
+ * Tenancy Decorators
3
+ *
4
+ * Provides class and property decorators for tenant-scoped SMRT objects.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { smrt, SmrtObject } from '@happyvertical/smrt-core';
9
+ * import { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';
10
+ *
11
+ * @smrt()
12
+ * @TenantScoped({ mode: 'optional' })
13
+ * class Document extends SmrtObject {
14
+ * @tenantId({ nullable: true })
15
+ * tenantId: string | null = null; // null = global document
16
+ *
17
+ * title: string = '';
18
+ * }
19
+ * ```
20
+ *
21
+ * @see https://github.com/happyvertical/smrt/issues/675
22
+ * @see https://github.com/happyvertical/smrt/issues/829
23
+ */
24
+ import { type CompatiblePropertyDecorator } from '@happyvertical/smrt-core';
25
+ import type { TenantIdFieldOptions } from './fields.js';
3
26
  /**
4
27
  * Options accepted by the `@TenantScoped()` class decorator.
5
28
  *
package/dist/index.d.ts CHANGED
@@ -1,3 +1,38 @@
1
+ /**
2
+ * @happyvertical/smrt-tenancy
3
+ *
4
+ * Production-ready multi-tenancy framework for SMRT with automatic tenant isolation.
5
+ *
6
+ * @example Basic setup
7
+ * ```typescript
8
+ * import { enableTenancy, TenantScoped, tenantId, withTenant } from '@happyvertical/smrt-tenancy';
9
+ * import { smrt, SmrtObject } from '@happyvertical/smrt-core';
10
+ *
11
+ * // 1. Enable tenancy globally (once at app startup)
12
+ * enableTenancy();
13
+ *
14
+ * // 2. Mark classes as tenant-scoped with @tenantId decorator
15
+ * @smrt()
16
+ * @TenantScoped({ mode: 'optional' })
17
+ * class Document extends SmrtObject {
18
+ * @tenantId({ nullable: true })
19
+ * tenantId: string | null = null; // null = global document
20
+ *
21
+ * title: string = '';
22
+ * }
23
+ *
24
+ * // 3. Wrap requests in tenant context
25
+ * await withTenant({ tenantId: 'tenant-123' }, async () => {
26
+ * // All queries automatically filtered by tenant
27
+ * const docs = await documentCollection.list({ where: { status: 'active' } });
28
+ * // Actually executes: WHERE tenant_id = 'tenant-123' AND status = 'active'
29
+ * });
30
+ * ```
31
+ *
32
+ * @see https://github.com/happyvertical/smrt/issues/675
33
+ * @see https://github.com/happyvertical/smrt/issues/829 - Decorator pattern for tenantId
34
+ */
35
+ import './__smrt-register__.js';
1
36
  export { type CliContextOptions, createCliContext, createExpressMiddleware, createSvelteKitHandle, type ExpressMiddlewareOptions, type SvelteKitHandleOptions, } from './adapters/index.js';
2
37
  export { type BillingOwnerMode, BillingRelationship, type BillingRelationshipAction, BillingRelationshipError, BillingRelationshipService, type BillingRelationshipServiceOptions, type BillingRelationshipView, } from './billing-relationship.js';
3
38
  export { enterTenantContext, getCurrentTenant, getTenantId, hasTenantContext, isSuperAdminBypass, isSystemContext, type MinimalTenantContext, requireTenant, requireTenantId, TenantContext, type TenantContextData, TenantContextError, TenantIsolationError, withSuperAdminBypass, withSystemContext, withTenant, withTenantSync, } from './context.js';
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { n as createExpressMiddleware, r as createCliContext, t as createSvelteK
3
3
  import { S as unregisterTenantScopedClass, _ as getAllTenantScopedClasses, a as setupTestTenancy, b as registerTenantScopedClass, c as disableTenancy, d as queryGlobal, f as queryWithGlobals, g as clearTenantScopedRegistry, h as isTenancyEnabled, i as resetTenancy, l as enableTenancy, m as runTenantScopedEntryPoint, n as assertTenantIsolationViolation, o as testTenantIsolation, p as withTenantGlobalRead, r as createTestTenantContext, s as createTenantInterceptor, t as assertTenantContextRequired, u as assertTenantReadAllowed, v as getTenantScopedConfig, x as registerTenantScopedConstructor, y as isTenantScopedClass } from "./chunks/testing-s12-pjzJ.js";
4
4
  import { ObjectRegistry, SmrtCollection, SmrtObject, applyPendingDecoratorRegistrations, crossPackageRef, field, isEmbeddedDatabase, isPostgresDatabase, registerCompatibleFieldDecorator, smrt, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
5
5
  //#region src/__smrt-register__.ts
6
- ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-tenancy\",\"packageVersion\":\"0.51.31\",\"objects\":{\"@happyvertical/smrt-tenancy:BillingRelationship\":{\"name\":\"billingrelationship\",\"className\":\"BillingRelationship\",\"qualifiedName\":\"@happyvertical/smrt-tenancy:BillingRelationship\",\"collection\":\"billingrelationships\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/tenancy/src/billing-relationship.ts\",\"packageName\":\"@happyvertical/smrt-tenancy\",\"fields\":{\"childTenantId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-users:Tenant\",\"_meta\":{\"required\":true}},\"resellerTenantId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-users:Tenant\",\"_meta\":{\"required\":true}},\"billingOwnerMode\":{\"type\":\"text\",\"required\":false,\"default\":\"self\"}},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"_smrt_billing_relationships\",\"conflictColumns\":[\"child_tenant_id\"],\"api\":false,\"cli\":false,\"mcp\":false},\"extends\":\"SmrtObject\",\"exportName\":\"BillingRelationship\",\"collectionExportName\":\"BillingRelationshipCollection\",\"validationRules\":[{\"field\":\"childTenantId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"},{\"field\":\"resellerTenantId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"}],\"schema\":{\"tableName\":\"_smrt_billing_relationships\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_billing_relationships\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"child_tenant_id\\\" UUID NOT NULL,\\n \\\"reseller_tenant_id\\\" UUID NOT NULL,\\n \\\"billing_owner_mode\\\" TEXT DEFAULT 'self'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"child_tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"reseller_tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"billing_owner_mode\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"self\"}},\"indexes\":[{\"name\":\"_smrt_billing_relationships_child_tenant_id_idx\",\"columns\":[\"child_tenant_id\"],\"unique\":true},{\"name\":\"_smrt_billing_relationships_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"_smrt_billing_relationships_created_at_idx\",\"columns\":[\"created_at\"]},{\"name\":\"_smrt_billing_relationships_reseller_tenant_id_idx\",\"columns\":[\"reseller_tenant_id\"]}],\"version\":\"4d7f410a\"},\"importPath\":\"@happyvertical/smrt-tenancy\"},\"@happyvertical/smrt-tenancy:BillingRelationshipCollection\":{\"name\":\"billingrelationshipcollection\",\"className\":\"BillingRelationshipCollection\",\"qualifiedName\":\"@happyvertical/smrt-tenancy:BillingRelationshipCollection\",\"collection\":\"billingrelationships\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/tenancy/src/billing-relationship.ts\",\"packageName\":\"@happyvertical/smrt-tenancy\",\"fields\":{},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"_smrt_billing_relationships\",\"api\":false,\"mcp\":false,\"cli\":false},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"BillingRelationship\",\"exportName\":\"BillingRelationshipCollection\",\"collectionExportName\":\"BillingRelationshipCollectionCollection\",\"schema\":{\"tableName\":\"_smrt_billing_relationships\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_billing_relationships\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"child_tenant_id\\\" UUID NOT NULL,\\n \\\"reseller_tenant_id\\\" UUID NOT NULL,\\n \\\"billing_owner_mode\\\" TEXT DEFAULT 'self'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"child_tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"reseller_tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"billing_owner_mode\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"self\"}},\"indexes\":[{\"name\":\"_smrt_billing_relationships_child_tenant_id_idx\",\"columns\":[\"child_tenant_id\"],\"unique\":true},{\"name\":\"_smrt_billing_relationships_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"_smrt_billing_relationships_created_at_idx\",\"columns\":[\"created_at\"]},{\"name\":\"_smrt_billing_relationships_reseller_tenant_id_idx\",\"columns\":[\"reseller_tenant_id\"]}],\"version\":\"4d7f410a\"},\"importPath\":\"@happyvertical/smrt-tenancy\"}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\"]}"));
6
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-tenancy\",\"packageVersion\":\"0.51.33\",\"objects\":{\"@happyvertical/smrt-tenancy:BillingRelationship\":{\"name\":\"billingrelationship\",\"className\":\"BillingRelationship\",\"qualifiedName\":\"@happyvertical/smrt-tenancy:BillingRelationship\",\"collection\":\"billingrelationships\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/tenancy/src/billing-relationship.ts\",\"packageName\":\"@happyvertical/smrt-tenancy\",\"fields\":{\"childTenantId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-users:Tenant\",\"_meta\":{\"required\":true}},\"resellerTenantId\":{\"type\":\"crossPackageRef\",\"required\":true,\"related\":\"@happyvertical/smrt-users:Tenant\",\"_meta\":{\"required\":true}},\"billingOwnerMode\":{\"type\":\"text\",\"required\":false,\"default\":\"self\"}},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"_smrt_billing_relationships\",\"conflictColumns\":[\"child_tenant_id\"],\"api\":false,\"cli\":false,\"mcp\":false},\"extends\":\"SmrtObject\",\"exportName\":\"BillingRelationship\",\"collectionExportName\":\"BillingRelationshipCollection\",\"validationRules\":[{\"field\":\"childTenantId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"},{\"field\":\"resellerTenantId\",\"rule\":\"required\",\"fieldType\":\"crossPackageRef\"}],\"schema\":{\"tableName\":\"_smrt_billing_relationships\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_billing_relationships\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"child_tenant_id\\\" UUID NOT NULL,\\n \\\"reseller_tenant_id\\\" UUID NOT NULL,\\n \\\"billing_owner_mode\\\" TEXT DEFAULT 'self'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"child_tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"reseller_tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"billing_owner_mode\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"self\"}},\"indexes\":[{\"name\":\"_smrt_billing_relationships_child_tenant_id_idx\",\"columns\":[\"child_tenant_id\"],\"unique\":true},{\"name\":\"_smrt_billing_relationships_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"_smrt_billing_relationships_created_at_idx\",\"columns\":[\"created_at\"]},{\"name\":\"_smrt_billing_relationships_reseller_tenant_id_idx\",\"columns\":[\"reseller_tenant_id\"]}],\"version\":\"4d7f410a\"},\"importPath\":\"@happyvertical/smrt-tenancy\"},\"@happyvertical/smrt-tenancy:BillingRelationshipCollection\":{\"name\":\"billingrelationshipcollection\",\"className\":\"BillingRelationshipCollection\",\"qualifiedName\":\"@happyvertical/smrt-tenancy:BillingRelationshipCollection\",\"collection\":\"billingrelationships\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/tenancy/src/billing-relationship.ts\",\"packageName\":\"@happyvertical/smrt-tenancy\",\"fields\":{},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"_smrt_billing_relationships\",\"api\":false,\"mcp\":false,\"cli\":false},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"BillingRelationship\",\"exportName\":\"BillingRelationshipCollection\",\"collectionExportName\":\"BillingRelationshipCollectionCollection\",\"schema\":{\"tableName\":\"_smrt_billing_relationships\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_billing_relationships\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"child_tenant_id\\\" UUID NOT NULL,\\n \\\"reseller_tenant_id\\\" UUID NOT NULL,\\n \\\"billing_owner_mode\\\" TEXT DEFAULT 'self'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"child_tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"reseller_tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":true,\"unique\":false},\"billing_owner_mode\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"self\"}},\"indexes\":[{\"name\":\"_smrt_billing_relationships_child_tenant_id_idx\",\"columns\":[\"child_tenant_id\"],\"unique\":true},{\"name\":\"_smrt_billing_relationships_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"_smrt_billing_relationships_created_at_idx\",\"columns\":[\"created_at\"]},{\"name\":\"_smrt_billing_relationships_reseller_tenant_id_idx\",\"columns\":[\"reseller_tenant_id\"]}],\"version\":\"4d7f410a\"},\"importPath\":\"@happyvertical/smrt-tenancy\"}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\"]}"));
7
7
  //#endregion
8
8
  //#region src/billing-relationship.ts
9
9
  var __defProp = Object.defineProperty;
@@ -1,4 +1,14 @@
1
- import { CollectionInterceptor, DispatchBus, InterceptorContext } from '@happyvertical/smrt-core';
1
+ /**
2
+ * Tenant Interceptor - Core enforcement mechanism
3
+ *
4
+ * Registers with GlobalInterceptors in smrt-core to automatically:
5
+ * - Filter queries by tenant ID
6
+ * - Validate tenant context on save/delete
7
+ * - Block or audit raw SQL on tenant-scoped classes
8
+ *
9
+ * @see https://github.com/happyvertical/smrt/issues/675
10
+ */
11
+ import { type CollectionInterceptor, type DispatchBus, type InterceptorContext } from '@happyvertical/smrt-core';
2
12
  import { isTenancyEnabled } from './enabled-state.js';
3
13
  /**
4
14
  * Policy controlling what happens when raw SQL is executed against a
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-tenancy",
5
- "packageVersion": "0.51.31",
5
+ "packageVersion": "0.51.33",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-tenancy:BillingRelationship": {
8
8
  "name": "billingrelationship",
@@ -3,12 +3,12 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-tenancy",
6
- "packageVersion": "0.51.31",
6
+ "packageVersion": "0.51.33",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "75157032de40738bcaf452e8a142cbda6bdd3fb006af04ab3ac0863dab1f7ab9",
11
- "packageJson": "7730fb98467d397eb13aa8a14a42409f9ac7108a29d35da6719dbcfb51473bbd",
10
+ "manifest": "ac7c5c8df2bd76523e33241d7f4d6c0601531c80627181cdf9e28cbc496aebcf",
11
+ "packageJson": "323b6a7f53dd497888818e5e04fe17fa3f6cfe40444af722925976e846364824",
12
12
  "agents": "7aaa00d7fb58fd1d31bafcbeeea9aace247a00c1332ad943e101023617b9aa93"
13
13
  },
14
14
  "exports": [
@@ -34,7 +34,7 @@
34
34
  "@types/node": "24.13.6",
35
35
  "svelte": "^5.57.1",
36
36
  "svelte-check": "^4.7.6",
37
- "typescript": "5.9.3",
37
+ "typescript": "6.0.3",
38
38
  "vite": "8.1.4",
39
39
  "vitest": "4.1.10"
40
40
  },
@@ -1,4 +1,43 @@
1
- import { SmrtCollection, SmrtObject } from '@happyvertical/smrt-core';
1
+ /**
2
+ * Shared raw-SQL helpers for tenant-scoped collections' "global" and
3
+ * "tenant + globals" lookups (#1600).
4
+ *
5
+ * Most domain models are `@TenantScoped`. Their collections historically
6
+ * hand-rolled two helpers the OLD way:
7
+ *
8
+ * ```typescript
9
+ * async findGlobal() { return this.list({ where: { tenantId: null } }); }
10
+ * async findWithGlobals(tid) { return this.query(
11
+ * `SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`, [tid]); }
12
+ * ```
13
+ *
14
+ * Under an ACTIVE tenant context with tenancy enabled (default
15
+ * `rawQueryPolicy: 'throw'`) BOTH break:
16
+ * - `findGlobal()` routes an explicit `tenant_id IS NULL` filter through
17
+ * `list()`, which the interceptor flags as an isolation violation → throws.
18
+ * - `findWithGlobals()` issues unflagged raw SQL on a tenant-scoped class,
19
+ * which `beforeQuery` blocks → throws.
20
+ * - `findWithGlobals()` also trusts the caller-supplied `tenantId`, so once the
21
+ * raw bypass is added a caller under tenant-A could read tenant-B by passing
22
+ * B's id.
23
+ *
24
+ * These helpers run raw with `{ allowRawOnTenantScoped: true }` (carrying the
25
+ * tenant predicate themselves), and `queryWithGlobals` re-implements the
26
+ * isolation guard the bypass disables (`assertTenantReadAllowed`): a caller
27
+ * under tenant-A must not read tenant-B's rows by passing tenant-B's id. A
28
+ * system / super-admin-bypass context keeps the deliberate cross-tenant
29
+ * capability for admin paths.
30
+ *
31
+ * STI scoping is derived automatically from the collection's item class via
32
+ * `collection.getStiChildMetaType()` (smrt-core), which mirrors the
33
+ * `_meta_type` scoping `list()` applies: STI **child** collections scope the
34
+ * shared table to their own subtype, while STI **base** and CTI collections do
35
+ * not (a base legitimately spans subtypes; CTI tables have no `_meta_type`).
36
+ * Callers never hand-classify their collection. Promoted from
37
+ * `@happyvertical/smrt-messages` (#1596) so every package shares one
38
+ * implementation.
39
+ */
40
+ import type { SmrtCollection, SmrtObject } from '@happyvertical/smrt-core';
2
41
  /**
3
42
  * Fail closed when an active tenant context requests a different tenant's rows.
4
43
  *
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,25 @@
1
- import { MinimalTenantContext, TenantContextData } from './context.js';
1
+ /**
2
+ * Testing Utilities for smrt-tenancy
3
+ *
4
+ * Helpers for testing tenant-scoped applications.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { createTestTenantContext, resetTenancy } from '@happyvertical/smrt-tenancy/testing';
9
+ *
10
+ * beforeEach(() => {
11
+ * resetTenancy(); // Clear all state
12
+ * });
13
+ *
14
+ * it('should filter by tenant', async () => {
15
+ * await createTestTenantContext({ tenantId: 'tenant-1' }, async () => {
16
+ * const docs = await collection.list({});
17
+ * // Only tenant-1 documents
18
+ * });
19
+ * });
20
+ * ```
21
+ */
22
+ import { type MinimalTenantContext, type TenantContextData } from './context.js';
2
23
  /**
3
24
  * Reset all tenancy state (for use in beforeEach/afterEach)
4
25
  *
package/dist/ui.d.ts CHANGED
@@ -1,4 +1,10 @@
1
- import { ModuleUISlot, SmrtModuleMeta } from '@happyvertical/smrt-types';
1
+ /**
2
+ * Tenancy Module UI Slot Declarations
3
+ *
4
+ * This file defines the UI extension points for the tenancy module.
5
+ * UI components are implemented in the ./svelte subpath.
6
+ */
7
+ import type { ModuleUISlot, SmrtModuleMeta } from '@happyvertical/smrt-types';
2
8
  /**
3
9
  * UI slot definitions for the tenancy module.
4
10
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-tenancy",
3
- "version": "0.51.31",
3
+ "version": "0.51.33",
4
4
  "smrtJsdoc": "strict",
5
5
  "description": "Production-ready multi-tenancy framework for SMRT with automatic tenant isolation and enforcement",
6
6
  "type": "module",
@@ -43,9 +43,9 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@happyvertical/logger": "^0.94.1",
46
- "@happyvertical/smrt-core": "0.51.31",
47
- "@happyvertical/smrt-types": "0.51.31",
48
- "@happyvertical/smrt-ui": "0.51.31",
46
+ "@happyvertical/smrt-core": "0.51.33",
47
+ "@happyvertical/smrt-types": "0.51.33",
48
+ "@happyvertical/smrt-ui": "0.51.33",
49
49
  "@happyvertical/sql": "^0.94.1",
50
50
  "@happyvertical/utils": "^0.94.1"
51
51
  },
@@ -58,13 +58,13 @@
58
58
  }
59
59
  },
60
60
  "devDependencies": {
61
- "@happyvertical/smrt-vitest": "0.51.31",
61
+ "@happyvertical/smrt-vitest": "0.51.33",
62
62
  "@sveltejs/package": "^2.5.8",
63
63
  "@sveltejs/vite-plugin-svelte": "^7.3.0",
64
64
  "@types/node": "24.13.6",
65
65
  "svelte": "^5.57.1",
66
66
  "svelte-check": "^4.7.6",
67
- "typescript": "5.9.3",
67
+ "typescript": "6.0.3",
68
68
  "vite": "8.1.4",
69
69
  "vitest": "4.1.10"
70
70
  },