@serialsubscriptions/platform-integration 0.0.8-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.
Files changed (64) hide show
  1. package/README.md +1 -0
  2. package/lib/SSIProject.d.ts +343 -0
  3. package/lib/SSIProject.js +429 -0
  4. package/lib/SSIProjectApi.d.ts +384 -0
  5. package/lib/SSIProjectApi.js +534 -0
  6. package/lib/SSISubscribedFeatureApi.d.ts +387 -0
  7. package/lib/SSISubscribedFeatureApi.js +511 -0
  8. package/lib/SSISubscribedLimitApi.d.ts +384 -0
  9. package/lib/SSISubscribedLimitApi.js +534 -0
  10. package/lib/SSISubscribedPlanApi.d.ts +395 -0
  11. package/lib/SSISubscribedPlanApi.js +567 -0
  12. package/lib/SubscribedPlanManager.d.ts +400 -0
  13. package/lib/SubscribedPlanManager.js +319 -0
  14. package/lib/UsageApi.d.ts +128 -0
  15. package/lib/UsageApi.js +224 -0
  16. package/lib/auth.server.d.ts +212 -0
  17. package/lib/auth.server.js +624 -0
  18. package/lib/cache/SSICache.d.ts +40 -0
  19. package/lib/cache/SSICache.js +134 -0
  20. package/lib/cache/backends/MemoryCacheBackend.d.ts +15 -0
  21. package/lib/cache/backends/MemoryCacheBackend.js +46 -0
  22. package/lib/cache/backends/RedisCacheBackend.d.ts +27 -0
  23. package/lib/cache/backends/RedisCacheBackend.js +95 -0
  24. package/lib/cache/constants.d.ts +7 -0
  25. package/lib/cache/constants.js +10 -0
  26. package/lib/cache/types.d.ts +27 -0
  27. package/lib/cache/types.js +2 -0
  28. package/lib/clientConfig.d.ts +42 -0
  29. package/lib/clientConfig.js +80 -0
  30. package/lib/frontend/index.d.ts +3 -0
  31. package/lib/frontend/index.js +12 -0
  32. package/lib/frontend/session/SessionClient.d.ts +36 -0
  33. package/lib/frontend/session/SessionClient.js +151 -0
  34. package/lib/index.d.ts +17 -0
  35. package/lib/index.js +40 -0
  36. package/lib/lib/session/SessionClient.d.ts +11 -0
  37. package/lib/lib/session/SessionClient.js +47 -0
  38. package/lib/lib/session/index.d.ts +3 -0
  39. package/lib/lib/session/index.js +3 -0
  40. package/lib/lib/session/stores/MemoryStore.d.ts +7 -0
  41. package/lib/lib/session/stores/MemoryStore.js +23 -0
  42. package/lib/lib/session/stores/index.d.ts +1 -0
  43. package/lib/lib/session/stores/index.js +1 -0
  44. package/lib/lib/session/types.d.ts +37 -0
  45. package/lib/lib/session/types.js +1 -0
  46. package/lib/requestConfig.d.ts +60 -0
  47. package/lib/requestConfig.js +151 -0
  48. package/lib/session/SessionClient.d.ts +19 -0
  49. package/lib/session/SessionClient.js +132 -0
  50. package/lib/session/SessionManager.d.ts +142 -0
  51. package/lib/session/SessionManager.js +437 -0
  52. package/lib/stateStore.d.ts +5 -0
  53. package/lib/stateStore.js +9 -0
  54. package/lib/storage/SSIStorage.d.ts +24 -0
  55. package/lib/storage/SSIStorage.js +117 -0
  56. package/lib/storage/backends/MemoryBackend.d.ts +10 -0
  57. package/lib/storage/backends/MemoryBackend.js +44 -0
  58. package/lib/storage/backends/PostgresBackend.d.ts +24 -0
  59. package/lib/storage/backends/PostgresBackend.js +106 -0
  60. package/lib/storage/backends/RedisBackend.d.ts +19 -0
  61. package/lib/storage/backends/RedisBackend.js +78 -0
  62. package/lib/storage/types.d.ts +27 -0
  63. package/lib/storage/types.js +2 -0
  64. package/package.json +74 -0
