@onesub/server 0.11.3 → 0.12.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/README.md +119 -2
- package/dist/__tests__/admin-customer-detail.test.d.ts +10 -0
- package/dist/__tests__/admin-customer-detail.test.d.ts.map +1 -0
- package/dist/__tests__/admin-customer-detail.test.js +146 -0
- package/dist/__tests__/admin-customer-detail.test.js.map +1 -0
- package/dist/__tests__/cache.test.d.ts +2 -0
- package/dist/__tests__/cache.test.d.ts.map +1 -0
- package/dist/__tests__/cache.test.js +34 -0
- package/dist/__tests__/cache.test.js.map +1 -0
- package/dist/__tests__/metrics.test.js +57 -0
- package/dist/__tests__/metrics.test.js.map +1 -1
- package/dist/__tests__/openapi.test.d.ts +2 -0
- package/dist/__tests__/openapi.test.d.ts.map +1 -0
- package/dist/__tests__/openapi.test.js +32 -0
- package/dist/__tests__/openapi.test.js.map +1 -0
- package/dist/__tests__/redis-store.test.d.ts +2 -0
- package/dist/__tests__/redis-store.test.d.ts.map +1 -0
- package/dist/__tests__/redis-store.test.js +117 -0
- package/dist/__tests__/redis-store.test.js.map +1 -0
- package/dist/__tests__/webhook-events.test.d.ts +2 -0
- package/dist/__tests__/webhook-events.test.d.ts.map +1 -0
- package/dist/__tests__/webhook-events.test.js +36 -0
- package/dist/__tests__/webhook-events.test.js.map +1 -0
- package/dist/__tests__/webhook-queue.test.d.ts +2 -0
- package/dist/__tests__/webhook-queue.test.d.ts.map +1 -0
- package/dist/__tests__/webhook-queue.test.js +26 -0
- package/dist/__tests__/webhook-queue.test.js.map +1 -0
- package/dist/cache.d.ts +32 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +43 -0
- package/dist/cache.js.map +1 -0
- package/dist/index.cjs +3114 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3072 -118
- package/dist/index.js.map +1 -1
- package/dist/openapi.d.ts +41 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +196 -0
- package/dist/openapi.js.map +1 -0
- package/dist/providers/apple.d.ts +1 -1
- package/dist/providers/apple.d.ts.map +1 -1
- package/dist/providers/apple.js +45 -34
- package/dist/providers/apple.js.map +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +49 -20
- package/dist/providers/google.js.map +1 -1
- package/dist/routes/admin.d.ts +6 -1
- package/dist/routes/admin.d.ts.map +1 -1
- package/dist/routes/admin.js +87 -1
- package/dist/routes/admin.js.map +1 -1
- package/dist/routes/metrics.d.ts.map +1 -1
- package/dist/routes/metrics.js +57 -1
- package/dist/routes/metrics.js.map +1 -1
- package/dist/routes/webhook.d.ts +2 -1
- package/dist/routes/webhook.d.ts.map +1 -1
- package/dist/routes/webhook.js +21 -1
- package/dist/routes/webhook.js.map +1 -1
- package/dist/stores/redis.d.ts +60 -0
- package/dist/stores/redis.d.ts.map +1 -0
- package/dist/stores/redis.js +255 -0
- package/dist/stores/redis.js.map +1 -0
- package/dist/stores/schema.d.ts.map +1 -1
- package/dist/stores/schema.js +10 -0
- package/dist/stores/schema.js.map +1 -1
- package/dist/tracing.d.ts +32 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +74 -0
- package/dist/tracing.js.map +1 -0
- package/dist/webhook-events.d.ts +58 -0
- package/dist/webhook-events.d.ts.map +1 -0
- package/dist/webhook-events.js +57 -0
- package/dist/webhook-events.js.map +1 -0
- package/dist/webhook-queue.d.ts +92 -0
- package/dist/webhook-queue.d.ts.map +1 -0
- package/dist/webhook-queue.js +108 -0
- package/dist/webhook-queue.js.map +1 -0
- package/package.json +33 -4
- package/sql/schema.sql +10 -0
|
@@ -0,0 +1,255 @@
|
|
|
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
|
+
const PUR_TX_PREFIX = 'onesub:purchase:tx:';
|
|
39
|
+
const PUR_USER_PREFIX = 'onesub:purchase:user:';
|
|
40
|
+
const PUR_USER_PRODUCT_PREFIX = 'onesub:purchase:user_product:';
|
|
41
|
+
const PUR_ALL = 'onesub:purchase:all';
|
|
42
|
+
export class RedisSubscriptionStore {
|
|
43
|
+
redis;
|
|
44
|
+
constructor(redis) {
|
|
45
|
+
this.redis = redis;
|
|
46
|
+
}
|
|
47
|
+
async save(sub) {
|
|
48
|
+
const score = Date.now();
|
|
49
|
+
const txKey = SUB_TX_PREFIX + sub.originalTransactionId;
|
|
50
|
+
const userKey = SUB_USER_PREFIX + sub.userId;
|
|
51
|
+
// Multi-key write — use a pipeline so the three commands are at least
|
|
52
|
+
// sent in one round-trip. Atomicity is best-effort; the only consistency
|
|
53
|
+
// hazard is a crash between commands, which leaves the user index without
|
|
54
|
+
// the new tx (recoverable: subsequent writes for the same tx fix it).
|
|
55
|
+
const pipeline = this.redis.multi();
|
|
56
|
+
pipeline.set(txKey, JSON.stringify(sub));
|
|
57
|
+
pipeline.zadd(userKey, score, sub.originalTransactionId);
|
|
58
|
+
pipeline.sadd(SUB_ALL, 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
|
+
const ids = await this.redis.smembers(SUB_ALL);
|
|
84
|
+
if (ids.length === 0)
|
|
85
|
+
return [];
|
|
86
|
+
const raws = await this.redis.mget(...ids.map((id) => SUB_TX_PREFIX + id));
|
|
87
|
+
return raws.filter((r) => r != null).map((r) => JSON.parse(r));
|
|
88
|
+
}
|
|
89
|
+
async listFiltered(opts) {
|
|
90
|
+
// Redis has no secondary indexes — for listFiltered we materialise the
|
|
91
|
+
// candidate set then apply filters in-process. Fine for small/medium data;
|
|
92
|
+
// for >100k rows pair this with Postgres or use Redis Stack with RediSearch.
|
|
93
|
+
const limit = opts.limit ?? 50;
|
|
94
|
+
const offset = opts.offset ?? 0;
|
|
95
|
+
let candidates;
|
|
96
|
+
if (opts.userId) {
|
|
97
|
+
candidates = await this.getAllByUserId(opts.userId);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
candidates = await this.listAll();
|
|
101
|
+
// Sort newest-first by purchasedAt as a stable proxy when no per-user
|
|
102
|
+
// sorted set is available. SubscriptionInfo doesn't carry updatedAt, so
|
|
103
|
+
// we fall back to expiresAt for a "freshest first" ordering.
|
|
104
|
+
candidates.sort((a, b) => Date.parse(b.expiresAt) - Date.parse(a.expiresAt));
|
|
105
|
+
}
|
|
106
|
+
const filtered = candidates.filter((s) => {
|
|
107
|
+
if (opts.status && s.status !== opts.status)
|
|
108
|
+
return false;
|
|
109
|
+
if (opts.productId && s.productId !== opts.productId)
|
|
110
|
+
return false;
|
|
111
|
+
if (opts.platform && s.platform !== opts.platform)
|
|
112
|
+
return false;
|
|
113
|
+
return true;
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
items: filtered.slice(offset, offset + limit),
|
|
117
|
+
total: filtered.length,
|
|
118
|
+
limit,
|
|
119
|
+
offset,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export class RedisPurchaseStore {
|
|
124
|
+
redis;
|
|
125
|
+
constructor(redis) {
|
|
126
|
+
this.redis = redis;
|
|
127
|
+
}
|
|
128
|
+
async savePurchase(purchase) {
|
|
129
|
+
const txKey = PUR_TX_PREFIX + purchase.transactionId;
|
|
130
|
+
// Owner check — same TRANSACTION_BELONGS_TO_OTHER_USER semantics as
|
|
131
|
+
// Postgres / InMemory implementations. Without this guard a stolen
|
|
132
|
+
// receipt could be re-bound to a different account.
|
|
133
|
+
const existing = await this.redis.get(txKey);
|
|
134
|
+
if (existing) {
|
|
135
|
+
const owner = JSON.parse(existing).userId;
|
|
136
|
+
if (owner !== purchase.userId) {
|
|
137
|
+
const err = new Error('TRANSACTION_BELONGS_TO_OTHER_USER');
|
|
138
|
+
err.code = 'TRANSACTION_BELONGS_TO_OTHER_USER';
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
return; // idempotent — same user
|
|
142
|
+
}
|
|
143
|
+
const score = Date.parse(purchase.purchasedAt) || Date.now();
|
|
144
|
+
const pipeline = this.redis.multi();
|
|
145
|
+
pipeline.set(txKey, JSON.stringify(purchase));
|
|
146
|
+
pipeline.zadd(PUR_USER_PREFIX + purchase.userId, score, purchase.transactionId);
|
|
147
|
+
pipeline.sadd(PUR_USER_PRODUCT_PREFIX + purchase.userId + ':' + purchase.productId, purchase.transactionId);
|
|
148
|
+
pipeline.sadd(PUR_ALL, purchase.transactionId);
|
|
149
|
+
await pipeline.exec();
|
|
150
|
+
}
|
|
151
|
+
async getPurchasesByUserId(userId) {
|
|
152
|
+
const ids = await this.redis.zrevrange(PUR_USER_PREFIX + userId, 0, -1);
|
|
153
|
+
if (ids.length === 0)
|
|
154
|
+
return [];
|
|
155
|
+
const raws = await this.redis.mget(...ids.map((id) => PUR_TX_PREFIX + id));
|
|
156
|
+
return raws.filter((r) => r != null).map((r) => JSON.parse(r));
|
|
157
|
+
}
|
|
158
|
+
async getPurchaseByTransactionId(txId) {
|
|
159
|
+
const raw = await this.redis.get(PUR_TX_PREFIX + txId);
|
|
160
|
+
return raw ? JSON.parse(raw) : null;
|
|
161
|
+
}
|
|
162
|
+
async hasPurchased(userId, productId) {
|
|
163
|
+
const count = await this.redis.scard(PUR_USER_PRODUCT_PREFIX + userId + ':' + productId);
|
|
164
|
+
return count > 0;
|
|
165
|
+
}
|
|
166
|
+
async reassignPurchase(transactionId, newUserId) {
|
|
167
|
+
const txKey = PUR_TX_PREFIX + transactionId;
|
|
168
|
+
const raw = await this.redis.get(txKey);
|
|
169
|
+
if (!raw)
|
|
170
|
+
return false;
|
|
171
|
+
const existing = JSON.parse(raw);
|
|
172
|
+
if (existing.userId === newUserId)
|
|
173
|
+
return true;
|
|
174
|
+
const updated = { ...existing, userId: newUserId };
|
|
175
|
+
const score = Date.parse(updated.purchasedAt) || Date.now();
|
|
176
|
+
const oldUserProductKey = PUR_USER_PRODUCT_PREFIX + existing.userId + ':' + existing.productId;
|
|
177
|
+
const newUserProductKey = PUR_USER_PRODUCT_PREFIX + newUserId + ':' + existing.productId;
|
|
178
|
+
const pipeline = this.redis.multi();
|
|
179
|
+
pipeline.set(txKey, JSON.stringify(updated));
|
|
180
|
+
pipeline.zrem(PUR_USER_PREFIX + existing.userId, transactionId);
|
|
181
|
+
pipeline.zadd(PUR_USER_PREFIX + newUserId, score, transactionId);
|
|
182
|
+
pipeline.srem(oldUserProductKey, transactionId);
|
|
183
|
+
pipeline.sadd(newUserProductKey, transactionId);
|
|
184
|
+
await pipeline.exec();
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
async deletePurchases(userId, productId) {
|
|
188
|
+
const userProductKey = PUR_USER_PRODUCT_PREFIX + userId + ':' + productId;
|
|
189
|
+
const ids = await this.redis.smembers(userProductKey);
|
|
190
|
+
if (ids.length === 0)
|
|
191
|
+
return 0;
|
|
192
|
+
const pipeline = this.redis.multi();
|
|
193
|
+
for (const id of ids) {
|
|
194
|
+
pipeline.del(PUR_TX_PREFIX + id);
|
|
195
|
+
pipeline.zrem(PUR_USER_PREFIX + userId, id);
|
|
196
|
+
pipeline.srem(PUR_ALL, id);
|
|
197
|
+
}
|
|
198
|
+
pipeline.del(userProductKey);
|
|
199
|
+
await pipeline.exec();
|
|
200
|
+
return ids.length;
|
|
201
|
+
}
|
|
202
|
+
async deletePurchaseByTransactionId(transactionId) {
|
|
203
|
+
const txKey = PUR_TX_PREFIX + transactionId;
|
|
204
|
+
const raw = await this.redis.get(txKey);
|
|
205
|
+
if (!raw)
|
|
206
|
+
return false;
|
|
207
|
+
const existing = JSON.parse(raw);
|
|
208
|
+
const pipeline = this.redis.multi();
|
|
209
|
+
pipeline.del(txKey);
|
|
210
|
+
pipeline.zrem(PUR_USER_PREFIX + existing.userId, transactionId);
|
|
211
|
+
pipeline.srem(PUR_USER_PRODUCT_PREFIX + existing.userId + ':' + existing.productId, transactionId);
|
|
212
|
+
pipeline.srem(PUR_ALL, transactionId);
|
|
213
|
+
await pipeline.exec();
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
async listAll() {
|
|
217
|
+
const ids = await this.redis.smembers(PUR_ALL);
|
|
218
|
+
if (ids.length === 0)
|
|
219
|
+
return [];
|
|
220
|
+
const raws = await this.redis.mget(...ids.map((id) => PUR_TX_PREFIX + id));
|
|
221
|
+
return raws.filter((r) => r != null).map((r) => JSON.parse(r));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Redis-backed cache adapter — share JWKS / OAuth tokens across cluster nodes.
|
|
226
|
+
*
|
|
227
|
+
* Implements `CacheAdapter` so it plugs into the same default-cache slot used
|
|
228
|
+
* by the Apple JWT minter and Google OAuth token minter.
|
|
229
|
+
*/
|
|
230
|
+
export class RedisCacheAdapter {
|
|
231
|
+
redis;
|
|
232
|
+
prefix;
|
|
233
|
+
constructor(redis, prefix = 'onesub:cache:') {
|
|
234
|
+
this.redis = redis;
|
|
235
|
+
this.prefix = prefix;
|
|
236
|
+
}
|
|
237
|
+
async get(key) {
|
|
238
|
+
const raw = await this.redis.get(this.prefix + key);
|
|
239
|
+
return raw ? JSON.parse(raw) : null;
|
|
240
|
+
}
|
|
241
|
+
async set(key, value, ttlSeconds) {
|
|
242
|
+
const fullKey = this.prefix + key;
|
|
243
|
+
const payload = JSON.stringify(value);
|
|
244
|
+
if (ttlSeconds && ttlSeconds > 0) {
|
|
245
|
+
await this.redis.set(fullKey, payload, 'EX', ttlSeconds);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
await this.redis.set(fullKey, payload);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async del(key) {
|
|
252
|
+
await this.redis.del(this.prefix + key);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
//# 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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/stores/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,wBAAwB,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/stores/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,wBAAwB,QAgC7B,CAAC;AAET,eAAO,MAAM,oBAAoB,QAqBzB,CAAC"}
|
package/dist/stores/schema.js
CHANGED
|
@@ -27,6 +27,16 @@ CREATE TABLE IF NOT EXISTS onesub_subscriptions (
|
|
|
27
27
|
CREATE INDEX IF NOT EXISTS idx_onesub_subscriptions_user_id
|
|
28
28
|
ON onesub_subscriptions (user_id, updated_at DESC);
|
|
29
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
|
+
|
|
30
40
|
-- Backfill columns for installs that already created the table from an older
|
|
31
41
|
-- schema. Safe to re-run.
|
|
32
42
|
ALTER TABLE onesub_subscriptions ADD COLUMN IF NOT EXISTS linked_purchase_token TEXT;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/stores/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG
|
|
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,32 @@
|
|
|
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
|
+
type Span = {
|
|
15
|
+
setAttribute: (key: string, value: string | number | boolean) => void;
|
|
16
|
+
recordException: (err: unknown) => void;
|
|
17
|
+
setStatus: (status: {
|
|
18
|
+
code: number;
|
|
19
|
+
message?: string;
|
|
20
|
+
}) => void;
|
|
21
|
+
end: () => void;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Wrap an async operation in a span. When otel isn't installed the function
|
|
25
|
+
* runs unmodified — no span object is allocated.
|
|
26
|
+
*
|
|
27
|
+
* Use for operations whose latency or failure rate is operationally
|
|
28
|
+
* interesting: receipt validation, store writes, outbound API calls.
|
|
29
|
+
*/
|
|
30
|
+
export declare function withSpan<T>(name: string, attributes: Record<string, string | number | boolean>, fn: (span: Span) => Promise<T>): Promise<T>;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=tracing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,KAAK,IAAI,GAAG;IACV,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;IACtE,eAAe,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,SAAS,EAAE,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAChE,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB,CAAC;AAkCF;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EACrD,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7B,OAAO,CAAC,CAAC,CAAC,CAmBZ"}
|
package/dist/tracing.js
ADDED
|
@@ -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
|
+
import type { CacheAdapter } from './cache.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pluggable webhook-event idempotency store.
|
|
4
|
+
*
|
|
5
|
+
* Apple sends `notificationUUID` and Google Pub/Sub sends `messageId` — both
|
|
6
|
+
* are guaranteed unique per notification. Apple/Google will retry on any
|
|
7
|
+
* non-2xx response (Apple: ~3 days, Google: configurable Pub/Sub policy), so
|
|
8
|
+
* the same notification can hit our handler multiple times if the previous
|
|
9
|
+
* response timed out, the DB was briefly down, etc. We must not double-apply
|
|
10
|
+
* state changes (extra subscription days, refunded purchase being deleted
|
|
11
|
+
* twice, consumption response being PUT twice).
|
|
12
|
+
*
|
|
13
|
+
* `markIfNew` returns `true` when the caller should process the event,
|
|
14
|
+
* `false` when it has been seen before. Implementations decide retention —
|
|
15
|
+
* once-seen IDs are kept long enough that retries from the source can't beat
|
|
16
|
+
* the TTL (we recommend ≥ 7 days).
|
|
17
|
+
*/
|
|
18
|
+
export interface WebhookEventStore {
|
|
19
|
+
/**
|
|
20
|
+
* Atomically register the given event id. Returns `true` if it's new (first
|
|
21
|
+
* time we've seen it), `false` if it was already seen.
|
|
22
|
+
*
|
|
23
|
+
* `provider` lets us key by source so Apple's notificationUUID and Google's
|
|
24
|
+
* messageId never collide.
|
|
25
|
+
*/
|
|
26
|
+
markIfNew(provider: 'apple' | 'google', eventId: string): Promise<boolean>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* In-memory implementation. Suitable for single-instance dev/test; for
|
|
30
|
+
* production multi-instance use the Redis or Postgres variants so retries to
|
|
31
|
+
* other nodes are still deduped.
|
|
32
|
+
*/
|
|
33
|
+
export declare class InMemoryWebhookEventStore implements WebhookEventStore {
|
|
34
|
+
private readonly ttlSeconds;
|
|
35
|
+
private readonly seen;
|
|
36
|
+
constructor(ttlSeconds?: number);
|
|
37
|
+
markIfNew(provider: 'apple' | 'google', eventId: string): Promise<boolean>;
|
|
38
|
+
private evictExpired;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Cache-backed implementation — works with any `CacheAdapter`.
|
|
42
|
+
*
|
|
43
|
+
* Uses a `get` → `set` sequence, which is **not** atomic under concurrent
|
|
44
|
+
* retries: two simultaneous calls for the same id can both read null and
|
|
45
|
+
* both return `true`. The downstream store PKs / BullMQ jobId dedup catch
|
|
46
|
+
* this worst case.
|
|
47
|
+
*
|
|
48
|
+
* For Redis deployments prefer `RedisWebhookEventStore` (from
|
|
49
|
+
* `@onesub/server`) which uses a single `SET NX` command and is fully
|
|
50
|
+
* atomic.
|
|
51
|
+
*/
|
|
52
|
+
export declare class CacheWebhookEventStore implements WebhookEventStore {
|
|
53
|
+
private readonly cache;
|
|
54
|
+
private readonly ttlSeconds;
|
|
55
|
+
constructor(cache: CacheAdapter, ttlSeconds?: number);
|
|
56
|
+
markIfNew(provider: 'apple' | 'google', eventId: string): Promise<boolean>;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=webhook-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-events.d.ts","sourceRoot":"","sources":["../src/webhook-events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC5E;AAKD;;;;GAIG;AACH,qBAAa,yBAA0B,YAAW,iBAAiB;IAGrD,OAAO,CAAC,QAAQ,CAAC,UAAU;IAFvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;gBAErB,UAAU,SAAsB;IAEvD,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQhF,OAAO,CAAC,YAAY;CAMrB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,sBAAuB,YAAW,iBAAiB;IAE5D,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;gBADV,KAAK,EAAE,YAAY,EACnB,UAAU,SAAsB;IAG7C,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAOjF"}
|
|
@@ -0,0 +1,57 @@
|
|
|
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` (including
|
|
32
|
+
* `RedisCacheAdapter`), so multi-instance deployments dedupe across nodes.
|
|
33
|
+
*
|
|
34
|
+
* Uses the cache's atomic `set` semantics: we call `get` then `set` with TTL.
|
|
35
|
+
* This is correct under low contention; for very high concurrency the
|
|
36
|
+
* implementation may want a backend with native SETNX. RedisCacheAdapter's
|
|
37
|
+
* `set` is idempotent, so concurrent first-writes converge — the behavior is
|
|
38
|
+
* "at-most-twice" rather than "exactly once" in the worst race, which the
|
|
39
|
+
* downstream store-level idempotency (`originalTransactionId` PK) catches.
|
|
40
|
+
*/
|
|
41
|
+
export class CacheWebhookEventStore {
|
|
42
|
+
cache;
|
|
43
|
+
ttlSeconds;
|
|
44
|
+
constructor(cache, ttlSeconds = DEFAULT_TTL_SECONDS) {
|
|
45
|
+
this.cache = cache;
|
|
46
|
+
this.ttlSeconds = ttlSeconds;
|
|
47
|
+
}
|
|
48
|
+
async markIfNew(provider, eventId) {
|
|
49
|
+
const key = `webhook:event:${provider}:${eventId}`;
|
|
50
|
+
const existing = await this.cache.get(key);
|
|
51
|
+
if (existing)
|
|
52
|
+
return false;
|
|
53
|
+
await this.cache.set(key, '1', this.ttlSeconds);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# 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;;;;;;;;;;GAUG;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,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook processing queue.
|
|
3
|
+
*
|
|
4
|
+
* The default in-process queue runs the handler synchronously inside the HTTP
|
|
5
|
+
* request — the same behavior you got before this interface existed. For
|
|
6
|
+
* production deployments that want decoupled retries, swap in a queue-backed
|
|
7
|
+
* implementation (BullMQ, SQS, etc.) so a slow DB or Apple/Google API call
|
|
8
|
+
* doesn't block the webhook from acknowledging.
|
|
9
|
+
*
|
|
10
|
+
* Failure semantics:
|
|
11
|
+
* - Handler throws → queue records the failure and (for retrying queues)
|
|
12
|
+
* re-runs with backoff. After max attempts the job
|
|
13
|
+
* lands in the dead-letter store.
|
|
14
|
+
* - Handler returns → ack to source.
|
|
15
|
+
*
|
|
16
|
+
* The HTTP route should always 200 once the job is *enqueued*. Synchronous
|
|
17
|
+
* processing happens to acknowledge later only because the in-process queue
|
|
18
|
+
* runs inline; for any async queue the route should ack after `enqueue()`.
|
|
19
|
+
*/
|
|
20
|
+
export interface WebhookJob<T = unknown> {
|
|
21
|
+
provider: 'apple' | 'google';
|
|
22
|
+
/** Source-supplied event id (notificationUUID / messageId) for tracing. */
|
|
23
|
+
eventId: string;
|
|
24
|
+
payload: T;
|
|
25
|
+
}
|
|
26
|
+
export type WebhookHandler<T = unknown> = (job: WebhookJob<T>) => Promise<void>;
|
|
27
|
+
export interface WebhookQueue {
|
|
28
|
+
/**
|
|
29
|
+
* Enqueue a job. Returns once the job is durably accepted (synchronous for
|
|
30
|
+
* in-process; persisted-to-Redis for BullMQ).
|
|
31
|
+
*/
|
|
32
|
+
enqueue<T>(job: WebhookJob<T>): Promise<void>;
|
|
33
|
+
/** Register the worker handler. Called once during middleware setup. */
|
|
34
|
+
setHandler<T>(handler: WebhookHandler<T>): void;
|
|
35
|
+
/** Optional: list jobs in the dead-letter queue (for the admin replay UI). */
|
|
36
|
+
listDeadLetters?(): Promise<DeadLetterRecord[]>;
|
|
37
|
+
/** Optional: replay a specific dead-letter job back through the handler. */
|
|
38
|
+
replayDeadLetter?(id: string): Promise<void>;
|
|
39
|
+
/** Optional: graceful shutdown. */
|
|
40
|
+
close?(): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export interface DeadLetterRecord {
|
|
43
|
+
id: string;
|
|
44
|
+
job: WebhookJob;
|
|
45
|
+
attempts: number;
|
|
46
|
+
lastError: string;
|
|
47
|
+
failedAt: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Synchronous, in-process implementation. Default — no extra infra required.
|
|
51
|
+
* Handler runs inside the HTTP request, so route latency = handler latency.
|
|
52
|
+
*
|
|
53
|
+
* Failures are NOT retried — the original Apple/Google source retry policy
|
|
54
|
+
* is the durability layer (4xx = no retry, 5xx = source retries). This
|
|
55
|
+
* matches the pre-queue behavior.
|
|
56
|
+
*/
|
|
57
|
+
export declare class InProcessWebhookQueue implements WebhookQueue {
|
|
58
|
+
private handler;
|
|
59
|
+
setHandler<T>(handler: WebhookHandler<T>): void;
|
|
60
|
+
enqueue<T>(job: WebhookJob<T>): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
export interface BullMQWebhookQueueOptions {
|
|
63
|
+
/** ioredis connection (or compatible options). Required. */
|
|
64
|
+
connection: unknown;
|
|
65
|
+
/** Queue name. Defaults to 'onesub-webhooks'. */
|
|
66
|
+
queueName?: string;
|
|
67
|
+
/** Max retry attempts before sending the job to the dead-letter list. */
|
|
68
|
+
maxAttempts?: number;
|
|
69
|
+
/** Backoff (ms) between attempts. Exponential: backoffMs * 2^(attempt-1). */
|
|
70
|
+
backoffMs?: number;
|
|
71
|
+
/** Worker concurrency. Defaults to 4. */
|
|
72
|
+
concurrency?: number;
|
|
73
|
+
}
|
|
74
|
+
export declare class BullMQWebhookQueue implements WebhookQueue {
|
|
75
|
+
private queueName;
|
|
76
|
+
private maxAttempts;
|
|
77
|
+
private backoffMs;
|
|
78
|
+
private concurrency;
|
|
79
|
+
private connection;
|
|
80
|
+
private queuePromise;
|
|
81
|
+
private workerPromise;
|
|
82
|
+
private handler;
|
|
83
|
+
constructor(opts: BullMQWebhookQueueOptions);
|
|
84
|
+
private getBullMQ;
|
|
85
|
+
private getQueue;
|
|
86
|
+
setHandler<T>(handler: WebhookHandler<T>): void;
|
|
87
|
+
enqueue<T>(job: WebhookJob<T>): Promise<void>;
|
|
88
|
+
listDeadLetters(): Promise<DeadLetterRecord[]>;
|
|
89
|
+
replayDeadLetter(id: string): Promise<void>;
|
|
90
|
+
close(): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=webhook-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-queue.d.ts","sourceRoot":"","sources":["../src/webhook-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,OAAO;IACrC,QAAQ,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC7B,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,CAAC,CAAC;CACZ;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhF,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,wEAAwE;IACxE,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAChD,8EAA8E;IAC9E,eAAe,CAAC,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAChD,4EAA4E;IAC5E,gBAAgB,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,mCAAmC;IACnC,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,UAAU,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,qBAAa,qBAAsB,YAAW,YAAY;IACxD,OAAO,CAAC,OAAO,CAA+B;IAE9C,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI;IAIzC,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAMpD;AAsDD,MAAM,WAAW,yBAAyB;IACxC,4DAA4D;IAC5D,UAAU,EAAE,OAAO,CAAC;IACpB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAU;IAI5B,OAAO,CAAC,YAAY,CAAqC;IACzD,OAAO,CAAC,aAAa,CAAsC;IAC3D,OAAO,CAAC,OAAO,CAA+B;gBAElC,IAAI,EAAE,yBAAyB;YAQ7B,SAAS;IAMvB,OAAO,CAAC,QAAQ;IAUhB,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI;IAoBzC,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAW7C,eAAe,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAY9C,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAU7B"}
|