@onesub/server 0.11.4 → 0.12.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 (127) hide show
  1. package/README.md +119 -2
  2. package/dist/__tests__/cache.test.d.ts +2 -0
  3. package/dist/__tests__/cache.test.d.ts.map +1 -0
  4. package/dist/__tests__/openapi.test.d.ts +2 -0
  5. package/dist/__tests__/openapi.test.d.ts.map +1 -0
  6. package/dist/__tests__/redis-store.test.d.ts +2 -0
  7. package/dist/__tests__/redis-store.test.d.ts.map +1 -0
  8. package/dist/__tests__/webhook-apple.test.d.ts +12 -0
  9. package/dist/__tests__/webhook-apple.test.d.ts.map +1 -0
  10. package/dist/__tests__/webhook-events.test.d.ts +2 -0
  11. package/dist/__tests__/webhook-events.test.d.ts.map +1 -0
  12. package/dist/__tests__/webhook-google.test.d.ts +13 -0
  13. package/dist/__tests__/webhook-google.test.d.ts.map +1 -0
  14. package/dist/__tests__/webhook-queue.test.d.ts +2 -0
  15. package/dist/__tests__/webhook-queue.test.d.ts.map +1 -0
  16. package/dist/cache.d.ts +32 -0
  17. package/dist/cache.d.ts.map +1 -0
  18. package/dist/index.cjs +3084 -0
  19. package/dist/index.cjs.map +1 -0
  20. package/dist/index.d.ts +34 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +3045 -118
  23. package/dist/index.js.map +1 -1
  24. package/dist/openapi.d.ts +41 -0
  25. package/dist/openapi.d.ts.map +1 -0
  26. package/dist/providers/apple.d.ts +1 -1
  27. package/dist/providers/apple.d.ts.map +1 -1
  28. package/dist/providers/google.d.ts.map +1 -1
  29. package/dist/routes/admin.d.ts +2 -1
  30. package/dist/routes/admin.d.ts.map +1 -1
  31. package/dist/routes/webhook-apple.d.ts +6 -0
  32. package/dist/routes/webhook-apple.d.ts.map +1 -0
  33. package/dist/routes/webhook-google.d.ts +7 -0
  34. package/dist/routes/webhook-google.d.ts.map +1 -0
  35. package/dist/routes/webhook.d.ts +6 -18
  36. package/dist/routes/webhook.d.ts.map +1 -1
  37. package/dist/stores/redis.d.ts +60 -0
  38. package/dist/stores/redis.d.ts.map +1 -0
  39. package/dist/stores/schema.d.ts.map +1 -1
  40. package/dist/tracing.d.ts +32 -0
  41. package/dist/tracing.d.ts.map +1 -0
  42. package/dist/webhook-events.d.ts +58 -0
  43. package/dist/webhook-events.d.ts.map +1 -0
  44. package/dist/webhook-queue.d.ts +92 -0
  45. package/dist/webhook-queue.d.ts.map +1 -0
  46. package/package.json +33 -4
  47. package/sql/schema.sql +10 -0
  48. package/dist/__tests__/admin-customer-detail.test.js +0 -146
  49. package/dist/__tests__/admin-customer-detail.test.js.map +0 -1
  50. package/dist/__tests__/admin-list-subscriptions.test.js +0 -192
  51. package/dist/__tests__/admin-list-subscriptions.test.js.map +0 -1
  52. package/dist/__tests__/admin-subscription-detail.test.js +0 -112
  53. package/dist/__tests__/admin-subscription-detail.test.js.map +0 -1
  54. package/dist/__tests__/apple-root-ca.test.js +0 -41
  55. package/dist/__tests__/apple-root-ca.test.js.map +0 -1
  56. package/dist/__tests__/apple-status-api.test.js +0 -345
  57. package/dist/__tests__/apple-status-api.test.js.map +0 -1
  58. package/dist/__tests__/entitlements.test.js +0 -268
  59. package/dist/__tests__/entitlements.test.js.map +0 -1
  60. package/dist/__tests__/error-codes.test.js +0 -168
  61. package/dist/__tests__/error-codes.test.js.map +0 -1
  62. package/dist/__tests__/fetch-hardening.test.js +0 -192
  63. package/dist/__tests__/fetch-hardening.test.js.map +0 -1
  64. package/dist/__tests__/google-paused.test.js +0 -126
  65. package/dist/__tests__/google-paused.test.js.map +0 -1
  66. package/dist/__tests__/google-price-change.test.js +0 -186
  67. package/dist/__tests__/google-price-change.test.js.map +0 -1
  68. package/dist/__tests__/google-subscriptions-v2.test.js +0 -206
  69. package/dist/__tests__/google-subscriptions-v2.test.js.map +0 -1
  70. package/dist/__tests__/lifecycle-scenarios.test.js +0 -579
  71. package/dist/__tests__/lifecycle-scenarios.test.js.map +0 -1
  72. package/dist/__tests__/lifecycle-states.test.js +0 -423
  73. package/dist/__tests__/lifecycle-states.test.js.map +0 -1
  74. package/dist/__tests__/linked-purchase-token.test.js +0 -227
  75. package/dist/__tests__/linked-purchase-token.test.js.map +0 -1
  76. package/dist/__tests__/metrics.test.js +0 -340
  77. package/dist/__tests__/metrics.test.js.map +0 -1
  78. package/dist/__tests__/mock-provider.test.js +0 -188
  79. package/dist/__tests__/mock-provider.test.js.map +0 -1
  80. package/dist/__tests__/paused-auto-resume.test.js +0 -162
  81. package/dist/__tests__/paused-auto-resume.test.js.map +0 -1
  82. package/dist/__tests__/providers.test.js +0 -321
  83. package/dist/__tests__/providers.test.js.map +0 -1
  84. package/dist/__tests__/refund-policy.test.js +0 -317
  85. package/dist/__tests__/refund-policy.test.js.map +0 -1
  86. package/dist/__tests__/schema.test.js +0 -46
  87. package/dist/__tests__/schema.test.js.map +0 -1
  88. package/dist/__tests__/store.test.js +0 -172
  89. package/dist/__tests__/store.test.js.map +0 -1
  90. package/dist/__tests__/test-utils.js +0 -30
  91. package/dist/__tests__/test-utils.js.map +0 -1
  92. package/dist/__tests__/webhook-refund.test.js +0 -313
  93. package/dist/__tests__/webhook-refund.test.js.map +0 -1
  94. package/dist/errors.js +0 -19
  95. package/dist/errors.js.map +0 -1
  96. package/dist/http.js +0 -36
  97. package/dist/http.js.map +0 -1
  98. package/dist/logger.js +0 -20
  99. package/dist/logger.js.map +0 -1
  100. package/dist/providers/apple-root-ca.js +0 -38
  101. package/dist/providers/apple-root-ca.js.map +0 -1
  102. package/dist/providers/apple.js +0 -499
  103. package/dist/providers/apple.js.map +0 -1
  104. package/dist/providers/google.js +0 -493
  105. package/dist/providers/google.js.map +0 -1
  106. package/dist/providers/mock.js +0 -93
  107. package/dist/providers/mock.js.map +0 -1
  108. package/dist/routes/admin.js +0 -251
  109. package/dist/routes/admin.js.map +0 -1
  110. package/dist/routes/entitlements.js +0 -130
  111. package/dist/routes/entitlements.js.map +0 -1
  112. package/dist/routes/metrics.js +0 -281
  113. package/dist/routes/metrics.js.map +0 -1
  114. package/dist/routes/purchase.js +0 -187
  115. package/dist/routes/purchase.js.map +0 -1
  116. package/dist/routes/status.js +0 -55
  117. package/dist/routes/status.js.map +0 -1
  118. package/dist/routes/validate.js +0 -70
  119. package/dist/routes/validate.js.map +0 -1
  120. package/dist/routes/webhook.js +0 -441
  121. package/dist/routes/webhook.js.map +0 -1
  122. package/dist/store.js +0 -145
  123. package/dist/store.js.map +0 -1
  124. package/dist/stores/postgres.js +0 -329
  125. package/dist/stores/postgres.js.map +0 -1
  126. package/dist/stores/schema.js +0 -57
  127. package/dist/stores/schema.js.map +0 -1
