@rpcbase/billing 0.2.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,405 +1,388 @@
1
- const assertValidDate$2 = (value, field) => {
2
- const date = new Date(value);
3
- if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`);
4
- return date;
1
+ //#region src/calendar.ts
2
+ var assertValidDate$2 = (value, field) => {
3
+ const date = new Date(value);
4
+ if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`);
5
+ return date;
5
6
  };
6
- const normalizeIntervalCount = (value) => {
7
- if (!Number.isInteger(value) || value < 1) throw new Error("intervalCount must be a positive integer");
8
- return value;
7
+ var normalizeIntervalCount = (value) => {
8
+ if (!Number.isInteger(value) || value < 1) throw new Error("intervalCount must be a positive integer");
9
+ return value;
9
10
  };
10
- const getDaysInUtcMonth = (year, month) => new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
11
- const createClampedUtcDate = (source, year, month) => {
12
- const day = Math.min(source.getUTCDate(), getDaysInUtcMonth(year, month));
13
- return new Date(Date.UTC(year, month, day, source.getUTCHours(), source.getUTCMinutes(), source.getUTCSeconds(), source.getUTCMilliseconds()));
11
+ var getDaysInUtcMonth = (year, month) => new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
12
+ var createClampedUtcDate = (source, year, month) => {
13
+ const day = Math.min(source.getUTCDate(), getDaysInUtcMonth(year, month));
14
+ return new Date(Date.UTC(year, month, day, source.getUTCHours(), source.getUTCMinutes(), source.getUTCSeconds(), source.getUTCMilliseconds()));
14
15
  };
15
- const addBillingInterval = (value, intervalUnit, intervalCount = 1) => {
16
- const date = assertValidDate$2(value, "value");
17
- const count = normalizeIntervalCount(intervalCount);
18
- if (intervalUnit === "year") {
19
- return createClampedUtcDate(date, date.getUTCFullYear() + count, date.getUTCMonth());
20
- }
21
- const absoluteMonth = date.getUTCFullYear() * 12 + date.getUTCMonth() + count;
22
- const year = Math.floor(absoluteMonth / 12);
23
- const month = (absoluteMonth % 12 + 12) % 12;
24
- return createClampedUtcDate(date, year, month);
16
+ var addBillingInterval = (value, intervalUnit, intervalCount = 1) => {
17
+ const date = assertValidDate$2(value, "value");
18
+ const count = normalizeIntervalCount(intervalCount);
19
+ if (intervalUnit === "year") return createClampedUtcDate(date, date.getUTCFullYear() + count, date.getUTCMonth());
20
+ const absoluteMonth = date.getUTCFullYear() * 12 + date.getUTCMonth() + count;
21
+ return createClampedUtcDate(date, Math.floor(absoluteMonth / 12), (absoluteMonth % 12 + 12) % 12);
25
22
  };
26
- const getEstimatedPeriodIndex = (anchor, asOf, intervalUnit, intervalCount) => {
27
- if (asOf.getTime() <= anchor.getTime()) return 0;
28
- const rawDifference = intervalUnit === "year" ? asOf.getUTCFullYear() - anchor.getUTCFullYear() : (asOf.getUTCFullYear() - anchor.getUTCFullYear()) * 12 + asOf.getUTCMonth() - anchor.getUTCMonth();
29
- return Math.max(0, Math.floor(rawDifference / intervalCount));
23
+ var getEstimatedPeriodIndex = (anchor, asOf, intervalUnit, intervalCount) => {
24
+ if (asOf.getTime() <= anchor.getTime()) return 0;
25
+ const rawDifference = intervalUnit === "year" ? asOf.getUTCFullYear() - anchor.getUTCFullYear() : (asOf.getUTCFullYear() - anchor.getUTCFullYear()) * 12 + asOf.getUTCMonth() - anchor.getUTCMonth();
26
+ return Math.max(0, Math.floor(rawDifference / intervalCount));
30
27
  };
31
- const resolveBillingPeriod = (params) => {
32
- const anchor = assertValidDate$2(params.billingAnchor, "billingAnchor");
33
- const asOf = assertValidDate$2(params.asOf, "asOf");
34
- const intervalCount = normalizeIntervalCount(params.intervalCount ?? 1);
35
- let periodIndex = getEstimatedPeriodIndex(anchor, asOf, params.intervalUnit, intervalCount);
36
- let start = addBillingInterval(anchor, params.intervalUnit, Math.max(1, periodIndex * intervalCount));
37
- if (periodIndex === 0) start = new Date(anchor);
38
- while (start.getTime() > asOf.getTime() && periodIndex > 0) {
39
- periodIndex -= 1;
40
- start = periodIndex === 0 ? new Date(anchor) : addBillingInterval(anchor, params.intervalUnit, periodIndex * intervalCount);
41
- }
42
- let end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount);
43
- while (end.getTime() <= asOf.getTime()) {
44
- periodIndex += 1;
45
- start = new Date(end);
46
- end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount);
47
- }
48
- return {
49
- start,
50
- end
51
- };
28
+ var resolveBillingPeriod = (params) => {
29
+ const anchor = assertValidDate$2(params.billingAnchor, "billingAnchor");
30
+ const asOf = assertValidDate$2(params.asOf, "asOf");
31
+ const intervalCount = normalizeIntervalCount(params.intervalCount ?? 1);
32
+ let periodIndex = getEstimatedPeriodIndex(anchor, asOf, params.intervalUnit, intervalCount);
33
+ let start = addBillingInterval(anchor, params.intervalUnit, Math.max(1, periodIndex * intervalCount));
34
+ if (periodIndex === 0) start = new Date(anchor);
35
+ while (start.getTime() > asOf.getTime() && periodIndex > 0) {
36
+ periodIndex -= 1;
37
+ start = periodIndex === 0 ? new Date(anchor) : addBillingInterval(anchor, params.intervalUnit, periodIndex * intervalCount);
38
+ }
39
+ let end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount);
40
+ while (end.getTime() <= asOf.getTime()) {
41
+ periodIndex += 1;
42
+ start = new Date(end);
43
+ end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount);
44
+ }
45
+ return {
46
+ start,
47
+ end
48
+ };
52
49
  };
53
- const assertValidDate$1 = (value, field) => {
54
- const date = new Date(value);
55
- if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`);
56
- return date;
50
+ //#endregion
51
+ //#region src/proration.ts
52
+ var assertValidDate$1 = (value, field) => {
53
+ const date = new Date(value);
54
+ if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`);
55
+ return date;
57
56
  };
58
- const calculateProratedAmount = (params) => {
59
- if (!Number.isSafeInteger(params.unitAmount) || params.unitAmount < 0) {
60
- throw new Error("unitAmount must be a non-negative safe integer");
61
- }
62
- const segmentStart = assertValidDate$1(params.segmentStart, "segmentStart");
63
- const segmentEnd = assertValidDate$1(params.segmentEnd, "segmentEnd");
64
- const periodStart = assertValidDate$1(params.periodStart, "periodStart");
65
- const periodEnd = assertValidDate$1(params.periodEnd, "periodEnd");
66
- const periodDuration = periodEnd.getTime() - periodStart.getTime();
67
- if (periodDuration <= 0) throw new Error("Billing period end must be after its start");
68
- const overlapStart = Math.max(segmentStart.getTime(), periodStart.getTime());
69
- const overlapEnd = Math.min(segmentEnd.getTime(), periodEnd.getTime());
70
- const overlapDuration = Math.max(0, overlapEnd - overlapStart);
71
- return Math.round(params.unitAmount * overlapDuration / periodDuration);
57
+ var calculateProratedAmount = (params) => {
58
+ if (!Number.isSafeInteger(params.unitAmount) || params.unitAmount < 0) throw new Error("unitAmount must be a non-negative safe integer");
59
+ const segmentStart = assertValidDate$1(params.segmentStart, "segmentStart");
60
+ const segmentEnd = assertValidDate$1(params.segmentEnd, "segmentEnd");
61
+ const periodStart = assertValidDate$1(params.periodStart, "periodStart");
62
+ const periodEnd = assertValidDate$1(params.periodEnd, "periodEnd");
63
+ const periodDuration = periodEnd.getTime() - periodStart.getTime();
64
+ if (periodDuration <= 0) throw new Error("Billing period end must be after its start");
65
+ const overlapStart = Math.max(segmentStart.getTime(), periodStart.getTime());
66
+ const overlapEnd = Math.min(segmentEnd.getTime(), periodEnd.getTime());
67
+ const overlapDuration = Math.max(0, overlapEnd - overlapStart);
68
+ return Math.round(params.unitAmount * overlapDuration / periodDuration);
72
69
  };
73
- const assertValidDate = (value, field) => {
74
- const date = new Date(value);
75
- if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`);
76
- return date;
70
+ //#endregion
71
+ //#region src/projection.ts
72
+ var assertValidDate = (value, field) => {
73
+ const date = new Date(value);
74
+ if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`);
75
+ return date;
77
76
  };
