@omnibase/core-js 0.7.2 → 0.7.4

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,641 @@
1
+ // src/tenants/invites.ts
2
+ var TenantInviteManager = class {
3
+ /**
4
+ * Creates a new TenantInviteManager instance
5
+ *
6
+ * Initializes the manager with the provided Omnibase client for making
7
+ * authenticated API requests to tenant invitation endpoints.
8
+ *
9
+ * @param omnibaseClient - Configured Omnibase client instance
10
+ *
11
+ * @group Tenant Invitations
12
+ */
13
+ constructor(omnibaseClient) {
14
+ this.omnibaseClient = omnibaseClient;
15
+ }
16
+ /**
17
+ * Accepts a tenant invitation using a secure token
18
+ *
19
+ * Processes a tenant invitation by validating the provided token and
20
+ * adding the authenticated user to the specified tenant. The invitation
21
+ * token is consumed during this process and cannot be used again.
22
+ *
23
+ * The function performs several validations:
24
+ * - Verifies the token exists and is valid
25
+ * - Checks that the invitation hasn't expired
26
+ * - Ensures the invitation hasn't already been used
27
+ * - Confirms the user is authenticated via session cookies
28
+ *
29
+ * Upon successful acceptance, the user is granted access to the tenant
30
+ * with the role specified in the original invitation. The invitation
31
+ * record is marked as used and cannot be accepted again.
32
+ *
33
+ * @param token - The secure invitation token from the email invitation
34
+ *
35
+ * @returns Promise resolving to the tenant ID and success confirmation
36
+ *
37
+ * @throws {Error} When the token parameter is missing or empty
38
+ * @throws {Error} When the invitation token is invalid or expired
39
+ * @throws {Error} When the invitation has already been accepted
40
+ * @throws {Error} When the user is not authenticated
41
+ * @throws {Error} When the API request fails due to network issues
42
+ * @throws {Error} When the server returns an error response (4xx, 5xx status codes)
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * // Typically called from an invitation link like:
47
+ * // https://app.com/accept-invite?token=inv_secure_token_abc123
48
+ *
49
+ * const urlParams = new URLSearchParams(window.location.search);
50
+ * const inviteToken = urlParams.get('token');
51
+ *
52
+ * if (inviteToken) {
53
+ * try {
54
+ * const result = await inviteManager.accept(inviteToken);
55
+ *
56
+ * // Success - redirect to tenant dashboard
57
+ * console.log(`Successfully joined tenant: ${result.data.tenant_id}`);
58
+ * window.location.href = `/dashboard?tenant=${result.data.tenant_id}`;
59
+ * } catch (error) {
60
+ * console.error('Failed to accept invitation:', error.message);
61
+ * }
62
+ * }
63
+ * ```
64
+ *
65
+ * @since 0.6.0
66
+ * @public
67
+ * @group Tenant Invitations
68
+ */
69
+ async accept(token) {
70
+ if (!token) {
71
+ throw new Error("Invite token is required");
72
+ }
73
+ const requestBody = {
74
+ token
75
+ };
76
+ try {
77
+ const response = await this.omnibaseClient.fetch(
78
+ `/api/v1/tenants/invites/accept`,
79
+ {
80
+ method: "PUT",
81
+ headers: {
82
+ "Content-Type": "application/json"
83
+ },
84
+ body: JSON.stringify(requestBody),
85
+ credentials: "include"
86
+ }
87
+ );
88
+ if (!response.ok) {
89
+ const errorData = await response.text();
90
+ throw new Error(
91
+ `Failed to accept invite: ${response.status} - ${errorData}`
92
+ );
93
+ }
94
+ const data = await response.json();
95
+ return data;
96
+ } catch (error) {
97
+ console.error("Error accepting tenant invite:", error);
98
+ throw error;
99
+ }
100
+ }
101
+ /**
102
+ * Creates a new user invitation for the active tenant
103
+ *
104
+ * Generates a secure invitation that allows a user to join the currently active
105
+ * tenant with the defined role. The invitation is sent to the provided email address
106
+ * and includes a time-limited token for security. The invite URL will be automatically
107
+ * appended with ?token=XYZ when sent to the user.
108
+ *
109
+ * The function creates the invitation record in the database and triggers an email
110
+ * notification to the invited user. The invitation expires after 7 days and can only
111
+ * be used once.
112
+ *
113
+ * Only existing tenant members with appropriate permissions (invite_user permission)
114
+ * can create invitations. The inviter's authentication and tenant context are validated
115
+ * via HTTP-only cookies sent with the request.
116
+ *
117
+ * @param inviteData - Configuration object for the invitation
118
+ * @param inviteData.email - Email address of the user to invite
119
+ * @param inviteData.role - Role the user will have after joining (e.g., 'member', 'admin')
120
+ * @param inviteData.invite_url - Base URL for the invitation link (will be appended with ?token=XYZ)
121
+ *
122
+ * @returns Promise resolving to the created invitation with secure token
123
+ *
124
+ * @throws {Error} When required fields (email, role, invite_url) are missing or empty
125
+ * @throws {Error} When the user doesn't have permission to invite users to the tenant
126
+ * @throws {Error} When the API request fails due to network issues
127
+ * @throws {Error} When the server returns an error response (4xx, 5xx status codes)
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * const invite = await inviteManager.create({
132
+ * email: 'colleague@company.com',
133
+ * role: 'member',
134
+ * invite_url: 'https://yourapp.com/accept-invite'
135
+ * });
136
+ *
137
+ * console.log(`Invite sent to: ${invite.data.invite.email}`);
138
+ * console.log(`Invite token: ${invite.data.invite.token}`);
139
+ * ```
140
+ *
141
+ * @since 0.6.0
142
+ * @public
143
+ * @group Tenant Invitations
144
+ */
145
+ async create(inviteData) {
146
+ if (!inviteData.email || !inviteData.role || !inviteData.invite_url) {
147
+ throw new Error(
148
+ "Missing data in `create` - email, role, and invite_url are required"
149
+ );
150
+ }
151
+ try {
152
+ const response = await this.omnibaseClient.fetch(
153
+ `/api/v1/tenants/invites`,
154
+ {
155
+ method: "POST",
156
+ headers: {
157
+ "Content-Type": "application/json"
158
+ },
159
+ body: JSON.stringify(inviteData),
160
+ credentials: "include"
161
+ }
162
+ );
163
+ if (!response.ok) {
164
+ const errorData = await response.text();
165
+ throw new Error(
166
+ `Failed to create invite: ${response.status} - ${errorData}`
167
+ );
168
+ }
169
+ const data = await response.json();
170
+ return data;
171
+ } catch (error) {
172
+ console.error("Error creating tenant user invite:", error);
173
+ throw error;
174
+ }
175
+ }
176
+ };
177
+
178
+ // src/tenants/management.ts
179
+ var TenantManger = class {
180
+ /**
181
+ * Creates a new TenantManger instance
182
+ *
183
+ * Initializes the manager with the provided Omnibase client for making
184
+ * authenticated API requests to tenant management endpoints.
185
+ *
186
+ * @param omnibaseClient - Configured Omnibase client instance
187
+ *
188
+ * @group Tenant Management
189
+ */
190
+ constructor(omnibaseClient) {
191
+ this.omnibaseClient = omnibaseClient;
192
+ }
193
+ /**
194
+ * Creates a new tenant in the multi-tenant system
195
+ *
196
+ * Establishes a new tenant with integrated Stripe billing setup and assigns
197
+ * the specified user as the tenant owner. The operation creates the necessary
198
+ * database records and returns a JWT token that enables Row-Level Security
199
+ * access to the tenant's isolated data.
200
+ *
201
+ * The function automatically handles Stripe customer creation for billing
202
+ * integration and sets up the initial tenant configuration. The returned
203
+ * token should be stored securely for subsequent API calls.
204
+ *
205
+ * @param tenantData - Configuration object for the new tenant
206
+ * @param tenantData.name - Display name for the tenant organization
207
+ * @param tenantData.billing_email - Email address for Stripe billing notifications
208
+ * @param tenantData.user_id - Unique identifier of the user who will own this tenant
209
+ *
210
+ * @returns Promise resolving to the created tenant with authentication token
211
+ *
212
+ * @throws {Error} When required fields (name, user_id) are missing or empty
213
+ * @throws {Error} When the API request fails due to network issues
214
+ * @throws {Error} When the server returns an error response (4xx, 5xx status codes)
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const newTenant = await tenantManager.createTenant({
219
+ * name: 'Acme Corporation',
220
+ * billing_email: 'billing@acme.com',
221
+ * user_id: 'user_123'
222
+ * });
223
+ *
224
+ * console.log(`Tenant created: ${newTenant.data.tenant.id}`);
225
+ * ```
226
+ *
227
+ * @since 0.6.0
228
+ * @public
229
+ * @group Tenant Management
230
+ */
231
+ async createTenant(tenantData) {
232
+ if (!tenantData.name || !tenantData.user_id) {
233
+ throw new Error("Name and user_id are required");
234
+ }
235
+ try {
236
+ const response = await this.omnibaseClient.fetch(`/api/v1/tenants`, {
237
+ method: "POST",
238
+ headers: {
239
+ "Content-Type": "application/json"
240
+ },
241
+ body: JSON.stringify(tenantData),
242
+ credentials: "include"
243
+ });
244
+ if (!response.ok) {
245
+ const errorData = await response.text();
246
+ throw new Error(
247
+ `Failed to create tenant: ${response.status} - ${errorData}`
248
+ );
249
+ }
250
+ const data = await response.json();
251
+ return data;
252
+ } catch (error) {
253
+ console.error("Error creating tenant:", error);
254
+ throw error;
255
+ }
256
+ }
257
+ /**
258
+ * Permanently deletes a tenant and all associated data
259
+ *
260
+ * ⚠️ **WARNING: This operation is irreversible and will permanently delete:**
261
+ * - The tenant record and all metadata
262
+ * - All user memberships and invitations for this tenant
263
+ * - All tenant-specific data protected by row-level security
264
+ * - Any tenant-related billing information
265
+ * - All tenant configuration and settings
266
+ *
267
+ * **Access Control:**
268
+ * Only tenant owners can delete a tenant. This operation requires:
269
+ * - User must be authenticated
270
+ * - User must have 'owner' role for the specified tenant
271
+ * - Tenant must exist and be accessible to the user
272
+ *
273
+ * **Security Considerations:**
274
+ * - All tenant data is immediately and permanently removed
275
+ * - Other tenant members lose access immediately
276
+ * - Any active sessions for this tenant are invalidated
277
+ * - Billing subscriptions are cancelled (if applicable)
278
+ * - Audit logs for deletion are maintained for compliance
279
+ *
280
+ * @returns Promise resolving to a confirmation message
281
+ *
282
+ * @throws {Error} When the tenantId parameter is missing or empty
283
+ * @throws {Error} When the user is not authenticated
284
+ * @throws {Error} When the user is not an owner of the specified tenant
285
+ * @throws {Error} When the tenant doesn't exist or is not accessible
286
+ * @throws {Error} When the API request fails due to network issues
287
+ * @throws {Error} When the server returns an error response (4xx, 5xx status codes)
288
+ *
289
+ * @example
290
+ * ```typescript
291
+ * // Always confirm before deleting
292
+ * const userConfirmed = confirm(
293
+ * 'Are you sure you want to delete this tenant? This action cannot be undone.'
294
+ * );
295
+ *
296
+ * if (userConfirmed) {
297
+ * try {
298
+ * const result = await tenantManager.deleteTenant();
299
+ * console.log(result.data.message);
300
+ *
301
+ * // Redirect user away from deleted tenant
302
+ * window.location.href = '/dashboard';
303
+ * } catch (error) {
304
+ * console.error('Failed to delete tenant:', error);
305
+ * }
306
+ * }
307
+ * ```
308
+ *
309
+ * @since 0.6.0
310
+ * @public
311
+ * @group Tenant Management
312
+ */
313
+ async deleteTenant() {
314
+ try {
315
+ const response = await this.omnibaseClient.fetch(`/api/v1/tenants`, {
316
+ method: "DELETE",
317
+ headers: {
318
+ "Content-Type": "application/json"
319
+ },
320
+ credentials: "include"
321
+ });
322
+ if (!response.ok) {
323
+ const errorData = await response.text();
324
+ throw new Error(
325
+ `Failed to delete tenant: ${response.status} - ${errorData}`
326
+ );
327
+ }
328
+ const data = await response.json();
329
+ return data;
330
+ } catch (error) {
331
+ console.error("Error deleting tenant:", error);
332
+ throw error;
333
+ }
334
+ }
335
+ /**
336
+ * Switches the user's active tenant context
337
+ *
338
+ * Changes the user's active tenant to the specified tenant ID, updating
339
+ * their authentication context and permissions. This function is essential
340
+ * for multi-tenant applications where users belong to multiple tenants
341
+ * and need to switch between them.
342
+ *
343
+ * The function performs several operations:
344
+ * - Validates that the user has access to the specified tenant
345
+ * - Updates the user's active tenant in their session
346
+ * - Generates a new JWT token with updated tenant claims
347
+ * - Updates any cached tenant-specific data
348
+ *
349
+ * After switching tenants, all subsequent API calls will be made within
350
+ * the context of the new active tenant, with row-level security policies
351
+ * applied accordingly. The new JWT token should be used for all future
352
+ * authenticated requests.
353
+ *
354
+ * @param tenantId - The ID of the tenant to switch to (must be a tenant the user belongs to)
355
+ *
356
+ * @returns Promise resolving to a new JWT token and success confirmation
357
+ *
358
+ * @throws {Error} When the tenantId parameter is missing or empty
359
+ * @throws {Error} When the user doesn't have access to the specified tenant
360
+ * @throws {Error} When the user is not authenticated
361
+ * @throws {Error} When the specified tenant doesn't exist
362
+ * @throws {Error} When the API request fails due to network issues
363
+ * @throws {Error} When the server returns an error response (4xx, 5xx status codes)
364
+ *
365
+ * @example
366
+ * ```typescript
367
+ * const result = await tenantManager.switchActiveTenant('tenant_xyz789');
368
+ *
369
+ * // Store the new token for future requests
370
+ * console.log(`Switched to tenant. New token: ${result.data.token}`);
371
+ *
372
+ * // Now all API calls will be in the context of tenant_xyz789
373
+ * const tenantData = await getCurrentTenantData();
374
+ * ```
375
+ *
376
+ * @since 0.6.0
377
+ * @public
378
+ * @group Tenant Management
379
+ */
380
+ async switchActiveTenant(tenantId) {
381
+ if (!tenantId) {
382
+ throw new Error("Tenant ID is required");
383
+ }
384
+ const requestBody = {
385
+ tenant_id: tenantId
386
+ };
387
+ try {
388
+ const response = await this.omnibaseClient.fetch(
389
+ `/api/v1/tenants/switch-active`,
390
+ {
391
+ method: "PUT",
392
+ headers: {
393
+ "Content-Type": "application/json"
394
+ },
395
+ body: JSON.stringify(requestBody),
396
+ credentials: "include"
397
+ }
398
+ );
399
+ if (!response.ok) {
400
+ const errorData = await response.text();
401
+ throw new Error(
402
+ `Failed to switch tenant: ${response.status} - ${errorData}`
403
+ );
404
+ }
405
+ const data = await response.json();
406
+ return data;
407
+ } catch (error) {
408
+ console.error("Error switching active tenant:", error);
409
+ throw error;
410
+ }
411
+ }
412
+ };
413
+
414
+ // src/tenants/user.ts
415
+ var TenantUserManager = class {
416
+ /**
417
+ * Creates a new tenant user manager
418
+ *
419
+ * @param omnibaseClient - Configured OmnibaseClient instance for API communication
420
+ *
421
+ * @group Tenant User Management
422
+ */
423
+ constructor(omnibaseClient) {
424
+ this.omnibaseClient = omnibaseClient;
425
+ }
426
+ /**
427
+ * Removes a user from the active tenant
428
+ *
429
+ * This method removes a specified user from the current active tenant. The operation
430
+ * requires the requesting user to have appropriate permissions (admin or owner role).
431
+ * The user being removed will lose access to the tenant and all its resources.
432
+ *
433
+ * Note: You cannot remove yourself from a tenant using this method. To leave a tenant,
434
+ * use the appropriate leave or delete tenant operations instead.
435
+ *
436
+ * @param data - Request data containing the user ID to remove
437
+ * @param data.user_id - ID of the user to remove from the tenant
438
+ *
439
+ * @returns Promise resolving to an API response confirming the removal
440
+ *
441
+ * @throws {Error} When user_id is not provided
442
+ * @throws {Error} When the API request fails (includes status code and error details)
443
+ * @throws {Error} When the user doesn't have permission to remove users
444
+ * @throws {Error} When the specified user is not a member of the tenant
445
+ *
446
+ * @example
447
+ * ```typescript
448
+ * // Remove a user from the active tenant
449
+ * try {
450
+ * await userManager.remove({ user_id: 'user_abc123' });
451
+ * console.log('User removed successfully');
452
+ * } catch (error) {
453
+ * if (error.message.includes('403')) {
454
+ * console.error('Insufficient permissions to remove user');
455
+ * } else if (error.message.includes('404')) {
456
+ * console.error('User not found in tenant');
457
+ * } else {
458
+ * console.error('Failed to remove user:', error);
459
+ * }
460
+ * }
461
+ * ```
462
+ *
463
+ * @since 0.6.0
464
+ * @public
465
+ * @group Tenant User Management
466
+ */
467
+ async remove(data) {
468
+ if (!data.user_id) {
469
+ throw new Error("user_id is required");
470
+ }
471
+ const response = await this.omnibaseClient.fetch("/api/v1/tenants/users", {
472
+ method: "DELETE",
473
+ body: JSON.stringify(data)
474
+ });
475
+ if (!response.ok) {
476
+ const errorData = await response.text();
477
+ throw new Error(
478
+ `Failed to delete user from tenant: ${response.status} - ${errorData}`
479
+ );
480
+ }
481
+ return await response.json();
482
+ }
483
+ /**
484
+ * Updates a user's role within the active tenant
485
+ *
486
+ * This method changes the role of a specified user in the current active tenant. The operation
487
+ * requires the requesting user to have appropriate permissions (typically admin or owner role).
488
+ * Role updates take effect immediately and affect the user's permissions and access rights
489
+ * within the tenant.
490
+ *
491
+ * Common roles include 'admin', 'member', and 'viewer', but the exact roles available depend
492
+ * on your tenant's configuration. Changing a user's role will modify their ability to perform
493
+ * various operations within the tenant.
494
+ *
495
+ * @param data - Request data containing the user ID and new role
496
+ * @param data.user_id - ID of the user whose role is being updated
497
+ * @param data.role - New role to assign to the user
498
+ *
499
+ * @returns Promise resolving to an API response confirming the role update
500
+ *
501
+ * @throws {Error} When user_id or role is not provided
502
+ * @throws {Error} When the API request fails (includes status code and error details)
503
+ * @throws {Error} When the user doesn't have permission to update roles
504
+ * @throws {Error} When the specified user is not a member of the tenant
505
+ * @throws {Error} When the specified role is invalid or not allowed
506
+ *
507
+ * @example
508
+ * ```typescript
509
+ * // Update a user's role to admin
510
+ * try {
511
+ * const result = await userManager.updateRole({
512
+ * user_id: 'user_abc123',
513
+ * role: 'admin'
514
+ * });
515
+ * console.log('Role updated successfully:', result.data.message);
516
+ * } catch (error) {
517
+ * if (error.message.includes('403')) {
518
+ * console.error('Insufficient permissions to update roles');
519
+ * } else if (error.message.includes('404')) {
520
+ * console.error('User not found in tenant');
521
+ * } else {
522
+ * console.error('Failed to update role:', error);
523
+ * }
524
+ * }
525
+ * ```
526
+ *
527
+ * @since 0.6.0
528
+ * @public
529
+ * @group Tenant User Management
530
+ */
531
+ async updateRole(data) {
532
+ if (!data.role || !data.user_id)
533
+ throw new Error("user_id and role is required");
534
+ const response = await this.omnibaseClient.fetch("/api/v1/tenants/users", {
535
+ method: "PUT",
536
+ body: JSON.stringify(data)
537
+ });
538
+ if (!response.ok) {
539
+ const errorData = await response.text();
540
+ throw new Error(
541
+ `Failed to update users role: ${response.status} - ${errorData}`
542
+ );
543
+ }
544
+ return await response.json();
545
+ }
546
+ };
547
+
548
+ // src/tenants/handler.ts
549
+ var TenantHandler = class {
550
+ /**
551
+ * Creates a new TenantHandler instance
552
+ *
553
+ * Initializes the handler with the provided Omnibase client and sets up
554
+ * the specialized manager instances for tenant and invitation operations.
555
+ * The client is used for all underlying HTTP requests and authentication.
556
+ *
557
+ * @param omnibaseClient - Configured Omnibase client instance
558
+ *
559
+ * @example
560
+ * ```typescript
561
+ * const client = new OmnibaseClient({
562
+ * apiKey: 'your-api-key',
563
+ * baseURL: 'https://api.yourapp.com'
564
+ * });
565
+ * const tenantHandler = new TenantHandler(client);
566
+ * ```
567
+ *
568
+ * @group Tenant Management
569
+ */
570
+ constructor(omnibaseClient) {
571
+ this.invites = new TenantInviteManager(omnibaseClient);
572
+ this.manage = new TenantManger(omnibaseClient);
573
+ this.user = new TenantUserManager(omnibaseClient);
574
+ }
575
+ /**
576
+ * Tenant user management operations
577
+ *
578
+ * Provides access to operations for managing users within tenants, including
579
+ * removing users from the active tenant. All operations respect user permissions
580
+ * and tenant ownership rules.
581
+ *
582
+ * @example
583
+ * ```typescript
584
+ * // Remove a user from the active tenant
585
+ * await tenantHandler.user.remove({ user_id: 'user_123' });
586
+ * ```
587
+ *
588
+ * @since 0.6.0
589
+ * @group Tenant Management
590
+ */
591
+ user;
592
+ /**
593
+ * Core tenant management operations
594
+ *
595
+ * Provides access to tenant lifecycle operations including creation,
596
+ * deletion, and active tenant switching. All operations respect user
597
+ * permissions and tenant ownership rules.
598
+ *
599
+ * @example
600
+ * ```typescript
601
+ * // Create a new tenant
602
+ * const tenant = await tenantHandler.manage.createTenant({
603
+ * name: 'New Company',
604
+ * billing_email: 'billing@newcompany.com',
605
+ * user_id: 'user_456'
606
+ * });
607
+ *
608
+ * // Switch to the tenant
609
+ * await tenantHandler.manage.switchActiveTenant(tenant.data.tenant.id);
610
+ *
611
+ * // Delete the tenant (owner only)
612
+ * await tenantHandler.manage.deleteTenant(tenant.data.tenant.id);
613
+ * ```
614
+ */
615
+ manage;
616
+ /**
617
+ * Tenant invitation management operations
618
+ *
619
+ * Provides access to user invitation functionality including creating
620
+ * invitations for new users and accepting existing invitations.
621
+ * Supports role-based access control and secure token-based workflows.
622
+ *
623
+ * @example
624
+ * ```typescript
625
+ * // Create an invitation
626
+ * const invite = await tenantHandler.invites.create({
627
+ * email: 'newuser@company.com',
628
+ * role: 'admin',
629
+ * invite_url: 'https://yourapp.com/accept-invite'
630
+ * });
631
+ *
632
+ * // Accept an invitation (from the invited user's session)
633
+ * const result = await tenantHandler.invites.accept('invite_token_xyz');
634
+ * ```
635
+ */
636
+ invites;
637
+ };
638
+
639
+ export {
640
+ TenantHandler
641
+ };