@@ -0,0 +1,41 @@
1
+ /**
2
+ * OpenAPI 3.1 spec for the onesub HTTP surface.
3
+ *
4
+ * Hand-maintained — kept in sync with the routes by a parity test (see
5
+ * __tests__/openapi.test.ts) that asserts every mounted path is described
6
+ * here. Generated clients live downstream:
7
+ *
8
+ * - swagger-typescript-api → typed fetch client
9
+ * - openapi-generator-cli → Kotlin/Swift/etc. clients
10
+ *
11
+ * Why hand-maintained instead of zod-to-openapi: the routes use zod for
12
+ * input validation but the request/response shapes the public docs need are
13
+ * a thin slice of those internal types. Hand-writing keeps the doc readable
14
+ * without coupling client output to internal validation strictness changes.
15
+ */
16
+ export interface OpenAPIDoc {
17
+ openapi: '3.1.0';
18
+ info: {
19
+ title: string;
20
+ version: string;
21
+ description: string;
22
+ };
23
+ servers: Array<{
24
+ url: string;
25
+ description?: string;
26
+ }>;
27
+ paths: Record<string, unknown>;
28
+ components: {
29
+ schemas: Record<string, unknown>;
30
+ securitySchemes: Record<string, unknown>;
31
+ };
32
+ }
33
+ export declare const ONESUB_OPENAPI: OpenAPIDoc;
34
+ /**
35
+ * Mount a route that serves the OpenAPI document. Useful for hosts that want
36
+ * to expose `/openapi.json` to API explorers / SDK generators.
37
+ *
38
+ * app.use('/openapi.json', openapiHandler());
39
+ */
40
+ export declare function openapiHandler(): (_req: import("express").Request, res: import("express").Response) => void;
41
+ //# sourceMappingURL=openapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,UAAU,EAAE;QACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC1C,CAAC;CACH;AAED,eAAO,MAAM,cAAc,EAAE,UAyK5B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,cAAc,KACpB,MAAM,OAAO,SAAS,EAAE,OAAO,EAAE,KAAK,OAAO,SAAS,EAAE,QAAQ,UAGzE"}
@@ -59,7 +59,7 @@ export declare function decodeAppleNotification(payload: AppleNotificationPayloa
59
59
  /** May be null for non-subscription notifications (consumable refund). */
60
60
  expiresAt: string | null;
61
61
  } | null>;
