@onesub/server 0.12.1 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/__tests__/admin-customer-detail.test.js +146 -0
  2. package/dist/__tests__/admin-customer-detail.test.js.map +1 -0
  3. package/dist/__tests__/admin-list-subscriptions.test.js +192 -0
  4. package/dist/__tests__/admin-list-subscriptions.test.js.map +1 -0
  5. package/dist/__tests__/admin-subscription-detail.test.js +112 -0
  6. package/dist/__tests__/admin-subscription-detail.test.js.map +1 -0
  7. package/dist/__tests__/apple-root-ca.test.js +41 -0
  8. package/dist/__tests__/apple-root-ca.test.js.map +1 -0
  9. package/dist/__tests__/apple-status-api.test.js +345 -0
  10. package/dist/__tests__/apple-status-api.test.js.map +1 -0
  11. package/dist/__tests__/cache.test.js +34 -0
  12. package/dist/__tests__/cache.test.js.map +1 -0
  13. package/dist/__tests__/entitlements.test.js +268 -0
  14. package/dist/__tests__/entitlements.test.js.map +1 -0
  15. package/dist/__tests__/error-codes.test.js +173 -0
  16. package/dist/__tests__/error-codes.test.js.map +1 -0
  17. package/dist/__tests__/fetch-hardening.test.js +192 -0
  18. package/dist/__tests__/fetch-hardening.test.js.map +1 -0
  19. package/dist/__tests__/google-paused.test.js +126 -0
  20. package/dist/__tests__/google-paused.test.js.map +1 -0
  21. package/dist/__tests__/google-price-change.test.js +186 -0
  22. package/dist/__tests__/google-price-change.test.js.map +1 -0
  23. package/dist/__tests__/google-subscriptions-v2.test.js +206 -0
  24. package/dist/__tests__/google-subscriptions-v2.test.js.map +1 -0
  25. package/dist/__tests__/lifecycle-scenarios.test.js +579 -0
  26. package/dist/__tests__/lifecycle-scenarios.test.js.map +1 -0
  27. package/dist/__tests__/lifecycle-states.test.js +423 -0
  28. package/dist/__tests__/lifecycle-states.test.js.map +1 -0
  29. package/dist/__tests__/linked-purchase-token.test.js +227 -0
  30. package/dist/__tests__/linked-purchase-token.test.js.map +1 -0
  31. package/dist/__tests__/metrics.test.js +340 -0
  32. package/dist/__tests__/metrics.test.js.map +1 -0
  33. package/dist/__tests__/mock-provider.test.js +188 -0
  34. package/dist/__tests__/mock-provider.test.js.map +1 -0
  35. package/dist/__tests__/openapi.test.js +32 -0
  36. package/dist/__tests__/openapi.test.js.map +1 -0
  37. package/dist/__tests__/paused-auto-resume.test.js +162 -0
  38. package/dist/__tests__/paused-auto-resume.test.js.map +1 -0
  39. package/dist/__tests__/providers.test.js +321 -0
  40. package/dist/__tests__/providers.test.js.map +1 -0
  41. package/dist/__tests__/redis-store.test.js +166 -0
  42. package/dist/__tests__/redis-store.test.js.map +1 -0
  43. package/dist/__tests__/refund-policy.test.js +317 -0
  44. package/dist/__tests__/refund-policy.test.js.map +1 -0
  45. package/dist/__tests__/schema.test.js +46 -0
  46. package/dist/__tests__/schema.test.js.map +1 -0
  47. package/dist/__tests__/store.test.js +172 -0
  48. package/dist/__tests__/store.test.js.map +1 -0
  49. package/dist/__tests__/test-utils.js +30 -0
  50. package/dist/__tests__/test-utils.js.map +1 -0
  51. package/dist/__tests__/webhook-apple.test.js +247 -0
  52. package/dist/__tests__/webhook-apple.test.js.map +1 -0
  53. package/dist/__tests__/webhook-events.test.js +36 -0
  54. package/dist/__tests__/webhook-events.test.js.map +1 -0
  55. package/dist/__tests__/webhook-google.test.js +255 -0
  56. package/dist/__tests__/webhook-google.test.js.map +1 -0
  57. package/dist/__tests__/webhook-queue.test.js +26 -0
  58. package/dist/__tests__/webhook-queue.test.js.map +1 -0
  59. package/dist/__tests__/webhook-refund.test.js +313 -0
  60. package/dist/__tests__/webhook-refund.test.js.map +1 -0
  61. package/dist/cache.js +39 -0
  62. package/dist/cache.js.map +1 -0
  63. package/dist/errors.js +19 -0
  64. package/dist/errors.js.map +1 -0
  65. package/dist/http.js +36 -0
  66. package/dist/http.js.map +1 -0
  67. package/dist/index.cjs +241 -9
  68. package/dist/index.cjs.map +1 -1
  69. package/dist/index.d.ts +1 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +241 -11
  72. package/dist/index.js.map +1 -1
  73. package/dist/logger.js +20 -0
  74. package/dist/logger.js.map +1 -0
  75. package/dist/openapi.js +196 -0
  76. package/dist/openapi.js.map +1 -0
  77. package/dist/providers/apple-root-ca.js +38 -0
  78. package/dist/providers/apple-root-ca.js.map +1 -0
  79. package/dist/providers/apple.d.ts +76 -0
  80. package/dist/providers/apple.d.ts.map +1 -1
  81. package/dist/providers/apple.js +630 -0
  82. package/dist/providers/apple.js.map +1 -0
  83. package/dist/providers/google.d.ts +25 -0
  84. package/dist/providers/google.d.ts.map +1 -1
  85. package/dist/providers/google.js +564 -0
  86. package/dist/providers/google.js.map +1 -0
  87. package/dist/providers/mock.js +93 -0
  88. package/dist/providers/mock.js.map +1 -0
  89. package/dist/routes/admin.d.ts.map +1 -1
  90. package/dist/routes/admin.js +327 -0
  91. package/dist/routes/admin.js.map +1 -0
  92. package/dist/routes/apple-offer.d.ts +21 -0
  93. package/dist/routes/apple-offer.d.ts.map +1 -0
  94. package/dist/routes/apple-offer.js +72 -0
  95. package/dist/routes/apple-offer.js.map +1 -0
  96. package/dist/routes/entitlements.js +130 -0
  97. package/dist/routes/entitlements.js.map +1 -0
  98. package/dist/routes/metrics.js +281 -0
  99. package/dist/routes/metrics.js.map +1 -0
  100. package/dist/routes/purchase.d.ts.map +1 -1
  101. package/dist/routes/purchase.js +204 -0
  102. package/dist/routes/purchase.js.map +1 -0
  103. package/dist/routes/status.js +55 -0
  104. package/dist/routes/status.js.map +1 -0
  105. package/dist/routes/validate.js +70 -0
  106. package/dist/routes/validate.js.map +1 -0
  107. package/dist/routes/webhook-apple.d.ts.map +1 -1
  108. package/dist/routes/webhook-apple.js +145 -0
  109. package/dist/routes/webhook-apple.js.map +1 -0
  110. package/dist/routes/webhook-google.d.ts.map +1 -1
  111. package/dist/routes/webhook-google.js +204 -0
  112. package/dist/routes/webhook-google.js.map +1 -0
  113. package/dist/routes/webhook.js +19 -0
  114. package/dist/routes/webhook.js.map +1 -0
  115. package/dist/store.js +145 -0
  116. package/dist/store.js.map +1 -0
  117. package/dist/stores/postgres.js +329 -0
  118. package/dist/stores/postgres.js.map +1 -0
  119. package/dist/stores/redis.js +297 -0
  120. package/dist/stores/redis.js.map +1 -0
  121. package/dist/stores/schema.js +67 -0
  122. package/dist/stores/schema.js.map +1 -0
  123. package/dist/tracing.js +74 -0
  124. package/dist/tracing.js.map +1 -0
  125. package/dist/webhook-events.js +58 -0
  126. package/dist/webhook-events.js.map +1 -0
  127. package/dist/webhook-queue.js +108 -0
  128. package/dist/webhook-queue.js.map +1 -0
  129. package/package.json +4 -4
