@onesub/server 0.14.0 → 0.16.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.
Files changed (140) hide show
  1. package/dist/__tests__/google-subscriptions-v2.test.d.ts +1 -1
  2. package/dist/__tests__/receipt-age.test.d.ts +7 -0
  3. package/dist/__tests__/receipt-age.test.d.ts.map +1 -0
  4. package/dist/__tests__/tracing.test.d.ts +2 -0
  5. package/dist/__tests__/tracing.test.d.ts.map +1 -0
  6. package/dist/cache.d.ts +5 -0
  7. package/dist/cache.d.ts.map +1 -1
  8. package/dist/index.cjs +178 -55
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.js +178 -55
  11. package/dist/index.js.map +1 -1
  12. package/dist/providers/apple.d.ts.map +1 -1
  13. package/dist/providers/google.d.ts.map +1 -1
  14. package/dist/providers/mock.d.ts.map +1 -1
  15. package/dist/routes/admin.d.ts.map +1 -1
  16. package/dist/routes/purchase.d.ts.map +1 -1
  17. package/dist/routes/validate.d.ts.map +1 -1
  18. package/dist/routes/webhook-apple.d.ts.map +1 -1
  19. package/dist/routes/webhook-google.d.ts +1 -1
  20. package/dist/routes/webhook-google.d.ts.map +1 -1
  21. package/dist/store.d.ts.map +1 -1
  22. package/dist/stores/postgres.d.ts.map +1 -1
  23. package/dist/stores/redis.d.ts +1 -0
  24. package/dist/stores/redis.d.ts.map +1 -1
  25. package/dist/tracing.d.ts.map +1 -1
  26. package/dist/webhook-events.d.ts +10 -0
  27. package/dist/webhook-events.d.ts.map +1 -1
  28. package/package.json +5 -3
  29. package/dist/__tests__/admin-customer-detail.test.js +0 -146
  30. package/dist/__tests__/admin-customer-detail.test.js.map +0 -1
  31. package/dist/__tests__/admin-list-subscriptions.test.js +0 -192
  32. package/dist/__tests__/admin-list-subscriptions.test.js.map +0 -1
  33. package/dist/__tests__/admin-subscription-detail.test.js +0 -112
  34. package/dist/__tests__/admin-subscription-detail.test.js.map +0 -1
  35. package/dist/__tests__/apple-root-ca.test.js +0 -41
  36. package/dist/__tests__/apple-root-ca.test.js.map +0 -1
  37. package/dist/__tests__/apple-status-api.test.js +0 -345
  38. package/dist/__tests__/apple-status-api.test.js.map +0 -1
  39. package/dist/__tests__/cache.test.js +0 -34
  40. package/dist/__tests__/cache.test.js.map +0 -1
  41. package/dist/__tests__/entitlements.test.js +0 -268
  42. package/dist/__tests__/entitlements.test.js.map +0 -1
  43. package/dist/__tests__/error-codes.test.js +0 -173
  44. package/dist/__tests__/error-codes.test.js.map +0 -1
  45. package/dist/__tests__/fetch-hardening.test.js +0 -192
  46. package/dist/__tests__/fetch-hardening.test.js.map +0 -1
  47. package/dist/__tests__/google-paused.test.js +0 -126
  48. package/dist/__tests__/google-paused.test.js.map +0 -1
  49. package/dist/__tests__/google-price-change.test.js +0 -186
  50. package/dist/__tests__/google-price-change.test.js.map +0 -1
  51. package/dist/__tests__/google-subscriptions-v2.test.js +0 -206
  52. package/dist/__tests__/google-subscriptions-v2.test.js.map +0 -1
  53. package/dist/__tests__/lifecycle-scenarios.test.js +0 -579
  54. package/dist/__tests__/lifecycle-scenarios.test.js.map +0 -1
  55. package/dist/__tests__/lifecycle-states.test.js +0 -423
  56. package/dist/__tests__/lifecycle-states.test.js.map +0 -1
  57. package/dist/__tests__/linked-purchase-token.test.js +0 -227
  58. package/dist/__tests__/linked-purchase-token.test.js.map +0 -1
  59. package/dist/__tests__/metrics.test.js +0 -340
  60. package/dist/__tests__/metrics.test.js.map +0 -1
  61. package/dist/__tests__/mock-provider.test.js +0 -188
  62. package/dist/__tests__/mock-provider.test.js.map +0 -1
  63. package/dist/__tests__/openapi.test.js +0 -32
  64. package/dist/__tests__/openapi.test.js.map +0 -1
  65. package/dist/__tests__/paused-auto-resume.test.js +0 -162
  66. package/dist/__tests__/paused-auto-resume.test.js.map +0 -1
  67. package/dist/__tests__/providers.test.js +0 -321
  68. package/dist/__tests__/providers.test.js.map +0 -1
  69. package/dist/__tests__/redis-store.test.js +0 -166
  70. package/dist/__tests__/redis-store.test.js.map +0 -1
  71. package/dist/__tests__/refund-policy.test.js +0 -317
  72. package/dist/__tests__/refund-policy.test.js.map +0 -1
  73. package/dist/__tests__/schema.test.js +0 -46
  74. package/dist/__tests__/schema.test.js.map +0 -1
  75. package/dist/__tests__/store.test.js +0 -172
  76. package/dist/__tests__/store.test.js.map +0 -1
  77. package/dist/__tests__/test-utils.js +0 -30
  78. package/dist/__tests__/test-utils.js.map +0 -1
  79. package/dist/__tests__/webhook-apple.test.js +0 -247
  80. package/dist/__tests__/webhook-apple.test.js.map +0 -1
  81. package/dist/__tests__/webhook-events.test.js +0 -36
  82. package/dist/__tests__/webhook-events.test.js.map +0 -1
  83. package/dist/__tests__/webhook-google.test.js +0 -255
  84. package/dist/__tests__/webhook-google.test.js.map +0 -1
  85. package/dist/__tests__/webhook-queue.test.js +0 -26
  86. package/dist/__tests__/webhook-queue.test.js.map +0 -1
  87. package/dist/__tests__/webhook-refund.test.js +0 -313
  88. package/dist/__tests__/webhook-refund.test.js.map +0 -1
  89. package/dist/cache.js +0 -39
  90. package/dist/cache.js.map +0 -1
  91. package/dist/errors.js +0 -19
  92. package/dist/errors.js.map +0 -1
  93. package/dist/http.js +0 -36
  94. package/dist/http.js.map +0 -1
  95. package/dist/logger.js +0 -20
  96. package/dist/logger.js.map +0 -1
  97. package/dist/openapi.js +0 -196
  98. package/dist/openapi.js.map +0 -1
  99. package/dist/providers/apple-root-ca.js +0 -38
  100. package/dist/providers/apple-root-ca.js.map +0 -1
  101. package/dist/providers/apple.js +0 -630
  102. package/dist/providers/apple.js.map +0 -1
  103. package/dist/providers/google.js +0 -564
  104. package/dist/providers/google.js.map +0 -1
  105. package/dist/providers/mock.js +0 -93
  106. package/dist/providers/mock.js.map +0 -1
  107. package/dist/routes/admin.js +0 -327
  108. package/dist/routes/admin.js.map +0 -1
  109. package/dist/routes/apple-offer.js +0 -72
  110. package/dist/routes/apple-offer.js.map +0 -1
  111. package/dist/routes/entitlements.js +0 -130
  112. package/dist/routes/entitlements.js.map +0 -1
  113. package/dist/routes/metrics.js +0 -281
  114. package/dist/routes/metrics.js.map +0 -1
  115. package/dist/routes/purchase.js +0 -204
  116. package/dist/routes/purchase.js.map +0 -1
  117. package/dist/routes/status.js +0 -55
  118. package/dist/routes/status.js.map +0 -1
  119. package/dist/routes/validate.js +0 -70
  120. package/dist/routes/validate.js.map +0 -1
  121. package/dist/routes/webhook-apple.js +0 -145
  122. package/dist/routes/webhook-apple.js.map +0 -1
  123. package/dist/routes/webhook-google.js +0 -204
  124. package/dist/routes/webhook-google.js.map +0 -1
  125. package/dist/routes/webhook.js +0 -19
  126. package/dist/routes/webhook.js.map +0 -1
  127. package/dist/store.js +0 -145
  128. package/dist/store.js.map +0 -1
  129. package/dist/stores/postgres.js +0 -329
  130. package/dist/stores/postgres.js.map +0 -1
  131. package/dist/stores/redis.js +0 -297
  132. package/dist/stores/redis.js.map +0 -1
  133. package/dist/stores/schema.js +0 -67
  134. package/dist/stores/schema.js.map +0 -1
  135. package/dist/tracing.js +0 -74
  136. package/dist/tracing.js.map +0 -1
  137. package/dist/webhook-events.js +0 -58
  138. package/dist/webhook-events.js.map +0 -1
  139. package/dist/webhook-queue.js +0 -108
  140. package/dist/webhook-queue.js.map +0 -1