62
- /** Test-only: clear the module-level Apple JWT cache. Not exported. */
62
+ /** Test-only: clear the Apple JWT cache. Not exported. */
63
63
  declare function clearAppleJwtCacheForTests(): void;
64
64
  export declare const __testing: {
65
65
  clearAppleJwtCacheForTests: typeof clearAppleJwtCacheForTests;
@@ -1 +1 @@
1
- {"version":3,"file":"apple.d.ts","sourceRoot":"","sources":["../../src/providers/apple.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,gBAAgB,CAAC;AAUxB,KAAK,WAAW,GAAG,WAAW,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;AA2F5D;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,gBAAgB,UAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAuBpF;AAsBD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAoDlC;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAsB,8BAA8B,CAClD,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,WAAW,EACnB,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAgFpC;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,wBAAwB,EACjC,mBAAmB,UAAQ,GAC1B,OAAO,CAAC;IACT,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gGAAgG;IAChG,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oGAAoG;IACpG,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,2EAA2E;IAC3E,WAAW,EAAE,YAAY,GAAG,SAAS,CAAC;IACtC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,GAAG,IAAI,CAAC,CA2CR;AAgED,uEAAuE;AACvE,iBAAS,0BAA0B,IAAI,IAAI,CAG1C;AAED,eAAO,MAAM,SAAS;;CAAiC,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,uBAAuB,EAC7B,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9B,OAAO,CAAC,IAAI,CAAC,CAgCf;AAgDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,4BAA4B,CAChD,qBAAqB,EAAE,MAAM,EAC7B,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9B,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA8ElC"}
1
+ {"version":3,"file":"apple.d.ts","sourceRoot":"","sources":["../../src/providers/apple.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,gBAAgB,CAAC;AAWxB,KAAK,WAAW,GAAG,WAAW,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;AA2F5D;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,gBAAgB,UAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAuBpF;AAsBD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAoDlC;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAsB,8BAA8B,CAClD,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,WAAW,EACnB,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAgFpC;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,wBAAwB,EACjC,mBAAmB,UAAQ,GAC1B,OAAO,CAAC;IACT,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gGAAgG;IAChG,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oGAAoG;IACpG,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,2EAA2E;IAC3E,WAAW,EAAE,YAAY,GAAG,SAAS,CAAC;IACtC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,GAAG,IAAI,CAAC,CA2CR;AAmED,0DAA0D;AAC1D,iBAAS,0BAA0B,IAAI,IAAI,CAO1C;AAED,eAAO,MAAM,SAAS;;CAAiC,CAAC;AAExD;;;;;;;;;;;GAWG;AACH,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,uBAAuB,EAC7B,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9B,OAAO,CAAC,IAAI,CAAC,CAgCf;AAgDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,4BAA4B,CAChD,qBAAqB,EAAE,MAAM,EAC7B,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9B,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA8ElC"}
@@ -1 +1 @@
1
- {"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/providers/google.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAUtG,KAAK,YAAY,GAAG,WAAW,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC;AAmD9D;;;GAGG;AACH,QAAA,MAAM,wBAAwB;;;;;;;;;;;;;;CAcpB,CAAC;AAEX,KAAK,sBAAsB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,OAAO,wBAAwB,CAAC,CAAC;AA8NvG;;;;;;;GAOG;AACH,wBAAsB,6BAA6B,CACjD,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAgCf;AAED;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAC/C,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CA8Bf;AA+BD;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAqElC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,2EAA2E;IAC3E,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,IAAI,GAAE,YAAY,GAAG,gBAAmC,GACvD,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAqDrC;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG;IAC5E,gBAAgB,EAAE,sBAAsB,CAAC;IACzC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,IAAI,CAuBP;AAED;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACvC,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;IACnB,uEAAuE;IACvE,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,yBAAyB,GACjC,wBAAwB,GAAG,IAAI,CAqBjC;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,OAAO,CAO5F;AAED,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,OAAO,CAK9F;AAED,wBAAgB,2BAA2B,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,OAAO,CAE7F;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,OAAO,CAEjG;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,OAAO,CAE5F;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,OAAO,CAE5F;AAED;;;GAGG;AACH,wBAAgB,wCAAwC,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,OAAO,CAE1G"}
@@ -1,6 +1,7 @@
1
1
  import { Router } from 'express';
2
2
  import type { OneSubServerConfig } from '@onesub/shared';
3
3
  import type { PurchaseStore, SubscriptionStore } from '../store.js';
4
+ import type { WebhookQueue } from '../webhook-queue.js';
4
5
  /**
5
6
  * Admin routes for testing / operational tasks. Require `X-Admin-Secret`
6
7
  * header matching config.adminSecret. If config.adminSecret is not set, the
@@ -27,5 +28,5 @@ import type { PurchaseStore, SubscriptionStore } from '../store.js';
27
28
  * → bundle of every record onesub knows for one user (subs + purchases +
28
29
  * entitlements when configured). Backs the dashboard customer detail page.
29
30
  */
30
- export declare function createAdminRouter(config: OneSubServerConfig, purchaseStore: PurchaseStore, store: SubscriptionStore): Router | null;
31
+ export declare function createAdminRouter(config: OneSubServerConfig, purchaseStore: PurchaseStore, store: SubscriptionStore, webhookQueue?: WebhookQueue): Router | null;
31
32
  //# sourceMappingURL=admin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/routes/admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAGV,kBAAkB,EAKnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAMpE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,kBAAkB,EAC1B,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,iBAAiB,GACvB,MAAM,GAAG,IAAI,CA8Nf"}
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/routes/admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAGV,kBAAkB,EAKnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIxD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,kBAAkB,EAC1B,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,iBAAiB,EACxB,YAAY,CAAC,EAAE,YAAY,GAC1B,MAAM,GAAG,IAAI,CAiQf"}
@@ -0,0 +1,6 @@
1
+ import type { Request, Response } from 'express';
2
+ import type { OneSubServerConfig } from '@onesub/shared';
3
+ import type { SubscriptionStore, PurchaseStore } from '../store.js';
4
+ import type { WebhookEventStore } from '../webhook-events.js';
5
+ export declare function handleAppleWebhook(req: Request, res: Response, config: OneSubServerConfig, store: SubscriptionStore, purchaseStore: PurchaseStore, webhookEventStore?: WebhookEventStore): Promise<void>;
6
+ //# sourceMappingURL=webhook-apple.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-apple.d.ts","sourceRoot":"","sources":["../../src/routes/webhook-apple.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAEV,kBAAkB,EAEnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AASpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAsB9D,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,EAC5B,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,OAAO,CAAC,IAAI,CAAC,CAsIf"}
@@ -0,0 +1,7 @@
1
+ import type { Request, Response } from 'express';
2
+ import type { OneSubServerConfig } from '@onesub/shared';
3
+ import type { SubscriptionStore, PurchaseStore } from '../store.js';
4
+ import type { WebhookEventStore } from '../webhook-events.js';
5
+ export declare function verifyGooglePushToken(req: Request, expectedAudience: string): Promise<boolean>;
6
+ export declare function handleGoogleWebhook(req: Request, res: Response, config: OneSubServerConfig, store: SubscriptionStore, purchaseStore: PurchaseStore, webhookEventStore?: WebhookEventStore): Promise<void>;
7
+ //# sourceMappingURL=webhook-google.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-google.d.ts","sourceRoot":"","sources":["../../src/routes/webhook-google.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAA6B,kBAAkB,EAAoB,MAAM,gBAAgB,CAAC;AAEtG,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAepE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAW9D,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWpG;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,EAC5B,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,OAAO,CAAC,IAAI,CAAC,CAmJf"}
@@ -1,26 +1,14 @@
1
1
  import { Router } from 'express';
2
2
  import type { OneSubServerConfig } from '@onesub/shared';
3
3
  import type { SubscriptionStore, PurchaseStore } from '../store.js';
4
+ import type { WebhookEventStore } from '../webhook-events.js';
4
5
  /**
5
6
  * Retry / durability semantics.
6
7
  *
7
- * Both Apple (App Store Server Notifications V2) and Google (Pub/Sub RTDN)
8
- * retry on any non-2xx response:
9
- * - Apple: up to 5 retries over ~3 days, exponential backoff.
10
- * - Google: per-Pub/Sub-subscription retry policy (default: retries until
11
- * the message is ack'd or the retention window expires, up to 7 days).
12
- *
13
- * This router uses that built-in retry instead of a local dead-letter queue:
14
- *
15
- * 4xx — the payload is unusable (missing signedPayload, bad signature,
16
- * package mismatch). Return 4xx so the sender does NOT retry, since
17
- * the same request would fail again.
18
- * 5xx — a transient failure on our side (DB down, network to Play API).
19
- * Return 5xx so Apple / Google retry the notification for us.
20
- * 2xx — processed, or intentionally ignored (e.g. test notification).
21
- *
22
- * If you need an explicit DLQ, wrap `store.save()` with your own error
23
- * handler before passing the store to `createOneSubMiddleware()`.
8
+ * Both Apple and Google retry on any non-2xx response.
9
+ * 4xx payload is unusable; do NOT retry.
10
+ * 5xx transient failure; source will retry.
11
+ * 2xx processed or intentionally ignored.
24
12
  */
25
- export declare function createWebhookRouter(config: OneSubServerConfig, store: SubscriptionStore, purchaseStore: PurchaseStore): Router;
13
+ export declare function createWebhookRouter(config: OneSubServerConfig, store: SubscriptionStore, purchaseStore: PurchaseStore, webhookEventStore?: WebhookEventStore): Router;
26
14
  //# sourceMappingURL=webhook.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/routes/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAGV,kBAAkB,EAEnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqHpE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,GAC3B,MAAM,CA6XR"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/routes/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAI9D;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,EAC5B,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,MAAM,CAYR"}
@@ -0,0 +1,60 @@
1
+ import type { SubscriptionInfo, PurchaseInfo } from '@onesub/shared';
2
+ import type { SubscriptionStore, PurchaseStore, ListFilteredOptions, ListFilteredResult } from '../store.js';
3
+ import type { CacheAdapter } from '../cache.js';
4
+ import type { WebhookEventStore } from '../webhook-events.js';
5
+ type IORedis = import('ioredis').Redis;
6
+ export declare class RedisSubscriptionStore implements SubscriptionStore {
7
+ private readonly redis;
8
+ constructor(redis: IORedis);
9
+ save(sub: SubscriptionInfo): Promise<void>;
10
+ getByUserId(userId: string): Promise<SubscriptionInfo | null>;
11
+ getAllByUserId(userId: string): Promise<SubscriptionInfo[]>;
12
+ getByTransactionId(txId: string): Promise<SubscriptionInfo | null>;
13
+ listAll(): Promise<SubscriptionInfo[]>;
14
+ listFiltered(opts: ListFilteredOptions): Promise<ListFilteredResult>;
15
+ }
16
+ export declare class RedisPurchaseStore implements PurchaseStore {
17
+ private readonly redis;
18
+ constructor(redis: IORedis);
19
+ savePurchase(purchase: PurchaseInfo): Promise<void>;
20
+ getPurchasesByUserId(userId: string): Promise<PurchaseInfo[]>;
21
+ getPurchaseByTransactionId(txId: string): Promise<PurchaseInfo | null>;
22
+ hasPurchased(userId: string, productId: string): Promise<boolean>;
23
+ reassignPurchase(transactionId: string, newUserId: string): Promise<boolean>;
24
+ deletePurchases(userId: string, productId: string): Promise<number>;
25
+ deletePurchaseByTransactionId(transactionId: string): Promise<boolean>;
26
+ listAll(): Promise<PurchaseInfo[]>;
27
+ }
28
+ /**
29
+ * Redis-backed cache adapter — share JWKS / OAuth tokens across cluster nodes.
30
+ *
31
+ * Implements `CacheAdapter` so it plugs into the same default-cache slot used
32
+ * by the Apple JWT minter and Google OAuth token minter.
33
+ */
34
+ export declare class RedisCacheAdapter implements CacheAdapter {
35
+ private readonly redis;
36
+ private readonly prefix;
37
+ constructor(redis: IORedis, prefix?: string);
38
+ get<T = unknown>(key: string): Promise<T | null>;
39
+ set<T = unknown>(key: string, value: T, ttlSeconds?: number): Promise<void>;
40
+ del(key: string): Promise<void>;
41
+ }
42
+ /**
43
+ * Redis-backed webhook idempotency store.
44
+ *
45
+ * Uses `SET key '1' EX ttl NX` — a single atomic command that sets the key
46
+ * only if it does not already exist. Returns 'OK' (new event) or null
47
+ * (already seen), with no race between a GET and a subsequent SET.
48
+ *
49
+ * Prefer this over `CacheWebhookEventStore(new RedisCacheAdapter(...))` for
50
+ * production deployments; the cache-based variant is non-atomic under
51
+ * concurrent retries.
52
+ */
53
+ export declare class RedisWebhookEventStore implements WebhookEventStore {
54
+ private readonly redis;
55
+ private readonly ttlSeconds;
56
+ constructor(redis: IORedis, ttlSeconds?: number);
57
+ markIfNew(provider: 'apple' | 'google', eventId: string): Promise<boolean>;
58
+ }
59
+ export {};
60
+ //# sourceMappingURL=redis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/stores/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,KAAK,OAAO,GAAG,OAAO,SAAS,EAAE,KAAK,CAAC;AAiDvC,qBAAa,sBAAuB,YAAW,iBAAiB;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,OAAO;IAErC,IAAI,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAa1C,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAS7D,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAQ3D,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAKlE,OAAO,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAQtC,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CA2C3E;AAED,qBAAa,kBAAmB,YAAW,aAAa;IAC1C,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,OAAO;IAErC,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BnD,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAO7D,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAKtE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKjE,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsB5E,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgBnE,6BAA6B,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAetE,OAAO,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;CAMzC;AAED;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,YAAY;IAElD,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,KAAK,EAAE,OAAO,EACd,MAAM,SAAkB;IAGrC,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAKhD,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU3E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGtC;AAKD;;;;;;;;;;GAUG;AACH,qBAAa,sBAAuB,YAAW,iBAAiB;IAE5D,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;gBADV,KAAK,EAAE,OAAO,EACd,UAAU,SAA8B;IAGrD,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAKjF"}
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/stores/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,wBAAwB,QAsB7B,CAAC;AAET,eAAO,MAAM,oBAAoB,QAqBzB,CAAC"}
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"}
@@ -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"}
@@ -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,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"}
package/package.json CHANGED
@@ -1,17 +1,29 @@
1
1
  {
2
2
  "name": "@onesub/server",
3
- "version": "0.11.4",
3
+ "version": "0.12.1",
4
4
  "description": "Server-side receipt validation middleware for react-native-iap. Apple StoreKit 2 + Google Play Billing. One line.",
5
- "main": "dist/index.js",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
8
  "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "sideEffects": false,
8
17
  "files": [
9
18
  "dist",
10
19
  "sql"
11
20
  ],
12
21
  "scripts": {
13
- "build": "tsc",
22
+ "build": "npm run build:types && npm run build:bundle",
23
+ "build:types": "tsc --emitDeclarationOnly",
24
+ "build:bundle": "tsup",
14
25
  "dev": "tsc --watch",
26
+ "size": "size-limit",
15
27
  "start": "node dist/index.js"
16
28
  },
17
29
  "keywords": [
@@ -41,24 +53,41 @@
41
53
  },
42
54
  "license": "MIT",
43
55
  "dependencies": {
44
- "@onesub/shared": "0.7.4",
56
+ "@onesub/shared": "0.7.6",
45
57
  "jose": "^6.2.2",
46
58
  "zod": "^4.3.6"
47
59
  },
48
60
  "peerDependencies": {
61
+ "@opentelemetry/api": ">=1.7.0",
62
+ "bullmq": ">=5.0.0",
49
63
  "express": "^4.17.0 || ^5.0.0",
64
+ "ioredis": ">=5.0.0",
50
65
  "pg": ">=8.0.0"
51
66
  },
52
67
  "peerDependenciesMeta": {
68
+ "@opentelemetry/api": {
69
+ "optional": true
70
+ },
71
+ "bullmq": {
72
+ "optional": true
73
+ },
74
+ "ioredis": {
75
+ "optional": true
76
+ },
53
77
  "pg": {
54
78
  "optional": true
55
79
  }
56
80
  },
57
81
  "devDependencies": {
82
+ "@size-limit/file": "^11.1.6",
58
83
  "@types/express": "^5.0.6",
84
+ "@types/ioredis-mock": "^8.2.5",
59
85
  "@types/node": "^20.17.0",
60
86
  "@types/pg": "^8.20.0",
61
87
  "express": "^5.2.1",
88
+ "ioredis-mock": "^8.9.0",
89
+ "size-limit": "^11.1.6",
90
+ "tsup": "^8.3.5",
62
91
  "typescript": "^5.7.0"
63
92
  },
64
93
  "engines": {
package/sql/schema.sql CHANGED
@@ -32,6 +32,16 @@ CREATE TABLE IF NOT EXISTS onesub_subscriptions (
32
32
  CREATE INDEX IF NOT EXISTS idx_onesub_subscriptions_user_id
33
33
  ON onesub_subscriptions (user_id, updated_at DESC);
34
34
 
35
+ -- Filter-helper indexes for /onesub/admin/subscriptions. Each filter column
36
+ -- is paired with updated_at DESC so the planner can serve "latest matching"
37
+ -- without sorting the full table once row count grows.
38
+ CREATE INDEX IF NOT EXISTS idx_onesub_subscriptions_status_updated
39
+ ON onesub_subscriptions (status, updated_at DESC);
40
+ CREATE INDEX IF NOT EXISTS idx_onesub_subscriptions_platform_updated
41
+ ON onesub_subscriptions (platform, updated_at DESC);
42
+ CREATE INDEX IF NOT EXISTS idx_onesub_subscriptions_product
43
+ ON onesub_subscriptions (product_id, updated_at DESC);
44
+
35
45
  -- Backfill columns for installs that already created the table from an older
36
46
  -- schema. Safe to re-run.
37
47
  ALTER TABLE onesub_subscriptions ADD COLUMN IF NOT EXISTS linked_purchase_token TEXT;