@omnibase/core-js 0.5.0 → 0.5.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.
- package/dist/chunk-767PUXYD.js +556 -0
- package/dist/chunk-DDFBRGMG.js +106 -0
- package/dist/chunk-LIS5WD3H.js +560 -0
- package/dist/index.cjs +1259 -0
- package/dist/index.d.cts +3 -10
- package/dist/index.d.ts +3 -3
- package/dist/index.js +51 -0
- package/dist/payments/index.d.cts +1986 -2
- package/dist/payments/index.d.ts +1986 -2
- package/dist/payments/index.js +7 -548
- package/dist/permissions/index.js +3 -102
- package/dist/tenants/index.d.cts +2 -1986
- package/dist/tenants/index.d.ts +2 -1986
- package/dist/tenants/index.js +3 -556
- package/package.json +7 -2
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,10 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
6
10
|
var __copyProps = (to, from, except, desc) => {
|
|
7
11
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
12
|
for (let key of __getOwnPropNames(from))
|
|
@@ -15,4 +19,1259 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
15
19
|
|
|
16
20
|
// src/index.ts
|
|
17
21
|
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
OmnibaseClient: () => OmnibaseClient
|
|
24
|
+
});
|
|
18
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/payments/checkout.ts
|
|
28
|
+
var CheckoutManager = class {
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the checkout manager
|
|
31
|
+
*
|
|
32
|
+
* @param paymentHandler - Payment handler instance for API communication
|
|
33
|
+
*
|
|
34
|
+
* @group Checkout
|
|
35
|
+
*/
|
|
36
|
+
constructor(omnibaseClient) {
|
|
37
|
+
this.omnibaseClient = omnibaseClient;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create a new Stripe checkout session
|
|
41
|
+
*
|
|
42
|
+
* Creates a checkout session with the specified options and returns
|
|
43
|
+
* the session URL for redirecting the user to complete payment.
|
|
44
|
+
* The session will be configured for either one-time payment or
|
|
45
|
+
* subscription based on the mode parameter.
|
|
46
|
+
*
|
|
47
|
+
* @param options - Configuration options for the checkout session
|
|
48
|
+
* @param options.price_id - Stripe price ID for the product/service
|
|
49
|
+
* @param options.mode - Payment mode ('payment' for one-time, 'subscription' for recurring)
|
|
50
|
+
* @param options.success_url - URL to redirect after successful payment
|
|
51
|
+
* @param options.cancel_url - URL to redirect if user cancels
|
|
52
|
+
* @param options.customer_id - Optional existing Stripe customer ID
|
|
53
|
+
*
|
|
54
|
+
* @returns Promise resolving to checkout session response with URL and session ID
|
|
55
|
+
*
|
|
56
|
+
* @throws {Error} When the API request fails due to network issues
|
|
57
|
+
* @throws {Error} When the server returns an error response (invalid price_id, etc.)
|
|
58
|
+
* @throws {ValidationError} When required parameters are missing or invalid
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* One-time payment checkout:
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const session = await checkoutManager.createSession({
|
|
64
|
+
* price_id: 'price_one_time_product',
|
|
65
|
+
* mode: 'payment',
|
|
66
|
+
* success_url: 'https://app.com/success',
|
|
67
|
+
* cancel_url: 'https://app.com/cancel'
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* // Redirect to Stripe checkout
|
|
71
|
+
* window.location.href = session.data.url;
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* Subscription checkout with existing customer:
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const session = await checkoutManager.createSession({
|
|
78
|
+
* price_id: 'price_monthly_plan',
|
|
79
|
+
* mode: 'subscription',
|
|
80
|
+
* success_url: 'https://app.com/dashboard?welcome=true',
|
|
81
|
+
* cancel_url: 'https://app.com/pricing',
|
|
82
|
+
* customer_id: 'cus_12345'
|
|
83
|
+
* });
|
|
84
|
+
*
|
|
85
|
+
* console.log(`Session created: ${session.data.sessionId}`);
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @since 1.0.0
|
|
89
|
+
* @group Checkout
|
|
90
|
+
*/
|
|
91
|
+
async createSession(options) {
|
|
92
|
+
const response = await this.omnibaseClient.fetch(
|
|
93
|
+
"/api/v1/payments/checkout",
|
|
94
|
+
{
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": "application/json"
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify(options)
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
const errorData = await response.text();
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Failed to create checkout session: ${response.status} - ${errorData}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const result = await response.json();
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/payments/config.ts
|
|
114
|
+
var ConfigManager = class {
|
|
115
|
+
constructor(omnibaseClient) {
|
|
116
|
+
this.omnibaseClient = omnibaseClient;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get the current Stripe configuration from the database
|
|
120
|
+
*
|
|
121
|
+
* Retrieves the latest Stripe configuration including products, prices,
|
|
122
|
+
* and UI customization data. This configuration represents the current
|
|
123
|
+
* active pricing structure with all UI elements for pricing table rendering.
|
|
124
|
+
*
|
|
125
|
+
* @returns Promise resolving to the current Stripe configuration
|
|
126
|
+
*
|
|
127
|
+
* @throws {Error} When the API request fails due to network issues
|
|
128
|
+
* @throws {Error} When the server returns an error response (4xx, 5xx status codes)
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* Basic usage:
|
|
132
|
+
* ```typescript
|
|
133
|
+
* const config = await getStripeConfig();
|
|
134
|
+
* console.log(`Found ${config.data.config.products.length} products`);
|
|
135
|
+
*
|
|
136
|
+
* // Access product UI configuration
|
|
137
|
+
* config.data.config.products.forEach(product => {
|
|
138
|
+
* console.log(`${product.name}: ${product.ui?.tagline || 'No tagline'}`);
|
|
139
|
+
* });
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
async getStripeConfig() {
|
|
143
|
+
try {
|
|
144
|
+
const response = await this.omnibaseClient.fetch(
|
|
145
|
+
`/api/v1/stripe/config`,
|
|
146
|
+
{
|
|
147
|
+
method: "GET",
|
|
148
|
+
headers: {
|
|
149
|
+
"Content-Type": "application/json"
|
|
150
|
+
},
|
|
151
|
+
credentials: "include"
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
const errorData = await response.text();
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Failed to get Stripe config: ${response.status} - ${errorData}`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const data = await response.json();
|
|
161
|
+
return data;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error("Error getting Stripe config:", error);
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get available products with UI-ready pricing data
|
|
169
|
+
*
|
|
170
|
+
* Transforms the raw Stripe configuration into UI-ready format for pricing
|
|
171
|
+
* table rendering. Includes formatted pricing, features, limits, and all
|
|
172
|
+
* display customizations needed for marketing pages.
|
|
173
|
+
*
|
|
174
|
+
* @returns Promise resolving to products ready for UI consumption
|
|
175
|
+
*
|
|
176
|
+
* @throws {Error} When the API request fails or configuration is invalid
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* Pricing table rendering:
|
|
180
|
+
* ```typescript
|
|
181
|
+
* const products = await getAvailableProducts();
|
|
182
|
+
*
|
|
183
|
+
* products.forEach(product => {
|
|
184
|
+
* const display = product.pricing_display;
|
|
185
|
+
* console.log(`${display.name} - ${display.tagline}`);
|
|
186
|
+
*
|
|
187
|
+
* display.prices.forEach(price => {
|
|
188
|
+
* console.log(` ${price.display_name}: ${price.formatted_price}`);
|
|
189
|
+
* });
|
|
190
|
+
* });
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
async getAvailableProducts() {
|
|
194
|
+
const configResponse = await this.getStripeConfig();
|
|
195
|
+
if (!configResponse.data?.config) {
|
|
196
|
+
throw new Error("No Stripe configuration found");
|
|
197
|
+
}
|
|
198
|
+
const products = configResponse.data.config.products;
|
|
199
|
+
return products.map(transformProductToUIReady).sort(
|
|
200
|
+
(a, b) => a.pricing_display.sort_order - b.pricing_display.sort_order
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get a specific product by ID
|
|
205
|
+
*
|
|
206
|
+
* Retrieves a single product configuration by its ID from the current
|
|
207
|
+
* Stripe configuration. Useful for product-specific operations.
|
|
208
|
+
*
|
|
209
|
+
* @param productId - The configuration product ID to retrieve
|
|
210
|
+
* @returns Promise resolving to the product or null if not found
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const product = await getProduct('starter_plan');
|
|
215
|
+
* if (product) {
|
|
216
|
+
* console.log(`Found product: ${product.name}`);
|
|
217
|
+
* }
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
async getProduct(productId) {
|
|
221
|
+
const configResponse = await this.getStripeConfig();
|
|
222
|
+
if (!configResponse.data?.config) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const product = configResponse.data.config.products.find(
|
|
226
|
+
(p) => p.id === productId
|
|
227
|
+
);
|
|
228
|
+
return product || null;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
function transformProductToUIReady(product) {
|
|
232
|
+
const ui = product.ui || {};
|
|
233
|
+
return {
|
|
234
|
+
...product,
|
|
235
|
+
pricing_display: {
|
|
236
|
+
name: ui.display_name || product.name,
|
|
237
|
+
tagline: ui.tagline,
|
|
238
|
+
features: ui.features || [],
|
|
239
|
+
badge: ui.badge,
|
|
240
|
+
cta_text: ui.cta_text || "Choose Plan",
|
|
241
|
+
highlighted: ui.highlighted || false,
|
|
242
|
+
sort_order: ui.sort_order || 0,
|
|
243
|
+
prices: product.prices.map((price) => {
|
|
244
|
+
const priceUI = price.ui || {};
|
|
245
|
+
return {
|
|
246
|
+
id: price.id,
|
|
247
|
+
display_name: priceUI.display_name || formatDefaultPriceName(price),
|
|
248
|
+
formatted_price: formatPrice(price, priceUI),
|
|
249
|
+
billing_period: priceUI.billing_period || formatDefaultBillingPeriod(price),
|
|
250
|
+
features: priceUI.features || [],
|
|
251
|
+
limits: priceUI.limits || []
|
|
252
|
+
};
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function formatPrice(price, priceUI) {
|
|
258
|
+
if (priceUI.price_display?.custom_text) {
|
|
259
|
+
return priceUI.price_display.custom_text;
|
|
260
|
+
}
|
|
261
|
+
if (!price.amount || price.amount === 0) {
|
|
262
|
+
return "Free";
|
|
263
|
+
}
|
|
264
|
+
const amount = price.amount / 100;
|
|
265
|
+
const currency = price.currency.toUpperCase();
|
|
266
|
+
let formattedPrice = "";
|
|
267
|
+
if (priceUI.price_display?.show_currency !== false) {
|
|
268
|
+
const currencySymbol = getCurrencySymbol(currency);
|
|
269
|
+
formattedPrice = `${currencySymbol}${amount.toFixed(2)}`;
|
|
270
|
+
} else {
|
|
271
|
+
formattedPrice = amount.toFixed(2);
|
|
272
|
+
}
|
|
273
|
+
if (priceUI.price_display?.suffix) {
|
|
274
|
+
formattedPrice += ` ${priceUI.price_display.suffix}`;
|
|
275
|
+
}
|
|
276
|
+
return formattedPrice;
|
|
277
|
+
}
|
|
278
|
+
function getCurrencySymbol(currency) {
|
|
279
|
+
const symbols = {
|
|
280
|
+
USD: "$",
|
|
281
|
+
EUR: "\u20AC",
|
|
282
|
+
GBP: "\xA3",
|
|
283
|
+
JPY: "\xA5",
|
|
284
|
+
CAD: "C$",
|
|
285
|
+
AUD: "A$"
|
|
286
|
+
};
|
|
287
|
+
return symbols[currency] || currency;
|
|
288
|
+
}
|
|
289
|
+
function formatDefaultPriceName(price) {
|
|
290
|
+
if (price.interval) {
|
|
291
|
+
return price.interval.charAt(0).toUpperCase() + price.interval.slice(1);
|
|
292
|
+
}
|
|
293
|
+
return "One-time";
|
|
294
|
+
}
|
|
295
|
+
function formatDefaultBillingPeriod(price) {
|
|
296
|
+
if (price.interval) {
|
|
297
|
+
const count = price.interval_count || 1;
|
|
298
|
+
const period = count === 1 ? price.interval : `${count} ${price.interval}s`;
|
|
299
|
+
return `per ${period}`;
|
|
300
|
+
}
|
|
301
|
+
return "one-time";
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/payments/portal.ts
|
|
305
|
+
var PortalManager = class {
|
|
306
|
+
/**
|
|
307
|
+
* Initialize the portal manager
|
|
308
|
+
*
|
|
309
|
+
* @param paymentHandler - Payment handler instance for API communication
|
|
310
|
+
*
|
|
311
|
+
* @group Portal
|
|
312
|
+
*/
|
|
313
|
+
constructor(omnibaseClient) {
|
|
314
|
+
this.omnibaseClient = omnibaseClient;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Create a new customer portal session
|
|
318
|
+
*
|
|
319
|
+
* Creates a portal session that allows the specified customer to
|
|
320
|
+
* manage their billing information, subscriptions, and payment methods.
|
|
321
|
+
* Returns a URL that the customer should be redirected to.
|
|
322
|
+
*
|
|
323
|
+
* The portal session is temporary and expires after a short period
|
|
324
|
+
* for security. Each access requires creating a new session.
|
|
325
|
+
*
|
|
326
|
+
* @param options - Configuration options for the portal session
|
|
327
|
+
* @param options.customer_id - Stripe customer ID for the user
|
|
328
|
+
* @param options.return_url - URL to redirect to when exiting the portal
|
|
329
|
+
*
|
|
330
|
+
* @returns Promise resolving to portal session response with access URL
|
|
331
|
+
*
|
|
332
|
+
* @throws {Error} When the API request fails due to network issues
|
|
333
|
+
* @throws {Error} When the server returns an error response (invalid customer_id, etc.)
|
|
334
|
+
* @throws {ValidationError} When required parameters are missing or invalid
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* Basic portal creation:
|
|
338
|
+
* ```typescript
|
|
339
|
+
* const portal = await portalManager.create({
|
|
340
|
+
* customer_id: 'cus_abc123',
|
|
341
|
+
* return_url: 'https://myapp.com/account/billing'
|
|
342
|
+
* });
|
|
343
|
+
*
|
|
344
|
+
* // Redirect user to portal
|
|
345
|
+
* window.location.href = portal.data.url;
|
|
346
|
+
* ```
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* With error handling:
|
|
350
|
+
* ```typescript
|
|
351
|
+
* try {
|
|
352
|
+
* const portal = await portalManager.create({
|
|
353
|
+
* customer_id: currentUser.stripeCustomerId,
|
|
354
|
+
* return_url: window.location.origin + '/billing'
|
|
355
|
+
* });
|
|
356
|
+
*
|
|
357
|
+
* window.location.href = portal.data.url;
|
|
358
|
+
* } catch (error) {
|
|
359
|
+
* console.error('Failed to create portal session:', error);
|
|
360
|
+
* showErrorMessage('Unable to access billing portal. Please try again.');
|
|
361
|
+
* }
|
|
362
|
+
* ```
|
|
363
|
+
*
|
|
364
|
+
* @since 1.0.0
|
|
365
|
+
* @group Portal
|
|
366
|
+
*/
|
|
367
|
+
async create(options) {
|
|
368
|
+
const response = await this.omnibaseClient.fetch(
|
|
369
|
+
"/api/v1/payments/portal",
|
|
370
|
+
{
|
|
371
|
+
method: "POST",
|
|
372
|
+
headers: {
|
|
373
|
+
"Content-Type": "application/json"
|
|
374
|
+
},
|
|
375
|
+
body: JSON.stringify(options)
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
if (!response.ok) {
|
|
379
|
+
const errorData = await response.text();
|
|
380
|
+
throw new Error(
|
|
381
|
+
`Failed to create customer portal: ${response.status} - ${errorData}`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
const result = await response.json();
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// src/payments/usage.ts
|
|
390
|
+
var UsageManager = class {
|
|
391
|
+
/**
|
|
392
|
+
* Initialize the usage manager
|
|
393
|
+
*
|
|
394
|
+
* @param paymentHandler - Payment handler instance for API communication
|
|
395
|
+
*
|
|
396
|
+
* @group Usage
|
|
397
|
+
*/
|
|
398
|
+
constructor(omnibaseClient) {
|
|
399
|
+
this.omnibaseClient = omnibaseClient;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Record a usage event for metered billing
|
|
403
|
+
*
|
|
404
|
+
* Records a usage event against a specific meter for billing calculation.
|
|
405
|
+
* The event will be aggregated with other usage events for the billing period
|
|
406
|
+
* to determine the customer's charges for metered products.
|
|
407
|
+
*
|
|
408
|
+
* Usage events should be recorded in real-time or as close to real-time as
|
|
409
|
+
* possible to ensure accurate billing and provide up-to-date usage visibility
|
|
410
|
+
* to customers.
|
|
411
|
+
*
|
|
412
|
+
* @param options - Usage recording options
|
|
413
|
+
* @param options.meter_event_name - Name of the meter to record against
|
|
414
|
+
* @param options.customer_id - Stripe customer ID
|
|
415
|
+
* @param options.value - Usage quantity as string
|
|
416
|
+
*
|
|
417
|
+
* @returns Promise resolving to API response confirmation
|
|
418
|
+
*
|
|
419
|
+
* @throws {Error} When the API request fails due to network issues
|
|
420
|
+
* @throws {Error} When the server returns an error response (invalid meter name, customer, etc.)
|
|
421
|
+
* @throws {ValidationError} When required parameters are missing or invalid
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* API call tracking:
|
|
425
|
+
* ```typescript
|
|
426
|
+
* // Record each API call
|
|
427
|
+
* await usageManager.recordUsage({
|
|
428
|
+
* meter_event_name: 'api_requests',
|
|
429
|
+
* customer_id: user.stripeCustomerId,
|
|
430
|
+
* value: '1'
|
|
431
|
+
* });
|
|
432
|
+
* ```
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* Batch usage recording:
|
|
436
|
+
* ```typescript
|
|
437
|
+
* // Record multiple operations at once
|
|
438
|
+
* const usageEvents = [
|
|
439
|
+
* { meter_event_name: 'compute_hours', customer_id: 'cus_123', value: '0.5' },
|
|
440
|
+
* { meter_event_name: 'storage_gb', customer_id: 'cus_123', value: '10' },
|
|
441
|
+
* { meter_event_name: 'api_calls', customer_id: 'cus_123', value: '50' }
|
|
442
|
+
* ];
|
|
443
|
+
*
|
|
444
|
+
* for (const event of usageEvents) {
|
|
445
|
+
* await usageManager.recordUsage(event);
|
|
446
|
+
* }
|
|
447
|
+
* ```
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
* With error handling:
|
|
451
|
+
* ```typescript
|
|
452
|
+
* try {
|
|
453
|
+
* await usageManager.recordUsage({
|
|
454
|
+
* meter_event_name: 'file_uploads',
|
|
455
|
+
* customer_id: currentUser.stripeCustomerId,
|
|
456
|
+
* value: String(uploadedFiles.length)
|
|
457
|
+
* });
|
|
458
|
+
* } catch (error) {
|
|
459
|
+
* console.error('Failed to record usage:', error);
|
|
460
|
+
* // Usage recording failure shouldn't block user operations
|
|
461
|
+
* // but should be logged for billing accuracy
|
|
462
|
+
* }
|
|
463
|
+
* ```
|
|
464
|
+
*
|
|
465
|
+
* @since 1.0.0
|
|
466
|
+
* @group Usage
|
|
467
|
+
*/
|
|
468
|
+
async recordUsage(options) {
|
|
469
|
+
const response = await this.omnibaseClient.fetch("/api/v1/payments/usage", {
|
|
470
|
+
method: "POST",
|
|
471
|
+
headers: {
|
|
472
|
+
"Content-Type": "application/json"
|
|
473
|
+
},
|
|
474
|
+
body: JSON.stringify(options)
|
|
475
|
+
});
|
|
476
|
+
if (!response.ok) {
|
|
477
|
+
const errorData = await response.text();
|
|
478
|
+
throw new Error(
|
|
479
|
+
`Failed to record usage: ${response.status} - ${errorData}`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
const result = await response.json();
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/payments/handler.ts
|
|
488
|
+
var PaymentHandler = class {
|
|
489
|
+
/**
|
|
490
|
+
* Initialize the payment handler with API configuration
|
|
491
|
+
*
|
|
492
|
+
* Creates a new payment handler instance that will communicate with
|
|
493
|
+
* the specified API endpoint. The handler automatically handles
|
|
494
|
+
* request formatting and authentication headers.
|
|
495
|
+
*
|
|
496
|
+
* @param apiUrl - Base URL for the payment API endpoint
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* ```typescript
|
|
500
|
+
* const paymentHandler = new PaymentHandler('https://api.myapp.com');
|
|
501
|
+
* ```
|
|
502
|
+
*
|
|
503
|
+
* @since 1.0.0
|
|
504
|
+
* @group Client
|
|
505
|
+
*/
|
|
506
|
+
constructor(omnibaseClient) {
|
|
507
|
+
this.omnibaseClient = omnibaseClient;
|
|
508
|
+
this.checkout = new CheckoutManager(this.omnibaseClient);
|
|
509
|
+
this.config = new ConfigManager(this.omnibaseClient);
|
|
510
|
+
this.portal = new PortalManager(this.omnibaseClient);
|
|
511
|
+
this.usage = new UsageManager(this.omnibaseClient);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Checkout session management
|
|
515
|
+
*
|
|
516
|
+
* Provides functionality for creating and managing Stripe checkout sessions
|
|
517
|
+
* for both one-time payments and subscription billing.
|
|
518
|
+
*
|
|
519
|
+
* @example
|
|
520
|
+
* ```typescript
|
|
521
|
+
* const session = await paymentHandler.checkout.createSession({
|
|
522
|
+
* price_id: 'price_monthly',
|
|
523
|
+
* mode: 'subscription',
|
|
524
|
+
* success_url: window.location.origin + '/success',
|
|
525
|
+
* cancel_url: window.location.origin + '/pricing'
|
|
526
|
+
* });
|
|
527
|
+
* ```
|
|
528
|
+
*/
|
|
529
|
+
checkout;
|
|
530
|
+
/**
|
|
531
|
+
* Stripe configuration management
|
|
532
|
+
*
|
|
533
|
+
* Handles retrieval and processing of database-backed Stripe configurations,
|
|
534
|
+
* providing UI-ready product and pricing data for rendering pricing tables.
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* ```typescript
|
|
538
|
+
* const products = await paymentHandler.config.getAvailableProducts();
|
|
539
|
+
* const config = await paymentHandler.config.getStripeConfig();
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
config;
|
|
543
|
+
/**
|
|
544
|
+
* Customer portal management
|
|
545
|
+
*
|
|
546
|
+
* Creates customer portal sessions for subscription management,
|
|
547
|
+
* billing history, and payment method updates.
|
|
548
|
+
*
|
|
549
|
+
* @example
|
|
550
|
+
* ```typescript
|
|
551
|
+
* const portal = await paymentHandler.portal.create({
|
|
552
|
+
* customer_id: 'cus_123',
|
|
553
|
+
* return_url: 'https://app.com/billing'
|
|
554
|
+
* });
|
|
555
|
+
* ```
|
|
556
|
+
*/
|
|
557
|
+
portal;
|
|
558
|
+
/**
|
|
559
|
+
* Usage tracking and metered billing
|
|
560
|
+
*
|
|
561
|
+
* Records usage events for metered billing products and manages
|
|
562
|
+
* usage-based pricing calculations.
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* ```typescript
|
|
566
|
+
* await paymentHandler.usage.recordUsage({
|
|
567
|
+
* meter_event_name: 'api_calls',
|
|
568
|
+
* customer_id: 'cus_123',
|
|
569
|
+
* value: '1'
|
|
570
|
+
* });
|
|
571
|
+
* ```
|
|
572
|
+
*/
|
|
573
|
+
usage;
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// src/permissions/handler.ts
|
|
577
|
+
var import_client = require("@ory/client");
|
|
578
|
+
var PermissionsClient = class {
|
|
579
|
+
/**
|
|
580
|
+
* Ory Keto RelationshipApi for managing subject-object relationships
|
|
581
|
+
*
|
|
582
|
+
* Provides methods for creating, updating, and deleting relationships between
|
|
583
|
+
* subjects (users, groups) and objects (tenants, resources). This API handles
|
|
584
|
+
* write operations and is used to establish permission structures.
|
|
585
|
+
*
|
|
586
|
+
* Key methods:
|
|
587
|
+
* - `createRelationship()` - Creates a new relationship tuple
|
|
588
|
+
* - `deleteRelationships()` - Removes existing relationship tuples
|
|
589
|
+
* - `getRelationships()` - Queries existing relationships
|
|
590
|
+
* - `patchRelationships()` - Updates multiple relationships atomically
|
|
591
|
+
*
|
|
592
|
+
* @example
|
|
593
|
+
* ```typescript
|
|
594
|
+
* // Create a relationship
|
|
595
|
+
* await client.relationships.createRelationship(
|
|
596
|
+
* undefined,
|
|
597
|
+
* {
|
|
598
|
+
* namespace: 'Tenant',
|
|
599
|
+
* object: 'tenant_123',
|
|
600
|
+
* relation: 'members',
|
|
601
|
+
* subjectId: 'user_456'
|
|
602
|
+
* }
|
|
603
|
+
* );
|
|
604
|
+
* ```
|
|
605
|
+
*
|
|
606
|
+
* @since 1.0.0
|
|
607
|
+
* @group Relationships
|
|
608
|
+
*/
|
|
609
|
+
relationships;
|
|
610
|
+
/**
|
|
611
|
+
* Ory Keto PermissionApi for checking permissions
|
|
612
|
+
*
|
|
613
|
+
* Provides methods for querying whether a subject has a specific permission
|
|
614
|
+
* on an object. This API handles read operations and is optimized for fast
|
|
615
|
+
* permission checks in your application logic.
|
|
616
|
+
*
|
|
617
|
+
* Key methods:
|
|
618
|
+
* - `checkPermission()` - Checks if a subject has permission on an object
|
|
619
|
+
* - `checkPermissionOrError()` - Same as above but throws error if denied
|
|
620
|
+
* - `expandPermissions()` - Expands relationships to show all granted permissions
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* ```typescript
|
|
624
|
+
* // Check permission
|
|
625
|
+
* const result = await client.permissions.checkPermission(
|
|
626
|
+
* undefined,
|
|
627
|
+
* {
|
|
628
|
+
* namespace: 'Tenant',
|
|
629
|
+
* object: 'tenant_123',
|
|
630
|
+
* relation: 'view',
|
|
631
|
+
* subjectId: 'user_456'
|
|
632
|
+
* }
|
|
633
|
+
* );
|
|
634
|
+
*
|
|
635
|
+
* console.log('Has permission:', result.data.allowed);
|
|
636
|
+
* ```
|
|
637
|
+
*
|
|
638
|
+
* @since 1.0.0
|
|
639
|
+
* @group Permissions
|
|
640
|
+
*/
|
|
641
|
+
permissions;
|
|
642
|
+
/**
|
|
643
|
+
* Creates a new PermissionsClient instance
|
|
644
|
+
*
|
|
645
|
+
* Initializes the client with separate endpoints for read and write operations.
|
|
646
|
+
* The client automatically appends the appropriate Keto API paths to the base URL
|
|
647
|
+
* for optimal performance and security separation.
|
|
648
|
+
*
|
|
649
|
+
* @param apiBaseUrl - The base URL for your Omnibase API instance
|
|
650
|
+
*
|
|
651
|
+
* @throws {Error} When the base URL is invalid or cannot be reached
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* ```typescript
|
|
655
|
+
* const client = new PermissionsClient('https://api.example.com');
|
|
656
|
+
* ```
|
|
657
|
+
*
|
|
658
|
+
* @example
|
|
659
|
+
* Local development:
|
|
660
|
+
* ```typescript
|
|
661
|
+
* const client = new PermissionsClient('http://localhost:8080');
|
|
662
|
+
* ```
|
|
663
|
+
*
|
|
664
|
+
* @since 1.0.0
|
|
665
|
+
* @group Client
|
|
666
|
+
*/
|
|
667
|
+
constructor(apiBaseUrl) {
|
|
668
|
+
this.relationships = new import_client.RelationshipApi(
|
|
669
|
+
void 0,
|
|
670
|
+
`${apiBaseUrl}/api/v1/permissions/write`
|
|
671
|
+
);
|
|
672
|
+
this.permissions = new import_client.PermissionApi(
|
|
673
|
+
void 0,
|
|
674
|
+
`${apiBaseUrl}/api/v1/permissions/read`
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
// src/tenants/invites.ts
|
|
680
|
+
var TenantInviteManager = class {
|
|
681
|
+
/**
|
|
682
|
+
* Creates a new TenantInviteManager instance
|
|
683
|
+
*
|
|
684
|
+
* Initializes the manager with the provided Omnibase client for making
|
|
685
|
+
* authenticated API requests to tenant invitation endpoints.
|
|
686
|
+
*
|
|
687
|
+
* @param omnibaseClient - Configured Omnibase client instance
|
|
688
|
+
*
|
|
689
|
+
* @group Tenant Invitations
|
|
690
|
+
*/
|
|
691
|
+
constructor(omnibaseClient) {
|
|
692
|
+
this.omnibaseClient = omnibaseClient;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Accepts a tenant invitation using a secure token
|
|
696
|
+
*
|
|
697
|
+
* Processes a tenant invitation by validating the provided token and
|
|
698
|
+
* adding the authenticated user to the specified tenant. The invitation
|
|
699
|
+
* token is consumed during this process and cannot be used again.
|
|
700
|
+
*
|
|
701
|
+
* The function performs several validations:
|
|
702
|
+
* - Verifies the token exists and is valid
|
|
703
|
+
* - Checks that the invitation hasn't expired
|
|
704
|
+
* - Ensures the invitation hasn't already been used
|
|
705
|
+
* - Confirms the user is authenticated via session cookies
|
|
706
|
+
*
|
|
707
|
+
* Upon successful acceptance, the user is granted access to the tenant
|
|
708
|
+
* with the role specified in the original invitation. The invitation
|
|
709
|
+
* record is marked as used and cannot be accepted again.
|
|
710
|
+
*
|
|
711
|
+
* @param token - The secure invitation token from the email invitation
|
|
712
|
+
*
|
|
713
|
+
* @returns Promise resolving to the tenant ID and success confirmation
|
|
714
|
+
*
|
|
715
|
+
* @throws {Error} When the token parameter is missing or empty
|
|
716
|
+
* @throws {Error} When the invitation token is invalid or expired
|
|
717
|
+
* @throws {Error} When the invitation has already been accepted
|
|
718
|
+
* @throws {Error} When the user is not authenticated
|
|
719
|
+
* @throws {Error} When the API request fails due to network issues
|
|
720
|
+
* @throws {Error} When the server returns an error response (4xx, 5xx status codes)
|
|
721
|
+
*
|
|
722
|
+
* @example
|
|
723
|
+
* Basic invitation acceptance:
|
|
724
|
+
* ```typescript
|
|
725
|
+
* const result = await acceptTenantInvite('inv_secure_token_abc123');
|
|
726
|
+
*
|
|
727
|
+
* console.log(`Successfully joined tenant: ${result.data.tenant_id}`);
|
|
728
|
+
* // User can now access tenant resources
|
|
729
|
+
* await switchActiveTenant(result.data.tenant_id);
|
|
730
|
+
* ```
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* Handling the invitation flow:
|
|
734
|
+
* ```typescript
|
|
735
|
+
* // Typically called from an invitation link like:
|
|
736
|
+
* // https://app.com/accept-invite?token=inv_secure_token_abc123
|
|
737
|
+
*
|
|
738
|
+
* const urlParams = new URLSearchParams(window.location.search);
|
|
739
|
+
* const inviteToken = urlParams.get('token');
|
|
740
|
+
*
|
|
741
|
+
* if (inviteToken) {
|
|
742
|
+
* try {
|
|
743
|
+
* const result = await acceptTenantInvite(inviteToken);
|
|
744
|
+
*
|
|
745
|
+
* // Success - redirect to tenant dashboard
|
|
746
|
+
* window.location.href = `/dashboard?tenant=${result.data.tenant_id}`;
|
|
747
|
+
* } catch (error) {
|
|
748
|
+
* console.error('Failed to accept invitation:', error.message);
|
|
749
|
+
* // Show error to user
|
|
750
|
+
* }
|
|
751
|
+
* }
|
|
752
|
+
* ```
|
|
753
|
+
*
|
|
754
|
+
*
|
|
755
|
+
* @since 1.0.0
|
|
756
|
+
* @public
|
|
757
|
+
* @group User Management
|
|
758
|
+
*/
|
|
759
|
+
async accept(token) {
|
|
760
|
+
if (!token) {
|
|
761
|
+
throw new Error("Invite token is required");
|
|
762
|
+
}
|
|
763
|
+
const requestBody = {
|
|
764
|
+
token
|
|
765
|
+
};
|
|
766
|
+
try {
|
|
767
|
+
const response = await this.omnibaseClient.fetch(
|
|
768
|
+
`/api/v1/tenants/invites/accept`,
|
|
769
|
+
{
|
|
770
|
+
method: "PUT",
|
|
771
|
+
headers: {
|
|
772
|
+
"Content-Type": "application/json"
|
|
773
|
+
},
|
|
774
|
+
body: JSON.stringify(requestBody),
|
|
775
|
+
credentials: "include"
|
|
776
|
+
}
|
|
777
|
+
);
|
|
778
|
+
if (!response.ok) {
|
|
779
|
+
const errorData = await response.text();
|
|
780
|
+
throw new Error(
|
|
781
|
+
`Failed to accept invite: ${response.status} - ${errorData}`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
const data = await response.json();
|
|
785
|
+
return data;
|
|
786
|
+
} catch (error) {
|
|
787
|
+
console.error("Error accepting tenant invite:", error);
|
|
788
|
+
throw error;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Creates a new user invitation for a specific tenant
|
|
793
|
+
*
|
|
794
|
+
* Generates a secure invitation that allows a user to join the specified
|
|
795
|
+
* tenant with the defined role. The invitation is sent to the provided
|
|
796
|
+
* email address and includes a time-limited token for security.
|
|
797
|
+
*
|
|
798
|
+
* The function creates the invitation record in the database and can
|
|
799
|
+
* trigger email notifications (depending on server configuration).
|
|
800
|
+
* The invitation expires after a predefined time period and can only
|
|
801
|
+
* be used once.
|
|
802
|
+
*
|
|
803
|
+
* Only existing tenant members with appropriate permissions can create
|
|
804
|
+
* invitations. The inviter's authentication is validated via HTTP-only
|
|
805
|
+
* cookies sent with the request.
|
|
806
|
+
*
|
|
807
|
+
* @param tenantId - Unique identifier of the tenant to invite the user to
|
|
808
|
+
* @param inviteData - Configuration object for the invitation
|
|
809
|
+
* @param inviteData.email - Email address of the user to invite
|
|
810
|
+
* @param inviteData.role - Role the user will have after joining (e.g., 'member', 'admin')
|
|
811
|
+
*
|
|
812
|
+
* @returns Promise resolving to the created invitation with secure token
|
|
813
|
+
*
|
|
814
|
+
* @throws {Error} When tenantId parameter is missing or empty
|
|
815
|
+
* @throws {Error} When required fields (email, role) are missing or empty
|
|
816
|
+
* @throws {Error} When the API request fails due to network issues
|
|
817
|
+
* @throws {Error} When the server returns an error response (4xx, 5xx status codes)
|
|
818
|
+
*
|
|
819
|
+
* @example
|
|
820
|
+
* Basic invitation creation:
|
|
821
|
+
* ```typescript
|
|
822
|
+
* const invite = await createTenantUserInvite('tenant_123', {
|
|
823
|
+
* email: 'colleague@company.com',
|
|
824
|
+
* role: 'member'
|
|
825
|
+
* });
|
|
826
|
+
*
|
|
827
|
+
* console.log(`Invite sent to: ${invite.data.invite.email}`);
|
|
828
|
+
* // The invite token can be used to generate invitation links
|
|
829
|
+
* const inviteLink = `https://app.com/accept-invite?token=${invite.data.invite.token}`;
|
|
830
|
+
* ```
|
|
831
|
+
*
|
|
832
|
+
* @example
|
|
833
|
+
* Creating admin invitation:
|
|
834
|
+
* ```typescript
|
|
835
|
+
* const adminInvite = await createTenantUserInvite('tenant_456', {
|
|
836
|
+
* email: 'admin@company.com',
|
|
837
|
+
* role: 'admin'
|
|
838
|
+
* });
|
|
839
|
+
*
|
|
840
|
+
* // Admin users get elevated permissions
|
|
841
|
+
* console.log(`Admin invite created with ID: ${adminInvite.data.invite.id}`);
|
|
842
|
+
* ```
|
|
843
|
+
*
|
|
844
|
+
*
|
|
845
|
+
* @since 1.0.0
|
|
846
|
+
* @public
|
|
847
|
+
* @group User Management
|
|
848
|
+
*/
|
|
849
|
+
async create(tenantId, inviteData) {
|
|
850
|
+
if (!tenantId) {
|
|
851
|
+
throw new Error("Tenant ID is required");
|
|
852
|
+
}
|
|
853
|
+
if (!inviteData.email || !inviteData.role) {
|
|
854
|
+
throw new Error("Email and role are required");
|
|
855
|
+
}
|
|
856
|
+
try {
|
|
857
|
+
const response = await this.omnibaseClient.fetch(
|
|
858
|
+
`/api/v1/tenants/${tenantId}/invites`,
|
|
859
|
+
{
|
|
860
|
+
method: "POST",
|
|
861
|
+
headers: {
|
|
862
|
+
"Content-Type": "application/json"
|
|
863
|
+
},
|
|
864
|
+
body: JSON.stringify(inviteData),
|
|
865
|
+
credentials: "include"
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
if (!response.ok) {
|
|
869
|
+
const errorData = await response.text();
|
|
870
|
+
throw new Error(
|
|
871
|
+
`Failed to create invite: ${response.status} - ${errorData}`
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
const data = await response.json();
|
|
875
|
+
return data;
|
|
876
|
+
} catch (error) {
|
|
877
|
+
console.error("Error creating tenant user invite:", error);
|
|
878
|
+
throw error;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// src/tenants/tenants.ts
|
|
884
|
+
var TenantManger = class {
|
|
885
|
+
/**
|
|
886
|
+
* Creates a new TenantManger instance
|
|
887
|
+
*
|
|
888
|
+
* Initializes the manager with the provided Omnibase client for making
|
|
889
|
+
* authenticated API requests to tenant management endpoints.
|
|
890
|
+
*
|
|
891
|
+
* @param omnibaseClient - Configured Omnibase client instance
|
|
892
|
+
*
|
|
893
|
+
* @group Tenant Management
|
|
894
|
+
*/
|
|
895
|
+
constructor(omnibaseClient) {
|
|
896
|
+
this.omnibaseClient = omnibaseClient;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Creates a new tenant in the multi-tenant system
|
|
900
|
+
*
|
|
901
|
+
* Establishes a new tenant with integrated Stripe billing setup and assigns
|
|
902
|
+
* the specified user as the tenant owner. The operation creates the necessary
|
|
903
|
+
* database records and returns a JWT token that enables Row-Level Security
|
|
904
|
+
* access to the tenant's isolated data.
|
|
905
|
+
*
|
|
906
|
+
* The function automatically handles Stripe customer creation for billing
|
|
907
|
+
* integration and sets up the initial tenant configuration. The returned
|
|
908
|
+
* token should be stored securely for subsequent API calls.
|
|
909
|
+
*
|
|
910
|
+
* @param tenantData - Configuration object for the new tenant
|
|
911
|
+
* @param tenantData.name - Display name for the tenant organization
|
|
912
|
+
* @param tenantData.billing_email - Email address for Stripe billing notifications
|
|
913
|
+
* @param tenantData.user_id - Unique identifier of the user who will own this tenant
|
|
914
|
+
*
|
|
915
|
+
* @returns Promise resolving to the created tenant with authentication token
|
|
916
|
+
*
|
|
917
|
+
* @throws {Error} When required fields (name, user_id) are missing or empty
|
|
918
|
+
* @throws {Error} When the API request fails due to network issues
|
|
919
|
+
* @throws {Error} When the server returns an error response (4xx, 5xx status codes)
|
|
920
|
+
*
|
|
921
|
+
* @example
|
|
922
|
+
* Basic tenant creation:
|
|
923
|
+
* ```typescript
|
|
924
|
+
* const newTenant = await createTenant({
|
|
925
|
+
* name: 'Acme Corporation',
|
|
926
|
+
* billing_email: 'billing@acme.com',
|
|
927
|
+
* user_id: 'user_123'
|
|
928
|
+
* });
|
|
929
|
+
*
|
|
930
|
+
* console.log(`Created tenant: ${newTenant.data.tenant.name}`);
|
|
931
|
+
* // Store the token for authenticated requests
|
|
932
|
+
* localStorage.setItem('tenant_token', newTenant.data.token);
|
|
933
|
+
* ```
|
|
934
|
+
*
|
|
935
|
+
*
|
|
936
|
+
* @since 1.0.0
|
|
937
|
+
* @public
|
|
938
|
+
* @group Tenant Management
|
|
939
|
+
*/
|
|
940
|
+
async createTenant(tenantData) {
|
|
941
|
+
if (!tenantData.name || !tenantData.user_id) {
|
|
942
|
+
throw new Error("Name and user_id are required");
|
|
943
|
+
}
|
|
944
|
+
try {
|
|
945
|
+
const response = await this.omnibaseClient.fetch(`/api/v1/tenants`, {
|
|
946
|
+
method: "POST",
|
|
947
|
+
headers: {
|
|
948
|
+
"Content-Type": "application/json"
|
|
949
|
+
},
|
|
950
|
+
body: JSON.stringify(tenantData),
|
|
951
|
+
credentials: "include"
|
|
952
|
+
});
|
|
953
|
+
if (!response.ok) {
|
|
954
|
+
const errorData = await response.text();
|
|
955
|
+
throw new Error(
|
|
956
|
+
`Failed to create tenant: ${response.status} - ${errorData}`
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
const data = await response.json();
|
|
960
|
+
return data;
|
|
961
|
+
} catch (error) {
|
|
962
|
+
console.error("Error creating tenant:", error);
|
|
963
|
+
throw error;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Permanently deletes a tenant and all associated data
|
|
968
|
+
*
|
|
969
|
+
* ⚠️ **WARNING: This operation is irreversible and will permanently delete:**
|
|
970
|
+
* - The tenant record and all metadata
|
|
971
|
+
* - All user memberships and invitations for this tenant
|
|
972
|
+
* - All tenant-specific data protected by row-level security
|
|
973
|
+
* - Any tenant-related billing information
|
|
974
|
+
* - All tenant configuration and settings
|
|
975
|
+
*
|
|
976
|
+
* **Access Control:**
|
|
977
|
+
* Only tenant owners can delete a tenant. This operation requires:
|
|
978
|
+
* - User must be authenticated
|
|
979
|
+
* - User must have 'owner' role for the specified tenant
|
|
980
|
+
* - Tenant must exist and be accessible to the user
|
|
981
|
+
*
|
|
982
|
+
* **Security Considerations:**
|
|
983
|
+
* - All tenant data is immediately and permanently removed
|
|
984
|
+
* - Other tenant members lose access immediately
|
|
985
|
+
* - Any active sessions for this tenant are invalidated
|
|
986
|
+
* - Billing subscriptions are cancelled (if applicable)
|
|
987
|
+
* - Audit logs for deletion are maintained for compliance
|
|
988
|
+
*
|
|
989
|
+
* @param tenantId - The unique identifier of the tenant to delete
|
|
990
|
+
*
|
|
991
|
+
* @returns Promise resolving to a confirmation message
|
|
992
|
+
*
|
|
993
|
+
* @throws {Error} When the tenantId parameter is missing or empty
|
|
994
|
+
* @throws {Error} When the user is not authenticated
|
|
995
|
+
* @throws {Error} When the user is not an owner of the specified tenant
|
|
996
|
+
* @throws {Error} When the tenant doesn't exist or is not accessible
|
|
997
|
+
* @throws {Error} When the API request fails due to network issues
|
|
998
|
+
* @throws {Error} When the server returns an error response (4xx, 5xx status codes)
|
|
999
|
+
*
|
|
1000
|
+
* @example
|
|
1001
|
+
* Basic tenant deletion with confirmation:
|
|
1002
|
+
* ```typescript
|
|
1003
|
+
* const tenantToDelete = 'tenant_abc123';
|
|
1004
|
+
*
|
|
1005
|
+
* // Always confirm before deleting
|
|
1006
|
+
* const userConfirmed = confirm(
|
|
1007
|
+
* 'Are you sure you want to delete this tenant? This action cannot be undone.'
|
|
1008
|
+
* );
|
|
1009
|
+
*
|
|
1010
|
+
* if (userConfirmed) {
|
|
1011
|
+
* try {
|
|
1012
|
+
* const result = await deleteTenant(tenantToDelete);
|
|
1013
|
+
* console.log(result.data.message); // "Tenant deleted successfully"
|
|
1014
|
+
*
|
|
1015
|
+
* // Redirect user away from deleted tenant
|
|
1016
|
+
* window.location.href = '/dashboard';
|
|
1017
|
+
* } catch (error) {
|
|
1018
|
+
* console.error('Failed to delete tenant:', error);
|
|
1019
|
+
* }
|
|
1020
|
+
* }
|
|
1021
|
+
* ```
|
|
1022
|
+
*
|
|
1023
|
+
* @since 1.0.0
|
|
1024
|
+
* @public
|
|
1025
|
+
* @group Tenant Management
|
|
1026
|
+
*/
|
|
1027
|
+
async deleteTenant(tenantId) {
|
|
1028
|
+
if (!tenantId) {
|
|
1029
|
+
throw new Error("Tenant ID is required");
|
|
1030
|
+
}
|
|
1031
|
+
try {
|
|
1032
|
+
const response = await this.omnibaseClient.fetch(
|
|
1033
|
+
`/api/v1/tenants/${tenantId}`,
|
|
1034
|
+
{
|
|
1035
|
+
method: "DELETE",
|
|
1036
|
+
headers: {
|
|
1037
|
+
"Content-Type": "application/json"
|
|
1038
|
+
},
|
|
1039
|
+
credentials: "include"
|
|
1040
|
+
}
|
|
1041
|
+
);
|
|
1042
|
+
if (!response.ok) {
|
|
1043
|
+
const errorData = await response.text();
|
|
1044
|
+
throw new Error(
|
|
1045
|
+
`Failed to delete tenant: ${response.status} - ${errorData}`
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
const data = await response.json();
|
|
1049
|
+
return data;
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
console.error("Error deleting tenant:", error);
|
|
1052
|
+
throw error;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Switches the user's active tenant context
|
|
1057
|
+
*
|
|
1058
|
+
* Changes the user's active tenant to the specified tenant ID, updating
|
|
1059
|
+
* their authentication context and permissions. This function is essential
|
|
1060
|
+
* for multi-tenant applications where users belong to multiple tenants
|
|
1061
|
+
* and need to switch between them.
|
|
1062
|
+
*
|
|
1063
|
+
* The function performs several operations:
|
|
1064
|
+
* - Validates that the user has access to the specified tenant
|
|
1065
|
+
* - Updates the user's active tenant in their session
|
|
1066
|
+
* - Generates a new JWT token with updated tenant claims
|
|
1067
|
+
* - Updates any cached tenant-specific data
|
|
1068
|
+
*
|
|
1069
|
+
* After switching tenants, all subsequent API calls will be made within
|
|
1070
|
+
* the context of the new active tenant, with row-level security policies
|
|
1071
|
+
* applied accordingly. The new JWT token should be used for all future
|
|
1072
|
+
* authenticated requests.
|
|
1073
|
+
*
|
|
1074
|
+
* @param tenantId - The ID of the tenant to switch to (must be a tenant the user belongs to)
|
|
1075
|
+
*
|
|
1076
|
+
* @returns Promise resolving to a new JWT token and success confirmation
|
|
1077
|
+
*
|
|
1078
|
+
* @throws {Error} When the tenantId parameter is missing or empty
|
|
1079
|
+
* @throws {Error} When the user doesn't have access to the specified tenant
|
|
1080
|
+
* @throws {Error} When the user is not authenticated
|
|
1081
|
+
* @throws {Error} When the specified tenant doesn't exist
|
|
1082
|
+
* @throws {Error} When the API request fails due to network issues
|
|
1083
|
+
* @throws {Error} When the server returns an error response (4xx, 5xx status codes)
|
|
1084
|
+
*
|
|
1085
|
+
* @example
|
|
1086
|
+
* Basic tenant switching:
|
|
1087
|
+
* ```typescript
|
|
1088
|
+
* const result = await switchActiveTenant('tenant_xyz789');
|
|
1089
|
+
*
|
|
1090
|
+
* // Now all API calls will be in the context of tenant_xyz789
|
|
1091
|
+
* const tenantData = await getCurrentTenantData();
|
|
1092
|
+
* ```
|
|
1093
|
+
*
|
|
1094
|
+
* @example
|
|
1095
|
+
* Using with tenant-aware data fetching:
|
|
1096
|
+
* ```typescript
|
|
1097
|
+
* // Switch tenant and immediately fetch tenant-specific data
|
|
1098
|
+
* const switchAndLoadTenant = async (tenantId: string) => {
|
|
1099
|
+
* try {
|
|
1100
|
+
* // Switch to new tenant context
|
|
1101
|
+
* const switchResult = await switchActiveTenant(tenantId);
|
|
1102
|
+
*
|
|
1103
|
+
* // Update authentication token
|
|
1104
|
+
* setAuthToken(switchResult.data.token);
|
|
1105
|
+
*
|
|
1106
|
+
* // Fetch data in new tenant context
|
|
1107
|
+
* const [tenantInfo, userPermissions, tenantSettings] = await Promise.all([
|
|
1108
|
+
* getTenantInfo(),
|
|
1109
|
+
* getUserPermissions(),
|
|
1110
|
+
* getTenantSettings()
|
|
1111
|
+
* ]);
|
|
1112
|
+
*
|
|
1113
|
+
* return {
|
|
1114
|
+
* tenant: tenantInfo,
|
|
1115
|
+
* permissions: userPermissions,
|
|
1116
|
+
* settings: tenantSettings
|
|
1117
|
+
* };
|
|
1118
|
+
* } catch (error) {
|
|
1119
|
+
* console.error('Failed to switch tenant and load data:', error);
|
|
1120
|
+
* throw error;
|
|
1121
|
+
* }
|
|
1122
|
+
* };
|
|
1123
|
+
* ```
|
|
1124
|
+
*
|
|
1125
|
+
* @since 1.0.0
|
|
1126
|
+
* @public
|
|
1127
|
+
* @group Tenant Management
|
|
1128
|
+
*/
|
|
1129
|
+
async switchActiveTenant(tenantId) {
|
|
1130
|
+
if (!tenantId) {
|
|
1131
|
+
throw new Error("Tenant ID is required");
|
|
1132
|
+
}
|
|
1133
|
+
const requestBody = {
|
|
1134
|
+
tenant_id: tenantId
|
|
1135
|
+
};
|
|
1136
|
+
try {
|
|
1137
|
+
const response = await this.omnibaseClient.fetch(
|
|
1138
|
+
`/api/v1/tenants/switch-active`,
|
|
1139
|
+
{
|
|
1140
|
+
method: "PUT",
|
|
1141
|
+
headers: {
|
|
1142
|
+
"Content-Type": "application/json"
|
|
1143
|
+
},
|
|
1144
|
+
body: JSON.stringify(requestBody),
|
|
1145
|
+
credentials: "include"
|
|
1146
|
+
}
|
|
1147
|
+
);
|
|
1148
|
+
if (!response.ok) {
|
|
1149
|
+
const errorData = await response.text();
|
|
1150
|
+
throw new Error(
|
|
1151
|
+
`Failed to switch tenant: ${response.status} - ${errorData}`
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
const data = await response.json();
|
|
1155
|
+
return data;
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
console.error("Error switching active tenant:", error);
|
|
1158
|
+
throw error;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
// src/tenants/handler.ts
|
|
1164
|
+
var TenantHandler = class {
|
|
1165
|
+
/**
|
|
1166
|
+
* Creates a new TenantHandler instance
|
|
1167
|
+
*
|
|
1168
|
+
* Initializes the handler with the provided Omnibase client and sets up
|
|
1169
|
+
* the specialized manager instances for tenant and invitation operations.
|
|
1170
|
+
* The client is used for all underlying HTTP requests and authentication.
|
|
1171
|
+
*
|
|
1172
|
+
* @param omnibaseClient - Configured Omnibase client instance
|
|
1173
|
+
*
|
|
1174
|
+
* @example
|
|
1175
|
+
* ```typescript
|
|
1176
|
+
* const client = new OmnibaseClient({
|
|
1177
|
+
* apiKey: 'your-api-key',
|
|
1178
|
+
* baseURL: 'https://api.yourapp.com'
|
|
1179
|
+
* });
|
|
1180
|
+
* const tenantHandler = new TenantHandler(client);
|
|
1181
|
+
* ```
|
|
1182
|
+
*
|
|
1183
|
+
* @group Tenant Management
|
|
1184
|
+
*/
|
|
1185
|
+
constructor(omnibaseClient) {
|
|
1186
|
+
this.omnibaseClient = omnibaseClient;
|
|
1187
|
+
this.invites = new TenantInviteManager(this.omnibaseClient);
|
|
1188
|
+
this.tenants = new TenantManger(this.omnibaseClient);
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Core tenant management operations
|
|
1192
|
+
*
|
|
1193
|
+
* Provides access to tenant lifecycle operations including creation,
|
|
1194
|
+
* deletion, and active tenant switching. All operations respect user
|
|
1195
|
+
* permissions and tenant ownership rules.
|
|
1196
|
+
*
|
|
1197
|
+
* @example
|
|
1198
|
+
* ```typescript
|
|
1199
|
+
* // Create a new tenant
|
|
1200
|
+
* const tenant = await tenantHandler.tenants.createTenant({
|
|
1201
|
+
* name: 'New Company',
|
|
1202
|
+
* billing_email: 'billing@newcompany.com',
|
|
1203
|
+
* user_id: 'user_456'
|
|
1204
|
+
* });
|
|
1205
|
+
*
|
|
1206
|
+
* // Switch to the tenant
|
|
1207
|
+
* await tenantHandler.tenants.switchActiveTenant(tenant.data.tenant.id);
|
|
1208
|
+
*
|
|
1209
|
+
* // Delete the tenant (owner only)
|
|
1210
|
+
* await tenantHandler.tenants.deleteTenant(tenant.data.tenant.id);
|
|
1211
|
+
* ```
|
|
1212
|
+
*/
|
|
1213
|
+
tenants;
|
|
1214
|
+
/**
|
|
1215
|
+
* Tenant invitation management operations
|
|
1216
|
+
*
|
|
1217
|
+
* Provides access to user invitation functionality including creating
|
|
1218
|
+
* invitations for new users and accepting existing invitations.
|
|
1219
|
+
* Supports role-based access control and secure token-based workflows.
|
|
1220
|
+
*
|
|
1221
|
+
* @example
|
|
1222
|
+
* ```typescript
|
|
1223
|
+
* // Create an invitation
|
|
1224
|
+
* const invite = await tenantHandler.invites.create('tenant_123', {
|
|
1225
|
+
* email: 'newuser@company.com',
|
|
1226
|
+
* role: 'admin'
|
|
1227
|
+
* });
|
|
1228
|
+
*
|
|
1229
|
+
* // Accept an invitation (from the invited user's session)
|
|
1230
|
+
* const result = await tenantHandler.invites.accept('invite_token_xyz');
|
|
1231
|
+
* ```
|
|
1232
|
+
*/
|
|
1233
|
+
invites;
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
// src/client.ts
|
|
1237
|
+
var OmnibaseClient = class {
|
|
1238
|
+
constructor(config) {
|
|
1239
|
+
this.config = config;
|
|
1240
|
+
this.permissions = new PermissionsClient(this.config.api_url);
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Main payment handler for all payment-related operations
|
|
1244
|
+
*
|
|
1245
|
+
* This class serves as the central coordinator for all payment functionality,
|
|
1246
|
+
* providing access to checkout sessions, billing configuration, customer portals,
|
|
1247
|
+
* and usage tracking. It handles the low-level HTTP communication with the
|
|
1248
|
+
* payment API and delegates specific operations to specialized managers.
|
|
1249
|
+
*
|
|
1250
|
+
* The handler automatically manages authentication, request formatting, and
|
|
1251
|
+
* provides a consistent interface across all payment operations.
|
|
1252
|
+
*
|
|
1253
|
+
* @example
|
|
1254
|
+
* ```typescript
|
|
1255
|
+
* // Create a checkout session
|
|
1256
|
+
* const checkout = await omnibase.payments.checkout.createSession({
|
|
1257
|
+
* price_id: 'price_123',
|
|
1258
|
+
* mode: 'subscription',
|
|
1259
|
+
* success_url: 'https://app.com/success',
|
|
1260
|
+
* cancel_url: 'https://app.com/cancel'
|
|
1261
|
+
* });
|
|
1262
|
+
*
|
|
1263
|
+
* // Get available products
|
|
1264
|
+
* const products = await omnibase.payments.config.getAvailableProducts();
|
|
1265
|
+
* ```
|
|
1266
|
+
*/
|
|
1267
|
+
payments = new PaymentHandler(this);
|
|
1268
|
+
tenants = new TenantHandler(this);
|
|
1269
|
+
permissions;
|
|
1270
|
+
async fetch(endpoint, options = {}) {
|
|
1271
|
+
return await fetch(this.config.api_url + endpoint, options);
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1275
|
+
0 && (module.exports = {
|
|
1276
|
+
OmnibaseClient
|
|
1277
|
+
});
|