@rpcbase/db 0.122.0 → 0.123.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"billingStorage.d.ts","sourceRoot":"","sources":["../src/billingStorage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAkE/C,eAAO,MAAM,0BAA0B,GAAI,QAAQ;IACjD,GAAG,EAAE,YAAY,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB,KAAG,cAwEH,CAAA"}
1
+ {"version":3,"file":"billingStorage.d.ts","sourceRoot":"","sources":["../src/billingStorage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AA8D/C,eAAO,MAAM,0BAA0B,GAAI,QAAQ;IACjD,GAAG,EAAE,YAAY,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB,KAAG,cAsEH,CAAA"}
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { b, a, c, g, d, e, f } from "./can-B4pD_pnR.js";
3
3
  import mongoose, { Schema as Schema$1, Types } from "mongoose";
4
4
  import { default as default2 } from "mongoose";
5
5
  import { z } from "zod";
6
- import { SUBSCRIPTION_STATUSES, SUBSCRIPTION_INTERVAL_UNITS, SUBSCRIPTION_TYPES, SUBSCRIPTION_SCOPES, SUBSCRIPTION_EVENT_TYPES } from "@rpcbase/billing";
6
+ import { SUBSCRIPTION_STATUSES, SUBSCRIPTION_INTERVAL_UNITS, SUBSCRIPTION_TYPES, SUBSCRIPTION_SCOPES, SUBSCRIPTION_EVENT_TYPES, SUBSCRIPTION_CHANGE_DIRECTIONS } from "@rpcbase/billing";
7
7
  import { timingSafeEqual, createHmac } from "node:crypto";
8
8
  import { w as withLocalizedStringFallback } from "./index-DrIoUXc2.js";
9
9
  import { E, a as a2, L, b as b2, e as e2, m, r, z as z2, c as c2, d as d2, f as f2 } from "./index-DrIoUXc2.js";
@@ -173,16 +173,15 @@ const ZRBTenantSubscriptionType = z.enum(SUBSCRIPTION_TYPES);
173
173
  const ZRBTenantSubscriptionScope = z.enum(SUBSCRIPTION_SCOPES);
174
174
  const ZRBTenantSubscriptionEventType = z.enum(SUBSCRIPTION_EVENT_TYPES);
175
175
  const ZRBTenantSubscriptionEventSource = z.enum(["admin", "system", "webhook", "user"]);
176
- const ZRBTenantSubscriptionChangeDirection = z.enum(["upgrade", "downgrade", "lateral"]);
177
- const ZRBTenantSubscriptionEvent = z.object({
178
- tenantId: z.string(),
179
- subscriptionId: z.string(),
180
- type: ZRBTenantSubscriptionEventType,
176
+ const ZRBTenantSubscriptionChangeDirection = z.enum(SUBSCRIPTION_CHANGE_DIRECTIONS);
177
+ const subscriptionEventBaseSchema = z.object({
178
+ tenantId: z.string().trim().min(1),
179
+ subscriptionId: z.string().trim().min(1),
181
180
  occurredAt: z.date(),
182
181
  effectiveAt: z.date(),
183
- subscriptionType: ZRBTenantSubscriptionType.optional(),
182
+ subscriptionType: ZRBTenantSubscriptionType,
184
183
  parentSubscriptionId: z.string().optional(),
185
- scope: ZRBTenantSubscriptionScope.optional(),
184
+ scope: ZRBTenantSubscriptionScope,
186
185
  scopeId: z.string().optional(),
187
186
  fromPlanKey: z.string().optional(),
188
187
  toPlanKey: z.string().optional(),
@@ -198,7 +197,7 @@ const ZRBTenantSubscriptionEvent = z.object({
198
197
  toPriceId: z.string().optional(),
199
198
  billingAnchor: z.date().optional(),
200
199
  supersedesEventId: z.string().optional(),
201
- idempotencyKey: z.string().optional(),
200
+ idempotencyKey: z.string().trim().min(1),
202
201
  direction: ZRBTenantSubscriptionChangeDirection.optional(),
203
202
  actorUserId: z.string().optional(),
204
203
  source: ZRBTenantSubscriptionEventSource.optional(),
@@ -208,6 +207,65 @@ const ZRBTenantSubscriptionEvent = z.object({
208
207
  providerPayload: z.unknown().optional(),
209
208
  metadata: z.record(z.string(), z.unknown()).optional()
210
209
  });
210
+ const subscriptionCreatedEventSchema = subscriptionEventBaseSchema.extend({
211
+ type: z.literal("created"),
212
+ toPlanKey: z.string().trim().min(1),
213
+ toStatus: ZRBTenantSubscriptionStatus,
214
+ toModules: z.array(z.string()),
215
+ toIntervalUnit: ZRBTenantSubscriptionIntervalUnit,
216
+ toIntervalCount: z.number().int().min(1),
217
+ billingAnchor: z.date()
218
+ });
219
+ const subscriptionPlanChangedEventSchema = subscriptionEventBaseSchema.extend({
220
+ type: z.literal("plan_changed"),
221
+ fromPlanKey: z.string().trim().min(1),
222
+ toPlanKey: z.string().trim().min(1),
223
+ fromModules: z.array(z.string()),
224
+ toModules: z.array(z.string()),
225
+ fromIntervalUnit: ZRBTenantSubscriptionIntervalUnit,
226
+ toIntervalUnit: ZRBTenantSubscriptionIntervalUnit,
227
+ fromIntervalCount: z.number().int().min(1),
228
+ toIntervalCount: z.number().int().min(1)
229
+ });
230
+ const subscriptionStatusChangedEventSchema = subscriptionEventBaseSchema.extend({
231
+ type: z.literal("status_changed"),
232
+ fromStatus: ZRBTenantSubscriptionStatus,
233
+ toStatus: ZRBTenantSubscriptionStatus
234
+ });
235
+ const subscriptionResumedEventSchema = subscriptionEventBaseSchema.extend({
236
+ type: z.literal("resumed"),
237
+ fromStatus: ZRBTenantSubscriptionStatus,
238
+ toStatus: z.literal("active")
239
+ });
240
+ const subscriptionCanceledEventSchema = subscriptionEventBaseSchema.extend({
241
+ type: z.literal("canceled"),
242
+ fromStatus: ZRBTenantSubscriptionStatus,
243
+ toStatus: z.literal("canceled")
244
+ });
245
+ const subscriptionRenewedEventSchema = subscriptionEventBaseSchema.extend({
246
+ type: z.literal("renewed"),
247
+ fromStatus: ZRBTenantSubscriptionStatus,
248
+ toStatus: ZRBTenantSubscriptionStatus
249
+ });
250
+ const ZRBTenantSubscriptionEvent = z.discriminatedUnion("type", [subscriptionCreatedEventSchema, subscriptionPlanChangedEventSchema, subscriptionStatusChangedEventSchema, subscriptionResumedEventSchema, subscriptionCanceledEventSchema, subscriptionRenewedEventSchema]).superRefine((event, ctx) => {
251
+ if (event.subscriptionType === "addon" && !event.parentSubscriptionId?.trim()) {
252
+ ctx.addIssue({
253
+ code: "custom",
254
+ message: "parentSubscriptionId is required for add-on subscriptions",
255
+ path: ["parentSubscriptionId"]
256
+ });
257
+ }
258
+ if (event.scope !== "tenant" && !event.scopeId?.trim()) {
259
+ ctx.addIssue({
260
+ code: "custom",
261
+ message: "scopeId is required for non-tenant subscriptions",
262
+ path: ["scopeId"]
263
+ });
264
+ }
265
+ });
266
+ const requiredForEventTypes = (...types) => function requiredForEventType() {
267
+ return Boolean(this.type && types.includes(this.type));
268
+ };
211
269
  const RBTenantSubscriptionEventSchema = new Schema$1({
212
270
  tenantId: {
213
271
  type: String,
@@ -235,54 +293,72 @@ const RBTenantSubscriptionEventSchema = new Schema$1({
235
293
  },
236
294
  subscriptionType: {
237
295
  type: String,
296
+ required: true,
238
297
  enum: ZRBTenantSubscriptionType.options
239
298
  },
240
299
  parentSubscriptionId: {
241
- type: String
300
+ type: String,
301
+ required() {
302
+ return this.subscriptionType === "addon";
303
+ }
242
304
  },
243
305
  scope: {
244
306
  type: String,
307
+ required: true,
245
308
  enum: ZRBTenantSubscriptionScope.options
246
309
  },
247
310
  scopeId: {
248
- type: String
311
+ type: String,
312
+ required() {
313
+ return Boolean(this.scope && this.scope !== "tenant");
314
+ }
249
315
  },
250
316
  fromPlanKey: {
251
- type: String
317
+ type: String,
318
+ required: requiredForEventTypes("plan_changed")
252
319
  },
253
320
  toPlanKey: {
254
- type: String
321
+ type: String,
322
+ required: requiredForEventTypes("created", "plan_changed")
255
323
  },
256
324
  fromStatus: {
257
325
  type: String,
326
+ required: requiredForEventTypes("status_changed", "resumed", "canceled", "renewed"),
258
327
  enum: ZRBTenantSubscriptionStatus.options
259
328
  },
260
329
  toStatus: {
261
330
  type: String,
331
+ required: requiredForEventTypes("created", "status_changed", "resumed", "canceled", "renewed"),
262
332
  enum: ZRBTenantSubscriptionStatus.options
263
333
  },
264
334
  fromModules: {
265
335
  type: [String],
336
+ required: requiredForEventTypes("plan_changed"),
266
337
  default: void 0
267
338
  },
268
339
  toModules: {
269
340
  type: [String],
341
+ required: requiredForEventTypes("created", "plan_changed"),
270
342
  default: void 0
271
343
  },
272
344
  fromIntervalUnit: {
273
345
  type: String,
346
+ required: requiredForEventTypes("plan_changed"),
274
347
  enum: ZRBTenantSubscriptionIntervalUnit.options
275
348
  },
276
349
  toIntervalUnit: {
277
350
  type: String,
351
+ required: requiredForEventTypes("created", "plan_changed"),
278
352
  enum: ZRBTenantSubscriptionIntervalUnit.options
279
353
  },
280
354
  fromIntervalCount: {
281
355
  type: Number,
356
+ required: requiredForEventTypes("plan_changed"),
282
357
  min: 1
283
358
  },
284
359
  toIntervalCount: {
285
360
  type: Number,
361
+ required: requiredForEventTypes("created", "plan_changed"),
286
362
  min: 1
287
363
  },
288
364
  fromPriceId: {
@@ -292,13 +368,15 @@ const RBTenantSubscriptionEventSchema = new Schema$1({
292
368
  type: String
293
369
  },
294
370
  billingAnchor: {
295
- type: Date
371
+ type: Date,
372
+ required: requiredForEventTypes("created")
296
373
  },
297
374
  supersedesEventId: {
298
375
  type: String
299
376
  },
300
377
  idempotencyKey: {
301
- type: String
378
+ type: String,
379
+ required: true
302
380
  },
303
381
  direction: {
304
382
  type: String,
@@ -338,14 +416,20 @@ RBTenantSubscriptionEventSchema.index({
338
416
  providerEventId: 1
339
417
  }, {
340
418
  unique: true,
341
- sparse: true
419
+ partialFilterExpression: {
420
+ provider: {
421
+ $type: "string"
422
+ },
423
+ providerEventId: {
424
+ $type: "string"
425
+ }
426
+ }
342
427
  });
343
428
  RBTenantSubscriptionEventSchema.index({
344
429
  tenantId: 1,
345
430
  idempotencyKey: 1
346
431
  }, {
347
- unique: true,
348
- sparse: true
432
+ unique: true
349
433
  });
350
434
  const ZRBTenantSubscriptionProjection = z.object({
351
435
  tenantId: z.string(),
@@ -2243,26 +2327,16 @@ const getTransactionSession = (transaction) => {
2243
2327
  };
2244
2328
  const toSubscriptionEvent = (document) => {
2245
2329
  const parsed = ZRBTenantSubscriptionEvent.parse(document);
2330
+ if (!document._id) throw new Error("Persisted subscription event is missing its id");
2246
2331
  return {
2247
2332
  ...parsed,
2248
- id: document._id ? String(document._id) : void 0
2333
+ id: String(document._id)
2249
2334
  };
2250
2335
  };
2251
- const getExistingEventQuery = (event, tenantId) => {
2252
- if (event.idempotencyKey) {
2253
- return {
2254
- tenantId,
2255
- idempotencyKey: event.idempotencyKey
2256
- };
2257
- }
2258
- if (event.provider && event.providerEventId) {
2259
- return {
2260
- provider: event.provider,
2261
- providerEventId: event.providerEventId
2262
- };
2263
- }
2264
- return null;
2265
- };
2336
+ const getExistingEventQuery = (event, tenantId) => ({
2337
+ tenantId,
2338
+ idempotencyKey: event.idempotencyKey
2339
+ });
2266
2340
  const isDuplicateKeyError = (error) => Boolean(error && typeof error === "object" && "code" in error && error.code === 11e3);
2267
2341
  const toProjectionDocument = (projection) => ({
2268
2342
  tenantId: projection.tenantId,
@@ -2308,16 +2382,14 @@ const createTenantBillingStorage = (params) => {
2308
2382
  session
2309
2383
  })),
2310
2384
  createEvent: async (event, transaction) => {
2311
- const existingQuery = getExistingEventQuery(event, params.tenantId);
2312
- if (existingQuery) {
2313
- const existing = await findExistingEvent(existingQuery, transaction);
2314
- if (existing) return existing;
2385
+ if (event.tenantId !== params.tenantId) {
2386
+ throw new Error("Billing event tenantId does not match the storage tenant");
2315
2387
  }
2388
+ const existingQuery = getExistingEventQuery(event, params.tenantId);
2389
+ const existing = await findExistingEvent(existingQuery, transaction);
2390
+ if (existing) return existing;
2316
2391
  const EventModel = await models.get("RBTenantSubscriptionEvent", params.ctx);
2317
- const eventDocument = ZRBTenantSubscriptionEvent.parse({
2318
- ...event,
2319
- tenantId: params.tenantId
2320
- });
2392
+ const eventDocument = ZRBTenantSubscriptionEvent.parse(event);
2321
2393
  const session = getTransactionSession(transaction);
2322
2394
  try {
2323
2395
  const documents = await EventModel.create([eventDocument], session ? {
@@ -2327,10 +2399,10 @@ const createTenantBillingStorage = (params) => {
2327
2399
  if (!document) throw new Error("Subscription event was not persisted");
2328
2400
  return toSubscriptionEvent(document);
2329
2401
  } catch (error) {
2330
- if (!existingQuery || !isDuplicateKeyError(error)) throw error;
2331
- const existing = await findExistingEvent(existingQuery, transaction);
2332
- if (!existing) throw error;
2333
- return existing;
2402
+ if (!isDuplicateKeyError(error)) throw error;
2403
+ const duplicate = await findExistingEvent(existingQuery, transaction);
2404
+ if (!duplicate) throw error;
2405
+ return duplicate;
2334
2406
  }
2335
2407
  },
2336
2408
  listEvents: async (tenantId, subscriptionId, transaction) => {