@@ -1,329 +0,0 @@
1
- import { SUBSCRIPTIONS_SCHEMA_SQL, PURCHASES_SCHEMA_SQL } from './schema.js';
2
- /**
3
- * PostgreSQL-backed subscription store.
4
- *
5
- * Uses raw `pg` (node-postgres) to avoid Prisma / ORM dependencies.
6
- * The `pg` package must be installed separately:
7
- *
8
- * npm install pg
9
- * npm install -D @types/pg
10
- *
11
- * Usage:
12
- * const store = new PostgresSubscriptionStore(process.env.DATABASE_URL!);
13
- * await store.initSchema();
14
- * app.use(createOneSubMiddleware({ ...config, store }));
15
- */
16
- export class PostgresSubscriptionStore {
17
- connectionString;
18
- // Lazy-loaded to avoid requiring `pg` unless this class is actually instantiated.
19
- poolPromise = null;
20
- constructor(connectionString) {
21
- this.connectionString = connectionString;
22
- }
23
- getPool() {
24
- if (!this.poolPromise) {
25
- this.poolPromise = (async () => {
26
- // Dynamic import so that `pg` is an optional peer dependency —
27
- // consumers who only use InMemorySubscriptionStore pay no cost.
28
- const pg = await import('pg').catch(() => {
29
- throw new Error('[onesub] PostgresSubscriptionStore requires the `pg` package. ' +
30
- 'Run: npm install pg');
31
- });
32
- const Pool = pg.default?.Pool ?? pg.Pool;
33
- return new Pool({ connectionString: this.connectionString, max: 10 });
34
- })();
35
- }
36
- return this.poolPromise;
37
- }
38
- /**
39
- * Creates the `onesub_subscriptions` table and indexes if they do not
40
- * already exist. Call this once during application startup.
41
- */
42
- async initSchema() {
43
- const pool = await this.getPool();
44
- // DDL mirrored in packages/server/sql/schema.sql — kept in sync by
45
- // schema parity test. Edit both (or only the .sql file and regenerate).
46
- await pool.query(SUBSCRIPTIONS_SCHEMA_SQL);
47
- }
48
- /**
49
- * Upserts the given subscription.
50
- * If a row with the same `original_transaction_id` already exists it is
51
- * updated in place; otherwise a new row is inserted.
52
- */
53
- async save(sub) {
54
- const pool = await this.getPool();
55
- await pool.query(`INSERT INTO onesub_subscriptions
56
- (original_transaction_id, user_id, product_id, platform, status,
57
- expires_at, purchased_at, will_renew, linked_purchase_token,
58
- auto_resume_time, updated_at)
59
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW())
60
- ON CONFLICT (original_transaction_id) DO UPDATE SET
61
- user_id = EXCLUDED.user_id,
62
- product_id = EXCLUDED.product_id,
63
- platform = EXCLUDED.platform,
64
- status = EXCLUDED.status,
65
- expires_at = EXCLUDED.expires_at,
66
- purchased_at = EXCLUDED.purchased_at,
67
- will_renew = EXCLUDED.will_renew,
68
- linked_purchase_token = EXCLUDED.linked_purchase_token,
69
- auto_resume_time = EXCLUDED.auto_resume_time,
70
- updated_at = NOW()`, [
71
- sub.originalTransactionId,
72
- sub.userId,
73
- sub.productId,
74
- sub.platform,
75
- sub.status,
76
- sub.expiresAt,
77
- sub.purchasedAt,
78
- sub.willRenew,
79
- sub.linkedPurchaseToken ?? null,
80
- sub.autoResumeTime ?? null,
81
- ]);
82
- }
83
- /**
84
- * Returns the most recently updated subscription for the given user, or
85
- * `null` if no record exists.
86
- *
87
- * "Most recent" is determined by `updated_at DESC` so that the latest
88
- * status change is always returned when a user has multiple subscriptions.
89
- */
90
- async getByUserId(userId) {
91
- const pool = await this.getPool();
92
- const result = await pool.query(`SELECT *
93
- FROM onesub_subscriptions
94
- WHERE user_id = $1
95
- ORDER BY updated_at DESC
96
- LIMIT 1`, [userId]);
97
- return result.rows.length > 0 ? rowToSubscriptionInfo(result.rows[0]) : null;
98
- }
99
- /**
100
- * Returns every subscription record for the user (across all productIds),
101
- * ordered most-recent-first. Used by entitlement evaluation, which needs to
102
- * see all of a user's active subscriptions to decide whether any of them
103
- * grants the requested entitlement.
104
- */
105
- async getAllByUserId(userId) {
106
- const pool = await this.getPool();
107
- const result = await pool.query(`SELECT *
108
- FROM onesub_subscriptions
109
- WHERE user_id = $1
110
- ORDER BY updated_at DESC`, [userId]);
111
- return result.rows.map(rowToSubscriptionInfo);
112
- }
113
- /**
114
- * Returns the subscription identified by `originalTransactionId` (the
115
- * table primary key), or `null` if it does not exist.
116
- */
117
- async getByTransactionId(txId) {
118
- const pool = await this.getPool();
119
- const result = await pool.query(`SELECT *
120
- FROM onesub_subscriptions
121
- WHERE original_transaction_id = $1`, [txId]);
122
- return result.rows.length > 0 ? rowToSubscriptionInfo(result.rows[0]) : null;
123
- }
124
- async listAll() {
125
- const pool = await this.getPool();
126
- const result = await pool.query(`SELECT * FROM onesub_subscriptions`);
127
- return result.rows.map(rowToSubscriptionInfo);
128
- }
129
- async listFiltered(opts) {
130
- const pool = await this.getPool();
131
- const limit = opts.limit ?? 50;
132
- const offset = opts.offset ?? 0;
133
- // Build the WHERE clause incrementally. Each non-undefined filter adds an
134
- // AND condition with a parameterised value (no SQL injection surface).
135
- const conditions = [];
136
- const params = [];
137
- if (opts.userId) {
138
- params.push(opts.userId);
139
- conditions.push(`user_id = $${params.length}`);
140
- }
141
- if (opts.status) {
142
- params.push(opts.status);
143
- conditions.push(`status = $${params.length}`);
144
- }
145
- if (opts.productId) {
146
- params.push(opts.productId);
147
- conditions.push(`product_id = $${params.length}`);
148
- }
149
- if (opts.platform) {
150
- params.push(opts.platform);
151
- conditions.push(`platform = $${params.length}`);
152
- }
153
- const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
154
- // Run the count + page in parallel — both queries hit the same WHERE clause
155
- // so they share the index plan, and we save a round-trip.
156
- const [countResult, pageResult] = await Promise.all([
157
- pool.query(`SELECT COUNT(*)::text AS total FROM onesub_subscriptions ${where}`, params),
158
- pool.query(`SELECT * FROM onesub_subscriptions ${where}
159
- ORDER BY updated_at DESC
160
- LIMIT $${params.length + 1} OFFSET $${params.length + 2}`, [...params, limit, offset]),
161
- ]);
162
- return {
163
- items: pageResult.rows.map(rowToSubscriptionInfo),
164
- total: parseInt(countResult.rows[0]?.total ?? '0', 10),
165
- limit,
166
- offset,
167
- };
168
- }
169
- /** Gracefully close the underlying connection pool. */
170
- async close() {
171
- if (this.poolPromise) {
172
- const pool = await this.poolPromise;
173
- await pool.end();
174
- this.poolPromise = null;
175
- }
176
- }
177
- }
178
- /**
179
- * PostgreSQL-backed purchase store for consumables and non-consumables.
180
- *
181
- * Uses raw `pg` (node-postgres). The `pg` package must be installed separately:
182
- *
183
- * npm install pg
184
- * npm install -D @types/pg
185
- *
186
- * Usage:
187
- * const store = new PostgresPurchaseStore(process.env.DATABASE_URL!);
188
- * await store.initSchema();
189
- * app.use(createOneSubMiddleware({ ...config, purchaseStore: store }));
190
- */
191
- export class PostgresPurchaseStore {
192
- connectionString;
193
- poolPromise = null;
194
- constructor(connectionString) {
195
- this.connectionString = connectionString;
196
- }
197
- getPool() {
198
- if (!this.poolPromise) {
199
- this.poolPromise = (async () => {
200
- const pg = await import('pg').catch(() => {
201
- throw new Error('[onesub] PostgresPurchaseStore requires the `pg` package. ' +
202
- 'Run: npm install pg');
203
- });
204
- const Pool = pg.default?.Pool ?? pg.Pool;
205
- return new Pool({ connectionString: this.connectionString, max: 10 });
206
- })();
207
- }
208
- return this.poolPromise;
209
- }
210
- /**
211
- * Creates the `onesub_purchases` table and indexes if they do not already exist.
212
- * Call this once during application startup.
213
- */
214
- async initSchema() {
215
- const pool = await this.getPool();
216
- // DDL mirrored in packages/server/sql/schema.sql — kept in sync by
217
- // schema parity test.
218
- await pool.query(PURCHASES_SCHEMA_SQL);
219
- }
220
- async savePurchase(purchase) {
221
- const pool = await this.getPool();
222
- // transactionId is PRIMARY KEY. If the same transactionId already belongs
223
- // to a different userId we refuse the save — the second call is either a
224
- // fraudulent receipt-reuse attempt or a legitimate device migration that
225
- // should go through an explicit admin transfer instead. Returning silently
226
- // (the old ON CONFLICT DO NOTHING behavior) leaves the server reporting
227
- // valid:true while the DB has no row for the current user, which lets the
228
- // caller keep re-"buying" forever.
229
- const existing = await pool.query(`SELECT user_id FROM onesub_purchases WHERE transaction_id = $1`, [purchase.transactionId]);
230
- if (existing.rows.length > 0) {
231
- const owner = existing.rows[0].user_id;
232
- if (owner !== purchase.userId) {
233
- const err = new Error('TRANSACTION_BELONGS_TO_OTHER_USER');
234
- err.code = 'TRANSACTION_BELONGS_TO_OTHER_USER';
235
- throw err;
236
- }
237
- // same user — idempotent no-op
238
- return;
239
- }
240
- await pool.query(`INSERT INTO onesub_purchases
241
- (transaction_id, user_id, product_id, platform, type, quantity, purchased_at)
242
- VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
243
- purchase.transactionId,
244
- purchase.userId,
245
- purchase.productId,
246
- purchase.platform,
247
- purchase.type,
248
- purchase.quantity,
249
- purchase.purchasedAt,
250
- ]);
251
- }
252
- async getPurchasesByUserId(userId) {
253
- const pool = await this.getPool();
254
- const result = await pool.query(`SELECT *
255
- FROM onesub_purchases
256
- WHERE user_id = $1
257
- ORDER BY purchased_at DESC`, [userId]);
258
- return result.rows.map(rowToPurchaseInfo);
259
- }
260
- async getPurchaseByTransactionId(txId) {
261
- const pool = await this.getPool();
262
- const result = await pool.query(`SELECT *
263
- FROM onesub_purchases
264
- WHERE transaction_id = $1`, [txId]);
265
- return result.rows.length > 0 ? rowToPurchaseInfo(result.rows[0]) : null;
266
- }
267
- async hasPurchased(userId, productId) {
268
- const pool = await this.getPool();
269
- const result = await pool.query(`SELECT EXISTS(
270
- SELECT 1 FROM onesub_purchases
271
- WHERE user_id = $1 AND product_id = $2
272
- ) AS exists`, [userId, productId]);
273
- return result.rows[0]?.exists === true;
274
- }
275
- async reassignPurchase(transactionId, newUserId) {
276
- const pool = await this.getPool();
277
- const result = await pool.query(`UPDATE onesub_purchases SET user_id = $2 WHERE transaction_id = $1`, [transactionId, newUserId]);
278
- return (result.rowCount ?? 0) > 0;
279
- }
280
- async deletePurchases(userId, productId) {
281
- const pool = await this.getPool();
282
- const result = await pool.query(`DELETE FROM onesub_purchases WHERE user_id = $1 AND product_id = $2`, [userId, productId]);
283
- return result.rowCount ?? 0;
284
- }
285
- async deletePurchaseByTransactionId(transactionId) {
286
- const pool = await this.getPool();
287
- const result = await pool.query(`DELETE FROM onesub_purchases WHERE transaction_id = $1`, [transactionId]);
288
- return (result.rowCount ?? 0) > 0;
289
- }
290
- async listAll() {
291
- const pool = await this.getPool();
292
- const result = await pool.query(`SELECT * FROM onesub_purchases`);
293
- return result.rows.map(rowToPurchaseInfo);
294
- }
295
- /** Gracefully close the underlying connection pool. */
296
- async close() {
297
- if (this.poolPromise) {
298
- const pool = await this.poolPromise;
299
- await pool.end();
300
- this.poolPromise = null;
301
- }
302
- }
303
- }
304
- function rowToSubscriptionInfo(row) {
305
- return {
306
- originalTransactionId: row.original_transaction_id,
307
- userId: row.user_id,
308
- productId: row.product_id,
309
- platform: row.platform,
310
- status: row.status,
311
- expiresAt: row.expires_at.toISOString(),
312
- purchasedAt: row.purchased_at.toISOString(),
313
- willRenew: row.will_renew,
314
- linkedPurchaseToken: row.linked_purchase_token ?? undefined,
315
- autoResumeTime: row.auto_resume_time?.toISOString(),
316
- };
317
- }
318
- function rowToPurchaseInfo(row) {
319
- return {
320
- transactionId: row.transaction_id,
321
- userId: row.user_id,
322
- productId: row.product_id,
323
- platform: row.platform,
324
- type: row.type,
325
- quantity: row.quantity,
326
- purchasedAt: row.purchased_at.toISOString(),
327
- };
328
- }
329
- //# sourceMappingURL=postgres.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/stores/postgres.ts"],"names":[],"mappings":"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"}
@@ -1,297 +0,0 @@
1
- /**
2
- * Redis-backed subscription / purchase / cache stores.
3
- *
4
- * Uses the `ioredis` package — kept as an optional peer dependency so callers
5
- * who only need InMemory or Postgres pay no install cost.
6
- *
7
- * npm install ioredis
8
- *
9
- * Usage:
10
- * import Redis from 'ioredis';
11
- * import {
12
- * RedisSubscriptionStore,
13
- * RedisPurchaseStore,
14
- * RedisCacheAdapter,
15
- * } from '@onesub/server';
16
- *
17
- * const redis = new Redis(process.env.REDIS_URL!);
18
- * const store = new RedisSubscriptionStore(redis);
19
- * const purchaseStore = new RedisPurchaseStore(redis);
20
- * const cache = new RedisCacheAdapter(redis);
21
- *
22
- * app.use(createOneSubMiddleware({ ...config, store, purchaseStore, cache }));
23
- *
24
- * Key layout:
25
- * onesub:sub:tx:<originalTransactionId> → JSON SubscriptionInfo
26
- * onesub:sub:user:<userId> → SortedSet of originalTransactionIds, scored by updatedAt (ms)
27
- * onesub:sub:all → Set of originalTransactionIds (for listAll/listFiltered)
28
- * onesub:purchase:tx:<transactionId> → JSON PurchaseInfo
29
- * onesub:purchase:user:<userId> → SortedSet of transactionIds, scored by purchasedAt (ms)
30
- * onesub:purchase:user_product:<u>:<p> → Set of transactionIds (for non-consumable hasPurchased)
31
- * onesub:purchase:all → Set of transactionIds
32
- * onesub:cache:<key> → string with TTL (RedisCacheAdapter)
33
- * onesub:webhook:event:<provider>:<id> → "1" with TTL (RedisWebhookEventStore)
34
- */
35
- const SUB_TX_PREFIX = 'onesub:sub:tx:';
36
- const SUB_USER_PREFIX = 'onesub:sub:user:';
37
- const SUB_ALL = 'onesub:sub:all';
38
- // Global sorted set (score = save timestamp ms) — enables ordered listAll and
39
- // O(log n + limit) fast-path pagination when no secondary filters are applied.
40
- const SUB_ALL_SORTED = 'onesub:sub:all:sorted';
41
- const PUR_TX_PREFIX = 'onesub:purchase:tx:';
42
- const PUR_USER_PREFIX = 'onesub:purchase:user:';
43
- const PUR_USER_PRODUCT_PREFIX = 'onesub:purchase:user_product:';
44
- const PUR_ALL = 'onesub:purchase:all';
45
- export class RedisSubscriptionStore {
46
- redis;
47
- constructor(redis) {
48
- this.redis = redis;
49
- }
50
- async save(sub) {
51
- const score = Date.now();
52
- const txKey = SUB_TX_PREFIX + sub.originalTransactionId;
53
- const userKey = SUB_USER_PREFIX + sub.userId;
54
- const pipeline = this.redis.multi();
55
- pipeline.set(txKey, JSON.stringify(sub));
56
- pipeline.zadd(userKey, score, sub.originalTransactionId);
57
- pipeline.sadd(SUB_ALL, sub.originalTransactionId);
58
- pipeline.zadd(SUB_ALL_SORTED, score, sub.originalTransactionId);
59
- await pipeline.exec();
60
- }
61
- async getByUserId(userId) {
62
- const userKey = SUB_USER_PREFIX + userId;
63
- // ZREVRANGE 0 0 = most-recent (highest score)
64
- const ids = await this.redis.zrevrange(userKey, 0, 0);
65
- if (ids.length === 0)
66
- return null;
67
- const raw = await this.redis.get(SUB_TX_PREFIX + ids[0]);
68
- return raw ? JSON.parse(raw) : null;
69
- }
70
- async getAllByUserId(userId) {
71
- const userKey = SUB_USER_PREFIX + userId;
72
- const ids = await this.redis.zrevrange(userKey, 0, -1);
73
- if (ids.length === 0)
74
- return [];
75
- const raws = await this.redis.mget(...ids.map((id) => SUB_TX_PREFIX + id));
76
- return raws.filter((r) => r != null).map((r) => JSON.parse(r));
77
- }
78
- async getByTransactionId(txId) {
79
- const raw = await this.redis.get(SUB_TX_PREFIX + txId);
80
- return raw ? JSON.parse(raw) : null;
81
- }
82
- async listAll() {
83
- // ZREVRANGE on the sorted set gives newest-first order (vs random SMEMBERS).
84
- const ids = await this.redis.zrevrange(SUB_ALL_SORTED, 0, -1);
85
- if (ids.length === 0)
86
- return [];
87
- const raws = await this.redis.mget(...ids.map((id) => SUB_TX_PREFIX + id));
88
- return raws.filter((r) => r != null).map((r) => JSON.parse(r));
89
- }
90
- async listFiltered(opts) {
91
- const limit = opts.limit ?? 50;
92
- const offset = opts.offset ?? 0;
93
- const hasSecondaryFilters = !!(opts.status || opts.productId || opts.platform);
94
- if (opts.userId) {
95
- // Per-user path — already O(log n) via per-user sorted set.
96
- const candidates = await this.getAllByUserId(opts.userId);
97
- const filtered = candidates.filter((s) => {
98
- if (opts.status && s.status !== opts.status)
99
- return false;
100
- if (opts.productId && s.productId !== opts.productId)
101
- return false;
102
- if (opts.platform && s.platform !== opts.platform)
103
- return false;
104
- return true;
105
- });
106
- return { items: filtered.slice(offset, offset + limit), total: filtered.length, limit, offset };
107
- }
108
- if (!hasSecondaryFilters) {
109
- // Fast path: pure pagination with no filter — O(log n + limit) via sorted set.
110
- const [ids, total] = await Promise.all([
111
- this.redis.zrevrange(SUB_ALL_SORTED, offset, offset + limit - 1),
112
- this.redis.zcard(SUB_ALL_SORTED),
113
- ]);
114
- if (ids.length === 0)
115
- return { items: [], total, limit, offset };
116
- const raws = await this.redis.mget(...ids.map((id) => SUB_TX_PREFIX + id));
117
- const items = raws.filter((r) => r != null).map((r) => JSON.parse(r));
118
- return { items, total, limit, offset };
119
- }
120
- // Slow path: secondary filters require full scan. Results come in
121
- // newest-first order. For >100k rows pair with Postgres or Redis Stack.
122
- const allIds = await this.redis.zrevrange(SUB_ALL_SORTED, 0, -1);
123
- if (allIds.length === 0)
124
- return { items: [], total: 0, limit, offset };
125
- const raws = await this.redis.mget(...allIds.map((id) => SUB_TX_PREFIX + id));
126
- const all = raws.filter((r) => r != null).map((r) => JSON.parse(r));
127
- const filtered = all.filter((s) => {
128
- if (opts.status && s.status !== opts.status)
129
- return false;
130
- if (opts.productId && s.productId !== opts.productId)
131
- return false;
132
- if (opts.platform && s.platform !== opts.platform)
133
- return false;
134
- return true;
135
- });
136
- return { items: filtered.slice(offset, offset + limit), total: filtered.length, limit, offset };
137
- }
138
- }
139
- export class RedisPurchaseStore {
140
- redis;
141
- constructor(redis) {
142
- this.redis = redis;
143
- }
144
- async savePurchase(purchase) {
145
- const txKey = PUR_TX_PREFIX + purchase.transactionId;
146
- // Owner check — same TRANSACTION_BELONGS_TO_OTHER_USER semantics as
147
- // Postgres / InMemory implementations. Without this guard a stolen
148
- // receipt could be re-bound to a different account.
149
- const existing = await this.redis.get(txKey);
150
- if (existing) {
151
- const owner = JSON.parse(existing).userId;
152
- if (owner !== purchase.userId) {
153
- const err = new Error('TRANSACTION_BELONGS_TO_OTHER_USER');
154
- err.code = 'TRANSACTION_BELONGS_TO_OTHER_USER';
155
- throw err;
156
- }
157
- return; // idempotent — same user
158
- }
159
- const score = Date.parse(purchase.purchasedAt) || Date.now();
160
- const pipeline = this.redis.multi();
161
- pipeline.set(txKey, JSON.stringify(purchase));
162
- pipeline.zadd(PUR_USER_PREFIX + purchase.userId, score, purchase.transactionId);
163
- pipeline.sadd(PUR_USER_PRODUCT_PREFIX + purchase.userId + ':' + purchase.productId, purchase.transactionId);
164
- pipeline.sadd(PUR_ALL, purchase.transactionId);
165
- await pipeline.exec();
166
- }
167
- async getPurchasesByUserId(userId) {
168
- const ids = await this.redis.zrevrange(PUR_USER_PREFIX + userId, 0, -1);
169
- if (ids.length === 0)
170
- return [];
171
- const raws = await this.redis.mget(...ids.map((id) => PUR_TX_PREFIX + id));
172
- return raws.filter((r) => r != null).map((r) => JSON.parse(r));
173
- }
174
- async getPurchaseByTransactionId(txId) {
175
- const raw = await this.redis.get(PUR_TX_PREFIX + txId);
176
- return raw ? JSON.parse(raw) : null;
177
- }
178
- async hasPurchased(userId, productId) {
179
- const count = await this.redis.scard(PUR_USER_PRODUCT_PREFIX + userId + ':' + productId);
180
- return count > 0;
181
- }
182
- async reassignPurchase(transactionId, newUserId) {
183
- const txKey = PUR_TX_PREFIX + transactionId;
184
- const raw = await this.redis.get(txKey);
185
- if (!raw)
186
- return false;
187
- const existing = JSON.parse(raw);
188
- if (existing.userId === newUserId)
189
- return true;
190
- const updated = { ...existing, userId: newUserId };
191
- const score = Date.parse(updated.purchasedAt) || Date.now();
192
- const oldUserProductKey = PUR_USER_PRODUCT_PREFIX + existing.userId + ':' + existing.productId;
193
- const newUserProductKey = PUR_USER_PRODUCT_PREFIX + newUserId + ':' + existing.productId;
194
- const pipeline = this.redis.multi();
195
- pipeline.set(txKey, JSON.stringify(updated));
196
- pipeline.zrem(PUR_USER_PREFIX + existing.userId, transactionId);
197
- pipeline.zadd(PUR_USER_PREFIX + newUserId, score, transactionId);
198
- pipeline.srem(oldUserProductKey, transactionId);
199
- pipeline.sadd(newUserProductKey, transactionId);
200
- await pipeline.exec();
201
- return true;
202
- }
203
- async deletePurchases(userId, productId) {
204
- const userProductKey = PUR_USER_PRODUCT_PREFIX + userId + ':' + productId;
205
- const ids = await this.redis.smembers(userProductKey);
206
- if (ids.length === 0)
207
- return 0;
208
- const pipeline = this.redis.multi();
209
- for (const id of ids) {
210
- pipeline.del(PUR_TX_PREFIX + id);
211
- pipeline.zrem(PUR_USER_PREFIX + userId, id);
212
- pipeline.srem(PUR_ALL, id);
213
- }
214
- pipeline.del(userProductKey);
215
- await pipeline.exec();
216
- return ids.length;
217
- }
218
- async deletePurchaseByTransactionId(transactionId) {
219
- const txKey = PUR_TX_PREFIX + transactionId;
220
- const raw = await this.redis.get(txKey);
221
- if (!raw)
222
- return false;
223
- const existing = JSON.parse(raw);
224
- const pipeline = this.redis.multi();
225
- pipeline.del(txKey);
226
- pipeline.zrem(PUR_USER_PREFIX + existing.userId, transactionId);
227
- pipeline.srem(PUR_USER_PRODUCT_PREFIX + existing.userId + ':' + existing.productId, transactionId);
228
- pipeline.srem(PUR_ALL, transactionId);
229
- await pipeline.exec();
230
- return true;
231
- }
232
- async listAll() {
233
- const ids = await this.redis.smembers(PUR_ALL);
234
- if (ids.length === 0)
235
- return [];
236
- const raws = await this.redis.mget(...ids.map((id) => PUR_TX_PREFIX + id));
237
- return raws.filter((r) => r != null).map((r) => JSON.parse(r));
238
- }
239
- }
240
- /**
241
- * Redis-backed cache adapter — share JWKS / OAuth tokens across cluster nodes.
242
- *
243
- * Implements `CacheAdapter` so it plugs into the same default-cache slot used
244
- * by the Apple JWT minter and Google OAuth token minter.
245
- */
246
- export class RedisCacheAdapter {
247
- redis;
248
- prefix;
249
- constructor(redis, prefix = 'onesub:cache:') {
250
- this.redis = redis;
251
- this.prefix = prefix;
252
- }
253
- async get(key) {
254
- const raw = await this.redis.get(this.prefix + key);
255
- return raw ? JSON.parse(raw) : null;
256
- }
257
- async set(key, value, ttlSeconds) {
258
- const fullKey = this.prefix + key;
259
- const payload = JSON.stringify(value);
260
- if (ttlSeconds && ttlSeconds > 0) {
261
- await this.redis.set(fullKey, payload, 'EX', ttlSeconds);
262
- }
263
- else {
264
- await this.redis.set(fullKey, payload);
265
- }
266
- }
267
- async del(key) {
268
- await this.redis.del(this.prefix + key);
269
- }
270
- }
271
- const WEBHOOK_EVENT_PREFIX = 'onesub:webhook:event:';
272
- const DEFAULT_WEBHOOK_TTL_SECONDS = 7 * 24 * 60 * 60; // 7 days — covers Apple's 3-day retry window
273
- /**
274
- * Redis-backed webhook idempotency store.
275
- *
276
- * Uses `SET key '1' EX ttl NX` — a single atomic command that sets the key
277
- * only if it does not already exist. Returns 'OK' (new event) or null
278
- * (already seen), with no race between a GET and a subsequent SET.
279
- *
280
- * Prefer this over `CacheWebhookEventStore(new RedisCacheAdapter(...))` for
281
- * production deployments; the cache-based variant is non-atomic under
282
- * concurrent retries.
283
- */
284
- export class RedisWebhookEventStore {
285
- redis;
286
- ttlSeconds;
287
- constructor(redis, ttlSeconds = DEFAULT_WEBHOOK_TTL_SECONDS) {
288
- this.redis = redis;
289
- this.ttlSeconds = ttlSeconds;
290
- }
291
- async markIfNew(provider, eventId) {
292
- const key = WEBHOOK_EVENT_PREFIX + provider + ':' + eventId;
293
- const result = await this.redis.set(key, '1', 'EX', this.ttlSeconds, 'NX');
294
- return result === 'OK';
295
- }
296
- }
297
- //# sourceMappingURL=redis.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/stores/redis.ts"],"names":[],"mappings":"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"}