@omnibase/core-js 0.5.9 → 0.6.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.
@@ -0,0 +1,558 @@
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
+ * Basic invitation acceptance:
46
+ * ```typescript
47
+ * const result = await acceptTenantInvite('inv_secure_token_abc123');
48
+ *
49
+ * console.log(`Successfully joined tenant: ${result.data.tenant_id}`);
50
+ * // User can now access tenant resources
51
+ * await switchActiveTenant(result.data.tenant_id);
52
+ * ```
53
+ *
54
+ * @example
55
+ * Handling the invitation flow:
56
+ * ```typescript
57
+ * // Typically called from an invitation link like:
58
+ * // https://app.com/accept-invite?token=inv_secure_token_abc123
59
+ *
60
+ * const urlParams = new URLSearchParams(window.location.search);
61
+ * const inviteToken = urlParams.get('token');
62
+ *
63
+ * if (inviteToken) {
64
+ * try {
65
+ * const result = await acceptTenantInvite(inviteToken);
66
+ *
67
+ * // Success - redirect to tenant dashboard
68
+ * window.location.href = `/dashboard?tenant=${result.data.tenant_id}`;
69
+ * } catch (error) {
70
+ * console.error('Failed to accept invitation:', error.message);
71
+ * // Show error to user
72
+ * }
73
+ * }
74
+ * ```
75
+ *
76
+ *
77
+ * @since 1.0.0
78
+ * @public
79
+ * @group User Management
80
+ */
81
+ async accept(token) {
82
+ if (!token) {
83
+ throw new Error("Invite token is required");
84
+ }
85
+ const requestBody = {
86
+ token
87
+ };
88
+ try {
89
+ const response = await this.omnibaseClient.fetch(
90
+ `/api/v1/tenants/invites/accept`,
91
+ {
92
+ method: "PUT",
93
+ headers: {
94
+ "Content-Type": "application/json"
95
+ },
96
+ body: JSON.stringify(requestBody),
97
+ credentials: "include"
98
+ }
99
+ );
100
+ if (!response.ok) {
101
+ const errorData = await response.text();
102
+ throw new Error(
103
+ `Failed to accept invite: ${response.status} - ${errorData}`
104
+ );
105
+ }
106
+ const data = await response.json();
107
+ return data;
108
+ } catch (error) {
109
+ console.error("Error accepting tenant invite:", error);
110
+ throw error;
111
+ }
112
+ }
113
+ /**
114
+ * Creates a new user invitation for a specific tenant
115
+ *
116
+ * Generates a secure invitation that allows a user to join the specified
117
+ * tenant with the defined role. The invitation is sent to the provided
118
+ * email address and includes a time-limited token for security.
119
+ *
120
+ * The function creates the invitation record in the database and can
121
+ * trigger email notifications (depending on server configuration).
122
+ * The invitation expires after a predefined time period and can only
123
+ * be used once.
124
+ *
125
+ * Only existing tenant members with appropriate permissions can create
126
+ * invitations. The inviter's authentication is validated via HTTP-only
127
+ * cookies sent with the request.
128
+ *
129
+ * @param tenantId - Unique identifier of the tenant to invite the user to
130
+ * @param inviteData - Configuration object for the invitation
131
+ * @param inviteData.email - Email address of the user to invite
132
+ * @param inviteData.role - Role the user will have after joining (e.g., 'member', 'admin')
133
+ *
134
+ * @returns Promise resolving to the created invitation with secure token
135
+ *
136
+ * @throws {Error} When tenantId parameter is missing or empty
137
+ * @throws {Error} When required fields (email, role) are missing or empty
138
+ * @throws {Error} When the API request fails due to network issues
139
+ * @throws {Error} When the server returns an error response (4xx, 5xx status codes)
140
+ *
141
+ * @example
142
+ * Basic invitation creation:
143
+ * ```typescript
144
+ * const invite = await createTenantUserInvite('tenant_123', {
145
+ * email: 'colleague@company.com',
146
+ * role: 'member'
147
+ * });
148
+ *
149
+ * console.log(`Invite sent to: ${invite.data.invite.email}`);
150
+ * // The invite token can be used to generate invitation links
151
+ * const inviteLink = `https://app.com/accept-invite?token=${invite.data.invite.token}`;
152
+ * ```
153
+ *
154
+ * @example
155
+ * Creating admin invitation:
156
+ * ```typescript
157
+ * const adminInvite = await createTenantUserInvite('tenant_456', {
158
+ * email: 'admin@company.com',
159
+ * role: 'admin'
160
+ * });
161
+ *
162
+ * // Admin users get elevated permissions
163
+ * console.log(`Admin invite created with ID: ${adminInvite.data.invite.id}`);
164
+ * ```
165
+ *
166
+ *
167
+ * @since 1.0.0
168
+ * @public
169
+ * @group User Management
170
+ */
171
+ async create(tenantId, inviteData) {
172
+ if (!tenantId) {
173
+ throw new Error("Tenant ID is required");
174
+ }
175
+ if (!inviteData.email || !inviteData.role) {
176
+ throw new Error("Email and role are required");
177
+ }
178
+ try {
179
+ console.log("PreFetch");
180
+ const response = await this.omnibaseClient.fetch(
181
+ `/api/v1/tenants/invites/${tenantId}`,
182
+ {
183
+ method: "POST",
184
+ headers: {
185
+ "Content-Type": "application/json"
186
+ },
187
+ body: JSON.stringify(inviteData),
188
+ credentials: "include"
189
+ }
190
+ );
191
+ console.log("PostFetch");
192
+ if (!response.ok) {
193
+ const errorData = await response.text();
194
+ throw new Error(
195
+ `Failed to create invite: ${response.status} - ${errorData}`
196
+ );
197
+ }
198
+ const data = await response.json();
199
+ return data;
200
+ } catch (error) {
201
+ console.error("Error creating tenant user invite:", error);
202
+ throw error;
203
+ }
204
+ }
205
+ };
206
+
207
+ // src/tenants/management.ts
208
+ var TenantManger = class {
209
+ /**
210
+ * Creates a new TenantManger instance
211
+ *
212
+ * Initializes the manager with the provided Omnibase client for making
213
+ * authenticated API requests to tenant management endpoints.
214
+ *
215
+ * @param omnibaseClient - Configured Omnibase client instance
216
+ *
217
+ * @group Tenant Management
218
+ */
219
+ constructor(omnibaseClient) {
220
+ this.omnibaseClient = omnibaseClient;
221
+ }
222
+ /**
223
+ * Creates a new tenant in the multi-tenant system
224
+ *
225
+ * Establishes a new tenant with integrated Stripe billing setup and assigns
226
+ * the specified user as the tenant owner. The operation creates the necessary
227
+ * database records and returns a JWT token that enables Row-Level Security
228
+ * access to the tenant's isolated data.
229
+ *
230
+ * The function automatically handles Stripe customer creation for billing
231
+ * integration and sets up the initial tenant configuration. The returned
232
+ * token should be stored securely for subsequent API calls.
233
+ *
234
+ * @param tenantData - Configuration object for the new tenant
235
+ * @param tenantData.name - Display name for the tenant organization
236
+ * @param tenantData.billing_email - Email address for Stripe billing notifications
237
+ * @param tenantData.user_id - Unique identifier of the user who will own this tenant
238
+ *
239
+ * @returns Promise resolving to the created tenant with authentication token
240
+ *
241
+ * @throws {Error} When required fields (name, user_id) are missing or empty
242
+ * @throws {Error} When the API request fails due to network issues
243
+ * @throws {Error} When the server returns an error response (4xx, 5xx status codes)
244
+ *
245
+ * @example
246
+ * Basic tenant creation:
247
+ * ```typescript
248
+ * const newTenant = await createTenant({
249
+ * name: 'Acme Corporation',
250
+ * billing_email: 'billing@acme.com',
251
+ * user_id: 'user_123'
252
+ * });
253
+ * ```
254
+ *
255
+ *
256
+ * @since 1.0.0
257
+ * @public
258
+ * @group Tenant Management
259
+ */
260
+ async createTenant(tenantData) {
261
+ if (!tenantData.name || !tenantData.user_id) {
262
+ throw new Error("Name and user_id are required");
263
+ }
264
+ try {
265
+ const response = await this.omnibaseClient.fetch(`/api/v1/tenants`, {
266
+ method: "POST",
267
+ headers: {
268
+ "Content-Type": "application/json"
269
+ },
270
+ body: JSON.stringify(tenantData),
271
+ credentials: "include"
272
+ });
273
+ if (!response.ok) {
274
+ const errorData = await response.text();
275
+ throw new Error(
276
+ `Failed to create tenant: ${response.status} - ${errorData}`
277
+ );
278
+ }
279
+ const data = await response.json();
280
+ return data;
281
+ } catch (error) {
282
+ console.error("Error creating tenant:", error);
283
+ throw error;
284
+ }
285
+ }
286
+ /**
287
+ * Permanently deletes a tenant and all associated data
288
+ *
289
+ * ⚠️ **WARNING: This operation is irreversible and will permanently delete:**
290
+ * - The tenant record and all metadata
291
+ * - All user memberships and invitations for this tenant
292
+ * - All tenant-specific data protected by row-level security
293
+ * - Any tenant-related billing information
294
+ * - All tenant configuration and settings
295
+ *
296
+ * **Access Control:**
297
+ * Only tenant owners can delete a tenant. This operation requires:
298
+ * - User must be authenticated
299
+ * - User must have 'owner' role for the specified tenant
300
+ * - Tenant must exist and be accessible to the user
301
+ *
302
+ * **Security Considerations:**
303
+ * - All tenant data is immediately and permanently removed
304
+ * - Other tenant members lose access immediately
305
+ * - Any active sessions for this tenant are invalidated
306
+ * - Billing subscriptions are cancelled (if applicable)
307
+ * - Audit logs for deletion are maintained for compliance
308
+ *
309
+ * @param tenantId - The unique identifier of the tenant to delete
310
+ *
311
+ * @returns Promise resolving to a confirmation message
312
+ *
313
+ * @throws {Error} When the tenantId parameter is missing or empty
314
+ * @throws {Error} When the user is not authenticated
315
+ * @throws {Error} When the user is not an owner of the specified tenant
316
+ * @throws {Error} When the tenant doesn't exist or is not accessible
317
+ * @throws {Error} When the API request fails due to network issues
318
+ * @throws {Error} When the server returns an error response (4xx, 5xx status codes)
319
+ *
320
+ * @example
321
+ * Basic tenant deletion with confirmation:
322
+ * ```typescript
323
+ * const tenantToDelete = 'tenant_abc123';
324
+ *
325
+ * // Always confirm before deleting
326
+ * const userConfirmed = confirm(
327
+ * 'Are you sure you want to delete this tenant? This action cannot be undone.'
328
+ * );
329
+ *
330
+ * if (userConfirmed) {
331
+ * try {
332
+ * const result = await deleteTenant(tenantToDelete);
333
+ * console.log(result.data.message); // "Tenant deleted successfully"
334
+ *
335
+ * // Redirect user away from deleted tenant
336
+ * window.location.href = '/dashboard';
337
+ * } catch (error) {
338
+ * console.error('Failed to delete tenant:', error);
339
+ * }
340
+ * }
341
+ * ```
342
+ *
343
+ * @since 1.0.0
344
+ * @public
345
+ * @group Tenant Management
346
+ */
347
+ async deleteTenant(tenantId) {
348
+ if (!tenantId) {
349
+ throw new Error("Tenant ID is required");
350
+ }
351
+ try {
352
+ const response = await this.omnibaseClient.fetch(
353
+ `/api/v1/tenants/${tenantId}`,
354
+ {
355
+ method: "DELETE",
356
+ headers: {
357
+ "Content-Type": "application/json"
358
+ },
359
+ credentials: "include"
360
+ }
361
+ );
362
+ if (!response.ok) {
363
+ const errorData = await response.text();
364
+ throw new Error(
365
+ `Failed to delete tenant: ${response.status} - ${errorData}`
366
+ );
367
+ }
368
+ const data = await response.json();
369
+ return data;
370
+ } catch (error) {
371
+ console.error("Error deleting tenant:", error);
372
+ throw error;
373
+ }
374
+ }
375
+ /**
376
+ * Switches the user's active tenant context
377
+ *
378
+ * Changes the user's active tenant to the specified tenant ID, updating
379
+ * their authentication context and permissions. This function is essential
380
+ * for multi-tenant applications where users belong to multiple tenants
381
+ * and need to switch between them.
382
+ *
383
+ * The function performs several operations:
384
+ * - Validates that the user has access to the specified tenant
385
+ * - Updates the user's active tenant in their session
386
+ * - Generates a new JWT token with updated tenant claims
387
+ * - Updates any cached tenant-specific data
388
+ *
389
+ * After switching tenants, all subsequent API calls will be made within
390
+ * the context of the new active tenant, with row-level security policies
391
+ * applied accordingly. The new JWT token should be used for all future
392
+ * authenticated requests.
393
+ *
394
+ * @param tenantId - The ID of the tenant to switch to (must be a tenant the user belongs to)
395
+ *
396
+ * @returns Promise resolving to a new JWT token and success confirmation
397
+ *
398
+ * @throws {Error} When the tenantId parameter is missing or empty
399
+ * @throws {Error} When the user doesn't have access to the specified tenant
400
+ * @throws {Error} When the user is not authenticated
401
+ * @throws {Error} When the specified tenant doesn't exist
402
+ * @throws {Error} When the API request fails due to network issues
403
+ * @throws {Error} When the server returns an error response (4xx, 5xx status codes)
404
+ *
405
+ * @example
406
+ * Basic tenant switching:
407
+ * ```typescript
408
+ * const result = await switchActiveTenant('tenant_xyz789');
409
+ *
410
+ * // Now all API calls will be in the context of tenant_xyz789
411
+ * const tenantData = await getCurrentTenantData();
412
+ * ```
413
+ *
414
+ * @example
415
+ * Using with tenant-aware data fetching:
416
+ * ```typescript
417
+ * // Switch tenant and immediately fetch tenant-specific data
418
+ * const switchAndLoadTenant = async (tenantId: string) => {
419
+ * try {
420
+ * // Switch to new tenant context
421
+ * const switchResult = await switchActiveTenant(tenantId);
422
+ *
423
+ * // Update authentication token
424
+ * setAuthToken(switchResult.data.token);
425
+ *
426
+ * // Fetch data in new tenant context
427
+ * const [tenantInfo, userPermissions, tenantSettings] = await Promise.all([
428
+ * getTenantInfo(),
429
+ * getUserPermissions(),
430
+ * getTenantSettings()
431
+ * ]);
432
+ *
433
+ * return {
434
+ * tenant: tenantInfo,
435
+ * permissions: userPermissions,
436
+ * settings: tenantSettings
437
+ * };
438
+ * } catch (error) {
439
+ * console.error('Failed to switch tenant and load data:', error);
440
+ * throw error;
441
+ * }
442
+ * };
443
+ * ```
444
+ *
445
+ * @since 1.0.0
446
+ * @public
447
+ * @group Tenant Management
448
+ */
449
+ async switchActiveTenant(tenantId) {
450
+ if (!tenantId) {
451
+ throw new Error("Tenant ID is required");
452
+ }
453
+ const requestBody = {
454
+ tenant_id: tenantId
455
+ };
456
+ try {
457
+ const response = await this.omnibaseClient.fetch(
458
+ `/api/v1/tenants/switch-active`,
459
+ {
460
+ method: "PUT",
461
+ headers: {
462
+ "Content-Type": "application/json"
463
+ },
464
+ body: JSON.stringify(requestBody),
465
+ credentials: "include"
466
+ }
467
+ );
468
+ if (!response.ok) {
469
+ const errorData = await response.text();
470
+ throw new Error(
471
+ `Failed to switch tenant: ${response.status} - ${errorData}`
472
+ );
473
+ }
474
+ const data = await response.json();
475
+ return data;
476
+ } catch (error) {
477
+ console.error("Error switching active tenant:", error);
478
+ throw error;
479
+ }
480
+ }
481
+ };
482
+
483
+ // src/tenants/handler.ts
484
+ var TenantHandler = class {
485
+ /**
486
+ * Creates a new TenantHandler instance
487
+ *
488
+ * Initializes the handler with the provided Omnibase client and sets up
489
+ * the specialized manager instances for tenant and invitation operations.
490
+ * The client is used for all underlying HTTP requests and authentication.
491
+ *
492
+ * @param omnibaseClient - Configured Omnibase client instance
493
+ *
494
+ * @example
495
+ * ```typescript
496
+ * const client = new OmnibaseClient({
497
+ * apiKey: 'your-api-key',
498
+ * baseURL: 'https://api.yourapp.com'
499
+ * });
500
+ * const tenantHandler = new TenantHandler(client);
501
+ * ```
502
+ *
503
+ * @group Tenant Management
504
+ */
505
+ constructor(omnibaseClient) {
506
+ this.omnibaseClient = omnibaseClient;
507
+ this.invites = new TenantInviteManager(this.omnibaseClient);
508
+ this.manage = new TenantManger(this.omnibaseClient);
509
+ }
510
+ /**
511
+ * Core tenant management operations
512
+ *
513
+ * Provides access to tenant lifecycle operations including creation,
514
+ * deletion, and active tenant switching. All operations respect user
515
+ * permissions and tenant ownership rules.
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * // Create a new tenant
520
+ * const tenant = await tenantHandler.tenants.createTenant({
521
+ * name: 'New Company',
522
+ * billing_email: 'billing@newcompany.com',
523
+ * user_id: 'user_456'
524
+ * });
525
+ *
526
+ * // Switch to the tenant
527
+ * await tenantHandler.tenants.switchActiveTenant(tenant.data.tenant.id);
528
+ *
529
+ * // Delete the tenant (owner only)
530
+ * await tenantHandler.tenants.deleteTenant(tenant.data.tenant.id);
531
+ * ```
532
+ */
533
+ manage;
534
+ /**
535
+ * Tenant invitation management operations
536
+ *
537
+ * Provides access to user invitation functionality including creating
538
+ * invitations for new users and accepting existing invitations.
539
+ * Supports role-based access control and secure token-based workflows.
540
+ *
541
+ * @example
542
+ * ```typescript
543
+ * // Create an invitation
544
+ * const invite = await tenantHandler.invites.create('tenant_123', {
545
+ * email: 'newuser@company.com',
546
+ * role: 'admin'
547
+ * });
548
+ *
549
+ * // Accept an invitation (from the invited user's session)
550
+ * const result = await tenantHandler.invites.accept('invite_token_xyz');
551
+ * ```
552
+ */
553
+ invites;
554
+ };
555
+
556
+ export {
557
+ TenantHandler
558
+ };