@@ -0,0 +1,297 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/stores/redis.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Embedded Postgres DDL mirrored from `packages/server/sql/schema.sql`.
3
+ *
4
+ * The `.sql` file is the single human-facing source of truth (mounted into
5
+ * docker-entrypoint-initdb.d, runnable via `psql -f`, readable by DBAs).
6
+ * These constants exist so that `initSchema()` can run without a filesystem
7
+ * lookup after the package is bundled/installed.
8
+ *
9
+ * A test in `__tests__/schema.test.ts` asserts that the two stay in sync —
10
+ * if you edit one, update the other (or the test will fail).
11
+ */
12
+ export const SUBSCRIPTIONS_SCHEMA_SQL = `
13
+ CREATE TABLE IF NOT EXISTS onesub_subscriptions (
14
+ original_transaction_id TEXT PRIMARY KEY,
15
+ user_id TEXT NOT NULL,
16
+ product_id TEXT NOT NULL,
17
+ platform TEXT NOT NULL,
18
+ status TEXT NOT NULL,
19
+ expires_at TIMESTAMPTZ NOT NULL,
20
+ purchased_at TIMESTAMPTZ NOT NULL,
21
+ will_renew BOOLEAN NOT NULL,
22
+ linked_purchase_token TEXT,
23
+ auto_resume_time TIMESTAMPTZ,
24
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
25
+ );
26
+
27
+ CREATE INDEX IF NOT EXISTS idx_onesub_subscriptions_user_id
28
+ ON onesub_subscriptions (user_id, updated_at DESC);
29
+
30
+ -- Filter-helper indexes for /onesub/admin/subscriptions. Each filter column
31
+ -- is paired with updated_at DESC so the planner can serve "latest matching"
32
+ -- without sorting the full table once row count grows.
33
+ CREATE INDEX IF NOT EXISTS idx_onesub_subscriptions_status_updated
34
+ ON onesub_subscriptions (status, updated_at DESC);
35
+ CREATE INDEX IF NOT EXISTS idx_onesub_subscriptions_platform_updated
36
+ ON onesub_subscriptions (platform, updated_at DESC);
37
+ CREATE INDEX IF NOT EXISTS idx_onesub_subscriptions_product
38
+ ON onesub_subscriptions (product_id, updated_at DESC);
39
+
40
+ -- Backfill columns for installs that already created the table from an older
41
+ -- schema. Safe to re-run.
42
+ ALTER TABLE onesub_subscriptions ADD COLUMN IF NOT EXISTS linked_purchase_token TEXT;
43
+ ALTER TABLE onesub_subscriptions ADD COLUMN IF NOT EXISTS auto_resume_time TIMESTAMPTZ;
44
+ `.trim();
45
+ export const PURCHASES_SCHEMA_SQL = `
46
+ CREATE TABLE IF NOT EXISTS onesub_purchases (
47
+ transaction_id TEXT PRIMARY KEY,
48
+ user_id TEXT NOT NULL,
49
+ product_id TEXT NOT NULL,
50
+ platform TEXT NOT NULL,
51
+ type TEXT NOT NULL,
52
+ quantity INTEGER NOT NULL DEFAULT 1,
53
+ purchased_at TIMESTAMPTZ NOT NULL,
54
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
55
+ );
56
+
57
+ CREATE INDEX IF NOT EXISTS idx_onesub_purchases_user_id
58
+ ON onesub_purchases (user_id, purchased_at DESC);
59
+
60
+ CREATE INDEX IF NOT EXISTS idx_onesub_purchases_user_product
61
+ ON onesub_purchases (user_id, product_id);
62
+
63
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_onesub_purchases_non_consumable
64
+ ON onesub_purchases (user_id, product_id)
65
+ WHERE type = 'non_consumable';
66
+ `.trim();
67
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/stores/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCvC,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;CAqBnC,CAAC,IAAI,EAAE,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Optional OpenTelemetry tracing.
3
+ *
4
+ * `@opentelemetry/api` is an optional peer dependency — when it's installed
5
+ * onesub wraps its hot paths in spans. When it isn't, every span helper is
6
+ * a zero-cost no-op (no dynamic import, no Promise overhead).
7
+ *
8
+ * Why a custom helper instead of @opentelemetry/instrumentation-express:
9
+ * the express instrumentation traces every request, but onesub's interesting
10
+ * spans are specific operations (Apple JWT mint, Google OAuth refresh,
11
+ * receipt validation, webhook dispatch). Hand-spanned hot paths give
12
+ * actionable traces without doubling latency-tracker overhead.
13
+ */
14
+ const NOOP_SPAN = {
15
+ setAttribute: () => { },
16
+ recordException: () => { },
17
+ setStatus: () => { },
18
+ end: () => { },
19
+ };
20
+ /**
21
+ * Lazy-loaded tracer. We don't reach for `@opentelemetry/api` until the
22
+ * first `withSpan` call so process startup pays nothing when otel is absent.
23
+ */
24
+ let cachedTracer = null;
25
+ let resolved = false;
26
+ function getTracer() {
27
+ if (resolved)
28
+ return cachedTracer;
29
+ resolved = true;
30
+ try {
31
+ // Resolve synchronously via require — otel api is CJS-friendly.
32
+ // require is undefined in pure ESM at runtime; in that case skip otel.
33
+ const req = globalThis.require;
34
+ if (!req)
35
+ return null;
36
+ const otel = req('@opentelemetry/api');
37
+ cachedTracer = otel.trace.getTracer('@onesub/server', '1.0.0');
38
+ return cachedTracer;
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ /**
45
+ * Wrap an async operation in a span. When otel isn't installed the function
46
+ * runs unmodified — no span object is allocated.
47
+ *
48
+ * Use for operations whose latency or failure rate is operationally
49
+ * interesting: receipt validation, store writes, outbound API calls.
50
+ */
51
+ export async function withSpan(name, attributes, fn) {
52
+ const tracer = getTracer();
53
+ if (!tracer)
54
+ return fn(NOOP_SPAN);
55
+ return tracer.startActiveSpan(name, async (span) => {
56
+ for (const [k, v] of Object.entries(attributes))
57
+ span.setAttribute(k, v);
58
+ try {
59
+ const result = await fn(span);
60
+ // status code 1 = OK, 2 = ERROR (avoid importing the enum from otel api)
61
+ span.setStatus({ code: 1 });
62
+ return result;
63
+ }
64
+ catch (err) {
65
+ span.recordException(err);
66
+ span.setStatus({ code: 2, message: err.message });
67
+ throw err;
68
+ }
69
+ finally {
70
+ span.end();
71
+ }
72
+ });
73
+ }
74
+ //# sourceMappingURL=tracing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracing.js","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,MAAM,SAAS,GAAS;IACtB,YAAY,EAAE,GAAG,EAAE,GAAE,CAAC;IACtB,eAAe,EAAE,GAAG,EAAE,GAAE,CAAC;IACzB,SAAS,EAAE,GAAG,EAAE,GAAE,CAAC;IACnB,GAAG,EAAE,GAAG,EAAE,GAAE,CAAC;CACd,CAAC;AAEF;;;GAGG;AACH,IAAI,YAAY,GAAkB,IAAI,CAAC;AACvC,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB,SAAS,SAAS;IAChB,IAAI,QAAQ;QAAE,OAAO,YAAY,CAAC;IAClC,QAAQ,GAAG,IAAI,CAAC;IAChB,IAAI,CAAC;QACH,gEAAgE;QAChE,uEAAuE;QACvE,MAAM,GAAG,GACP,UACD,CAAC,OAAO,CAAC;QACV,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,MAAM,IAAI,GAAG,GAAG,CAAC,oBAAoB,CAA+D,CAAC;QACrG,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;QAC/D,OAAO,YAAY,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,UAAqD,EACrD,EAA8B;IAE9B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IAElC,OAAO,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACjD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC;YAAE,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACzE,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;YAC9B,yEAAyE;YACzE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;YAC5B,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;YAC1B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7D,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,58 @@
1
+ /** Default 7-day retention — covers Apple's 3-day retry window plus headroom. */
2
+ const DEFAULT_TTL_SECONDS = 7 * 24 * 60 * 60;
3
+ /**
4
+ * In-memory implementation. Suitable for single-instance dev/test; for
5
+ * production multi-instance use the Redis or Postgres variants so retries to
6
+ * other nodes are still deduped.
7
+ */
8
+ export class InMemoryWebhookEventStore {
9
+ ttlSeconds;
10
+ seen = new Map();
11
+ constructor(ttlSeconds = DEFAULT_TTL_SECONDS) {
12
+ this.ttlSeconds = ttlSeconds;
13
+ }
14
+ async markIfNew(provider, eventId) {
15
+ this.evictExpired();
16
+ const key = `${provider}:${eventId}`;
17
+ if (this.seen.has(key))
18
+ return false;
19
+ this.seen.set(key, Date.now() + this.ttlSeconds * 1000);
20
+ return true;
21
+ }
22
+ evictExpired() {
23
+ const now = Date.now();
24
+ for (const [key, expiresAt] of this.seen) {
25
+ if (expiresAt < now)
26
+ this.seen.delete(key);
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ * Cache-backed implementation — works with any `CacheAdapter`.
32
+ *
33
+ * Uses a `get` → `set` sequence, which is **not** atomic under concurrent
34
+ * retries: two simultaneous calls for the same id can both read null and
35
+ * both return `true`. The downstream store PKs / BullMQ jobId dedup catch
36
+ * this worst case.
37
+ *
38
+ * For Redis deployments prefer `RedisWebhookEventStore` (from
39
+ * `@onesub/server`) which uses a single `SET NX` command and is fully
40
+ * atomic.
41
+ */
42
+ export class CacheWebhookEventStore {
43
+ cache;
44
+ ttlSeconds;
45
+ constructor(cache, ttlSeconds = DEFAULT_TTL_SECONDS) {
46
+ this.cache = cache;
47
+ this.ttlSeconds = ttlSeconds;
48
+ }
49
+ async markIfNew(provider, eventId) {
50
+ const key = `webhook:event:${provider}:${eventId}`;
51
+ const existing = await this.cache.get(key);
52
+ if (existing)
53
+ return false;
54
+ await this.cache.set(key, '1', this.ttlSeconds);
55
+ return true;
56
+ }
57
+ }
58
+ //# sourceMappingURL=webhook-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-events.js","sourceRoot":"","sources":["../src/webhook-events.ts"],"names":[],"mappings":"AA6BA,iFAAiF;AACjF,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAE7C;;;;GAIG;AACH,MAAM,OAAO,yBAAyB;IAGP;IAFZ,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IAElD,YAA6B,aAAa,mBAAmB;QAAhC,eAAU,GAAV,UAAU,CAAsB;IAAG,CAAC;IAEjE,KAAK,CAAC,SAAS,CAAC,QAA4B,EAAE,OAAe;QAC3D,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,MAAM,GAAG,GAAG,GAAG,QAAQ,IAAI,OAAO,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,YAAY;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACzC,IAAI,SAAS,GAAG,GAAG;gBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,sBAAsB;IAEd;IACA;IAFnB,YACmB,KAAmB,EACnB,aAAa,mBAAmB;QADhC,UAAK,GAAL,KAAK,CAAc;QACnB,eAAU,GAAV,UAAU,CAAsB;IAChD,CAAC;IAEJ,KAAK,CAAC,SAAS,CAAC,QAA4B,EAAE,OAAe;QAC3D,MAAM,GAAG,GAAG,iBAAiB,QAAQ,IAAI,OAAO,EAAE,CAAC;QACnD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAS,GAAG,CAAC,CAAC;QACnD,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC3B,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Synchronous, in-process implementation. Default — no extra infra required.
3
+ * Handler runs inside the HTTP request, so route latency = handler latency.
4
+ *
5
+ * Failures are NOT retried — the original Apple/Google source retry policy
6
+ * is the durability layer (4xx = no retry, 5xx = source retries). This
7
+ * matches the pre-queue behavior.
8
+ */
9
+ export class InProcessWebhookQueue {
10
+ handler = null;
11
+ setHandler(handler) {
12
+ this.handler = handler;
13
+ }
14
+ async enqueue(job) {
15
+ if (!this.handler) {
16
+ throw new Error('[onesub] webhook queue has no handler registered');
17
+ }
18
+ await this.handler(job);
19
+ }
20
+ }
21
+ export class BullMQWebhookQueue {
22
+ queueName;
23
+ maxAttempts;
24
+ backoffMs;
25
+ concurrency;
26
+ connection;
27
+ // Lazy-loaded so `bullmq` doesn't have to be installed unless this class
28
+ // is instantiated.
29
+ queuePromise = null;
30
+ workerPromise = null;
31
+ handler = null;
32
+ constructor(opts) {
33
+ this.connection = opts.connection;
34
+ this.queueName = opts.queueName ?? 'onesub-webhooks';
35
+ this.maxAttempts = opts.maxAttempts ?? 5;
36
+ this.backoffMs = opts.backoffMs ?? 1000;
37
+ this.concurrency = opts.concurrency ?? 4;
38
+ }
39
+ async getBullMQ() {
40
+ return import('bullmq').catch(() => {
41
+ throw new Error('[onesub] BullMQWebhookQueue requires the `bullmq` package. Run: npm install bullmq');
42
+ });
43
+ }
44
+ getQueue() {
45
+ if (!this.queuePromise) {
46
+ this.queuePromise = (async () => {
47
+ const { Queue } = await this.getBullMQ();
48
+ return new Queue(this.queueName, { connection: this.connection });
49
+ })();
50
+ }
51
+ return this.queuePromise;
52
+ }
53
+ setHandler(handler) {
54
+ this.handler = handler;
55
+ if (!this.workerPromise) {
56
+ this.workerPromise = (async () => {
57
+ const { Worker } = await this.getBullMQ();
58
+ return new Worker(this.queueName, async (job) => {
59
+ if (!this.handler)
60
+ throw new Error('[onesub] handler not set');
61
+ await this.handler(job.data);
62
+ }, {
63
+ connection: this.connection,
64
+ concurrency: this.concurrency,
65
+ });
66
+ })();
67
+ }
68
+ }
69
+ async enqueue(job) {
70
+ const queue = await this.getQueue();
71
+ await queue.add('webhook', job, {
72
+ attempts: this.maxAttempts,
73
+ backoff: { type: 'exponential', delay: this.backoffMs },
74
+ removeOnFail: false,
75
+ removeOnComplete: { age: 24 * 60 * 60, count: 1000 },
76
+ jobId: `${job.provider}:${job.eventId}`,
77
+ });
78
+ }
79
+ async listDeadLetters() {
80
+ const queue = await this.getQueue();
81
+ const failed = await queue.getFailed();
82
+ return failed.map((j) => ({
83
+ id: String(j.id),
84
+ job: j.data,
85
+ attempts: j.attemptsMade,
86
+ lastError: j.failedReason ?? 'unknown',
87
+ failedAt: new Date(j.finishedOn ?? Date.now()).toISOString(),
88
+ }));
89
+ }
90
+ async replayDeadLetter(id) {
91
+ const queue = await this.getQueue();
92
+ const job = await queue.getJob(id);
93
+ if (!job)
94
+ throw new Error(`[onesub] dead-letter job ${id} not found`);
95
+ await job.retry();
96
+ }
97
+ async close() {
98
+ if (this.workerPromise) {
99
+ const worker = await this.workerPromise;
100
+ await worker.close();
101
+ }
102
+ if (this.queuePromise) {
103
+ const queue = await this.queuePromise;
104
+ await queue.close();
105
+ }
106
+ }
107
+ }
108
+ //# sourceMappingURL=webhook-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-queue.js","sourceRoot":"","sources":["../src/webhook-queue.ts"],"names":[],"mappings":"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"}