@happyvertical/smrt-users 0.37.1 → 0.37.2

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 CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  Multi-tenant user management with RBAC, hierarchical tenants, session handling, and SvelteKit integration.
4
4
 
5
- ## Models (13)
5
+ ## Models (14)
6
6
 
7
7
  | Model | Key Pattern |
8
8
  |-------|-------------|
9
9
  | User | Auth identity. `profileId` is plain string (not FK) to smrt-profiles. Email auto-lowercased. |
10
+ | AccessRequest | "Request access / waitlist" record captured before a `User` exists. CLOSED generated surface (`api`/`mcp`/`cli` = `[]`) — all access via `AccessRequestService`. Email normalized + indexed; JSON `requestContext` (NOT `context` — reserved for slug scoping). |
10
11
  | Tenant | **STI** + hierarchical parent-child. `hierarchyPath` (materialized path), `hierarchyLevel`. Max depth 10. |
11
12
  | Session | Server-side. Secure UUID. TTL in **seconds** (not ms). Status auto-updates to EXPIRED on access. |
12
13
  | MagicLinkToken | Single-use email login token. Backed by `MagicLinkService`. |
package/README.md CHANGED
@@ -228,6 +228,64 @@ Optional-tenancy and global tables are skipped and returned in
228
228
  `result.skipped` instead of generating unsafe policies. Custom permissions can
229
229
  participate in RLS by adding explicit Postgres bindings as shown above.
230
230
 
231
+ ### Access requests (request access / waitlist)
232
+
233
+ Capture a prospective user from a public form *before* they are a real `User`,
234
+ let an operator triage, and **graduate** an approved request into a `User`
235
+ (optionally attached to a tenant). `createAccessRequest` is **public-safe** (no
236
+ auth) — expose it from your own rate-limited endpoint. Operator methods are gated
237
+ by an optional `authorize` hook (capabilities `access-requests:read` /
238
+ `access-requests:manage`). Lifecycle events let apps send invites/notifications;
239
+ this package never owns email delivery.
240
+
241
+ ```typescript
242
+ import { AccessRequestService } from '@happyvertical/smrt-users';
243
+
244
+ const accessRequests = await AccessRequestService.create({
245
+ db: { type: 'postgres', url: process.env.DATABASE_URL },
246
+
247
+ // Optional: gate operator methods against your permission system.
248
+ // (createAccessRequest is always public-safe and never calls this.)
249
+ authorize: async ({ capability, by }) => {
250
+ if (!by || !(await isPlatformOperator(by, capability))) {
251
+ throw new Error(`Missing capability: ${capability}`);
252
+ }
253
+ },
254
+
255
+ // Optional: react to lifecycle changes (send a magic link on graduate, etc.).
256
+ onEvent: async (event) => {
257
+ if (event.type === 'access-request.graduated' && event.user) {
258
+ await sendWelcomeEmail(event.user.email);
259
+ }
260
+ },
261
+ });
262
+
263
+ // 1) Public form handler (app adds rate-limiting) — no auth required.
264
+ const request = await accessRequests.createAccessRequest({
265
+ email: 'jane@example.com',
266
+ name: 'Jane Doe',
267
+ source: 'www',
268
+ context: { company: 'Acme', intendedUse: 'evaluation' },
269
+ });
270
+
271
+ // 2) Operator triages the queue.
272
+ const open = await accessRequests.listAccessRequests({
273
+ status: AccessRequestStatus.REQUESTED,
274
+ by: operatorId,
275
+ });
276
+ await accessRequests.approveAccessRequest(request.id, { by: operatorId });
277
+
278
+ // 3) Graduate into a User — operator picks new-vs-existing tenant per request:
279
+ // a) brand-new tenant, requester as owner
280
+ const { user, tenant, membership } = await accessRequests.graduateAccessRequest(
281
+ request.id,
282
+ { by: operatorId, tenant: { create: { name: 'Acme Inc' } } },
283
+ );
284
+ // b) existing tenant: { tenant: { tenantId, role: 'member' } }
285
+ // c) user only: { tenant: 'none' }
286
+ // Graduation is idempotent and reuses the existing User/Membership paths.
287
+ ```
288
+
231
289
  ### SvelteKit hooks
