@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.
- package/README.md +1 -0
- package/lib/SSIProject.d.ts +343 -0
- package/lib/SSIProject.js +429 -0
- package/lib/SSIProjectApi.d.ts +384 -0
- package/lib/SSIProjectApi.js +534 -0
- package/lib/SSISubscribedFeatureApi.d.ts +387 -0
- package/lib/SSISubscribedFeatureApi.js +511 -0
- package/lib/SSISubscribedLimitApi.d.ts +384 -0
- package/lib/SSISubscribedLimitApi.js +534 -0
- package/lib/SSISubscribedPlanApi.d.ts +395 -0
- package/lib/SSISubscribedPlanApi.js +567 -0
- package/lib/SubscribedPlanManager.d.ts +400 -0
- package/lib/SubscribedPlanManager.js +319 -0
- package/lib/UsageApi.d.ts +128 -0
- package/lib/UsageApi.js +224 -0
- package/lib/auth.server.d.ts +212 -0
- package/lib/auth.server.js +624 -0
- package/lib/cache/SSICache.d.ts +40 -0
- package/lib/cache/SSICache.js +134 -0
- package/lib/cache/backends/MemoryCacheBackend.d.ts +15 -0
- package/lib/cache/backends/MemoryCacheBackend.js +46 -0
- package/lib/cache/backends/RedisCacheBackend.d.ts +27 -0
- package/lib/cache/backends/RedisCacheBackend.js +95 -0
- package/lib/cache/constants.d.ts +7 -0
- package/lib/cache/constants.js +10 -0
- package/lib/cache/types.d.ts +27 -0
- package/lib/cache/types.js +2 -0
- package/lib/clientConfig.d.ts +42 -0
- package/lib/clientConfig.js +80 -0
- package/lib/frontend/index.d.ts +3 -0
- package/lib/frontend/index.js +12 -0
- package/lib/frontend/session/SessionClient.d.ts +36 -0
- package/lib/frontend/session/SessionClient.js +151 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +40 -0
- package/lib/lib/session/SessionClient.d.ts +11 -0
- package/lib/lib/session/SessionClient.js +47 -0
- package/lib/lib/session/index.d.ts +3 -0
- package/lib/lib/session/index.js +3 -0
- package/lib/lib/session/stores/MemoryStore.d.ts +7 -0
- package/lib/lib/session/stores/MemoryStore.js +23 -0
- package/lib/lib/session/stores/index.d.ts +1 -0
- package/lib/lib/session/stores/index.js +1 -0
- package/lib/lib/session/types.d.ts +37 -0
- package/lib/lib/session/types.js +1 -0
- package/lib/requestConfig.d.ts +60 -0
- package/lib/requestConfig.js +151 -0
- package/lib/session/SessionClient.d.ts +19 -0
- package/lib/session/SessionClient.js +132 -0
- package/lib/session/SessionManager.d.ts +142 -0
- package/lib/session/SessionManager.js +437 -0
- package/lib/stateStore.d.ts +5 -0
- package/lib/stateStore.js +9 -0
- package/lib/storage/SSIStorage.d.ts +24 -0
- package/lib/storage/SSIStorage.js +117 -0
- package/lib/storage/backends/MemoryBackend.d.ts +10 -0
- package/lib/storage/backends/MemoryBackend.js +44 -0
- package/lib/storage/backends/PostgresBackend.d.ts +24 -0
- package/lib/storage/backends/PostgresBackend.js +106 -0
- package/lib/storage/backends/RedisBackend.d.ts +19 -0
- package/lib/storage/backends/RedisBackend.js +78 -0
- package/lib/storage/types.d.ts +27 -0
- package/lib/storage/types.js +2 -0
- 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
|
+
}
|
package/lib/UsageApi.js
ADDED
|
@@ -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;
|