@@ -0,0 +1,319 @@
1
+ "use strict";
2
+ /**
3
+ * @file
4
+ * SubscribedPlanManager - High-level manager for subscribed plans with features and limits
5
+ *
6
+ * This manager wires together the three JSON:API clients (SSISubscribedPlanApi,
7
+ * SSISubscribedFeatureApi, SSISubscribedLimitApi) and provides a convenient interface
8
+ * for fetching subscribed plans with their associated features and limits already attached.
9
+ *
10
+ * Usage:
11
+ * @example
12
+ * ```typescript
13
+ * const domain = 'https://account.serialsubscriptionsdev.com';
14
+ * const manager = new SubscribedPlanManager(domain);
15
+ *
16
+ * // Use the ACCESS_TOKEN from your session (not id_token). Set it before each request if the manager is shared.
17
+ * manager.setBearerToken(accessTokenFromSession);
18
+ *
19
+ * // Get all plans with features and limits
20
+ * const allPlans = await manager.getAllPlans();
21
+ *
22
+ * // Get a single plan by internal numeric ID
23
+ * const plan = await manager.getPlanById(1);
24
+ *
25
+ * // Get plans by project ID
26
+ * const projectPlans = await manager.getPlansByProjectId(42);
27
+ *
28
+ * // Get single plan for a project (or null)
29
+ * const plan = await manager.getPlanForProject(42);
30
+ *
31
+ * // Get plans by user internal ID
32
+ * const userPlans = await manager.getPlansByUserId(3);
33
+ * ```
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SubscribedPlanManager = exports.SUBSCRIPTION_STATUS = void 0;
37
+ const SSISubscribedPlanApi_1 = require("./SSISubscribedPlanApi");
38
+ const SSISubscribedFeatureApi_1 = require("./SSISubscribedFeatureApi");
39
+ const SSISubscribedLimitApi_1 = require("./SSISubscribedLimitApi");
40
+ const SSICache_1 = require("./cache/SSICache");
41
+ /**
42
+ * Attributes for a subscribed_plan--subscribed_plan resource.
43
+ * Based on your JSON sample; extra fields will still be present in the raw JSON:API resource.
44
+ */
45
+ /**
46
+ * Known values for subscribed plan `status` (and for project `subscription_status`
47
+ * when synced from the platform). Document the full list in SUBSCRIBEDPLAN_MANAGER.md.
48
+ */
49
+ exports.SUBSCRIPTION_STATUS = {
50
+ // Add values as the platform defines them, e.g.:
51
+ // ACTIVE: 'active',
52
+ // CANCELLED: 'cancelled',
53
+ // TRIALING: 'trialing',
54
+ // PAST_DUE: 'past_due',
55
+ };
56
+ /**
57
+ * SubscribedPlanManager
58
+ *
59
+ * Fetches subscribed plans and hydrates each with its subscribed features and limits.
60
+ *
61
+ * Filtering conventions (using internal integer IDs):
62
+ * - By plan ID: filter[drupal_internal__id]={subscribed_plan_int_id}
63
+ * - By project ID: filter[project_id]={project_int_id}
64
+ * - By user ID: filter[user_id.meta.drupal_internal__target_id]={user_int_id}
65
+ *
66
+ * For features/limits attached to a plan:
67
+ * - filter[subscribed_plan_id.meta.drupal_internal__target_id]={subscribed_plan_int_id}
68
+ */
69
+ class SubscribedPlanManager {
70
+ constructor(domain, options = {}) {
71
+ const mkOptions = (apiBasePath) => apiBasePath || options.timeout
72
+ ? {
73
+ apiBasePath,
74
+ timeout: options.timeout,
75
+ }
76
+ : undefined;
77
+ this.planApi = new SSISubscribedPlanApi_1.SSISubscribedPlanApi(domain, mkOptions(options.planApiBasePath));
78
+ this.featureApi = new SSISubscribedFeatureApi_1.SSISubscribedFeatureApi(domain, mkOptions(options.featureApiBasePath));
79
+ this.limitApi = new SSISubscribedLimitApi_1.SSISubscribedLimitApi(domain, mkOptions(options.limitApiBasePath));
80
+ // Initialize cache instances with prefixes for features and limits
81
+ const baseCache = SSICache_1.SSICache.fromEnv();
82
+ this.featureCache = baseCache.withPrefix('features');
83
+ this.limitCache = baseCache.withPrefix('limits');
84
+ }
85
+ /**
86
+ * Set a shared Bearer token on all three underlying API clients.
87
+ * Use the OAuth access_token from your session (e.g. SessionManager.getSessionData(sessionId, 'access_token')).
88
+ * Do not use the id_token — the JSON:API expects the access_token for authorization.
89
+ * If the manager is shared across requests, call this with the current request's access_token before each API call.
90
+ */
91
+ setBearerToken(token) {
92
+ this.planApi.setBearerToken(token);
93
+ this.featureApi.setBearerToken(token);
94
+ this.limitApi.setBearerToken(token);
95
+ }
96
+ /**
97
+ * Get *all* subscribed plans for the current context,
98
+ * each with its features[] and limits[].
99
+ */
100
+ async getAllPlans() {
101
+ const doc = await this.planApi.list();
102
+ return this.hydratePlans(doc);
103
+ }
104
+ /**
105
+ * Get a single subscribed plan by its internal numeric ID
106
+ * (attributes.drupal_internal__id), with features[] and limits[].
107
+ */
108
+ async getPlanById(subscribedPlanId) {
109
+ const doc = await this.planApi.list({
110
+ filters: {
111
+ // ?filter[drupal_internal__id]={subscribedPlanId}
112
+ drupal_internal__id: subscribedPlanId,
113
+ },
114
+ pagination: {
115
+ limit: 1,
116
+ },
117
+ });
118
+ const plans = await this.hydratePlans(doc);
119
+ return plans[0] ?? null;
120
+ }
121
+ /**
122
+ * Get all subscribed plans for a given project_id.
123
+ *
124
+ * Uses: ?filter[project_id]={project_int_id}
125
+ */
126
+ async getPlansByProjectId(projectId) {
127
+ const doc = await this.planApi.list({
128
+ filters: {
129
+ project_id: projectId,
130
+ },
131
+ });
132
+ return this.hydratePlans(doc);
133
+ }
134
+ /**
135
+ * Get the single subscribed plan for a given project_id (with features and limits).
136
+ * Returns the first matching plan, or null if none. Use when at most one plan per project.
137
+ *
138
+ * Uses: ?filter[project_id]={project_int_id}
139
+ */
140
+ async getPlanForProject(projectId) {
141
+ const plans = await this.getPlansByProjectId(projectId);
142
+ return plans[0] ?? null;
143
+ }
144
+ /**
145
+ * Filter an array of plans by project_id.
146
+ *
147
+ * @param plans - Array of subscribed plans to filter.
148
+ * @param projectId - The project ID to filter by.
149
+ * @returns Array of plans that match the project ID.
150
+ */
151
+ getPlanByProjectId(plans, projectId) {
152
+ return plans.filter((plan) => plan.project_id === projectId);
153
+ }
154
+ /**
155
+ * Get all subscribed plans for a given user (internal user ID).
156
+ *
157
+ * Uses: ?filter[user_id.meta.drupal_internal__target_id]={user_int_id}
158
+ */
159
+ async getPlansByUserId(userId) {
160
+ const doc = await this.planApi.list({
161
+ filters: {
162
+ 'user_id.meta.drupal_internal__target_id': userId,
163
+ },
164
+ });
165
+ return this.hydratePlans(doc);
166
+ }
167
+ /**
168
+ * Get the internal numeric ID of a limit by its name from a plan.
169
+ *
170
+ * @param plan - The subscribed plan to search in.
171
+ * @param limit_name - The name of the limit to find.
172
+ * @returns The internal numeric ID (drupal_internal__id) of the limit, or null if not found.
173
+ */
174
+ getLimitId(plan, limit_name) {
175
+ const limit = plan.limits.find((l) => l.limit_name === limit_name);
176
+ return limit ? limit.drupal_internal__id : null;
177
+ }
178
+ /**
179
+ * Internal helper to find a limit by name or ID.
180
+ */
181
+ findLimit(plan, identifier) {
182
+ if (typeof identifier === 'string') {
183
+ return plan.limits.find((l) => l.limit_name === identifier) ?? null;
184
+ }
185
+ else {
186
+ return plan.limits.find((l) => l.drupal_internal__id === identifier) ?? null;
187
+ }
188
+ }
189
+ isLimitEnabled(plan, identifier) {
190
+ const limit = this.findLimit(plan, identifier);
191
+ return limit?.status ?? false;
192
+ }
193
+ getLimitType(plan, identifier) {
194
+ const limit = this.findLimit(plan, identifier);
195
+ return limit?.limit_type ?? null;
196
+ }
197
+ getLimitMax(plan, identifier) {
198
+ const limit = this.findLimit(plan, identifier);
199
+ return limit?.limit_value ?? null;
200
+ }
201
+ limitAllowsOverage(plan, identifier) {
202
+ const limit = this.findLimit(plan, identifier);
203
+ return limit?.allow_overage ?? false;
204
+ }
205
+ limitOverageMax(plan, identifier) {
206
+ const limit = this.findLimit(plan, identifier);
207
+ if (!limit || !limit.allow_overage) {
208
+ return 0;
209
+ }
210
+ return limit.overage_limit;
211
+ }
212
+ getLimitUnits(plan, identifier) {
213
+ const limit = this.findLimit(plan, identifier);
214
+ return limit?.limit_units ?? null;
215
+ }
216
+ getLimitUnitsPlural(plan, identifier) {
217
+ const limit = this.findLimit(plan, identifier);
218
+ return limit?.limit_units_plural ?? null;
219
+ }
220
+ getLimitPeriod(plan, identifier) {
221
+ const limit = this.findLimit(plan, identifier);
222
+ return limit?.limit_period ?? null;
223
+ }
224
+ /**
225
+ * Internal: turn a JSON:API document of subscribed plans into hydrated SubscribedPlan[]
226
+ * with features[] and limits[] attached.
227
+ */
228
+ async hydratePlans(doc) {
229
+ const data = Array.isArray(doc.data) ? doc.data : doc.data ? [doc.data] : [];
230
+ const plans = data.filter((resource) => resource.type === 'subscribed_plan--subscribed_plan');
231
+ const hydrated = [];
232
+ for (const planResource of plans) {
233
+ hydrated.push(await this.hydrateSinglePlan(planResource));
234
+ }
235
+ return hydrated;
236
+ }
237
+ /**
238
+ * Internal: hydrate a single plan resource with its features and limits.
239
+ * Features and limits are cached since they never change.
240
+ */
241
+ async hydrateSinglePlan(planResource) {
242
+ const attributes = (planResource.attributes || {});
243
+ const internalId = attributes.drupal_internal__id;
244
+ if (typeof internalId !== 'number') {
245
+ throw new Error('Subscribed plan is missing attributes.drupal_internal__id (required for feature/limit lookups).');
246
+ }
247
+ // Cache key format: plan:{internalId}
248
+ const featureCacheKey = `plan:${internalId}`;
249
+ const limitCacheKey = `plan:${internalId}`;
250
+ // Fetch features and limits in parallel, using cache with remember() pattern
251
+ const [features, limits] = await Promise.all([
252
+ this.featureCache.remember(featureCacheKey, SubscribedPlanManager.CACHE_TTL_SEC, async () => {
253
+ const featuresDoc = await this.featureApi.list({
254
+ filters: {
255
+ // ?filter[subscribed_plan_id.meta.drupal_internal__target_id]={internalId}
256
+ 'subscribed_plan_id.meta.drupal_internal__target_id': internalId,
257
+ },
258
+ });
259
+ return this.mapFeatures(featuresDoc);
260
+ }),
261
+ this.limitCache.remember(limitCacheKey, SubscribedPlanManager.CACHE_TTL_SEC, async () => {
262
+ const limitsDoc = await this.limitApi.list({
263
+ filters: {
264
+ 'subscribed_plan_id.meta.drupal_internal__target_id': internalId,
265
+ },
266
+ });
267
+ return this.mapLimits(limitsDoc);
268
+ }),
269
+ ]);
270
+ const uuid = planResource.id ?? '';
271
+ const plan = {
272
+ uuid,
273
+ ...attributes,
274
+ relationships: planResource.relationships,
275
+ features,
276
+ limits,
277
+ };
278
+ return plan;
279
+ }
280
+ /**
281
+ * Internal: map a subscribed_feature JSON:API document to SubscribedFeature[].
282
+ */
283
+ mapFeatures(doc) {
284
+ const data = Array.isArray(doc.data) ? doc.data : doc.data ? [doc.data] : [];
285
+ return data
286
+ .filter((resource) => resource.type === 'subscribed_feature--subscribed_feature')
287
+ .map((resource) => {
288
+ const attrs = (resource.attributes || {});
289
+ const uuid = resource.id ?? '';
290
+ const feature = {
291
+ uuid,
292
+ ...attrs,
293
+ relationships: resource.relationships,
294
+ };
295
+ return feature;
296
+ });
297
+ }
298
+ /**
299
+ * Internal: map a subscribed_limit JSON:API document to SubscribedLimit[].
300
+ */
301
+ mapLimits(doc) {
302
+ const data = Array.isArray(doc.data) ? doc.data : doc.data ? [doc.data] : [];
303
+ return data
304
+ .filter((resource) => resource.type === 'subscribed_limit--subscribed_limit')
305
+ .map((resource) => {
306
+ const attrs = (resource.attributes || {});
307
+ const uuid = resource.id ?? '';
308
+ const limit = {
309
+ uuid,
310
+ ...attrs,
311
+ relationships: resource.relationships,
312
+ };
313
+ return limit;
314
+ });
315
+ }
316
+ }
317
+ exports.SubscribedPlanManager = SubscribedPlanManager;
318
+ // Cache TTL: 24 hours (86400 seconds) - features and limits never change
319
+ SubscribedPlanManager.CACHE_TTL_SEC = 86400;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Usage Reporting API Client
3
+ *
4
+ * Sends usage events to the subscription usage API endpoint.
5
+ * Requires a base URL and Bearer token for authentication.
6
+ */
7
+ /**
8
+ * Usage event data structure
9
+ */
10
+ export interface UsageEvent {
11
+ /** The internal ID of the Limit entity this usage event applies to (required) */
12
+ limit_id: number;
13
+ /** Positive usage amount for this event (optional, default: 1.0) */
14
+ amount?: number;
15
+ /** Project entity ID when mode supports projects (optional) */
16
+ project_id?: number;
17
+ /** Aggregate/grouping identifier (e.g. campaign/job ID) (optional) */
18
+ aggregate_id?: number;
19
+ /** Human-readable name for the aggregate (not indexed) (optional) */
20
+ aggregate_name?: string;
21
+ /** Arbitrary JSON metadata from the client (optional) */
22
+ metadata?: Record<string, any> | string;
23
+ /** Unix timestamp (UTC seconds since epoch) for when the usage event occurred (optional) */
24
+ event_timestamp?: number;
25
+ }
26
+ /**
27
+ * API response structure
28
+ */
29
+ export interface UsageApiResponse {
30
+ /** Status of the response (always "recorded" on success) */
31
+ status: string;
32
+ /** Number of events successfully processed */
33
+ count: number;
34
+ }
35
+ /**
36
+ * API error response structure
37
+ */
38
+ export interface UsageApiError {
39
+ message: string;
40
+ status?: number;
41
+ statusText?: string;
42
+ }
43
+ /**
44
+ * Usage information structure
45
+ */
46
+ export interface UsageInfo {
47
+ /** The internal ID of the Limit entity */
48
+ limit_id: number;
49
+ /** Total usage amount for this limit */
50
+ total_usage: number;
51
+ }
52
+ /**
53
+ * Project usage information structure
54
+ */
55
+ export interface ProjectUsageInfo {
56
+ /** The internal ID of the Limit entity */
57
+ limit_id: number;
58
+ /** Total usage amount for this limit */
59
+ total_usage: number;
60
+ /** The project entity ID */
61
+ project_id: number;
62
+ }
63
+ /**
64
+ * Usage API client for reporting subscription usage events
65
+ */
66
+ export declare class UsageApi {
67
+ private baseUrl;
68
+ private bearerToken;
69
+ private endpoint;
70
+ /**
71
+ * Creates a new UsageApi instance
72
+ *
73
+ * @param baseUrl - The base URL of the API (e.g., "https://account.serialsubscriptionsdev.com/")
74
+ * @param bearerToken - The Bearer token for authentication
75
+ */
76
+ constructor(baseUrl: string, bearerToken: string);
77
+ /**
78
+ * Reports a single usage event
79
+ *
80
+ * @param event - The usage event to report
81
+ * @returns Promise resolving to the API response
82
+ * @throws {UsageApiError} If the request fails
83
+ */
84
+ reportEvent(event: UsageEvent): Promise<UsageApiResponse>;
85
+ /**
86
+ * Reports multiple usage events in a single batch request
87
+ *
88
+ * All events in a batch are processed within a single database transaction.
89
+ * If any event fails validation, the entire batch is rolled back.
90
+ *
91
+ * @param events - Array of usage events to report
92
+ * @returns Promise resolving to the API response
93
+ * @throws {UsageApiError} If the request fails
94
+ */
95
+ reportEvents(events: UsageEvent[]): Promise<UsageApiResponse>;
96
+ /**
97
+ * Gets usage information for all limits
98
+ *
99
+ * @returns Promise resolving to an array of usage information
100
+ * @throws {UsageApiError} If the request fails
101
+ */
102
+ getUsageAll(): Promise<UsageInfo[]>;
103
+ /**
104
+ * Gets usage information for a specific limit
105
+ *
106
+ * @param limit_id - The limit ID to get usage for
107
+ * @returns Promise resolving to the total usage value, or undefined if not found
108
+ * @throws {UsageApiError} If the request fails
109
+ */
110
+ getUsage(limit_id: number): Promise<number | undefined>;
111
+ /**
112
+ * Gets usage information for all limits for a specific project
113
+ *
114
+ * @param project_id - The project ID to get usage for
115
+ * @returns Promise resolving to an array of project usage information
116
+ * @throws {UsageApiError} If the request fails
117
+ */
118
+ getProjectUsageAll(project_id: number): Promise<ProjectUsageInfo[]>;
119
+ /**
120
+ * Gets usage information for a specific limit within a project
121
+ *
122
+ * @param project_id - The project ID to get usage for
123
+ * @param limit_id - The limit ID to get usage for
124
+ * @returns Promise resolving to the total usage value, or undefined if not found
125
+ * @throws {UsageApiError} If the request fails
126
+ */
127
+ getProjectUsage(project_id: number, limit_id: number): Promise<number | undefined>;
128
+ }
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ /**
3
+ * Usage Reporting API Client
4
+ *
5
+ * Sends usage events to the subscription usage API endpoint.
6
+ * Requires a base URL and Bearer token for authentication.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.UsageApi = void 0;
10
+ /**
11
+ * Usage API client for reporting subscription usage events
12
+ */
13
+ class UsageApi {
14
+ /**
15
+ * Creates a new UsageApi instance
16
+ *
17
+ * @param baseUrl - The base URL of the API (e.g., "https://account.serialsubscriptionsdev.com/")
18
+ * @param bearerToken - The Bearer token for authentication
19
+ */
20
+ constructor(baseUrl, bearerToken) {
21
+ this.endpoint = '/api/v1/usage/report';
22
+ // Normalize base URL: ensure it ends with a slash
23
+ this.baseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
24
+ this.bearerToken = bearerToken;
25
+ }
26
+ /**
27
+ * Reports a single usage event
28
+ *
29
+ * @param event - The usage event to report
30
+ * @returns Promise resolving to the API response
31
+ * @throws {UsageApiError} If the request fails
32
+ */
33
+ async reportEvent(event) {
34
+ return this.reportEvents([event]);
35
+ }
36
+ /**
37
+ * Reports multiple usage events in a single batch request
38
+ *
39
+ * All events in a batch are processed within a single database transaction.
40
+ * If any event fails validation, the entire batch is rolled back.
41
+ *
42
+ * @param events - Array of usage events to report
43
+ * @returns Promise resolving to the API response
44
+ * @throws {UsageApiError} If the request fails
45
+ */
46
+ async reportEvents(events) {
47
+ if (events.length === 0) {
48
+ throw {
49
+ message: 'At least one event is required',
50
+ };
51
+ }
52
+ // Normalize: if single event, send as object; if multiple, send as array
53
+ const payload = events.length === 1 ? events[0] : events;
54
+ const url = `${this.baseUrl}${this.endpoint.replace(/^\//, '')}`;
55
+ try {
56
+ const response = await fetch(url, {
57
+ method: 'POST',
58
+ headers: {
59
+ 'Authorization': `Bearer ${this.bearerToken}`,
60
+ 'Content-Type': 'application/json',
61
+ 'Accept': 'application/json',
62
+ },
63
+ body: JSON.stringify(payload),
64
+ });
65
+ if (!response.ok) {
66
+ let errorMessage = `API request failed with status ${response.status}`;
67
+ try {
68
+ const errorData = await response.json();
69
+ if (errorData.message) {
70
+ errorMessage = errorData.message;
71
+ }
72
+ }
73
+ catch {
74
+ // If response is not JSON, use the status text
75
+ errorMessage = response.statusText || errorMessage;
76
+ }
77
+ throw {
78
+ message: errorMessage,
79
+ status: response.status,
80
+ statusText: response.statusText,
81
+ };
82
+ }
83
+ const data = await response.json();
84
+ return data;
85
+ }
86
+ catch (error) {
87
+ if (error && typeof error === 'object' && 'status' in error) {
88
+ // Re-throw UsageApiError
89
+ throw error;
90
+ }
91
+ // Handle network errors or other exceptions
92
+ throw {
93
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
94
+ };
95
+ }
96
+ }
97
+ /**
98
+ * Gets usage information for all limits
99
+ *
100
+ * @returns Promise resolving to an array of usage information
101
+ * @throws {UsageApiError} If the request fails
102
+ */
103
+ async getUsageAll() {
104
+ const endpoint = '/api/v1/usage/get';
105
+ const url = `${this.baseUrl}${endpoint.replace(/^\//, '')}`;
106
+ try {
107
+ const response = await fetch(url, {
108
+ method: 'GET',
109
+ headers: {
110
+ 'Authorization': `Bearer ${this.bearerToken}`,
111
+ 'Accept': 'application/json',
112
+ },
113
+ });
114
+ if (!response.ok) {
115
+ let errorMessage = `API request failed with status ${response.status}`;
116
+ try {
117
+ const errorData = await response.json();
118
+ if (errorData.message) {
119
+ errorMessage = errorData.message;
120
+ }
121
+ }
122
+ catch {
123
+ // If response is not JSON, use the status text
124
+ errorMessage = response.statusText || errorMessage;
125
+ }
126
+ throw {
127
+ message: errorMessage,
128
+ status: response.status,
129
+ statusText: response.statusText,
130
+ };
131
+ }
132
+ const data = await response.json();
133
+ return data;
134
+ }
135
+ catch (error) {
136
+ if (error && typeof error === 'object' && 'status' in error) {
137
+ // Re-throw UsageApiError
138
+ throw error;
139
+ }
140
+ // Handle network errors or other exceptions
141
+ throw {
142
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
143
+ };
144
+ }
145
+ }
146
+ /**
147
+ * Gets usage information for a specific limit
148
+ *
149
+ * @param limit_id - The limit ID to get usage for
150
+ * @returns Promise resolving to the total usage value, or undefined if not found
151
+ * @throws {UsageApiError} If the request fails
152
+ */
153
+ async getUsage(limit_id) {
154
+ const allUsage = await this.getUsageAll();
155
+ const usageInfo = allUsage.find((info) => info.limit_id === limit_id);
156
+ return usageInfo?.total_usage;
157
+ }
158
+ /**
159
+ * Gets usage information for all limits for a specific project
160
+ *
161
+ * @param project_id - The project ID to get usage for
162
+ * @returns Promise resolving to an array of project usage information
163
+ * @throws {UsageApiError} If the request fails
164
+ */
165
+ async getProjectUsageAll(project_id) {
166
+ const endpoint = '/api/v1/usage/get';
167
+ const url = `${this.baseUrl}${endpoint.replace(/^\//, '')}`;
168
+ try {
169
+ const response = await fetch(url, {
170
+ method: 'POST',
171
+ headers: {
172
+ 'Authorization': `Bearer ${this.bearerToken}`,
173
+ 'Content-Type': 'application/json',
174
+ 'Accept': 'application/json',
175
+ },
176
+ body: JSON.stringify({ project_id }),
177
+ });
178
+ if (!response.ok) {
179
+ let errorMessage = `API request failed with status ${response.status}`;
180
+ try {
181
+ const errorData = await response.json();
182
+ if (errorData.message) {
183
+ errorMessage = errorData.message;
184
+ }
185
+ }
186
+ catch {
187
+ // If response is not JSON, use the status text
188
+ errorMessage = response.statusText || errorMessage;
189
+ }
190
+ throw {
191
+ message: errorMessage,
192
+ status: response.status,
193
+ statusText: response.statusText,
194
+ };
195
+ }
196
+ const data = await response.json();
197
+ return data;
198
+ }
199
+ catch (error) {
200
+ if (error && typeof error === 'object' && 'status' in error) {
201
+ // Re-throw UsageApiError
202
+ throw error;
203
+ }
204
+ // Handle network errors or other exceptions
205
+ throw {
206
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
207
+ };
208
+ }
209
+ }
210
+ /**
211
+ * Gets usage information for a specific limit within a project
212
+ *
213
+ * @param project_id - The project ID to get usage for
214
+ * @param limit_id - The limit ID to get usage for
215
+ * @returns Promise resolving to the total usage value, or undefined if not found
216
+ * @throws {UsageApiError} If the request fails
217
+ */
218
+ async getProjectUsage(project_id, limit_id) {
219
+ const allUsage = await this.getProjectUsageAll(project_id);
220
+ const usageInfo = allUsage.find((info) => info.limit_id === limit_id);
221
+ return usageInfo?.total_usage;
222
+ }
223
+ }
224
+ exports.UsageApi = UsageApi;