@omnibase/core-js 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,651 @@
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
+ * @param tenantId - The unique identifier of the tenant to delete
281
+ *
282
+ * @returns Promise resolving to a confirmation message
283
+ *
284
+ * @throws {Error} When the tenantId parameter is missing or empty
285
+ * @throws {Error} When the user is not authenticated
286
+ * @throws {Error} When the user is not an owner of the specified tenant
287
+ * @throws {Error} When the tenant doesn't exist or is not accessible
288
+ * @throws {Error} When the API request fails due to network issues
289
+ * @throws {Error} When the server returns an error response (4xx, 5xx status codes)
290
+ *
291
+ * @example
292
+ * ```typescript
293
+ * const tenantToDelete = 'tenant_abc123';
294
+ *
295
+ * // Always confirm before deleting
296
+ * const userConfirmed = confirm(
297
+ * 'Are you sure you want to delete this tenant? This action cannot be undone.'
298
+ * );
299
+ *
300
+ * if (userConfirmed) {
301
+ * try {
302
+ * const result = await tenantManager.deleteTenant(tenantToDelete);
303
+ * console.log(result.data.message);
304
+ *
305
+ * // Redirect user away from deleted tenant
306
+ * window.location.href = '/dashboard';
307
+ * } catch (error) {
308
+ * console.error('Failed to delete tenant:', error);
309
+ * }
310
+ * }
311
+ * ```
312
+ *
313
+ * @since 0.6.0
314
+ * @public
315
+ * @group Tenant Management
316
+ */
317
+ async deleteTenant(tenantId) {
318
+ if (!tenantId) {
319
+ throw new Error("Tenant ID is required");
320
+ }
321
+ try {
322
+ const response = await this.omnibaseClient.fetch(
323
+ `/api/v1/tenants/${tenantId}`,
324
+ {
325
+ method: "DELETE",
326
+ headers: {
327
+ "Content-Type": "application/json"
328
+ },
329
+ credentials: "include"
330
+ }
331
+ );
332
+ if (!response.ok) {
333
+ const errorData = await response.text();
334
+ throw new Error(
335
+ `Failed to delete tenant: ${response.status} - ${errorData}`
336
+ );
337
+ }
338
+ const data = await response.json();
339
+ return data;
340
+ } catch (error) {
341
+ console.error("Error deleting tenant:", error);
342
+ throw error;
343
+ }
344
+ }
345
+ /**
346
+ * Switches the user's active tenant context
347
+ *
348
+ * Changes the user's active tenant to the specified tenant ID, updating
349
+ * their authentication context and permissions. This function is essential
350
+ * for multi-tenant applications where users belong to multiple tenants
351
+ * and need to switch between them.
352
+ *
353
+ * The function performs several operations:
354
+ * - Validates that the user has access to the specified tenant
355
+ * - Updates the user's active tenant in their session
356
+ * - Generates a new JWT token with updated tenant claims
357
+ * - Updates any cached tenant-specific data
358
+ *
359
+ * After switching tenants, all subsequent API calls will be made within
360
+ * the context of the new active tenant, with row-level security policies
361
+ * applied accordingly. The new JWT token should be used for all future
362
+ * authenticated requests.
363
+ *
364
+ * @param tenantId - The ID of the tenant to switch to (must be a tenant the user belongs to)
365
+ *
366
+ * @returns Promise resolving to a new JWT token and success confirmation
367
+ *
368
+ * @throws {Error} When the tenantId parameter is missing or empty
369
+ * @throws {Error} When the user doesn't have access to the specified tenant
370
+ * @throws {Error} When the user is not authenticated
371
+ * @throws {Error} When the specified tenant doesn't exist
372
+ * @throws {Error} When the API request fails due to network issues
373
+ * @throws {Error} When the server returns an error response (4xx, 5xx status codes)
374
+ *
375
+ * @example
376
+ * ```typescript
377
+ * const result = await tenantManager.switchActiveTenant('tenant_xyz789');
378
+ *
379
+ * // Store the new token for future requests
380
+ * console.log(`Switched to tenant. New token: ${result.data.token}`);
381
+ *
382
+ * // Now all API calls will be in the context of tenant_xyz789
383
+ * const tenantData = await getCurrentTenantData();
384
+ * ```
385
+ *
386
+ * @since 0.6.0
387
+ * @public
388
+ * @group Tenant Management
389
+ */
390
+ async switchActiveTenant(tenantId) {
391
+ if (!tenantId) {
392
+ throw new Error("Tenant ID is required");
393
+ }
394
+ const requestBody = {
395
+ tenant_id: tenantId
396
+ };
397
+ try {
398
+ const response = await this.omnibaseClient.fetch(
399
+ `/api/v1/tenants/switch-active`,
400
+ {
401
+ method: "PUT",
402
+ headers: {
403
+ "Content-Type": "application/json"
404
+ },
405
+ body: JSON.stringify(requestBody),
406
+ credentials: "include"
407
+ }
408
+ );
409
+ if (!response.ok) {
410
+ const errorData = await response.text();
411
+ throw new Error(
412
+ `Failed to switch tenant: ${response.status} - ${errorData}`
413
+ );
414
+ }
415
+ const data = await response.json();
416
+ return data;
417
+ } catch (error) {
418
+ console.error("Error switching active tenant:", error);
419
+ throw error;
420
+ }
421
+ }
422
+ };
423
+
424
+ // src/tenants/user.ts
425
+ var TenantUserManager = class {
426
+ /**
427
+ * Creates a new tenant user manager
428
+ *
429
+ * @param omnibaseClient - Configured OmnibaseClient instance for API communication
430
+ *
431
+ * @group Tenant User Management
432
+ */
433
+ constructor(omnibaseClient) {
434
+ this.omnibaseClient = omnibaseClient;
435
+ }
436
+ /**
437
+ * Removes a user from the active tenant
438
+ *
439
+ * This method removes a specified user from the current active tenant. The operation
440
+ * requires the requesting user to have appropriate permissions (admin or owner role).
441
+ * The user being removed will lose access to the tenant and all its resources.
442
+ *
443
+ * Note: You cannot remove yourself from a tenant using this method. To leave a tenant,
444
+ * use the appropriate leave or delete tenant operations instead.
445
+ *
446
+ * @param data - Request data containing the user ID to remove
447
+ * @param data.user_id - ID of the user to remove from the tenant
448
+ *
449
+ * @returns Promise resolving to an API response confirming the removal
450
+ *
451
+ * @throws {Error} When user_id is not provided
452
+ * @throws {Error} When the API request fails (includes status code and error details)
453
+ * @throws {Error} When the user doesn't have permission to remove users
454
+ * @throws {Error} When the specified user is not a member of the tenant
455
+ *
456
+ * @example
457
+ * ```typescript
458
+ * // Remove a user from the active tenant
459
+ * try {
460
+ * await userManager.remove({ user_id: 'user_abc123' });
461
+ * console.log('User removed successfully');
462
+ * } catch (error) {
463
+ * if (error.message.includes('403')) {
464
+ * console.error('Insufficient permissions to remove user');
465
+ * } else if (error.message.includes('404')) {
466
+ * console.error('User not found in tenant');
467
+ * } else {
468
+ * console.error('Failed to remove user:', error);
469
+ * }
470
+ * }
471
+ * ```
472
+ *
473
+ * @since 0.6.0
474
+ * @public
475
+ * @group Tenant User Management
476
+ */
477
+ async remove(data) {
478
+ if (!data.user_id) {
479
+ throw new Error("user_id is required");
480
+ }
481
+ const response = await this.omnibaseClient.fetch("/api/v1/tenants/users", {
482
+ method: "DELETE",
483
+ body: JSON.stringify(data)
484
+ });
485
+ if (!response.ok) {
486
+ const errorData = await response.text();
487
+ throw new Error(
488
+ `Failed to delete user from tenant: ${response.status} - ${errorData}`
489
+ );
490
+ }
491
+ return await response.json();
492
+ }
493
+ /**
494
+ * Updates a user's role within the active tenant
495
+ *
496
+ * This method changes the role of a specified user in the current active tenant. The operation
497
+ * requires the requesting user to have appropriate permissions (typically admin or owner role).
498
+ * Role updates take effect immediately and affect the user's permissions and access rights
499
+ * within the tenant.
500
+ *
501
+ * Common roles include 'admin', 'member', and 'viewer', but the exact roles available depend
502
+ * on your tenant's configuration. Changing a user's role will modify their ability to perform
503
+ * various operations within the tenant.
504
+ *
505
+ * @param data - Request data containing the user ID and new role
506
+ * @param data.user_id - ID of the user whose role is being updated
507
+ * @param data.role - New role to assign to the user
508
+ *
509
+ * @returns Promise resolving to an API response confirming the role update
510
+ *
511
+ * @throws {Error} When user_id or role is not provided
512
+ * @throws {Error} When the API request fails (includes status code and error details)
513
+ * @throws {Error} When the user doesn't have permission to update roles
514
+ * @throws {Error} When the specified user is not a member of the tenant
515
+ * @throws {Error} When the specified role is invalid or not allowed
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * // Update a user's role to admin
520
+ * try {
521
+ * const result = await userManager.updateRole({
522
+ * user_id: 'user_abc123',
523
+ * role: 'admin'
524
+ * });
525
+ * console.log('Role updated successfully:', result.data.message);
526
+ * } catch (error) {
527
+ * if (error.message.includes('403')) {
528
+ * console.error('Insufficient permissions to update roles');
529
+ * } else if (error.message.includes('404')) {
530
+ * console.error('User not found in tenant');
531
+ * } else {
532
+ * console.error('Failed to update role:', error);
533
+ * }
534
+ * }
535
+ * ```
536
+ *
537
+ * @since 0.6.0
538
+ * @public
539
+ * @group Tenant User Management
540
+ */
541
+ async updateRole(data) {
542
+ if (!data.role || !data.user_id)
543
+ throw new Error("user_id and role is required");
544
+ const response = await this.omnibaseClient.fetch("/api/v1/tenants/users", {
545
+ method: "PUT",
546
+ body: JSON.stringify(data)
547
+ });
548
+ if (!response.ok) {
549
+ const errorData = await response.text();
550
+ throw new Error(
551
+ `Failed to update users role: ${response.status} - ${errorData}`
552
+ );
553
+ }
554
+ return await response.json();
555
+ }
556
+ };
557
+
558
+ // src/tenants/handler.ts
559
+ var TenantHandler = class {
560
+ /**
561
+ * Creates a new TenantHandler instance
562
+ *
563
+ * Initializes the handler with the provided Omnibase client and sets up
564
+ * the specialized manager instances for tenant and invitation operations.
565
+ * The client is used for all underlying HTTP requests and authentication.
566
+ *
567
+ * @param omnibaseClient - Configured Omnibase client instance
568
+ *
569
+ * @example
570
+ * ```typescript
571
+ * const client = new OmnibaseClient({
572
+ * apiKey: 'your-api-key',
573
+ * baseURL: 'https://api.yourapp.com'
574
+ * });
575
+ * const tenantHandler = new TenantHandler(client);
576
+ * ```
577
+ *
578
+ * @group Tenant Management
579
+ */
580
+ constructor(omnibaseClient) {
581
+ this.invites = new TenantInviteManager(omnibaseClient);
582
+ this.manage = new TenantManger(omnibaseClient);
583
+ this.user = new TenantUserManager(omnibaseClient);
584
+ }
585
+ /**
586
+ * Tenant user management operations
587
+ *
588
+ * Provides access to operations for managing users within tenants, including
589
+ * removing users from the active tenant. All operations respect user permissions
590
+ * and tenant ownership rules.
591
+ *
592
+ * @example
593
+ * ```typescript
594
+ * // Remove a user from the active tenant
595
+ * await tenantHandler.user.remove({ user_id: 'user_123' });
596
+ * ```
597
+ *
598
+ * @since 0.6.0
599
+ * @group Tenant Management
600
+ */
601
+ user;
602
+ /**
603
+ * Core tenant management operations
604
+ *
605
+ * Provides access to tenant lifecycle operations including creation,
606
+ * deletion, and active tenant switching. All operations respect user
607
+ * permissions and tenant ownership rules.
608
+ *
609
+ * @example
610
+ * ```typescript
611
+ * // Create a new tenant
612
+ * const tenant = await tenantHandler.manage.createTenant({
613
+ * name: 'New Company',
614
+ * billing_email: 'billing@newcompany.com',
615
+ * user_id: 'user_456'
616
+ * });
617
+ *
618
+ * // Switch to the tenant
619
+ * await tenantHandler.manage.switchActiveTenant(tenant.data.tenant.id);
620
+ *
621
+ * // Delete the tenant (owner only)
622
+ * await tenantHandler.manage.deleteTenant(tenant.data.tenant.id);
623
+ * ```
624
+ */
625
+ manage;
626
+ /**
627
+ * Tenant invitation management operations
628
+ *
629
+ * Provides access to user invitation functionality including creating
630
+ * invitations for new users and accepting existing invitations.
631
+ * Supports role-based access control and secure token-based workflows.
632
+ *
633
+ * @example
634
+ * ```typescript
635
+ * // Create an invitation
636
+ * const invite = await tenantHandler.invites.create({
637
+ * email: 'newuser@company.com',
638
+ * role: 'admin',
639
+ * invite_url: 'https://yourapp.com/accept-invite'
640
+ * });
641
+ *
642
+ * // Accept an invitation (from the invited user's session)
643
+ * const result = await tenantHandler.invites.accept('invite_token_xyz');
644
+ * ```
645
+ */
646
+ invites;
647
+ };
648
+
649
+ export {
650
+ TenantHandler
651
+ };