232
290
 
233
291
  ```typescript
@@ -399,6 +457,7 @@ TenantService supports three modes: `flexible` (no auto-create), `personal` (aut
399
457
  | `MembershipOverride` | Per-user permission grant/deny on a membership. |
400
458
  | `TenantPermissionOverride` | Tenant-level permission override (INHERIT/GRANT/DENY). |
401
459
  | `GroupMember`, `GroupRole`, `RolePermission` | Junction tables for groups and role-permission assignments. |
460
+ | `AccessRequest` | "Request access / waitlist" record captured before a `User` exists. Closed generated surface — all access via `AccessRequestService`. |
402
461
 
403
462
  ### Collections
404
463
 
@@ -409,6 +468,7 @@ TenantService supports three modes: `flexible` (no auto-create), `personal` (aut
409
468
  | `MembershipCollection` | Membership CRUD, `findByUserAndTenant()` |
410
469
  | `MembershipOverrideCollection`, `TenantPermissionOverrideCollection` | Override management at membership and tenant levels |
411
470
  | `GroupCollection`, `GroupMemberCollection`, `GroupRoleCollection`, `RolePermissionCollection` | Group and role-permission junction management |
471
+ | `AccessRequestCollection` | AccessRequest queries: `findByEmail()`, `findOpenByEmail()`, `findByStatus()`, `findOpen()` |
412
472
 
413
473
  ### Services
414
474
 
@@ -423,6 +483,7 @@ TenantService supports three modes: `flexible` (no auto-create), `personal` (aut
423
483
  | `withSessionPermissionContext()` | Loads a session, optionally enters tenancy context, and exposes a request-scoped database/permission context. |
424
484
  | `getCurrentSessionPermissionContext()`, `getRequestScopedDatabase()` | Read the active request/session context inside app code. |
425
485
  | `TenantService` | Policy-driven tenant lifecycle. `ensureTenantForUser()`, `createTenantWithOwnership()`. |
486
+ | `AccessRequestService` | Request-access/waitlist lifecycle + graduation. `createAccessRequest()` (public-safe), `list`/`get`/`approve`/`decline`/`cancel`, `graduateAccessRequest()` (new/existing/no tenant). Capability + event hooks. |
426
487
 
427
488
  ### SvelteKit (`@happyvertical/smrt-users/sveltekit`)
428
489
 
@@ -441,7 +502,9 @@ TenantService supports three modes: `flexible` (no auto-create), `personal` (aut
441
502
  | Export | Description |
442
503
  |--------|-------------|
443
504
  | `UserStatus`, `TenantStatus`, `SessionStatus`, `MembershipStatus` | Status enums |
505
+ | `AccessRequestStatus` | Access-request lifecycle enum (`REQUESTED`/`APPROVED`/`DECLINED`/`GRADUATED`/`CANCELED`) |
444
506
  | `OverrideEffect`, `TenantPermissionEffect` | Override effect enums |
507
+ | `ACCESS_REQUEST_CAPABILITIES`, `AccessRequestError` | Operator capability slugs; typed domain error (`error.code`) |
445
508
  | `DEFAULT_ROLE_SLUGS`, `DEFAULT_ROLES`, `DEFAULT_TENANT_POLICY` | System role slugs, role configs, default tenant policy |
446
509
  | `DEFAULT_SESSION_TTL`, `MAX_TENANT_HIERARCHY_DEPTH` | 604800 (7 days in seconds), 10 |
447
510
  | `TenantHierarchyError` | Thrown when hierarchy depth limit is exceeded |