78
- const compareEvents = (left, right) => {
79
- const effectiveDelta = left.effectiveAt.getTime() - right.effectiveAt.getTime();
80
- if (effectiveDelta !== 0) return effectiveDelta;
81
- const occurredDelta = left.occurredAt.getTime() - right.occurredAt.getTime();
82
- if (occurredDelta !== 0) return occurredDelta;
83
- return (left.id ?? "").localeCompare(right.id ?? "");
77
+ var compareEvents = (left, right) => {
78
+ const effectiveDelta = left.effectiveAt.getTime() - right.effectiveAt.getTime();
79
+ if (effectiveDelta !== 0) return effectiveDelta;
80
+ const occurredDelta = left.occurredAt.getTime() - right.occurredAt.getTime();
81
+ if (occurredDelta !== 0) return occurredDelta;
82
+ return (left.id ?? "").localeCompare(right.id ?? "");
84
83
  };
85
- const sortSubscriptionEvents = (events) => events.map((event) => {
86
- const dates = {
87
- occurredAt: assertValidDate(event.occurredAt, "occurredAt"),
88
- effectiveAt: assertValidDate(event.effectiveAt, "effectiveAt")
89
- };
90
- if (event.type === "created") {
91
- return {
92
- ...event,
93
- ...dates,
94
- billingAnchor: assertValidDate(event.billingAnchor, "billingAnchor")
95
- };
96
- }
97
- return {
98
- ...event,
99
- ...dates,
100
- billingAnchor: event.billingAnchor ? assertValidDate(event.billingAnchor, "billingAnchor") : void 0
101
- };
84
+ var sortSubscriptionEvents = (events) => events.map((event) => {
85
+ const dates = {
86
+ occurredAt: assertValidDate(event.occurredAt, "occurredAt"),
87
+ effectiveAt: assertValidDate(event.effectiveAt, "effectiveAt")
88
+ };
89
+ if (event.type === "created") return {
90
+ ...event,
91
+ ...dates,
92
+ billingAnchor: assertValidDate(event.billingAnchor, "billingAnchor")
93
+ };
94
+ return {
95
+ ...event,
96
+ ...dates,
97
+ billingAnchor: event.billingAnchor ? assertValidDate(event.billingAnchor, "billingAnchor") : void 0
98
+ };
102
99
  }).sort(compareEvents);
103
- const getEffectiveEvents = (events) => {
104
- const ordered = sortSubscriptionEvents(events);
105
- const supersededIds = new Set(ordered.map((event) => event.supersedesEventId).filter((eventId) => Boolean(eventId)));
106
- return ordered.filter((event) => !(event.id && supersededIds.has(event.id)));
100
+ var getEffectiveEvents = (events) => {
101
+ const ordered = sortSubscriptionEvents(events);
102
+ const supersededIds = new Set(ordered.map((event) => event.supersedesEventId).filter((eventId) => Boolean(eventId)));
103
+ return ordered.filter((event) => !(event.id && supersededIds.has(event.id)));
107
104
  };
108
- const assertSingleSubscription = (events) => {
109
- if (events.length === 0) throw new Error("At least one subscription event is required");
110
- const subscriptionIds = new Set(events.map((event) => event.subscriptionId));
111
- if (subscriptionIds.size > 1) throw new Error("Events from multiple subscriptions cannot be projected together");
112
- const tenantIds = new Set(events.map((event) => event.tenantId));
113
- if (tenantIds.size > 1) throw new Error("Events from multiple tenants cannot be projected together");
114
- return events[0].subscriptionId;
105
+ var assertSingleSubscription = (events) => {
106
+ if (events.length === 0) throw new Error("At least one subscription event is required");
107
+ if (new Set(events.map((event) => event.subscriptionId)).size > 1) throw new Error("Events from multiple subscriptions cannot be projected together");
108
+ if (new Set(events.map((event) => event.tenantId)).size > 1) throw new Error("Events from multiple tenants cannot be projected together");
109
+ return events[0].subscriptionId;
115
110
  };
116
- const assertNonEmptyString = (value, field) => {
117
- if (typeof value !== "string" || !value.trim()) throw new Error(`${field} is required`);
111
+ var assertNonEmptyString = (value, field) => {
112
+ if (typeof value !== "string" || !value.trim()) throw new Error(`${field} is required`);
118
113
  };
119
- const assertPositiveInteger = (value, field) => {
120
- if (!Number.isInteger(value) || Number(value) < 1) throw new Error(`${field} must be a positive integer`);
114
+ var assertPositiveInteger = (value, field) => {
115
+ if (!Number.isInteger(value) || Number(value) < 1) throw new Error(`${field} must be a positive integer`);
121
116
  };
122
117
  function assertCreatedEvent(event) {
123
- if (event.type !== "created") throw new Error("The first subscription event must be created");
124
- assertNonEmptyString(event.tenantId, "tenantId");
125
- assertNonEmptyString(event.subscriptionId, "subscriptionId");
126
- if (!event.subscriptionType) throw new Error("subscriptionType is required");
127
- if (!event.scope) throw new Error("scope is required");
128
- if (event.subscriptionType === "addon") assertNonEmptyString(event.parentSubscriptionId, "parentSubscriptionId");
129
- if (event.scope !== "tenant") assertNonEmptyString(event.scopeId, "scopeId");
130
- assertNonEmptyString(event.toPlanKey, "toPlanKey");
131
- if (!event.toStatus) throw new Error("Created subscription events require toStatus");
132
- if (!Array.isArray(event.toModules)) throw new Error("Created subscription events require toModules");
133
- if (!event.toIntervalUnit) throw new Error("Created subscription events require toIntervalUnit");
134
- assertPositiveInteger(event.toIntervalCount, "toIntervalCount");
135
- assertValidDate(event.billingAnchor, "billingAnchor");
118
+ if (event.type !== "created") throw new Error("The first subscription event must be created");
119
+ assertNonEmptyString(event.tenantId, "tenantId");
120
+ assertNonEmptyString(event.subscriptionId, "subscriptionId");
121
+ if (!event.subscriptionType) throw new Error("subscriptionType is required");
122
+ if (!event.scope) throw new Error("scope is required");
123
+ if (event.subscriptionType === "addon") assertNonEmptyString(event.parentSubscriptionId, "parentSubscriptionId");
124
+ if (event.scope !== "tenant") assertNonEmptyString(event.scopeId, "scopeId");
125
+ assertNonEmptyString(event.toPlanKey, "toPlanKey");
126
+ if (!event.toStatus) throw new Error("Created subscription events require toStatus");
127
+ if (!Array.isArray(event.toModules)) throw new Error("Created subscription events require toModules");
128
+ if (!event.toIntervalUnit) throw new Error("Created subscription events require toIntervalUnit");
129
+ assertPositiveInteger(event.toIntervalCount, "toIntervalCount");
130
+ assertValidDate(event.billingAnchor, "billingAnchor");
136
131
  }
137
- const assertTransitionEvent = (event) => {
138
- assertNonEmptyString(event.idempotencyKey, "idempotencyKey");
139
- switch (event.type) {
140
- case "created":
141
- assertCreatedEvent(event);
142
- return;
143
- case "plan_changed":
144
- assertNonEmptyString(event.fromPlanKey, "fromPlanKey");
145
- assertNonEmptyString(event.toPlanKey, "toPlanKey");
146
- if (!Array.isArray(event.fromModules)) throw new Error("Plan changes require fromModules");
147
- if (!Array.isArray(event.toModules)) throw new Error("Plan changes require toModules");
148
- if (!event.fromIntervalUnit) throw new Error("Plan changes require fromIntervalUnit");
149
- if (!event.toIntervalUnit) throw new Error("Plan changes require toIntervalUnit");
150
- assertPositiveInteger(event.fromIntervalCount, "fromIntervalCount");
151
- assertPositiveInteger(event.toIntervalCount, "toIntervalCount");
152
- return;
153
- case "status_changed":
154
- if (!event.fromStatus || !event.toStatus) throw new Error("Status changes require fromStatus and toStatus");
155
- return;
156
- case "resumed":
157
- if (!event.fromStatus || event.toStatus !== "active") {
158
- throw new Error("Resumed events require fromStatus and active toStatus");
159
- }
160
- return;
161
- case "canceled":
162
- if (!event.fromStatus || event.toStatus !== "canceled") {
163
- throw new Error("Canceled events require fromStatus and canceled toStatus");
164
- }
165
- return;
166
- case "renewed":
167
- if (!event.fromStatus || !event.toStatus) throw new Error("Renewed events require fromStatus and toStatus");
168
- }
132
+ var assertTransitionEvent = (event) => {
133
+ assertNonEmptyString(event.idempotencyKey, "idempotencyKey");
134
+ switch (event.type) {
135
+ case "created":
136
+ assertCreatedEvent(event);
137
+ return;
138
+ case "plan_changed":
139
+ assertNonEmptyString(event.fromPlanKey, "fromPlanKey");
140
+ assertNonEmptyString(event.toPlanKey, "toPlanKey");
141
+ if (!Array.isArray(event.fromModules)) throw new Error("Plan changes require fromModules");
142
+ if (!Array.isArray(event.toModules)) throw new Error("Plan changes require toModules");
143
+ if (!event.fromIntervalUnit) throw new Error("Plan changes require fromIntervalUnit");
144
+ if (!event.toIntervalUnit) throw new Error("Plan changes require toIntervalUnit");
145
+ assertPositiveInteger(event.fromIntervalCount, "fromIntervalCount");
146
+ assertPositiveInteger(event.toIntervalCount, "toIntervalCount");
147
+ return;
148
+ case "status_changed":
149
+ if (!event.fromStatus || !event.toStatus) throw new Error("Status changes require fromStatus and toStatus");
150
+ return;
151
+ case "resumed":
152
+ if (!event.fromStatus || event.toStatus !== "active") throw new Error("Resumed events require fromStatus and active toStatus");
153
+ return;
154
+ case "canceled":
155
+ if (!event.fromStatus || event.toStatus !== "canceled") throw new Error("Canceled events require fromStatus and canceled toStatus");
156
+ return;
157
+ case "renewed": if (!event.fromStatus || !event.toStatus) throw new Error("Renewed events require fromStatus and toStatus");
158
+ }
169
159
  };
170
- const assertConsistentEventEnvelope = (event, created) => {
171
- if (event.tenantId !== created.tenantId) throw new Error("Subscription event tenantId cannot change");
172
- if (event.subscriptionType !== created.subscriptionType) {
173
- throw new Error("Subscription event subscriptionType cannot change");
174
- }
175
- if ((event.parentSubscriptionId ?? null) !== (created.parentSubscriptionId ?? null)) {
176
- throw new Error("Subscription event parentSubscriptionId cannot change");
177
- }
178
- if (event.scope !== created.scope) throw new Error("Subscription event scope cannot change");
179
- if ((event.scopeId ?? null) !== (created.scopeId ?? null)) {
180
- throw new Error("Subscription event scopeId cannot change");
181
- }
160
+ var assertConsistentEventEnvelope = (event, created) => {
161
+ if (event.tenantId !== created.tenantId) throw new Error("Subscription event tenantId cannot change");
162
+ if (event.subscriptionType !== created.subscriptionType) throw new Error("Subscription event subscriptionType cannot change");
163
+ if ((event.parentSubscriptionId ?? null) !== (created.parentSubscriptionId ?? null)) throw new Error("Subscription event parentSubscriptionId cannot change");
164
+ if (event.scope !== created.scope) throw new Error("Subscription event scope cannot change");
165
+ if ((event.scopeId ?? null) !== (created.scopeId ?? null)) throw new Error("Subscription event scopeId cannot change");
182
166
  };
183
- const projectSubscription = (events, options = {}) => {
184
- if (events.length === 0) return null;
185
- const subscriptionId = assertSingleSubscription(events);
186
- const ordered = getEffectiveEvents(events);
187
- const created = ordered[0];
188
- assertCreatedEvent(created);
189
- ordered.forEach((event, index) => {
190
- if (index > 0 && event.type === "created") {
191
- throw new Error("A subscription event stream cannot contain multiple created events");
192
- }
193
- assertTransitionEvent(event);
194
- assertConsistentEventEnvelope(event, created);
195
- });
196
- const asOf = assertValidDate(options.asOf ?? /* @__PURE__ */ new Date(), "asOf");
197
- const applicable = ordered.filter((event) => event.effectiveAt.getTime() <= asOf.getTime());
198
- if (applicable.length === 0) return null;
199
- const type = created.subscriptionType;
200
- const parentSubscriptionId = created.parentSubscriptionId ?? null;
201
- const scope = created.scope;
202
- const scopeId = created.scopeId ?? null;
203
- let planKey = created.toPlanKey;
204
- let priceId = null;
205
- let status = created.toStatus;
206
- let intervalUnit = created.toIntervalUnit;
207
- let intervalCount = created.toIntervalCount;
208
- let modules = [...created.toModules];
209
- let billingAnchor = created.billingAnchor;
210
- let provider = null;
211
- let canceledAt = null;
212
- for (const event of applicable.slice(1)) {
213
- planKey = event.toPlanKey ?? planKey;
214
- priceId = event.toPriceId ?? priceId;
215
- status = event.toStatus ?? status;
216
- intervalUnit = event.toIntervalUnit ?? intervalUnit;
217
- intervalCount = event.toIntervalCount ?? intervalCount;
218
- modules = event.toModules ? [...event.toModules] : modules;
219
- billingAnchor = event.billingAnchor ?? billingAnchor;
220
- provider = event.provider ?? provider;
221
- if (event.type === "canceled") {
222
- canceledAt = new Date(event.effectiveAt);
223
- } else if (event.type === "resumed") {
224
- canceledAt = null;
225
- }
226
- }
227
- const futureEvents = ordered.filter((event) => event.effectiveAt.getTime() > asOf.getTime());
228
- const scheduledCancellation = futureEvents.find((event) => event.type === "canceled") ?? null;
229
- const scheduledPlanEvent = futureEvents.find((event) => event.type === "plan_changed") ?? null;
230
- const scheduledPlan = scheduledPlanEvent ? {
231
- planKey: scheduledPlanEvent.toPlanKey,
232
- effectiveAt: new Date(scheduledPlanEvent.effectiveAt)
233
- } : null;
234
- const periodAsOf = canceledAt && canceledAt.getTime() <= asOf.getTime() ? new Date(canceledAt.getTime() - 1) : asOf;
235
- const period = resolveBillingPeriod({
236
- billingAnchor,
237
- intervalUnit,
238
- intervalCount,
239
- asOf: periodAsOf
240
- });
241
- return {
242
- subscriptionId,
243
- type,
244
- parentSubscriptionId,
245
- scope,
246
- scopeId,
247
- planKey,
248
- priceId,
249
- status,
250
- intervalUnit,
251
- intervalCount,
252
- modules,
253
- billingAnchor: new Date(billingAnchor),
254
- currentPeriodStart: period.start,
255
- currentPeriodEnd: period.end,
256
- cancelAtPeriodEnd: status !== "canceled" && Boolean(scheduledCancellation),
257
- cancelAt: scheduledCancellation ? new Date(scheduledCancellation.effectiveAt) : canceledAt ? new Date(canceledAt) : null,
258
- canceledAt: canceledAt ? new Date(canceledAt) : null,
259
- scheduledPlan,
260
- provider
261
- };
167
+ var projectSubscription = (events, options = {}) => {
168
+ if (events.length === 0) return null;
169
+ const subscriptionId = assertSingleSubscription(events);
170
+ const ordered = getEffectiveEvents(events);
171
+ const created = ordered[0];
172
+ assertCreatedEvent(created);
173
+ ordered.forEach((event, index) => {
174
+ if (index > 0 && event.type === "created") throw new Error("A subscription event stream cannot contain multiple created events");
175
+ assertTransitionEvent(event);
176
+ assertConsistentEventEnvelope(event, created);
177
+ });
178
+ const asOf = assertValidDate(options.asOf ?? /* @__PURE__ */ new Date(), "asOf");
179
+ const applicable = ordered.filter((event) => event.effectiveAt.getTime() <= asOf.getTime());
180
+ if (applicable.length === 0) return null;
181
+ const type = created.subscriptionType;
182
+ const parentSubscriptionId = created.parentSubscriptionId ?? null;
183
+ const scope = created.scope;
184
+ const scopeId = created.scopeId ?? null;
185
+ let planKey = created.toPlanKey;
186
+ let priceId = null;
187
+ let status = created.toStatus;
188
+ let intervalUnit = created.toIntervalUnit;
189
+ let intervalCount = created.toIntervalCount;
190
+ let modules = [...created.toModules];
191
+ let billingAnchor = created.billingAnchor;
192
+ let provider = null;
193
+ let canceledAt = null;
194
+ for (const event of applicable.slice(1)) {
195
+ planKey = event.toPlanKey ?? planKey;
196
+ priceId = event.toPriceId ?? priceId;
197
+ status = event.toStatus ?? status;
198
+ intervalUnit = event.toIntervalUnit ?? intervalUnit;
199
+ intervalCount = event.toIntervalCount ?? intervalCount;
200
+ modules = event.toModules ? [...event.toModules] : modules;
201
+ billingAnchor = event.billingAnchor ?? billingAnchor;
202
+ provider = event.provider ?? provider;
203
+ if (event.type === "canceled") canceledAt = new Date(event.effectiveAt);
204
+ else if (event.type === "resumed") canceledAt = null;
205
+ }
206
+ const futureEvents = ordered.filter((event) => event.effectiveAt.getTime() > asOf.getTime());
207
+ const scheduledCancellation = futureEvents.find((event) => event.type === "canceled") ?? null;
208
+ const scheduledPlanEvent = futureEvents.find((event) => event.type === "plan_changed") ?? null;
209
+ const scheduledPlan = scheduledPlanEvent ? {
210
+ planKey: scheduledPlanEvent.toPlanKey,
211
+ effectiveAt: new Date(scheduledPlanEvent.effectiveAt)
212
+ } : null;
213
+ const periodAsOf = canceledAt && canceledAt.getTime() <= asOf.getTime() ? /* @__PURE__ */ new Date(canceledAt.getTime() - 1) : asOf;
214
+ const period = resolveBillingPeriod({
215
+ billingAnchor,
216
+ intervalUnit,
217
+ intervalCount,
218
+ asOf: periodAsOf
219
+ });
220
+ return {
221
+ subscriptionId,
222
+ type,
223
+ parentSubscriptionId,
224
+ scope,
225
+ scopeId,
226
+ planKey,
227
+ priceId,
228
+ status,
229
+ intervalUnit,
230
+ intervalCount,
231
+ modules,
232
+ billingAnchor: new Date(billingAnchor),
233
+ currentPeriodStart: period.start,
234
+ currentPeriodEnd: period.end,
235
+ cancelAtPeriodEnd: status !== "canceled" && Boolean(scheduledCancellation),
236
+ cancelAt: scheduledCancellation ? new Date(scheduledCancellation.effectiveAt) : canceledAt ? new Date(canceledAt) : null,
237
+ canceledAt: canceledAt ? new Date(canceledAt) : null,
238
+ scheduledPlan,
239
+ provider
240
+ };
262
241
  };
263
- const getSubscriptionTransitionDates = (events, params) => {
264
- assertSingleSubscription(events);
265
- const after = assertValidDate(params.after, "after");
266
- const before = assertValidDate(params.before, "before");
267
- return [...new Set(getEffectiveEvents(events).map((event) => event.effectiveAt.getTime()).filter((time) => time > after.getTime() && time < before.getTime()))].sort((left, right) => left - right).map((time) => new Date(time));
242
+ var getSubscriptionTransitionDates = (events, params) => {
243
+ assertSingleSubscription(events);
244
+ const after = assertValidDate(params.after, "after");
245
+ const before = assertValidDate(params.before, "before");
246
+ return [...new Set(getEffectiveEvents(events).map((event) => event.effectiveAt.getTime()).filter((time) => time > after.getTime() && time < before.getTime()))].sort((left, right) => left - right).map((time) => new Date(time));
268
247
  };
269
- const DEFAULT_BILLABLE_STATUSES = ["active"];
270
- const assertValidWindow = (from, to) => {
271
- const normalizedFrom = new Date(from);
272
- const normalizedTo = new Date(to);
273
- if (Number.isNaN(normalizedFrom.getTime()) || Number.isNaN(normalizedTo.getTime())) {
274
- throw new Error("Billing segment window must contain valid dates");
275
- }
276
- if (normalizedTo.getTime() <= normalizedFrom.getTime()) {
277
- throw new Error("Billing segment window end must be after its start");
278
- }
279
- return {
280
- from: normalizedFrom,
281
- to: normalizedTo
282
- };
248
+ //#endregion
249
+ //#region src/segments.ts
250
+ var DEFAULT_BILLABLE_STATUSES = ["active"];
251
+ var assertValidWindow = (from, to) => {
252
+ const normalizedFrom = new Date(from);
253
+ const normalizedTo = new Date(to);
254
+ if (Number.isNaN(normalizedFrom.getTime()) || Number.isNaN(normalizedTo.getTime())) throw new Error("Billing segment window must contain valid dates");
255
+ if (normalizedTo.getTime() <= normalizedFrom.getTime()) throw new Error("Billing segment window end must be after its start");
256
+ return {
257
+ from: normalizedFrom,
258
+ to: normalizedTo
259
+ };
283
260
  };
284
- const buildBillableSegments = (events, params) => {
285
- if (events.length === 0) return [];
286
- const window = assertValidWindow(params.from, params.to);
287
- const billableStatuses = new Set(params.billableStatuses ?? DEFAULT_BILLABLE_STATUSES);
288
- const transitionDates = getSubscriptionTransitionDates(events, {
289
- after: window.from,
290
- before: window.to
291
- });
292
- const segments = [];
293
- let cursor = new Date(window.from);
294
- while (cursor.getTime() < window.to.getTime()) {
295
- const projection = projectSubscription(events, {
296
- asOf: cursor
297
- });
298
- const nextTransition = transitionDates.find((date) => date.getTime() > cursor.getTime()) ?? null;
299
- const nextPeriodEnd = projection && projection.currentPeriodEnd.getTime() > cursor.getTime() ? projection.currentPeriodEnd : null;
300
- const candidateTimes = [window.to.getTime(), nextTransition?.getTime() ?? Number.POSITIVE_INFINITY, nextPeriodEnd?.getTime() ?? Number.POSITIVE_INFINITY];
301
- const nextTime = Math.min(...candidateTimes);
302
- if (!Number.isFinite(nextTime) || nextTime <= cursor.getTime()) {
303
- throw new Error("Unable to advance while building billing segments");
304
- }
305
- const next = new Date(nextTime);
306
- if (projection && billableStatuses.has(projection.status)) {
307
- segments.push({
308
- subscriptionId: projection.subscriptionId,
309
- planKey: projection.planKey,
310
- priceId: projection.priceId,
311
- status: projection.status,
312
- modules: [...projection.modules],
313
- start: new Date(cursor),
314
- end: next,
315
- periodStart: new Date(projection.currentPeriodStart),
316
- periodEnd: new Date(projection.currentPeriodEnd)
317
- });
318
- }
319
- cursor = next;
320
- }
321
- return segments;
261
+ var buildBillableSegments = (events, params) => {
262
+ if (events.length === 0) return [];
263
+ const window = assertValidWindow(params.from, params.to);
264
+ const billableStatuses = new Set(params.billableStatuses ?? DEFAULT_BILLABLE_STATUSES);
265
+ const transitionDates = getSubscriptionTransitionDates(events, {
266
+ after: window.from,
267
+ before: window.to
268
+ });
269
+ const segments = [];
270
+ let cursor = new Date(window.from);
271
+ while (cursor.getTime() < window.to.getTime()) {
272
+ const projection = projectSubscription(events, { asOf: cursor });
273
+ const nextTransition = transitionDates.find((date) => date.getTime() > cursor.getTime()) ?? null;
274
+ const nextPeriodEnd = projection && projection.currentPeriodEnd.getTime() > cursor.getTime() ? projection.currentPeriodEnd : null;
275
+ const candidateTimes = [
276
+ window.to.getTime(),
277
+ nextTransition?.getTime() ?? Number.POSITIVE_INFINITY,
278
+ nextPeriodEnd?.getTime() ?? Number.POSITIVE_INFINITY
279
+ ];
280
+ const nextTime = Math.min(...candidateTimes);
281
+ if (!Number.isFinite(nextTime) || nextTime <= cursor.getTime()) throw new Error("Unable to advance while building billing segments");
282
+ const next = new Date(nextTime);
283
+ if (projection && billableStatuses.has(projection.status)) segments.push({
284
+ subscriptionId: projection.subscriptionId,
285
+ planKey: projection.planKey,
286
+ priceId: projection.priceId,
287
+ status: projection.status,
288
+ modules: [...projection.modules],
289
+ start: new Date(cursor),
290
+ end: next,
291
+ periodStart: new Date(projection.currentPeriodStart),
292
+ periodEnd: new Date(projection.currentPeriodEnd)
293
+ });
294
+ cursor = next;
295
+ }
296
+ return segments;
322
297
  };
323
- const runWithOptionalTransaction = async (storage, operation) => {
324
- if (!storage.runInTransaction) return operation();
325
- return storage.runInTransaction((transaction) => operation(transaction));
298
+ //#endregion
299
+ //#region src/storage.ts
300
+ var runWithOptionalTransaction = async (storage, operation) => {
301
+ if (!storage.runInTransaction) return operation();
302
+ return storage.runInTransaction((transaction) => operation(transaction));
326
303
  };
327
- const buildStoredProjection = (params) => {
328
- return {
329
- ...params.projection,
330
- tenantId: params.tenantId,
331
- latestEventId: params.latestEvent.id,
332
- latestEventAt: new Date(params.latestEvent.occurredAt),
333
- projectedAt: new Date(params.projectedAt)
334
- };
304
+ var buildStoredProjection = (params) => {
305
+ return {
306
+ ...params.projection,
307
+ tenantId: params.tenantId,
308
+ latestEventId: params.latestEvent.id,
309
+ latestEventAt: new Date(params.latestEvent.occurredAt),
310
+ projectedAt: new Date(params.projectedAt)
311
+ };
335
312
  };
336
- const projectAndStore = async (params) => {
337
- const events = await params.storage.listEvents(params.tenantId, params.subscriptionId, params.transaction);
338
- const projection = projectSubscription(events, {
339
- asOf: params.asOf
340
- });
341
- if (!projection) return null;
342
- const latestEvent = [...events].sort((left, right) => {
343
- const occurredDelta = right.occurredAt.getTime() - left.occurredAt.getTime();
344
- if (occurredDelta !== 0) return occurredDelta;
345
- return right.id.localeCompare(left.id);
346
- })[0];
347
- const storedProjection = buildStoredProjection({
348
- tenantId: params.tenantId,
349
- projection,
350
- latestEvent,
351
- projectedAt: params.asOf
352
- });
353
- await params.storage.upsertProjection(storedProjection, params.transaction);
354
- return storedProjection;
313
+ var projectAndStore = async (params) => {
314
+ const events = await params.storage.listEvents(params.tenantId, params.subscriptionId, params.transaction);
315
+ const projection = projectSubscription(events, { asOf: params.asOf });
316
+ if (!projection) return null;
317
+ const latestEvent = [...events].sort((left, right) => {
318
+ const occurredDelta = right.occurredAt.getTime() - left.occurredAt.getTime();
319
+ if (occurredDelta !== 0) return occurredDelta;
320
+ return right.id.localeCompare(left.id);
321
+ })[0];
322
+ const storedProjection = buildStoredProjection({
323
+ tenantId: params.tenantId,
324
+ projection,
325
+ latestEvent,
326
+ projectedAt: params.asOf
327
+ });
328
+ await params.storage.upsertProjection(storedProjection, params.transaction);
329
+ return storedProjection;
355
330
  };
356
- const appendSubscriptionEvent = async (params) => {
357
- if (params.event.tenantId !== params.tenantId) {
358
- throw new Error("Billing event tenantId does not match the requested tenant");
359
- }
360
- return runWithOptionalTransaction(params.storage, async (transaction) => {
361
- const event = await params.storage.createEvent(params.event, transaction);
362
- const projection = await projectAndStore({
363
- storage: params.storage,
364
- tenantId: params.tenantId,
365
- subscriptionId: event.subscriptionId,
366
- asOf: params.asOf ?? /* @__PURE__ */ new Date(),
367
- transaction
368
- });
369
- return {
370
- event,
371
- projection
372
- };
373
- });
331
+ var appendSubscriptionEvent = async (params) => {
332
+ if (params.event.tenantId !== params.tenantId) throw new Error("Billing event tenantId does not match the requested tenant");
333
+ return runWithOptionalTransaction(params.storage, async (transaction) => {
334
+ const event = await params.storage.createEvent(params.event, transaction);
335
+ return {
336
+ event,
337
+ projection: await projectAndStore({
338
+ storage: params.storage,
339
+ tenantId: params.tenantId,
340
+ subscriptionId: event.subscriptionId,
341
+ asOf: params.asOf ?? /* @__PURE__ */ new Date(),
342
+ transaction
343
+ })
344
+ };
345
+ });
374
346
  };
375
- const rebuildSubscriptionProjection = async (params) => runWithOptionalTransaction(params.storage, (transaction) => projectAndStore({
376
- storage: params.storage,
377
- tenantId: params.tenantId,
378
- subscriptionId: params.subscriptionId,
379
- asOf: params.asOf ?? /* @__PURE__ */ new Date(),
380
- transaction
347
+ var rebuildSubscriptionProjection = async (params) => runWithOptionalTransaction(params.storage, (transaction) => projectAndStore({
348
+ storage: params.storage,
349
+ tenantId: params.tenantId,
350
+ subscriptionId: params.subscriptionId,
351
+ asOf: params.asOf ?? /* @__PURE__ */ new Date(),
352
+ transaction
381
353
  }));
382
- const SUBSCRIPTION_STATUSES = ["trialing", "active", "past_due", "paused", "canceled", "expired"];
383
- const SUBSCRIPTION_INTERVAL_UNITS = ["month", "year"];
384
- const SUBSCRIPTION_TYPES = ["primary", "addon"];
385
- const SUBSCRIPTION_SCOPES = ["tenant", "workspace", "shop", "custom"];
386
- const SUBSCRIPTION_EVENT_TYPES = ["created", "plan_changed", "status_changed", "resumed", "canceled", "renewed"];
387
- const SUBSCRIPTION_CHANGE_DIRECTIONS = ["upgrade", "downgrade", "lateral"];
388
- export {
389
- SUBSCRIPTION_CHANGE_DIRECTIONS,
390
- SUBSCRIPTION_EVENT_TYPES,
391
- SUBSCRIPTION_INTERVAL_UNITS,
392
- SUBSCRIPTION_SCOPES,
393
- SUBSCRIPTION_STATUSES,
394
- SUBSCRIPTION_TYPES,
395
- addBillingInterval,
396
- appendSubscriptionEvent,
397
- buildBillableSegments,
398
- calculateProratedAmount,
399
- getSubscriptionTransitionDates,
400
- projectSubscription,
401
- rebuildSubscriptionProjection,
402
- resolveBillingPeriod,
403
- sortSubscriptionEvents
404
- };
405
- //# sourceMappingURL=index.js.map
354
+ //#endregion
355
+ //#region src/types.ts
356
+ var SUBSCRIPTION_STATUSES = [
357
+ "trialing",
358
+ "active",
359
+ "past_due",
360
+ "paused",
361
+ "canceled",
362
+ "expired"
363
+ ];
364
+ var SUBSCRIPTION_INTERVAL_UNITS = ["month", "year"];
365
+ var SUBSCRIPTION_TYPES = ["primary", "addon"];
366
+ var SUBSCRIPTION_SCOPES = [
367
+ "tenant",
368
+ "workspace",
369
+ "shop",
370
+ "custom"
371
+ ];
372
+ var SUBSCRIPTION_EVENT_TYPES = [
373
+ "created",
374
+ "plan_changed",
375
+ "status_changed",
376
+ "resumed",
377
+ "canceled",
378
+ "renewed"
379
+ ];
380
+ var SUBSCRIPTION_CHANGE_DIRECTIONS = [
381
+ "upgrade",
382
+ "downgrade",
383
+ "lateral"
384
+ ];
385
+ //#endregion
386
+ export { SUBSCRIPTION_CHANGE_DIRECTIONS, SUBSCRIPTION_EVENT_TYPES, SUBSCRIPTION_INTERVAL_UNITS, SUBSCRIPTION_SCOPES, SUBSCRIPTION_STATUSES, SUBSCRIPTION_TYPES, addBillingInterval, appendSubscriptionEvent, buildBillableSegments, calculateProratedAmount, getSubscriptionTransitionDates, projectSubscription, rebuildSubscriptionProjection, resolveBillingPeriod, sortSubscriptionEvents };
387
+
388
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/calendar.ts","../src/proration.ts","../src/projection.ts","../src/segments.ts","../src/storage.ts","../src/types.ts"],"sourcesContent":["import type { SubscriptionIntervalUnit } from \"./types\"\n\n\nconst assertValidDate = (value: Date, field: string): Date => {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`)\n return date\n}\n\nconst normalizeIntervalCount = (value: number): number => {\n if (!Number.isInteger(value) || value < 1) throw new Error(\"intervalCount must be a positive integer\")\n return value\n}\n\nconst getDaysInUtcMonth = (year: number, month: number): number =>\n new Date(Date.UTC(year, month + 1, 0)).getUTCDate()\n\nconst createClampedUtcDate = (source: Date, year: number, month: number): Date => {\n const day = Math.min(source.getUTCDate(), getDaysInUtcMonth(year, month))\n return new Date(Date.UTC(\n year,\n month,\n day,\n source.getUTCHours(),\n source.getUTCMinutes(),\n source.getUTCSeconds(),\n source.getUTCMilliseconds(),\n ))\n}\n\nexport const addBillingInterval = (\n value: Date,\n intervalUnit: SubscriptionIntervalUnit,\n intervalCount = 1,\n): Date => {\n const date = assertValidDate(value, \"value\")\n const count = normalizeIntervalCount(intervalCount)\n\n if (intervalUnit === \"year\") {\n return createClampedUtcDate(date, date.getUTCFullYear() + count, date.getUTCMonth())\n }\n\n const absoluteMonth = date.getUTCFullYear() * 12 + date.getUTCMonth() + count\n const year = Math.floor(absoluteMonth / 12)\n const month = ((absoluteMonth % 12) + 12) % 12\n return createClampedUtcDate(date, year, month)\n}\n\nconst getEstimatedPeriodIndex = (\n anchor: Date,\n asOf: Date,\n intervalUnit: SubscriptionIntervalUnit,\n intervalCount: number,\n): number => {\n if (asOf.getTime() <= anchor.getTime()) return 0\n\n const rawDifference = intervalUnit === \"year\"\n ? asOf.getUTCFullYear() - anchor.getUTCFullYear()\n : (asOf.getUTCFullYear() - anchor.getUTCFullYear()) * 12 + asOf.getUTCMonth() - anchor.getUTCMonth()\n\n return Math.max(0, Math.floor(rawDifference / intervalCount))\n}\n\nexport const resolveBillingPeriod = (params: {\n billingAnchor: Date\n intervalUnit: SubscriptionIntervalUnit\n intervalCount?: number\n asOf: Date\n}): { start: Date; end: Date } => {\n const anchor = assertValidDate(params.billingAnchor, \"billingAnchor\")\n const asOf = assertValidDate(params.asOf, \"asOf\")\n const intervalCount = normalizeIntervalCount(params.intervalCount ?? 1)\n\n let periodIndex = getEstimatedPeriodIndex(anchor, asOf, params.intervalUnit, intervalCount)\n let start = addBillingInterval(anchor, params.intervalUnit, Math.max(1, periodIndex * intervalCount))\n\n if (periodIndex === 0) start = new Date(anchor)\n\n while (start.getTime() > asOf.getTime() && periodIndex > 0) {\n periodIndex -= 1\n start = periodIndex === 0\n ? new Date(anchor)\n : addBillingInterval(anchor, params.intervalUnit, periodIndex * intervalCount)\n }\n\n let end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount)\n while (end.getTime() <= asOf.getTime()) {\n periodIndex += 1\n start = new Date(end)\n end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount)\n }\n\n return { start, end }\n}\n","const assertValidDate = (value: Date, field: string): Date => {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`)\n return date\n}\n\nexport const calculateProratedAmount = (params: {\n unitAmount: number\n segmentStart: Date\n segmentEnd: Date\n periodStart: Date\n periodEnd: Date\n}): number => {\n if (!Number.isSafeInteger(params.unitAmount) || params.unitAmount < 0) {\n throw new Error(\"unitAmount must be a non-negative safe integer\")\n }\n\n const segmentStart = assertValidDate(params.segmentStart, \"segmentStart\")\n const segmentEnd = assertValidDate(params.segmentEnd, \"segmentEnd\")\n const periodStart = assertValidDate(params.periodStart, \"periodStart\")\n const periodEnd = assertValidDate(params.periodEnd, \"periodEnd\")\n const periodDuration = periodEnd.getTime() - periodStart.getTime()\n if (periodDuration <= 0) throw new Error(\"Billing period end must be after its start\")\n\n const overlapStart = Math.max(segmentStart.getTime(), periodStart.getTime())\n const overlapEnd = Math.min(segmentEnd.getTime(), periodEnd.getTime())\n const overlapDuration = Math.max(0, overlapEnd - overlapStart)\n\n return Math.round(params.unitAmount * overlapDuration / periodDuration)\n}\n","import { resolveBillingPeriod } from \"./calendar\"\nimport type {\n ScheduledPlanChange,\n SubscriptionCreatedEvent,\n SubscriptionEvent,\n SubscriptionProjection,\n SubscriptionStatus,\n} from \"./types\"\n\n\nconst assertValidDate = (value: Date, field: string): Date => {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`)\n return date\n}\n\nconst compareEvents = (left: SubscriptionEvent, right: SubscriptionEvent): number => {\n const effectiveDelta = left.effectiveAt.getTime() - right.effectiveAt.getTime()\n if (effectiveDelta !== 0) return effectiveDelta\n\n const occurredDelta = left.occurredAt.getTime() - right.occurredAt.getTime()\n if (occurredDelta !== 0) return occurredDelta\n\n return (left.id ?? \"\").localeCompare(right.id ?? \"\")\n}\n\nexport const sortSubscriptionEvents = (events: readonly SubscriptionEvent[]): SubscriptionEvent[] =>\n events\n .map((event): SubscriptionEvent => {\n const dates = {\n occurredAt: assertValidDate(event.occurredAt, \"occurredAt\"),\n effectiveAt: assertValidDate(event.effectiveAt, \"effectiveAt\"),\n }\n if (event.type === \"created\") {\n return {\n ...event,\n ...dates,\n billingAnchor: assertValidDate(event.billingAnchor, \"billingAnchor\"),\n }\n }\n return {\n ...event,\n ...dates,\n billingAnchor: event.billingAnchor\n ? assertValidDate(event.billingAnchor, \"billingAnchor\")\n : undefined,\n }\n })\n .sort(compareEvents)\n\nconst getEffectiveEvents = (events: readonly SubscriptionEvent[]): SubscriptionEvent[] => {\n const ordered = sortSubscriptionEvents(events)\n const supersededIds = new Set(\n ordered\n .map((event) => event.supersedesEventId)\n .filter((eventId): eventId is string => Boolean(eventId)),\n )\n\n return ordered.filter((event) =>\n !(event.id && supersededIds.has(event.id)),\n )\n}\n\nconst assertSingleSubscription = (events: readonly SubscriptionEvent[]): string => {\n if (events.length === 0) throw new Error(\"At least one subscription event is required\")\n const subscriptionIds = new Set(events.map((event) => event.subscriptionId))\n if (subscriptionIds.size > 1) throw new Error(\"Events from multiple subscriptions cannot be projected together\")\n const tenantIds = new Set(events.map((event) => event.tenantId))\n if (tenantIds.size > 1) throw new Error(\"Events from multiple tenants cannot be projected together\")\n return events[0]!.subscriptionId\n}\n\nconst assertNonEmptyString = (value: unknown, field: string): void => {\n if (typeof value !== \"string\" || !value.trim()) throw new Error(`${field} is required`)\n}\n\nconst assertPositiveInteger = (value: unknown, field: string): void => {\n if (!Number.isInteger(value) || Number(value) < 1) throw new Error(`${field} must be a positive integer`)\n}\n\nfunction assertCreatedEvent(\n event: SubscriptionEvent,\n): asserts event is SubscriptionCreatedEvent {\n if (event.type !== \"created\") throw new Error(\"The first subscription event must be created\")\n assertNonEmptyString(event.tenantId, \"tenantId\")\n assertNonEmptyString(event.subscriptionId, \"subscriptionId\")\n if (!event.subscriptionType) throw new Error(\"subscriptionType is required\")\n if (!event.scope) throw new Error(\"scope is required\")\n if (event.subscriptionType === \"addon\") assertNonEmptyString(event.parentSubscriptionId, \"parentSubscriptionId\")\n if (event.scope !== \"tenant\") assertNonEmptyString(event.scopeId, \"scopeId\")\n assertNonEmptyString(event.toPlanKey, \"toPlanKey\")\n if (!event.toStatus) throw new Error(\"Created subscription events require toStatus\")\n if (!Array.isArray(event.toModules)) throw new Error(\"Created subscription events require toModules\")\n if (!event.toIntervalUnit) throw new Error(\"Created subscription events require toIntervalUnit\")\n assertPositiveInteger(event.toIntervalCount, \"toIntervalCount\")\n assertValidDate(event.billingAnchor, \"billingAnchor\")\n}\n\nconst assertTransitionEvent = (event: SubscriptionEvent): void => {\n assertNonEmptyString(event.idempotencyKey, \"idempotencyKey\")\n switch (event.type) {\n case \"created\":\n assertCreatedEvent(event)\n return\n case \"plan_changed\":\n assertNonEmptyString(event.fromPlanKey, \"fromPlanKey\")\n assertNonEmptyString(event.toPlanKey, \"toPlanKey\")\n if (!Array.isArray(event.fromModules)) throw new Error(\"Plan changes require fromModules\")\n if (!Array.isArray(event.toModules)) throw new Error(\"Plan changes require toModules\")\n if (!event.fromIntervalUnit) throw new Error(\"Plan changes require fromIntervalUnit\")\n if (!event.toIntervalUnit) throw new Error(\"Plan changes require toIntervalUnit\")\n assertPositiveInteger(event.fromIntervalCount, \"fromIntervalCount\")\n assertPositiveInteger(event.toIntervalCount, \"toIntervalCount\")\n return\n case \"status_changed\":\n if (!event.fromStatus || !event.toStatus) throw new Error(\"Status changes require fromStatus and toStatus\")\n return\n case \"resumed\":\n if (!event.fromStatus || event.toStatus !== \"active\") {\n throw new Error(\"Resumed events require fromStatus and active toStatus\")\n }\n return\n case \"canceled\":\n if (!event.fromStatus || event.toStatus !== \"canceled\") {\n throw new Error(\"Canceled events require fromStatus and canceled toStatus\")\n }\n return\n case \"renewed\":\n if (!event.fromStatus || !event.toStatus) throw new Error(\"Renewed events require fromStatus and toStatus\")\n }\n}\n\nconst assertConsistentEventEnvelope = (\n event: SubscriptionEvent,\n created: SubscriptionCreatedEvent,\n): void => {\n if (event.tenantId !== created.tenantId) throw new Error(\"Subscription event tenantId cannot change\")\n if (event.subscriptionType !== created.subscriptionType) {\n throw new Error(\"Subscription event subscriptionType cannot change\")\n }\n if ((event.parentSubscriptionId ?? null) !== (created.parentSubscriptionId ?? null)) {\n throw new Error(\"Subscription event parentSubscriptionId cannot change\")\n }\n if (event.scope !== created.scope) throw new Error(\"Subscription event scope cannot change\")\n if ((event.scopeId ?? null) !== (created.scopeId ?? null)) {\n throw new Error(\"Subscription event scopeId cannot change\")\n }\n}\n\nexport const projectSubscription = (\n events: readonly SubscriptionEvent[],\n options: { asOf?: Date } = {},\n): SubscriptionProjection | null => {\n if (events.length === 0) return null\n\n const subscriptionId = assertSingleSubscription(events)\n const ordered = getEffectiveEvents(events)\n const created = ordered[0]!\n assertCreatedEvent(created)\n ordered.forEach((event, index) => {\n if (index > 0 && event.type === \"created\") {\n throw new Error(\"A subscription event stream cannot contain multiple created events\")\n }\n assertTransitionEvent(event)\n assertConsistentEventEnvelope(event, created)\n })\n\n const asOf = assertValidDate(options.asOf ?? new Date(), \"asOf\")\n const applicable = ordered.filter((event) => event.effectiveAt.getTime() <= asOf.getTime())\n if (applicable.length === 0) return null\n\n const type = created.subscriptionType\n const parentSubscriptionId = created.parentSubscriptionId ?? null\n const scope = created.scope\n const scopeId = created.scopeId ?? null\n let planKey = created.toPlanKey\n let priceId: string | null = null\n let status: SubscriptionStatus = created.toStatus\n let intervalUnit = created.toIntervalUnit\n let intervalCount = created.toIntervalCount\n let modules = [...created.toModules]\n let billingAnchor = created.billingAnchor\n let provider: string | null = null\n let canceledAt: Date | null = null\n\n for (const event of applicable.slice(1)) {\n planKey = event.toPlanKey ?? planKey\n priceId = event.toPriceId ?? priceId\n status = event.toStatus ?? status\n intervalUnit = event.toIntervalUnit ?? intervalUnit\n intervalCount = event.toIntervalCount ?? intervalCount\n modules = event.toModules ? [...event.toModules] : modules\n billingAnchor = event.billingAnchor ?? billingAnchor\n provider = event.provider ?? provider\n\n if (event.type === \"canceled\") {\n canceledAt = new Date(event.effectiveAt)\n } else if (event.type === \"resumed\") {\n canceledAt = null\n }\n }\n\n const futureEvents = ordered.filter((event) => event.effectiveAt.getTime() > asOf.getTime())\n const scheduledCancellation = futureEvents.find((event) => event.type === \"canceled\") ?? null\n const scheduledPlanEvent = futureEvents.find((event) => event.type === \"plan_changed\") ?? null\n const scheduledPlan: ScheduledPlanChange | null = scheduledPlanEvent\n ? {\n planKey: scheduledPlanEvent.toPlanKey,\n effectiveAt: new Date(scheduledPlanEvent.effectiveAt),\n }\n : null\n\n const periodAsOf = canceledAt && canceledAt.getTime() <= asOf.getTime()\n ? new Date(canceledAt.getTime() - 1)\n : asOf\n const period = resolveBillingPeriod({\n billingAnchor,\n intervalUnit,\n intervalCount,\n asOf: periodAsOf,\n })\n\n return {\n subscriptionId,\n type,\n parentSubscriptionId,\n scope,\n scopeId,\n planKey,\n priceId,\n status,\n intervalUnit,\n intervalCount,\n modules,\n billingAnchor: new Date(billingAnchor),\n currentPeriodStart: period.start,\n currentPeriodEnd: period.end,\n cancelAtPeriodEnd: status !== \"canceled\" && Boolean(scheduledCancellation),\n cancelAt: scheduledCancellation\n ? new Date(scheduledCancellation.effectiveAt)\n : canceledAt\n ? new Date(canceledAt)\n : null,\n canceledAt: canceledAt ? new Date(canceledAt) : null,\n scheduledPlan,\n provider,\n }\n}\n\nexport const getSubscriptionTransitionDates = (\n events: readonly SubscriptionEvent[],\n params: { after: Date; before: Date },\n): Date[] => {\n assertSingleSubscription(events)\n const after = assertValidDate(params.after, \"after\")\n const before = assertValidDate(params.before, \"before\")\n\n return [...new Set(\n getEffectiveEvents(events)\n .map((event) => event.effectiveAt.getTime())\n .filter((time) => time > after.getTime() && time < before.getTime()),\n )]\n .sort((left, right) => left - right)\n .map((time) => new Date(time))\n}\n","import { getSubscriptionTransitionDates, projectSubscription } from \"./projection\"\nimport type {\n BillableSubscriptionSegment,\n SubscriptionEvent,\n SubscriptionStatus,\n} from \"./types\"\n\n\nconst DEFAULT_BILLABLE_STATUSES: readonly SubscriptionStatus[] = [\"active\"]\n\nconst assertValidWindow = (from: Date, to: Date): { from: Date; to: Date } => {\n const normalizedFrom = new Date(from)\n const normalizedTo = new Date(to)\n if (Number.isNaN(normalizedFrom.getTime()) || Number.isNaN(normalizedTo.getTime())) {\n throw new Error(\"Billing segment window must contain valid dates\")\n }\n if (normalizedTo.getTime() <= normalizedFrom.getTime()) {\n throw new Error(\"Billing segment window end must be after its start\")\n }\n return { from: normalizedFrom, to: normalizedTo }\n}\n\nexport const buildBillableSegments = (\n events: readonly SubscriptionEvent[],\n params: {\n from: Date\n to: Date\n billableStatuses?: readonly SubscriptionStatus[]\n },\n): BillableSubscriptionSegment[] => {\n if (events.length === 0) return []\n\n const window = assertValidWindow(params.from, params.to)\n const billableStatuses = new Set(params.billableStatuses ?? DEFAULT_BILLABLE_STATUSES)\n const transitionDates = getSubscriptionTransitionDates(events, {\n after: window.from,\n before: window.to,\n })\n const segments: BillableSubscriptionSegment[] = []\n let cursor = new Date(window.from)\n\n while (cursor.getTime() < window.to.getTime()) {\n const projection = projectSubscription(events, { asOf: cursor })\n const nextTransition = transitionDates.find((date) => date.getTime() > cursor.getTime()) ?? null\n const nextPeriodEnd = projection && projection.currentPeriodEnd.getTime() > cursor.getTime()\n ? projection.currentPeriodEnd\n : null\n const candidateTimes = [\n window.to.getTime(),\n nextTransition?.getTime() ?? Number.POSITIVE_INFINITY,\n nextPeriodEnd?.getTime() ?? Number.POSITIVE_INFINITY,\n ]\n const nextTime = Math.min(...candidateTimes)\n\n if (!Number.isFinite(nextTime) || nextTime <= cursor.getTime()) {\n throw new Error(\"Unable to advance while building billing segments\")\n }\n\n const next = new Date(nextTime)\n if (projection && billableStatuses.has(projection.status)) {\n segments.push({\n subscriptionId: projection.subscriptionId,\n planKey: projection.planKey,\n priceId: projection.priceId,\n status: projection.status,\n modules: [...projection.modules],\n start: new Date(cursor),\n end: next,\n periodStart: new Date(projection.currentPeriodStart),\n periodEnd: new Date(projection.currentPeriodEnd),\n })\n }\n\n cursor = next\n }\n\n return segments\n}\n","import { projectSubscription } from \"./projection\"\nimport type {\n PersistedSubscriptionEvent,\n StoredSubscriptionProjection,\n SubscriptionEvent,\n SubscriptionProjection,\n} from \"./types\"\n\n\nexport type BillingStorageTransaction = unknown\n\nexport type BillingStorage = {\n runInTransaction?: <T>(operation: (transaction: BillingStorageTransaction) => Promise<T>) => Promise<T>\n createEvent: (\n event: SubscriptionEvent,\n transaction?: BillingStorageTransaction,\n ) => Promise<PersistedSubscriptionEvent>\n listEvents: (\n tenantId: string,\n subscriptionId: string,\n transaction?: BillingStorageTransaction,\n ) => Promise<PersistedSubscriptionEvent[]>\n upsertProjection: (\n projection: StoredSubscriptionProjection,\n transaction?: BillingStorageTransaction,\n ) => Promise<void>\n}\n\nconst runWithOptionalTransaction = async <T>(\n storage: BillingStorage,\n operation: (transaction?: BillingStorageTransaction) => Promise<T>,\n): Promise<T> => {\n if (!storage.runInTransaction) return operation()\n return storage.runInTransaction((transaction) => operation(transaction))\n}\n\nconst buildStoredProjection = (params: {\n tenantId: string\n projection: SubscriptionProjection\n latestEvent: PersistedSubscriptionEvent\n projectedAt: Date\n}): StoredSubscriptionProjection => {\n return {\n ...params.projection,\n tenantId: params.tenantId,\n latestEventId: params.latestEvent.id,\n latestEventAt: new Date(params.latestEvent.occurredAt),\n projectedAt: new Date(params.projectedAt),\n }\n}\n\nconst projectAndStore = async (params: {\n storage: BillingStorage\n tenantId: string\n subscriptionId: string\n asOf: Date\n transaction?: BillingStorageTransaction\n}): Promise<StoredSubscriptionProjection | null> => {\n const events = await params.storage.listEvents(\n params.tenantId,\n params.subscriptionId,\n params.transaction,\n )\n const projection = projectSubscription(events, { asOf: params.asOf })\n if (!projection) return null\n\n const latestEvent = [...events].sort((left, right) => {\n const occurredDelta = right.occurredAt.getTime() - left.occurredAt.getTime()\n if (occurredDelta !== 0) return occurredDelta\n return right.id.localeCompare(left.id)\n })[0]!\n\n const storedProjection = buildStoredProjection({\n tenantId: params.tenantId,\n projection,\n latestEvent,\n projectedAt: params.asOf,\n })\n await params.storage.upsertProjection(storedProjection, params.transaction)\n return storedProjection\n}\n\nexport const appendSubscriptionEvent = async (params: {\n storage: BillingStorage\n tenantId: string\n event: SubscriptionEvent\n asOf?: Date\n}): Promise<{\n event: PersistedSubscriptionEvent\n projection: StoredSubscriptionProjection | null\n}> => {\n if (params.event.tenantId !== params.tenantId) {\n throw new Error(\"Billing event tenantId does not match the requested tenant\")\n }\n\n return runWithOptionalTransaction(params.storage, async (transaction) => {\n const event = await params.storage.createEvent(params.event, transaction)\n const projection = await projectAndStore({\n storage: params.storage,\n tenantId: params.tenantId,\n subscriptionId: event.subscriptionId,\n asOf: params.asOf ?? new Date(),\n transaction,\n })\n\n return { event, projection }\n })\n}\n\nexport const rebuildSubscriptionProjection = async (params: {\n storage: BillingStorage\n tenantId: string\n subscriptionId: string\n asOf?: Date\n}): Promise<StoredSubscriptionProjection | null> => runWithOptionalTransaction(\n params.storage,\n (transaction) => projectAndStore({\n storage: params.storage,\n tenantId: params.tenantId,\n subscriptionId: params.subscriptionId,\n asOf: params.asOf ?? new Date(),\n transaction,\n }),\n)\n","export const SUBSCRIPTION_STATUSES = [\n \"trialing\",\n \"active\",\n \"past_due\",\n \"paused\",\n \"canceled\",\n \"expired\",\n] as const\n\nexport type SubscriptionStatus = typeof SUBSCRIPTION_STATUSES[number]\n\nexport const SUBSCRIPTION_INTERVAL_UNITS = [\"month\", \"year\"] as const\n\nexport type SubscriptionIntervalUnit = typeof SUBSCRIPTION_INTERVAL_UNITS[number]\n\nexport const SUBSCRIPTION_TYPES = [\"primary\", \"addon\"] as const\n\nexport type SubscriptionType = typeof SUBSCRIPTION_TYPES[number]\n\nexport const SUBSCRIPTION_SCOPES = [\"tenant\", \"workspace\", \"shop\", \"custom\"] as const\n\nexport type SubscriptionScope = typeof SUBSCRIPTION_SCOPES[number]\n\nexport const SUBSCRIPTION_EVENT_TYPES = [\n \"created\",\n \"plan_changed\",\n \"status_changed\",\n \"resumed\",\n \"canceled\",\n \"renewed\",\n] as const\n\nexport type SubscriptionEventType = typeof SUBSCRIPTION_EVENT_TYPES[number]\n\nexport const SUBSCRIPTION_CHANGE_DIRECTIONS = [\n \"upgrade\",\n \"downgrade\",\n \"lateral\",\n] as const\n\nexport type SubscriptionChangeDirection = typeof SUBSCRIPTION_CHANGE_DIRECTIONS[number]\n\nexport type SubscriptionEventBase = {\n id?: string\n tenantId: string\n subscriptionId: string\n occurredAt: Date\n effectiveAt: Date\n subscriptionType: SubscriptionType\n parentSubscriptionId?: string\n scope: SubscriptionScope\n scopeId?: string\n fromPlanKey?: string\n toPlanKey?: string\n fromStatus?: SubscriptionStatus\n toStatus?: SubscriptionStatus\n fromModules?: string[]\n toModules?: string[]\n fromIntervalUnit?: SubscriptionIntervalUnit\n toIntervalUnit?: SubscriptionIntervalUnit\n fromIntervalCount?: number\n toIntervalCount?: number\n fromPriceId?: string\n toPriceId?: string\n billingAnchor?: Date\n supersedesEventId?: string\n idempotencyKey: string\n direction?: SubscriptionChangeDirection\n actorUserId?: string\n source?: \"admin\" | \"system\" | \"webhook\" | \"user\"\n reason?: string\n provider?: string\n providerEventId?: string\n providerPayload?: unknown\n metadata?: Record<string, unknown>\n}\n\nexport type SubscriptionCreatedEvent = SubscriptionEventBase & {\n type: \"created\"\n toPlanKey: string\n toStatus: SubscriptionStatus\n toModules: string[]\n toIntervalUnit: SubscriptionIntervalUnit\n toIntervalCount: number\n billingAnchor: Date\n}\n\nexport type SubscriptionPlanChangedEvent = SubscriptionEventBase & {\n type: \"plan_changed\"\n fromPlanKey: string\n toPlanKey: string\n fromModules: string[]\n toModules: string[]\n fromIntervalUnit: SubscriptionIntervalUnit\n toIntervalUnit: SubscriptionIntervalUnit\n fromIntervalCount: number\n toIntervalCount: number\n}\n\nexport type SubscriptionStatusChangedEvent = SubscriptionEventBase & {\n type: \"status_changed\"\n fromStatus: SubscriptionStatus\n toStatus: SubscriptionStatus\n}\n\nexport type SubscriptionResumedEvent = SubscriptionEventBase & {\n type: \"resumed\"\n fromStatus: SubscriptionStatus\n toStatus: \"active\"\n}\n\nexport type SubscriptionCanceledEvent = SubscriptionEventBase & {\n type: \"canceled\"\n fromStatus: SubscriptionStatus\n toStatus: \"canceled\"\n}\n\nexport type SubscriptionRenewedEvent = SubscriptionEventBase & {\n type: \"renewed\"\n fromStatus: SubscriptionStatus\n toStatus: SubscriptionStatus\n}\n\nexport type SubscriptionEvent =\n | SubscriptionCreatedEvent\n | SubscriptionPlanChangedEvent\n | SubscriptionStatusChangedEvent\n | SubscriptionResumedEvent\n | SubscriptionCanceledEvent\n | SubscriptionRenewedEvent\n\nexport type PersistedSubscriptionEvent = SubscriptionEvent & {\n id: string\n}\n\nexport type ScheduledPlanChange = {\n planKey: string\n effectiveAt: Date\n}\n\nexport type SubscriptionProjection = {\n subscriptionId: string\n type: SubscriptionType\n parentSubscriptionId: string | null\n scope: SubscriptionScope\n scopeId: string | null\n planKey: string\n priceId: string | null\n status: SubscriptionStatus\n intervalUnit: SubscriptionIntervalUnit\n intervalCount: number\n modules: string[]\n billingAnchor: Date\n currentPeriodStart: Date\n currentPeriodEnd: Date\n cancelAtPeriodEnd: boolean\n cancelAt: Date | null\n canceledAt: Date | null\n scheduledPlan: ScheduledPlanChange | null\n provider: string | null\n}\n\nexport type BillableSubscriptionSegment = {\n subscriptionId: string\n planKey: string\n priceId: string | null\n status: SubscriptionStatus\n modules: string[]\n start: Date\n end: Date\n periodStart: Date\n periodEnd: Date\n}\n\nexport type StoredSubscriptionProjection = SubscriptionProjection & {\n tenantId: string\n latestEventId: string\n latestEventAt: Date\n projectedAt: Date\n}\n"],"names":["assertValidDate","value","field","date","Date","Number","isNaN","getTime","Error","normalizeIntervalCount","isInteger","getDaysInUtcMonth","year","month","UTC","getUTCDate","createClampedUtcDate","source","day","Math","min","getUTCHours","getUTCMinutes","getUTCSeconds","getUTCMilliseconds","addBillingInterval","intervalUnit","intervalCount","count","getUTCFullYear","getUTCMonth","absoluteMonth","floor","getEstimatedPeriodIndex","anchor","asOf","rawDifference","max","resolveBillingPeriod","params","billingAnchor","periodIndex","start","end","calculateProratedAmount","isSafeInteger","unitAmount","segmentStart","segmentEnd","periodStart","periodEnd","periodDuration","overlapStart","overlapEnd","overlapDuration","round","compareEvents","left","right","effectiveDelta","effectiveAt","occurredDelta","occurredAt","id","localeCompare","sortSubscriptionEvents","events","map","event","dates","type","undefined","sort","getEffectiveEvents","ordered","supersededIds","Set","supersedesEventId","filter","eventId","Boolean","has","assertSingleSubscription","length","subscriptionIds","subscriptionId","size","tenantIds","tenantId","assertNonEmptyString","trim","assertPositiveInteger","assertCreatedEvent","subscriptionType","scope","parentSubscriptionId","scopeId","toPlanKey","toStatus","Array","isArray","toModules","toIntervalUnit","toIntervalCount","assertTransitionEvent","idempotencyKey","fromPlanKey","fromModules","fromIntervalUnit","fromIntervalCount","fromStatus","assertConsistentEventEnvelope","created","projectSubscription","options","forEach","index","applicable","planKey","priceId","status","modules","provider","canceledAt","slice","toPriceId","futureEvents","scheduledCancellation","find","scheduledPlanEvent","scheduledPlan","periodAsOf","period","currentPeriodStart","currentPeriodEnd","cancelAtPeriodEnd","cancelAt","getSubscriptionTransitionDates","after","before","time","DEFAULT_BILLABLE_STATUSES","assertValidWindow","from","to","normalizedFrom","normalizedTo","buildBillableSegments","window","billableStatuses","transitionDates","segments","cursor","projection","nextTransition","nextPeriodEnd","candidateTimes","POSITIVE_INFINITY","nextTime","isFinite","next","push","runWithOptionalTransaction","storage","operation","runInTransaction","transaction","buildStoredProjection","latestEventId","latestEvent","latestEventAt","projectedAt","projectAndStore","listEvents","storedProjection","upsertProjection","appendSubscriptionEvent","createEvent","rebuildSubscriptionProjection","SUBSCRIPTION_STATUSES","SUBSCRIPTION_INTERVAL_UNITS","SUBSCRIPTION_TYPES","SUBSCRIPTION_SCOPES","SUBSCRIPTION_EVENT_TYPES","SUBSCRIPTION_CHANGE_DIRECTIONS"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["SubscriptionIntervalUnit","assertValidDate","value","Date","field","date","Number","isNaN","getTime","Error","normalizeIntervalCount","isInteger","getDaysInUtcMonth","year","month","UTC","getUTCDate","createClampedUtcDate","source","day","Math","min","getUTCHours","getUTCMinutes","getUTCSeconds","getUTCMilliseconds","addBillingInterval","intervalUnit","intervalCount","count","getUTCFullYear","getUTCMonth","absoluteMonth","floor","getEstimatedPeriodIndex","anchor","asOf","rawDifference","max","resolveBillingPeriod","params","billingAnchor","start","end","periodIndex","assertValidDate","value","Date","field","date","Number","isNaN","getTime","Error","calculateProratedAmount","params","unitAmount","segmentStart","segmentEnd","periodStart","periodEnd","isSafeInteger","periodDuration","overlapStart","Math","max","overlapEnd","min","overlapDuration","round","resolveBillingPeriod","ScheduledPlanChange","SubscriptionCreatedEvent","SubscriptionEvent","SubscriptionProjection","SubscriptionStatus","assertValidDate","value","Date","field","date","Number","isNaN","getTime","Error","compareEvents","left","right","effectiveDelta","effectiveAt","occurredDelta","occurredAt","id","localeCompare","sortSubscriptionEvents","events","map","event","dates","type","billingAnchor","undefined","sort","getEffectiveEvents","ordered","supersededIds","Set","supersedesEventId","filter","eventId","Boolean","has","assertSingleSubscription","length","subscriptionIds","subscriptionId","size","tenantIds","tenantId","assertNonEmptyString","trim","assertPositiveInteger","isInteger","assertCreatedEvent","subscriptionType","scope","parentSubscriptionId","scopeId","toPlanKey","toStatus","Array","isArray","toModules","toIntervalUnit","toIntervalCount","assertTransitionEvent","idempotencyKey","fromPlanKey","fromModules","fromIntervalUnit","fromIntervalCount","fromStatus","assertConsistentEventEnvelope","created","projectSubscription","options","asOf","forEach","index","applicable","planKey","priceId","status","intervalUnit","intervalCount","modules","provider","canceledAt","slice","toPriceId","futureEvents","scheduledCancellation","find","scheduledPlanEvent","scheduledPlan","periodAsOf","period","currentPeriodStart","start","currentPeriodEnd","end","cancelAtPeriodEnd","cancelAt","getSubscriptionTransitionDates","params","after","before","time","getSubscriptionTransitionDates","projectSubscription","BillableSubscriptionSegment","SubscriptionEvent","SubscriptionStatus","DEFAULT_BILLABLE_STATUSES","assertValidWindow","from","Date","to","normalizedFrom","normalizedTo","Number","isNaN","getTime","Error","buildBillableSegments","events","params","billableStatuses","length","window","Set","transitionDates","after","before","segments","cursor","projection","asOf","nextTransition","find","date","nextPeriodEnd","currentPeriodEnd","candidateTimes","POSITIVE_INFINITY","nextTime","Math","min","isFinite","next","has","status","push","subscriptionId","planKey","priceId","modules","start","end","periodStart","currentPeriodStart","periodEnd","projectSubscription","PersistedSubscriptionEvent","StoredSubscriptionProjection","SubscriptionEvent","SubscriptionProjection","BillingStorageTransaction","BillingStorage","runInTransaction","operation","transaction","Promise","T","createEvent","event","listEvents","tenantId","subscriptionId","upsertProjection","projection","runWithOptionalTransaction","storage","buildStoredProjection","params","latestEvent","projectedAt","Date","latestEventId","id","latestEventAt","occurredAt","projectAndStore","asOf","events","sort","left","right","occurredDelta","getTime","localeCompare","storedProjection","appendSubscriptionEvent","Error","rebuildSubscriptionProjection","SUBSCRIPTION_STATUSES","const","SubscriptionStatus","SUBSCRIPTION_INTERVAL_UNITS","SubscriptionIntervalUnit","SUBSCRIPTION_TYPES","SubscriptionType","SUBSCRIPTION_SCOPES","SubscriptionScope","SUBSCRIPTION_EVENT_TYPES","SubscriptionEventType","SUBSCRIPTION_CHANGE_DIRECTIONS","SubscriptionChangeDirection","SubscriptionEventBase","id","tenantId","subscriptionId","occurredAt","Date","effectiveAt","subscriptionType","parentSubscriptionId","scope","scopeId","fromPlanKey","toPlanKey","fromStatus","toStatus","fromModules","toModules","fromIntervalUnit","toIntervalUnit","fromIntervalCount","toIntervalCount","fromPriceId","toPriceId","billingAnchor","supersedesEventId","idempotencyKey","direction","actorUserId","source","reason","provider","providerEventId","providerPayload","metadata","Record","SubscriptionCreatedEvent","type","SubscriptionPlanChangedEvent","SubscriptionStatusChangedEvent","SubscriptionResumedEvent","SubscriptionCanceledEvent","SubscriptionRenewedEvent","SubscriptionEvent","PersistedSubscriptionEvent","ScheduledPlanChange","planKey","SubscriptionProjection","priceId","status","intervalUnit","intervalCount","modules","currentPeriodStart","currentPeriodEnd","cancelAtPeriodEnd","cancelAt","canceledAt","scheduledPlan","BillableSubscriptionSegment","start","end","periodStart","periodEnd","StoredSubscriptionProjection","latestEventId","latestEventAt","projectedAt"],"sources":["../src/calendar.ts","../src/proration.ts","../src/projection.ts","../src/segments.ts","../src/storage.ts","../src/types.ts"],"sourcesContent":["import type { SubscriptionIntervalUnit } from \"./types\"\n\n\nconst assertValidDate = (value: Date, field: string): Date => {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`)\n return date\n}\n\nconst normalizeIntervalCount = (value: number): number => {\n if (!Number.isInteger(value) || value < 1) throw new Error(\"intervalCount must be a positive integer\")\n return value\n}\n\nconst getDaysInUtcMonth = (year: number, month: number): number =>\n new Date(Date.UTC(year, month + 1, 0)).getUTCDate()\n\nconst createClampedUtcDate = (source: Date, year: number, month: number): Date => {\n const day = Math.min(source.getUTCDate(), getDaysInUtcMonth(year, month))\n return new Date(Date.UTC(\n year,\n month,\n day,\n source.getUTCHours(),\n source.getUTCMinutes(),\n source.getUTCSeconds(),\n source.getUTCMilliseconds(),\n ))\n}\n\nexport const addBillingInterval = (\n value: Date,\n intervalUnit: SubscriptionIntervalUnit,\n intervalCount = 1,\n): Date => {\n const date = assertValidDate(value, \"value\")\n const count = normalizeIntervalCount(intervalCount)\n\n if (intervalUnit === \"year\") {\n return createClampedUtcDate(date, date.getUTCFullYear() + count, date.getUTCMonth())\n }\n\n const absoluteMonth = date.getUTCFullYear() * 12 + date.getUTCMonth() + count\n const year = Math.floor(absoluteMonth / 12)\n const month = ((absoluteMonth % 12) + 12) % 12\n return createClampedUtcDate(date, year, month)\n}\n\nconst getEstimatedPeriodIndex = (\n anchor: Date,\n asOf: Date,\n intervalUnit: SubscriptionIntervalUnit,\n intervalCount: number,\n): number => {\n if (asOf.getTime() <= anchor.getTime()) return 0\n\n const rawDifference = intervalUnit === \"year\"\n ? asOf.getUTCFullYear() - anchor.getUTCFullYear()\n : (asOf.getUTCFullYear() - anchor.getUTCFullYear()) * 12 + asOf.getUTCMonth() - anchor.getUTCMonth()\n\n return Math.max(0, Math.floor(rawDifference / intervalCount))\n}\n\nexport const resolveBillingPeriod = (params: {\n billingAnchor: Date\n intervalUnit: SubscriptionIntervalUnit\n intervalCount?: number\n asOf: Date\n}): { start: Date; end: Date } => {\n const anchor = assertValidDate(params.billingAnchor, \"billingAnchor\")\n const asOf = assertValidDate(params.asOf, \"asOf\")\n const intervalCount = normalizeIntervalCount(params.intervalCount ?? 1)\n\n let periodIndex = getEstimatedPeriodIndex(anchor, asOf, params.intervalUnit, intervalCount)\n let start = addBillingInterval(anchor, params.intervalUnit, Math.max(1, periodIndex * intervalCount))\n\n if (periodIndex === 0) start = new Date(anchor)\n\n while (start.getTime() > asOf.getTime() && periodIndex > 0) {\n periodIndex -= 1\n start = periodIndex === 0\n ? new Date(anchor)\n : addBillingInterval(anchor, params.intervalUnit, periodIndex * intervalCount)\n }\n\n let end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount)\n while (end.getTime() <= asOf.getTime()) {\n periodIndex += 1\n start = new Date(end)\n end = addBillingInterval(anchor, params.intervalUnit, (periodIndex + 1) * intervalCount)\n }\n\n return { start, end }\n}\n","const assertValidDate = (value: Date, field: string): Date => {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`)\n return date\n}\n\nexport const calculateProratedAmount = (params: {\n unitAmount: number\n segmentStart: Date\n segmentEnd: Date\n periodStart: Date\n periodEnd: Date\n}): number => {\n if (!Number.isSafeInteger(params.unitAmount) || params.unitAmount < 0) {\n throw new Error(\"unitAmount must be a non-negative safe integer\")\n }\n\n const segmentStart = assertValidDate(params.segmentStart, \"segmentStart\")\n const segmentEnd = assertValidDate(params.segmentEnd, \"segmentEnd\")\n const periodStart = assertValidDate(params.periodStart, \"periodStart\")\n const periodEnd = assertValidDate(params.periodEnd, \"periodEnd\")\n const periodDuration = periodEnd.getTime() - periodStart.getTime()\n if (periodDuration <= 0) throw new Error(\"Billing period end must be after its start\")\n\n const overlapStart = Math.max(segmentStart.getTime(), periodStart.getTime())\n const overlapEnd = Math.min(segmentEnd.getTime(), periodEnd.getTime())\n const overlapDuration = Math.max(0, overlapEnd - overlapStart)\n\n return Math.round(params.unitAmount * overlapDuration / periodDuration)\n}\n","import { resolveBillingPeriod } from \"./calendar\"\nimport type {\n ScheduledPlanChange,\n SubscriptionCreatedEvent,\n SubscriptionEvent,\n SubscriptionProjection,\n SubscriptionStatus,\n} from \"./types\"\n\n\nconst assertValidDate = (value: Date, field: string): Date => {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) throw new Error(`${field} must be a valid date`)\n return date\n}\n\nconst compareEvents = (left: SubscriptionEvent, right: SubscriptionEvent): number => {\n const effectiveDelta = left.effectiveAt.getTime() - right.effectiveAt.getTime()\n if (effectiveDelta !== 0) return effectiveDelta\n\n const occurredDelta = left.occurredAt.getTime() - right.occurredAt.getTime()\n if (occurredDelta !== 0) return occurredDelta\n\n return (left.id ?? \"\").localeCompare(right.id ?? \"\")\n}\n\nexport const sortSubscriptionEvents = (events: readonly SubscriptionEvent[]): SubscriptionEvent[] =>\n events\n .map((event): SubscriptionEvent => {\n const dates = {\n occurredAt: assertValidDate(event.occurredAt, \"occurredAt\"),\n effectiveAt: assertValidDate(event.effectiveAt, \"effectiveAt\"),\n }\n if (event.type === \"created\") {\n return {\n ...event,\n ...dates,\n billingAnchor: assertValidDate(event.billingAnchor, \"billingAnchor\"),\n }\n }\n return {\n ...event,\n ...dates,\n billingAnchor: event.billingAnchor\n ? assertValidDate(event.billingAnchor, \"billingAnchor\")\n : undefined,\n }\n })\n .sort(compareEvents)\n\nconst getEffectiveEvents = (events: readonly SubscriptionEvent[]): SubscriptionEvent[] => {\n const ordered = sortSubscriptionEvents(events)\n const supersededIds = new Set(\n ordered\n .map((event) => event.supersedesEventId)\n .filter((eventId): eventId is string => Boolean(eventId)),\n )\n\n return ordered.filter((event) =>\n !(event.id && supersededIds.has(event.id)),\n )\n}\n\nconst assertSingleSubscription = (events: readonly SubscriptionEvent[]): string => {\n if (events.length === 0) throw new Error(\"At least one subscription event is required\")\n const subscriptionIds = new Set(events.map((event) => event.subscriptionId))\n if (subscriptionIds.size > 1) throw new Error(\"Events from multiple subscriptions cannot be projected together\")\n const tenantIds = new Set(events.map((event) => event.tenantId))\n if (tenantIds.size > 1) throw new Error(\"Events from multiple tenants cannot be projected together\")\n return events[0]!.subscriptionId\n}\n\nconst assertNonEmptyString = (value: unknown, field: string): void => {\n if (typeof value !== \"string\" || !value.trim()) throw new Error(`${field} is required`)\n}\n\nconst assertPositiveInteger = (value: unknown, field: string): void => {\n if (!Number.isInteger(value) || Number(value) < 1) throw new Error(`${field} must be a positive integer`)\n}\n\nfunction assertCreatedEvent(\n event: SubscriptionEvent,\n): asserts event is SubscriptionCreatedEvent {\n if (event.type !== \"created\") throw new Error(\"The first subscription event must be created\")\n assertNonEmptyString(event.tenantId, \"tenantId\")\n assertNonEmptyString(event.subscriptionId, \"subscriptionId\")\n if (!event.subscriptionType) throw new Error(\"subscriptionType is required\")\n if (!event.scope) throw new Error(\"scope is required\")\n if (event.subscriptionType === \"addon\") assertNonEmptyString(event.parentSubscriptionId, \"parentSubscriptionId\")\n if (event.scope !== \"tenant\") assertNonEmptyString(event.scopeId, \"scopeId\")\n assertNonEmptyString(event.toPlanKey, \"toPlanKey\")\n if (!event.toStatus) throw new Error(\"Created subscription events require toStatus\")\n if (!Array.isArray(event.toModules)) throw new Error(\"Created subscription events require toModules\")\n if (!event.toIntervalUnit) throw new Error(\"Created subscription events require toIntervalUnit\")\n assertPositiveInteger(event.toIntervalCount, \"toIntervalCount\")\n assertValidDate(event.billingAnchor, \"billingAnchor\")\n}\n\nconst assertTransitionEvent = (event: SubscriptionEvent): void => {\n assertNonEmptyString(event.idempotencyKey, \"idempotencyKey\")\n switch (event.type) {\n case \"created\":\n assertCreatedEvent(event)\n return\n case \"plan_changed\":\n assertNonEmptyString(event.fromPlanKey, \"fromPlanKey\")\n assertNonEmptyString(event.toPlanKey, \"toPlanKey\")\n if (!Array.isArray(event.fromModules)) throw new Error(\"Plan changes require fromModules\")\n if (!Array.isArray(event.toModules)) throw new Error(\"Plan changes require toModules\")\n if (!event.fromIntervalUnit) throw new Error(\"Plan changes require fromIntervalUnit\")\n if (!event.toIntervalUnit) throw new Error(\"Plan changes require toIntervalUnit\")\n assertPositiveInteger(event.fromIntervalCount, \"fromIntervalCount\")\n assertPositiveInteger(event.toIntervalCount, \"toIntervalCount\")\n return\n case \"status_changed\":\n if (!event.fromStatus || !event.toStatus) throw new Error(\"Status changes require fromStatus and toStatus\")\n return\n case \"resumed\":\n if (!event.fromStatus || event.toStatus !== \"active\") {\n throw new Error(\"Resumed events require fromStatus and active toStatus\")\n }\n return\n case \"canceled\":\n if (!event.fromStatus || event.toStatus !== \"canceled\") {\n throw new Error(\"Canceled events require fromStatus and canceled toStatus\")\n }\n return\n case \"renewed\":\n if (!event.fromStatus || !event.toStatus) throw new Error(\"Renewed events require fromStatus and toStatus\")\n }\n}\n\nconst assertConsistentEventEnvelope = (\n event: SubscriptionEvent,\n created: SubscriptionCreatedEvent,\n): void => {\n if (event.tenantId !== created.tenantId) throw new Error(\"Subscription event tenantId cannot change\")\n if (event.subscriptionType !== created.subscriptionType) {\n throw new Error(\"Subscription event subscriptionType cannot change\")\n }\n if ((event.parentSubscriptionId ?? null) !== (created.parentSubscriptionId ?? null)) {\n throw new Error(\"Subscription event parentSubscriptionId cannot change\")\n }\n if (event.scope !== created.scope) throw new Error(\"Subscription event scope cannot change\")\n if ((event.scopeId ?? null) !== (created.scopeId ?? null)) {\n throw new Error(\"Subscription event scopeId cannot change\")\n }\n}\n\nexport const projectSubscription = (\n events: readonly SubscriptionEvent[],\n options: { asOf?: Date } = {},\n): SubscriptionProjection | null => {\n if (events.length === 0) return null\n\n const subscriptionId = assertSingleSubscription(events)\n const ordered = getEffectiveEvents(events)\n const created = ordered[0]!\n assertCreatedEvent(created)\n ordered.forEach((event, index) => {\n if (index > 0 && event.type === \"created\") {\n throw new Error(\"A subscription event stream cannot contain multiple created events\")\n }\n assertTransitionEvent(event)\n assertConsistentEventEnvelope(event, created)\n })\n\n const asOf = assertValidDate(options.asOf ?? new Date(), \"asOf\")\n const applicable = ordered.filter((event) => event.effectiveAt.getTime() <= asOf.getTime())\n if (applicable.length === 0) return null\n\n const type = created.subscriptionType\n const parentSubscriptionId = created.parentSubscriptionId ?? null\n const scope = created.scope\n const scopeId = created.scopeId ?? null\n let planKey = created.toPlanKey\n let priceId: string | null = null\n let status: SubscriptionStatus = created.toStatus\n let intervalUnit = created.toIntervalUnit\n let intervalCount = created.toIntervalCount\n let modules = [...created.toModules]\n let billingAnchor = created.billingAnchor\n let provider: string | null = null\n let canceledAt: Date | null = null\n\n for (const event of applicable.slice(1)) {\n planKey = event.toPlanKey ?? planKey\n priceId = event.toPriceId ?? priceId\n status = event.toStatus ?? status\n intervalUnit = event.toIntervalUnit ?? intervalUnit\n intervalCount = event.toIntervalCount ?? intervalCount\n modules = event.toModules ? [...event.toModules] : modules\n billingAnchor = event.billingAnchor ?? billingAnchor\n provider = event.provider ?? provider\n\n if (event.type === \"canceled\") {\n canceledAt = new Date(event.effectiveAt)\n } else if (event.type === \"resumed\") {\n canceledAt = null\n }\n }\n\n const futureEvents = ordered.filter((event) => event.effectiveAt.getTime() > asOf.getTime())\n const scheduledCancellation = futureEvents.find((event) => event.type === \"canceled\") ?? null\n const scheduledPlanEvent = futureEvents.find((event) => event.type === \"plan_changed\") ?? null\n const scheduledPlan: ScheduledPlanChange | null = scheduledPlanEvent\n ? {\n planKey: scheduledPlanEvent.toPlanKey,\n effectiveAt: new Date(scheduledPlanEvent.effectiveAt),\n }\n : null\n\n const periodAsOf = canceledAt && canceledAt.getTime() <= asOf.getTime()\n ? new Date(canceledAt.getTime() - 1)\n : asOf\n const period = resolveBillingPeriod({\n billingAnchor,\n intervalUnit,\n intervalCount,\n asOf: periodAsOf,\n })\n\n return {\n subscriptionId,\n type,\n parentSubscriptionId,\n scope,\n scopeId,\n planKey,\n priceId,\n status,\n intervalUnit,\n intervalCount,\n modules,\n billingAnchor: new Date(billingAnchor),\n currentPeriodStart: period.start,\n currentPeriodEnd: period.end,\n cancelAtPeriodEnd: status !== \"canceled\" && Boolean(scheduledCancellation),\n cancelAt: scheduledCancellation\n ? new Date(scheduledCancellation.effectiveAt)\n : canceledAt\n ? new Date(canceledAt)\n : null,\n canceledAt: canceledAt ? new Date(canceledAt) : null,\n scheduledPlan,\n provider,\n }\n}\n\nexport const getSubscriptionTransitionDates = (\n events: readonly SubscriptionEvent[],\n params: { after: Date; before: Date },\n): Date[] => {\n assertSingleSubscription(events)\n const after = assertValidDate(params.after, \"after\")\n const before = assertValidDate(params.before, \"before\")\n\n return [...new Set(\n getEffectiveEvents(events)\n .map((event) => event.effectiveAt.getTime())\n .filter((time) => time > after.getTime() && time < before.getTime()),\n )]\n .sort((left, right) => left - right)\n .map((time) => new Date(time))\n}\n","import { getSubscriptionTransitionDates, projectSubscription } from \"./projection\"\nimport type {\n BillableSubscriptionSegment,\n SubscriptionEvent,\n SubscriptionStatus,\n} from \"./types\"\n\n\nconst DEFAULT_BILLABLE_STATUSES: readonly SubscriptionStatus[] = [\"active\"]\n\nconst assertValidWindow = (from: Date, to: Date): { from: Date; to: Date } => {\n const normalizedFrom = new Date(from)\n const normalizedTo = new Date(to)\n if (Number.isNaN(normalizedFrom.getTime()) || Number.isNaN(normalizedTo.getTime())) {\n throw new Error(\"Billing segment window must contain valid dates\")\n }\n if (normalizedTo.getTime() <= normalizedFrom.getTime()) {\n throw new Error(\"Billing segment window end must be after its start\")\n }\n return { from: normalizedFrom, to: normalizedTo }\n}\n\nexport const buildBillableSegments = (\n events: readonly SubscriptionEvent[],\n params: {\n from: Date\n to: Date\n billableStatuses?: readonly SubscriptionStatus[]\n },\n): BillableSubscriptionSegment[] => {\n if (events.length === 0) return []\n\n const window = assertValidWindow(params.from, params.to)\n const billableStatuses = new Set(params.billableStatuses ?? DEFAULT_BILLABLE_STATUSES)\n const transitionDates = getSubscriptionTransitionDates(events, {\n after: window.from,\n before: window.to,\n })\n const segments: BillableSubscriptionSegment[] = []\n let cursor = new Date(window.from)\n\n while (cursor.getTime() < window.to.getTime()) {\n const projection = projectSubscription(events, { asOf: cursor })\n const nextTransition = transitionDates.find((date) => date.getTime() > cursor.getTime()) ?? null\n const nextPeriodEnd = projection && projection.currentPeriodEnd.getTime() > cursor.getTime()\n ? projection.currentPeriodEnd\n : null\n const candidateTimes = [\n window.to.getTime(),\n nextTransition?.getTime() ?? Number.POSITIVE_INFINITY,\n nextPeriodEnd?.getTime() ?? Number.POSITIVE_INFINITY,\n ]\n const nextTime = Math.min(...candidateTimes)\n\n if (!Number.isFinite(nextTime) || nextTime <= cursor.getTime()) {\n throw new Error(\"Unable to advance while building billing segments\")\n }\n\n const next = new Date(nextTime)\n if (projection && billableStatuses.has(projection.status)) {\n segments.push({\n subscriptionId: projection.subscriptionId,\n planKey: projection.planKey,\n priceId: projection.priceId,\n status: projection.status,\n modules: [...projection.modules],\n start: new Date(cursor),\n end: next,\n periodStart: new Date(projection.currentPeriodStart),\n periodEnd: new Date(projection.currentPeriodEnd),\n })\n }\n\n cursor = next\n }\n\n return segments\n}\n","import { projectSubscription } from \"./projection\"\nimport type {\n PersistedSubscriptionEvent,\n StoredSubscriptionProjection,\n SubscriptionEvent,\n SubscriptionProjection,\n} from \"./types\"\n\n\nexport type BillingStorageTransaction = unknown\n\nexport type BillingStorage = {\n runInTransaction?: <T>(operation: (transaction: BillingStorageTransaction) => Promise<T>) => Promise<T>\n createEvent: (\n event: SubscriptionEvent,\n transaction?: BillingStorageTransaction,\n ) => Promise<PersistedSubscriptionEvent>\n listEvents: (\n tenantId: string,\n subscriptionId: string,\n transaction?: BillingStorageTransaction,\n ) => Promise<PersistedSubscriptionEvent[]>\n upsertProjection: (\n projection: StoredSubscriptionProjection,\n transaction?: BillingStorageTransaction,\n ) => Promise<void>\n}\n\nconst runWithOptionalTransaction = async <T>(\n storage: BillingStorage,\n operation: (transaction?: BillingStorageTransaction) => Promise<T>,\n): Promise<T> => {\n if (!storage.runInTransaction) return operation()\n return storage.runInTransaction((transaction) => operation(transaction))\n}\n\nconst buildStoredProjection = (params: {\n tenantId: string\n projection: SubscriptionProjection\n latestEvent: PersistedSubscriptionEvent\n projectedAt: Date\n}): StoredSubscriptionProjection => {\n return {\n ...params.projection,\n tenantId: params.tenantId,\n latestEventId: params.latestEvent.id,\n latestEventAt: new Date(params.latestEvent.occurredAt),\n projectedAt: new Date(params.projectedAt),\n }\n}\n\nconst projectAndStore = async (params: {\n storage: BillingStorage\n tenantId: string\n subscriptionId: string\n asOf: Date\n transaction?: BillingStorageTransaction\n}): Promise<StoredSubscriptionProjection | null> => {\n const events = await params.storage.listEvents(\n params.tenantId,\n params.subscriptionId,\n params.transaction,\n )\n const projection = projectSubscription(events, { asOf: params.asOf })\n if (!projection) return null\n\n const latestEvent = [...events].sort((left, right) => {\n const occurredDelta = right.occurredAt.getTime() - left.occurredAt.getTime()\n if (occurredDelta !== 0) return occurredDelta\n return right.id.localeCompare(left.id)\n })[0]!\n\n const storedProjection = buildStoredProjection({\n tenantId: params.tenantId,\n projection,\n latestEvent,\n projectedAt: params.asOf,\n })\n await params.storage.upsertProjection(storedProjection, params.transaction)\n return storedProjection\n}\n\nexport const appendSubscriptionEvent = async (params: {\n storage: BillingStorage\n tenantId: string\n event: SubscriptionEvent\n asOf?: Date\n}): Promise<{\n event: PersistedSubscriptionEvent\n projection: StoredSubscriptionProjection | null\n}> => {\n if (params.event.tenantId !== params.tenantId) {\n throw new Error(\"Billing event tenantId does not match the requested tenant\")\n }\n\n return runWithOptionalTransaction(params.storage, async (transaction) => {\n const event = await params.storage.createEvent(params.event, transaction)\n const projection = await projectAndStore({\n storage: params.storage,\n tenantId: params.tenantId,\n subscriptionId: event.subscriptionId,\n asOf: params.asOf ?? new Date(),\n transaction,\n })\n\n return { event, projection }\n })\n}\n\nexport const rebuildSubscriptionProjection = async (params: {\n storage: BillingStorage\n tenantId: string\n subscriptionId: string\n asOf?: Date\n}): Promise<StoredSubscriptionProjection | null> => runWithOptionalTransaction(\n params.storage,\n (transaction) => projectAndStore({\n storage: params.storage,\n tenantId: params.tenantId,\n subscriptionId: params.subscriptionId,\n asOf: params.asOf ?? new Date(),\n transaction,\n }),\n)\n","export const SUBSCRIPTION_STATUSES = [\n \"trialing\",\n \"active\",\n \"past_due\",\n \"paused\",\n \"canceled\",\n \"expired\",\n] as const\n\nexport type SubscriptionStatus = typeof SUBSCRIPTION_STATUSES[number]\n\nexport const SUBSCRIPTION_INTERVAL_UNITS = [\"month\", \"year\"] as const\n\nexport type SubscriptionIntervalUnit = typeof SUBSCRIPTION_INTERVAL_UNITS[number]\n\nexport const SUBSCRIPTION_TYPES = [\"primary\", \"addon\"] as const\n\nexport type SubscriptionType = typeof SUBSCRIPTION_TYPES[number]\n\nexport const SUBSCRIPTION_SCOPES = [\"tenant\", \"workspace\", \"shop\", \"custom\"] as const\n\nexport type SubscriptionScope = typeof SUBSCRIPTION_SCOPES[number]\n\nexport const SUBSCRIPTION_EVENT_TYPES = [\n \"created\",\n \"plan_changed\",\n \"status_changed\",\n \"resumed\",\n \"canceled\",\n \"renewed\",\n] as const\n\nexport type SubscriptionEventType = typeof SUBSCRIPTION_EVENT_TYPES[number]\n\nexport const SUBSCRIPTION_CHANGE_DIRECTIONS = [\n \"upgrade\",\n \"downgrade\",\n \"lateral\",\n] as const\n\nexport type SubscriptionChangeDirection = typeof SUBSCRIPTION_CHANGE_DIRECTIONS[number]\n\nexport type SubscriptionEventBase = {\n id?: string\n tenantId: string\n subscriptionId: string\n occurredAt: Date\n effectiveAt: Date\n subscriptionType: SubscriptionType\n parentSubscriptionId?: string\n scope: SubscriptionScope\n scopeId?: string\n fromPlanKey?: string\n toPlanKey?: string\n fromStatus?: SubscriptionStatus\n toStatus?: SubscriptionStatus\n fromModules?: string[]\n toModules?: string[]\n fromIntervalUnit?: SubscriptionIntervalUnit\n toIntervalUnit?: SubscriptionIntervalUnit\n fromIntervalCount?: number\n toIntervalCount?: number\n fromPriceId?: string\n toPriceId?: string\n billingAnchor?: Date\n supersedesEventId?: string\n idempotencyKey: string\n direction?: SubscriptionChangeDirection\n actorUserId?: string\n source?: \"admin\" | \"system\" | \"webhook\" | \"user\"\n reason?: string\n provider?: string\n providerEventId?: string\n providerPayload?: unknown\n metadata?: Record<string, unknown>\n}\n\nexport type SubscriptionCreatedEvent = SubscriptionEventBase & {\n type: \"created\"\n toPlanKey: string\n toStatus: SubscriptionStatus\n toModules: string[]\n toIntervalUnit: SubscriptionIntervalUnit\n toIntervalCount: number\n billingAnchor: Date\n}\n\nexport type SubscriptionPlanChangedEvent = SubscriptionEventBase & {\n type: \"plan_changed\"\n fromPlanKey: string\n toPlanKey: string\n fromModules: string[]\n toModules: string[]\n fromIntervalUnit: SubscriptionIntervalUnit\n toIntervalUnit: SubscriptionIntervalUnit\n fromIntervalCount: number\n toIntervalCount: number\n}\n\nexport type SubscriptionStatusChangedEvent = SubscriptionEventBase & {\n type: \"status_changed\"\n fromStatus: SubscriptionStatus\n toStatus: SubscriptionStatus\n}\n\nexport type SubscriptionResumedEvent = SubscriptionEventBase & {\n type: \"resumed\"\n fromStatus: SubscriptionStatus\n toStatus: \"active\"\n}\n\nexport type SubscriptionCanceledEvent = SubscriptionEventBase & {\n type: \"canceled\"\n fromStatus: SubscriptionStatus\n toStatus: \"canceled\"\n}\n\nexport type SubscriptionRenewedEvent = SubscriptionEventBase & {\n type: \"renewed\"\n fromStatus: SubscriptionStatus\n toStatus: SubscriptionStatus\n}\n\nexport type SubscriptionEvent =\n | SubscriptionCreatedEvent\n | SubscriptionPlanChangedEvent\n | SubscriptionStatusChangedEvent\n | SubscriptionResumedEvent\n | SubscriptionCanceledEvent\n | SubscriptionRenewedEvent\n\nexport type PersistedSubscriptionEvent = SubscriptionEvent & {\n id: string\n}\n\nexport type ScheduledPlanChange = {\n planKey: string\n effectiveAt: Date\n}\n\nexport type SubscriptionProjection = {\n subscriptionId: string\n type: SubscriptionType\n parentSubscriptionId: string | null\n scope: SubscriptionScope\n scopeId: string | null\n planKey: string\n priceId: string | null\n status: SubscriptionStatus\n intervalUnit: SubscriptionIntervalUnit\n intervalCount: number\n modules: string[]\n billingAnchor: Date\n currentPeriodStart: Date\n currentPeriodEnd: Date\n cancelAtPeriodEnd: boolean\n cancelAt: Date | null\n canceledAt: Date | null\n scheduledPlan: ScheduledPlanChange | null\n provider: string | null\n}\n\nexport type BillableSubscriptionSegment = {\n subscriptionId: string\n planKey: string\n priceId: string | null\n status: SubscriptionStatus\n modules: string[]\n start: Date\n end: Date\n periodStart: Date\n periodEnd: Date\n}\n\nexport type StoredSubscriptionProjection = SubscriptionProjection & {\n tenantId: string\n latestEventId: string\n latestEventAt: Date\n projectedAt: Date\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/billing",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"