payment-kit 1.29.0 → 1.29.2

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 (312) hide show
  1. package/api/dev.ts +41 -2
  2. package/api/hono.d.ts +42 -0
  3. package/api/node-sqlite.d.ts +12 -0
  4. package/api/src/bootstrap.ts +36 -0
  5. package/api/src/crons/base.ts +3 -3
  6. package/api/src/crons/currency.ts +1 -1
  7. package/api/src/crons/index.ts +27 -24
  8. package/api/src/crons/metering-subscription-detection.ts +1 -1
  9. package/api/src/crons/overdue-detection.ts +2 -2
  10. package/api/src/crons/retry-pending-events.ts +6 -0
  11. package/api/src/index.ts +22 -161
  12. package/api/src/integrations/app-store/client.ts +3 -4
  13. package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
  14. package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
  15. package/api/src/integrations/arcblock/token.ts +21 -7
  16. package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
  17. package/api/src/integrations/google-play/handlers/voided.ts +2 -2
  18. package/api/src/integrations/google-play/verify.ts +3 -2
  19. package/api/src/integrations/iap-reconcile.ts +3 -5
  20. package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
  21. package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
  22. package/api/src/libs/archive/query.ts +19 -0
  23. package/api/src/libs/audit.ts +61 -4
  24. package/api/src/libs/auth.ts +99 -38
  25. package/api/src/libs/context.ts +78 -1
  26. package/api/src/libs/currency.ts +2 -2
  27. package/api/src/libs/dayjs.ts +8 -2
  28. package/api/src/libs/drivers/auth-storage.ts +118 -0
  29. package/api/src/libs/drivers/cron.ts +264 -0
  30. package/api/src/libs/drivers/db.ts +170 -0
  31. package/api/src/libs/drivers/identity.ts +81 -0
  32. package/api/src/libs/drivers/index.ts +40 -0
  33. package/api/src/libs/drivers/locks.ts +226 -0
  34. package/api/src/libs/drivers/migrate-runner.ts +70 -0
  35. package/api/src/libs/drivers/queue.ts +104 -0
  36. package/api/src/libs/drivers/secrets.ts +194 -0
  37. package/api/src/libs/env.ts +170 -54
  38. package/api/src/libs/exchange-rate/service.ts +7 -6
  39. package/api/src/libs/http-fetch-adapter.ts +50 -0
  40. package/api/src/libs/invoice.ts +1 -1
  41. package/api/src/libs/lock.ts +51 -47
  42. package/api/src/libs/logger.ts +48 -8
  43. package/api/src/libs/notification/index.ts +1 -1
  44. package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
  45. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
  46. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
  47. package/api/src/libs/overdraft-protection.ts +1 -1
  48. package/api/src/libs/payout.ts +1 -1
  49. package/api/src/libs/queue/index.ts +259 -52
  50. package/api/src/libs/queue/runtime.ts +175 -0
  51. package/api/src/libs/resource.ts +3 -3
  52. package/api/src/libs/secrets.ts +38 -0
  53. package/api/src/libs/session.ts +3 -2
  54. package/api/src/libs/subscription.ts +5 -5
  55. package/api/src/libs/tenant.ts +92 -0
  56. package/api/src/libs/url.ts +3 -3
  57. package/api/src/libs/util.ts +21 -13
  58. package/api/src/middlewares/hono/cdn.ts +63 -0
  59. package/api/src/middlewares/hono/context.ts +73 -0
  60. package/api/src/middlewares/hono/csrf.ts +72 -0
  61. package/api/src/middlewares/hono/fallback.ts +194 -0
  62. package/api/src/middlewares/hono/pipeline.ts +73 -0
  63. package/api/src/middlewares/hono/resource-mount.ts +42 -0
  64. package/api/src/middlewares/hono/resource.ts +63 -0
  65. package/api/src/middlewares/hono/security.ts +214 -0
  66. package/api/src/middlewares/hono/session.ts +114 -0
  67. package/api/src/middlewares/hono/xss.ts +61 -0
  68. package/api/src/queues/auto-recharge.ts +12 -10
  69. package/api/src/queues/checkout-session.ts +17 -12
  70. package/api/src/queues/credit-consume.ts +40 -36
  71. package/api/src/queues/credit-grant.ts +25 -18
  72. package/api/src/queues/credit-reconciliation.ts +7 -5
  73. package/api/src/queues/discount-status.ts +9 -6
  74. package/api/src/queues/event.ts +12 -4
  75. package/api/src/queues/exchange-rate-health.ts +49 -30
  76. package/api/src/queues/invoice.ts +18 -15
  77. package/api/src/queues/notification.ts +14 -7
  78. package/api/src/queues/payment.ts +41 -28
  79. package/api/src/queues/payout.ts +9 -5
  80. package/api/src/queues/refund.ts +18 -12
  81. package/api/src/queues/subscription.ts +83 -53
  82. package/api/src/queues/token-transfer.ts +15 -10
  83. package/api/src/queues/usage-record.ts +8 -5
  84. package/api/src/queues/vendors/commission.ts +7 -5
  85. package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
  86. package/api/src/queues/vendors/fulfillment.ts +4 -2
  87. package/api/src/queues/vendors/return-processor.ts +5 -3
  88. package/api/src/queues/vendors/return-scanner.ts +5 -4
  89. package/api/src/queues/vendors/status-check.ts +10 -7
  90. package/api/src/queues/webhook.ts +60 -32
  91. package/api/src/routes/connect/shared.ts +1 -2
  92. package/api/src/routes/connect/subscribe.ts +3 -3
  93. package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
  94. package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
  95. package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
  96. package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
  97. package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
  98. package/api/src/routes/hono/credit-tokens.ts +43 -0
  99. package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
  100. package/api/src/routes/{customers.ts → hono/customers.ts} +193 -223
  101. package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
  102. package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
  103. package/api/src/routes/{events.ts → hono/events.ts} +107 -71
  104. package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
  105. package/api/src/routes/hono/exchange-rates.ts +77 -0
  106. package/api/src/routes/hono/index.ts +115 -0
  107. package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
  108. package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
  109. package/api/src/routes/hono/integrations/stripe.ts +74 -0
  110. package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
  111. package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
  112. package/api/src/routes/hono/meters.ts +288 -0
  113. package/api/src/routes/hono/passports.ts +73 -0
  114. package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
  115. package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
  116. package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
  117. package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
  118. package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
  119. package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
  120. package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
  121. package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
  122. package/api/src/routes/{products.ts → hono/products.ts} +172 -159
  123. package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
  124. package/api/src/routes/hono/redirect.ts +24 -0
  125. package/api/src/routes/{refunds.ts → hono/refunds.ts} +96 -80
  126. package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
  127. package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
  128. package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
  129. package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
  130. package/api/src/routes/hono/tool.ts +69 -0
  131. package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
  132. package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
  133. package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
  134. package/api/src/routes/hono/webhook-endpoints.ts +126 -0
  135. package/api/src/service.ts +667 -0
  136. package/api/src/store/migrations/20230911-seeding.ts +2 -1
  137. package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
  138. package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
  139. package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
  140. package/api/src/store/models/auto-recharge-config.ts +22 -10
  141. package/api/src/store/models/checkout-session.ts +15 -14
  142. package/api/src/store/models/coupon.ts +29 -20
  143. package/api/src/store/models/credit-grant.ts +38 -29
  144. package/api/src/store/models/credit-transaction.ts +32 -21
  145. package/api/src/store/models/customer.ts +19 -17
  146. package/api/src/store/models/discount.ts +11 -2
  147. package/api/src/store/models/entitlement-grant.ts +21 -9
  148. package/api/src/store/models/entitlement-product.ts +21 -9
  149. package/api/src/store/models/entitlement.ts +19 -10
  150. package/api/src/store/models/event.ts +18 -9
  151. package/api/src/store/models/exchange-rate-provider.ts +17 -4
  152. package/api/src/store/models/invoice-item.ts +18 -9
  153. package/api/src/store/models/invoice.ts +16 -8
  154. package/api/src/store/models/meter-event.ts +27 -9
  155. package/api/src/store/models/meter.ts +31 -22
  156. package/api/src/store/models/payment-currency.ts +25 -8
  157. package/api/src/store/models/payment-intent.ts +15 -6
  158. package/api/src/store/models/payment-link.ts +15 -6
  159. package/api/src/store/models/payment-method.ts +38 -22
  160. package/api/src/store/models/payment-stat.ts +18 -9
  161. package/api/src/store/models/payout.ts +15 -6
  162. package/api/src/store/models/price-quote.ts +17 -8
  163. package/api/src/store/models/price.ts +24 -12
  164. package/api/src/store/models/pricing-table.ts +29 -20
  165. package/api/src/store/models/product-vendor.ts +20 -10
  166. package/api/src/store/models/product.ts +15 -6
  167. package/api/src/store/models/promotion-code.ts +14 -6
  168. package/api/src/store/models/refund.ts +15 -6
  169. package/api/src/store/models/revenue-snapshot.ts +21 -9
  170. package/api/src/store/models/setting.ts +18 -9
  171. package/api/src/store/models/setup-intent.ts +36 -27
  172. package/api/src/store/models/subscription-item.ts +21 -9
  173. package/api/src/store/models/subscription-schedule.ts +21 -9
  174. package/api/src/store/models/subscription.ts +21 -10
  175. package/api/src/store/models/tax-rate.ts +29 -21
  176. package/api/src/store/models/usage-record.ts +11 -2
  177. package/api/src/store/models/webhook-attempt.ts +18 -9
  178. package/api/src/store/models/webhook-endpoint.ts +18 -9
  179. package/api/src/store/scoped-core.ts +55 -0
  180. package/api/src/store/scoped.ts +247 -0
  181. package/api/src/store/sequelize.ts +66 -22
  182. package/api/src/store/sql-migrations.ts +20 -0
  183. package/api/src/store/tenant-backfill.ts +260 -0
  184. package/api/src/store/tenant-model.ts +124 -0
  185. package/api/src/store/tenant-tables.ts +50 -0
  186. package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
  187. package/api/tests/fixtures/bare-query-violation.ts +13 -0
  188. package/api/tests/fixtures/core-env-violation.ts +10 -0
  189. package/api/tests/fixtures/host-read-violation.ts +19 -0
  190. package/api/tests/fixtures/tenants.ts +4 -0
  191. package/api/tests/integrations/iap-tenant.spec.ts +284 -0
  192. package/api/tests/libs/archive-query.spec.ts +26 -0
  193. package/api/tests/libs/audit-tenant.spec.ts +153 -0
  194. package/api/tests/libs/context.spec.ts +204 -0
  195. package/api/tests/libs/core-config.spec.ts +115 -0
  196. package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
  197. package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
  198. package/api/tests/libs/lock-tenant.spec.ts +66 -0
  199. package/api/tests/libs/scoped.spec.ts +222 -0
  200. package/api/tests/libs/secrets-facade.spec.ts +52 -0
  201. package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
  202. package/api/tests/libs/tenant-middleware.spec.ts +42 -0
  203. package/api/tests/libs/tenant-scanner.spec.ts +120 -0
  204. package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
  205. package/api/tests/middlewares/hono/context.spec.ts +113 -0
  206. package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
  207. package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
  208. package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
  209. package/api/tests/middlewares/hono/security.spec.ts +181 -0
  210. package/api/tests/middlewares/hono/session.spec.ts +42 -0
  211. package/api/tests/middlewares/hono/xss.spec.ts +81 -0
  212. package/api/tests/models/tenant-backfill.spec.ts +287 -0
  213. package/api/tests/models/tenant-columns-model.spec.ts +46 -0
  214. package/api/tests/models/tenant-columns.spec.ts +161 -0
  215. package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
  216. package/api/tests/queues/credit-consume.spec.ts +8 -1
  217. package/api/tests/queues/event-tenant.spec.ts +236 -0
  218. package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
  219. package/api/tests/queues/queue-parity.spec.ts +249 -0
  220. package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
  221. package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
  222. package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
  223. package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
  224. package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
  225. package/api/tests/service/collapse.spec.ts +96 -0
  226. package/api/tests/store/tenant-crosscut.spec.ts +202 -0
  227. package/api/tests/store/tenant-model-spike.spec.ts +177 -0
  228. package/api/tests/store/tenant-model.spec.ts +162 -0
  229. package/api/tests/store/tenant-residual.spec.ts +196 -0
  230. package/api/third.d.ts +4 -0
  231. package/blocklet.yml +1 -1
  232. package/cloudflare/README.md +26 -6
  233. package/cloudflare/build.ts +28 -13
  234. package/cloudflare/did-connect-auth.ts +0 -217
  235. package/cloudflare/docs/2026-06-10-bundle-size-analysis.md +288 -0
  236. package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
  237. package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
  238. package/cloudflare/migrations/0008_schema_parity.sql +16 -0
  239. package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
  240. package/cloudflare/queue-runtime-mode.ts +13 -0
  241. package/cloudflare/run-build.js +31 -56
  242. package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
  243. package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
  244. package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
  245. package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
  246. package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
  247. package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
  248. package/cloudflare/shims/blocklet-sdk/util-csrf.ts +13 -0
  249. package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
  250. package/cloudflare/shims/cron.ts +38 -158
  251. package/cloudflare/shims/events.ts +124 -0
  252. package/cloudflare/shims/fastq.ts +15 -1
  253. package/cloudflare/shims/nedb-storage.ts +16 -8
  254. package/cloudflare/shims/node-fetch.ts +35 -0
  255. package/cloudflare/shims/xss.ts +8 -0
  256. package/cloudflare/tenant-middleware.ts +36 -0
  257. package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
  258. package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
  259. package/cloudflare/worker.ts +204 -433
  260. package/cloudflare/wrangler.local-e2e.jsonc +26 -0
  261. package/jest.config.js +3 -1
  262. package/package.json +33 -38
  263. package/scripts/core-env-whitelist.json +1 -0
  264. package/scripts/e2e-12b-runtime.ts +149 -0
  265. package/scripts/e2e-core-config.ts +125 -0
  266. package/scripts/e2e-d1-tenancy.ts +116 -0
  267. package/scripts/e2e-d2-cron-queue.ts +139 -0
  268. package/scripts/e2e-d3-embedded-multi.ts +171 -0
  269. package/scripts/e2e-hono-s2.ts +125 -0
  270. package/scripts/e2e-hono-s3e.ts +135 -0
  271. package/scripts/e2e-hono-s4.ts +114 -0
  272. package/scripts/e2e-migration-contract.ts +100 -0
  273. package/scripts/e2e-s0.ts +61 -0
  274. package/scripts/e2e-s1.ts +107 -0
  275. package/scripts/e2e-s2.ts +178 -0
  276. package/scripts/e2e-s3.ts +110 -0
  277. package/scripts/e2e-s4.ts +191 -0
  278. package/scripts/e2e-s5.ts +139 -0
  279. package/scripts/e2e-s6.ts +127 -0
  280. package/scripts/e2e-tenant-model.ts +119 -0
  281. package/scripts/e2e-tenant-worker.ts +199 -0
  282. package/scripts/gen-sql-migrations.js +46 -0
  283. package/scripts/phase8-codemod.js +219 -0
  284. package/scripts/phase9a-env-getters-codemod.js +82 -0
  285. package/scripts/scan-core-env.js +109 -0
  286. package/scripts/scan-tenant-queries.js +235 -0
  287. package/scripts/schema-drift-guard.ts +210 -0
  288. package/scripts/tenant-scan-whitelist.json +1 -0
  289. package/src/env.d.ts +13 -1
  290. package/tsconfig.json +1 -1
  291. package/api/src/libs/did-space.ts +0 -235
  292. package/api/src/libs/middleware.ts +0 -50
  293. package/api/src/libs/security.ts +0 -192
  294. package/api/src/queues/space.ts +0 -662
  295. package/api/src/routes/credit-tokens.ts +0 -38
  296. package/api/src/routes/exchange-rates.ts +0 -87
  297. package/api/src/routes/index.ts +0 -142
  298. package/api/src/routes/integrations/stripe.ts +0 -61
  299. package/api/src/routes/meters.ts +0 -274
  300. package/api/src/routes/passports.ts +0 -68
  301. package/api/src/routes/redirect.ts +0 -20
  302. package/api/src/routes/tool.ts +0 -65
  303. package/api/src/routes/webhook-endpoints.ts +0 -126
  304. package/api/tests/routes/credit-grants.spec.ts +0 -1261
  305. package/cloudflare/shims/did-space-js.ts +0 -17
  306. package/cloudflare/shims/did-space.ts +0 -11
  307. package/cloudflare/shims/express-compat/index.ts +0 -80
  308. package/cloudflare/shims/express-compat/types.ts +0 -41
  309. package/cloudflare/shims/lock.ts +0 -115
  310. package/cloudflare/shims/queue.ts +0 -611
  311. package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
  312. package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
@@ -0,0 +1,124 @@
1
+ // EventEmitter shim for CF Workers.
2
+ //
3
+ // workerd's nodejs_compat provides `node:events` for ESM `import from 'events'`,
4
+ // but the esbuild banner's globalThis.require polyfill (build.ts) does NOT list
5
+ // `events`, so CJS `const { EventEmitter } = require('events')` resolves to an
6
+ // empty object and `class X extends EventEmitter` throws at global init
7
+ // ("Class extends value undefined"). Two core drivers hit this:
8
+ // - api/src/libs/drivers/locks.ts (MemoryLock)
9
+ // - api/src/libs/drivers/auth-storage.ts (DbAuthStorage extends EventEmitter)
10
+ //
11
+ // Aliasing `events` to this shim (build.ts) makes BOTH the CJS-require and
12
+ // ESM-import forms resolve to one deterministic implementation, removing the
13
+ // fragile split between nodejs_compat (ESM) and the banner (CJS). Only the core
14
+ // queue/lock/auth-storage event buses use it — emit / on / once / removeListener
15
+ // are the methods exercised (verified by grep), so a compact but correct
16
+ // implementation suffices.
17
+
18
+ type Listener = (...args: any[]) => void;
19
+
20
+ export class EventEmitter {
21
+ private _events: Map<string | symbol, Listener[]> = new Map();
22
+
23
+ private _maxListeners = 10;
24
+
25
+ addListener(event: string | symbol, listener: Listener): this {
26
+ return this.on(event, listener);
27
+ }
28
+
29
+ on(event: string | symbol, listener: Listener): this {
30
+ const list = this._events.get(event);
31
+ if (list) {
32
+ list.push(listener);
33
+ } else {
34
+ this._events.set(event, [listener]);
35
+ }
36
+ return this;
37
+ }
38
+
39
+ prependListener(event: string | symbol, listener: Listener): this {
40
+ const list = this._events.get(event);
41
+ if (list) {
42
+ list.unshift(listener);
43
+ } else {
44
+ this._events.set(event, [listener]);
45
+ }
46
+ return this;
47
+ }
48
+
49
+ once(event: string | symbol, listener: Listener): this {
50
+ const wrapper = (...args: any[]) => {
51
+ this.removeListener(event, wrapper);
52
+ listener.apply(this, args);
53
+ };
54
+ // keep a handle to the original so removeListener(event, listener) still works
55
+ (wrapper as any).listener = listener;
56
+ return this.on(event, wrapper);
57
+ }
58
+
59
+ prependOnceListener(event: string | symbol, listener: Listener): this {
60
+ const wrapper = (...args: any[]) => {
61
+ this.removeListener(event, wrapper);
62
+ listener.apply(this, args);
63
+ };
64
+ (wrapper as any).listener = listener;
65
+ return this.prependListener(event, wrapper);
66
+ }
67
+
68
+ removeListener(event: string | symbol, listener: Listener): this {
69
+ const list = this._events.get(event);
70
+ if (!list) return this;
71
+ const idx = list.findIndex((l) => l === listener || (l as any).listener === listener);
72
+ if (idx >= 0) {
73
+ list.splice(idx, 1);
74
+ if (list.length === 0) this._events.delete(event);
75
+ }
76
+ return this;
77
+ }
78
+
79
+ off(event: string | symbol, listener: Listener): this {
80
+ return this.removeListener(event, listener);
81
+ }
82
+
83
+ removeAllListeners(event?: string | symbol): this {
84
+ if (event === undefined) {
85
+ this._events.clear();
86
+ } else {
87
+ this._events.delete(event);
88
+ }
89
+ return this;
90
+ }
91
+
92
+ emit(event: string | symbol, ...args: any[]): boolean {
93
+ const list = this._events.get(event);
94
+ if (!list || list.length === 0) return false;
95
+ // copy so once()-removals during iteration don't skip listeners
96
+ for (const listener of [...list]) {
97
+ listener.apply(this, args);
98
+ }
99
+ return true;
100
+ }
101
+
102
+ listeners(event: string | symbol): Listener[] {
103
+ return [...(this._events.get(event) || [])];
104
+ }
105
+
106
+ listenerCount(event: string | symbol): number {
107
+ return this._events.get(event)?.length || 0;
108
+ }
109
+
110
+ eventNames(): (string | symbol)[] {
111
+ return [...this._events.keys()];
112
+ }
113
+
114
+ setMaxListeners(n: number): this {
115
+ this._maxListeners = n;
116
+ return this;
117
+ }
118
+
119
+ getMaxListeners(): number {
120
+ return this._maxListeners;
121
+ }
122
+ }
123
+
124
+ export default EventEmitter;
@@ -12,7 +12,21 @@
12
12
 
13
13
  type Task = { data: any; cb?: Function };
14
14
 
15
- export default function fastq(_context: any, worker: Function, _concurrency: number) {
15
+ // Phase 9 (W2-1b): faithful fastq executor driver for the worker.
16
+ //
17
+ // Match the real fastq calling convention. The Node queue engine
18
+ // (api/src/libs/queue) calls `fastq(workerFn, concurrency)` (2-arg form);
19
+ // real fastq detects a function first arg and shifts (context => null). The
20
+ // previous shim assumed the 3-arg `fastq(context, worker, concurrency)` form,
21
+ // so the 2-arg call landed `worker` on `context` and every job execution threw
22
+ // "worker is not a function". This shift makes the shim a drop-in executor so
23
+ // the SAME engine runs identically on Node (real fastq) and the worker (shim).
24
+ export default function fastq(context: any, worker: Function, concurrency: number) {
25
+ if (typeof context === 'function') {
26
+ concurrency = worker as unknown as number;
27
+ worker = context;
28
+ context = null;
29
+ }
16
30
  const pending: Task[] = [];
17
31
  let running = false;
18
32
  let drainFn: (() => void) | null = null;
@@ -1,9 +1,17 @@
1
- // NeDB → D1 storage shim for DID Connect sessions
2
- export default class AuthStorage {
3
- constructor(_opts?: any) {}
4
- create(_id: string, _data: any) { return Promise.resolve(); }
5
- read(_id: string) { return Promise.resolve(null); }
6
- update(_id: string, _data: any) { return Promise.resolve(); }
7
- delete(_id: string) { return Promise.resolve(); }
8
- on(_event: string, _fn: Function) {}
1
+ // NeDB → D1 storage shim for DID Connect sessions (Phase 8, W2-1a).
2
+ //
3
+ // Was a no-op stub (DID Connect state silently dropped in the worker). Now a
4
+ // thin worker adapter: it builds a D1 db driver from the bound D1 database and
5
+ // delegates to the real, contract-tested DbAuthStorage. The `{ dbPath }` option
6
+ // is ignored — persistence is the D1 binding, not a disk file.
7
+
8
+ import { createD1DbDriver, DbAuthStorage } from '../../api/src/libs/drivers';
9
+
10
+ import { getDB } from './sequelize-d1/model';
11
+
12
+ export default class AuthStorage extends DbAuthStorage {
13
+ constructor(_opts?: any) {
14
+ // lazy getter — the D1 binding is set per request, not at module import
15
+ super(createD1DbDriver(() => getDB()));
16
+ }
9
17
  }
@@ -0,0 +1,35 @@
1
+ // node-fetch shim for CF Workers — forwards to the runtime's native fetch.
2
+ // node-fetch is pulled in transitively by @apple/app-store-server-library (IAP
3
+ // JWS verification). On Node it drags in a polyfill chain that is dead weight on
4
+ // Workers (which has native fetch/URL/TextDecoder):
5
+ // encoding (iconv-lite CJK code tables) 481KB + tr46 (IDNA map) 259KB
6
+ // + whatwg-url 22KB + node-fetch 19KB ≈ 781KB, never executed at runtime.
7
+ // Forwarding node-fetch → native fetch drops that whole chain (~754KB raw).
8
+ // Only implements the node-fetch v2 export surface actually referenced.
9
+
10
+ export default globalThis.fetch.bind(globalThis);
11
+
12
+ export const Headers = globalThis.Headers;
13
+ export const Request = globalThis.Request;
14
+ export const Response = globalThis.Response;
15
+ export const FormData = globalThis.FormData;
16
+ export const Blob = globalThis.Blob;
17
+
18
+ export class FetchError extends Error {
19
+ type: string;
20
+ constructor(message: string, type = 'system') {
21
+ super(message);
22
+ this.name = 'FetchError';
23
+ this.type = type;
24
+ }
25
+ }
26
+
27
+ export class AbortError extends Error {
28
+ type = 'aborted';
29
+ constructor(message: string) {
30
+ super(message);
31
+ this.name = 'AbortError';
32
+ }
33
+ }
34
+
35
+ export const isRedirect = (code: number) => [301, 302, 303, 307, 308].includes(code);
@@ -1,3 +1,11 @@
1
1
  export function xss(_opts?: any) {
2
2
  return (_req: any, _res: any, next: any) => next();
3
3
  }
4
+
5
+ // Phase 4 (express→hono): the hono xss middleware (LITE app-shell on CF) calls
6
+ // initSanitize(...)(body) to produce sanitizedBody. The old express-compat worker
7
+ // path set req.body WITHOUT xss sanitization, so an identity sanitizer preserves
8
+ // the worker's exact behavior (the node host uses the real @blocklet/xss).
9
+ export function initSanitize(_opts?: any) {
10
+ return <T>(body: T): T => body;
11
+ }
@@ -0,0 +1,36 @@
1
+ // Phase 7 (W1′): worker tenant-context wiring.
2
+ //
3
+ // The CF worker's `*` middleware only set up DB/env — it never established a
4
+ // tenant context, so every TenantModel query in the worker fell back to the
5
+ // single-mode default. This Hono middleware mirrors the Express
6
+ // `contextMiddleware`: it resolves the request's raw Host to a tenant via the
7
+ // SINGLE-POINT resolver (`resolveTenantForHost`) and wraps the downstream chain
8
+ // in `context.withTenant` so the resolved tenant flows through AsyncLocalStorage
9
+ // into every handler/query.
10
+ //
11
+ // single mode: resolves to the deployment app DID (standalone worker unchanged).
12
+ // multi mode : unknown/missing Host -> 400 fail-closed, no default-tenant
13
+ // fallback, and the handler never runs.
14
+
15
+ import type { Context, Next } from 'hono';
16
+
17
+ import { context } from '../api/src/libs/context';
18
+ import { TenantError, TENANT_HOST_UNRESOLVED } from '../api/src/libs/tenant';
19
+ import { resolveTenantForHost } from '../api/src/libs/drivers/identity';
20
+
21
+ export function tenantMiddleware() {
22
+ return async (c: Context, next: Next) => {
23
+ let instanceDid: string;
24
+ try {
25
+ // raw Host header only — never a proxy header (X-Forwarded-Host); the CF
26
+ // route is responsible for ensuring it cannot be forged by the client.
27
+ instanceDid = await resolveTenantForHost(c.req.header('host'));
28
+ } catch (err) {
29
+ if (err instanceof TenantError && err.code === TENANT_HOST_UNRESOLVED) {
30
+ return c.json({ error: { code: err.code, message: err.message } }, 400);
31
+ }
32
+ throw err;
33
+ }
34
+ return context.withTenant(instanceDid, () => next());
35
+ };
36
+ }
@@ -0,0 +1,160 @@
1
+ // Phase 7 (W1′): worker `withTenant` wiring. The CF worker had no tenant
2
+ // context — its `*` middleware only `setDB`. This drives the real Hono tenant
3
+ // middleware (the same factory mounted in worker.ts) over a tiny Hono app so
4
+ // the actual Host -> instanceDid resolution, multi-mode fail-closed 4xx, and
5
+ // `context.withTenant` ALS wrapping are exercised end to end.
6
+
7
+ import { Hono } from 'hono';
8
+
9
+ import { tenantMiddleware } from '../tenant-middleware';
10
+ import { getInstanceDid, context } from '../../api/src/libs/context';
11
+ import { getDefaultInstanceDid } from '../../api/src/libs/tenant';
12
+ import { setIdentityDriver, createDefaultIdentityDriver, type IdentityDriver } from '../../api/src/libs/drivers/identity';
13
+
14
+ const TENANT_A = 'did:abt:zHOSTA';
15
+ const TENANT_B = 'did:abt:zHOSTB';
16
+
17
+ // Build a Hono app wired exactly like worker.ts: tenant middleware on /api/*,
18
+ // then a handler that reports the tenant resolved from the ALS context.
19
+ function buildApp() {
20
+ const app = new Hono();
21
+ app.use('/api/*', tenantMiddleware());
22
+ app.get('/api/whoami', (c) => c.json({ tenant: getInstanceDid() }));
23
+ // /health is OUTSIDE /api/* — must never require a tenant (infra health check)
24
+ app.get('/health', (c) => c.json({ status: 'ok' }));
25
+ return app;
26
+ }
27
+
28
+ async function get(app: Hono, path: string, headers: Record<string, string> = {}) {
29
+ const res = await app.request(path, { headers });
30
+ let body: any;
31
+ try {
32
+ body = await res.json();
33
+ } catch {
34
+ body = await res.text();
35
+ }
36
+ return { status: res.status, body };
37
+ }
38
+
39
+ const ORIGINAL_MODE = process.env.PAYMENT_TENANT_MODE;
40
+
41
+ afterEach(() => {
42
+ if (ORIGINAL_MODE === undefined) delete process.env.PAYMENT_TENANT_MODE;
43
+ else process.env.PAYMENT_TENANT_MODE = ORIGINAL_MODE;
44
+ setIdentityDriver(createDefaultIdentityDriver());
45
+ });
46
+
47
+ describe('single mode (standalone worker legacy) — unchanged', () => {
48
+ beforeEach(() => {
49
+ process.env.PAYMENT_TENANT_MODE = 'single';
50
+ });
51
+
52
+ it('any host resolves to the deployment app DID', async () => {
53
+ const expected = getDefaultInstanceDid();
54
+ const app = buildApp();
55
+ const r1 = await get(app, '/api/whoami', { Host: 'anything.example.com' });
56
+ expect(r1.status).toBe(200);
57
+ expect(r1.body.tenant).toBe(expected);
58
+ const r2 = await get(app, '/api/whoami', { Host: 'other.example.com' });
59
+ expect(r2.body.tenant).toBe(expected); // default regardless of host
60
+ });
61
+
62
+ it('a request with no Host still resolves (single-mode default, no crash)', async () => {
63
+ const expected = getDefaultInstanceDid();
64
+ const app = buildApp();
65
+ const r = await get(app, '/api/whoami');
66
+ expect(r.status).toBe(200);
67
+ expect(r.body.tenant).toBe(expected);
68
+ });
69
+ });
70
+
71
+ describe('multi mode — Host maps to tenant, fail-closed on unknown', () => {
72
+ const identity: IdentityDriver = {
73
+ resolveInstanceDidForHost(host) {
74
+ if (host === 'a.example.com') return TENANT_A;
75
+ if (host === 'b.example.com') return TENANT_B;
76
+ return null;
77
+ },
78
+ };
79
+
80
+ beforeEach(() => {
81
+ process.env.PAYMENT_TENANT_MODE = 'multi';
82
+ setIdentityDriver(identity);
83
+ });
84
+
85
+ // Happy path — two hosts see two tenants (data isolation)
86
+ it('two hosts resolve to two tenants', async () => {
87
+ const app = buildApp();
88
+ const a = await get(app, '/api/whoami', { Host: 'a.example.com' });
89
+ const b = await get(app, '/api/whoami', { Host: 'b.example.com' });
90
+ expect(a.body.tenant).toBe(TENANT_A);
91
+ expect(b.body.tenant).toBe(TENANT_B);
92
+ expect(a.body.tenant).not.toBe(b.body.tenant);
93
+ });
94
+
95
+ // Bad input — missing Host fails closed (no default-tenant fallback)
96
+ it('missing Host fails closed with 4xx (no APP_PID fallback)', async () => {
97
+ const app = buildApp();
98
+ const r = await get(app, '/api/whoami');
99
+ expect(r.status).toBe(400);
100
+ expect(r.body.error.code).toBe('TENANT_HOST_UNRESOLVED');
101
+ expect(r.body.tenant).toBeUndefined();
102
+ });
103
+
104
+ // Data leak — unknown (unregistered) host must never fall into a tenant
105
+ it('unknown host fails closed with 4xx + error code', async () => {
106
+ const app = buildApp();
107
+ const r = await get(app, '/api/whoami', { Host: 'unknown.example.com' });
108
+ expect(r.status).toBe(400);
109
+ expect(r.body.error.code).toBe('TENANT_HOST_UNRESOLVED');
110
+ expect(r.body.tenant).toBeUndefined();
111
+ });
112
+
113
+ // Security — a forged X-Forwarded-Host must not change resolution (raw Host only)
114
+ it('SECURITY: X-Forwarded-Host cannot override the raw Host', async () => {
115
+ const app = buildApp();
116
+ const r = await get(app, '/api/whoami', { Host: 'a.example.com', 'X-Forwarded-Host': 'b.example.com' });
117
+ expect(r.body.tenant).toBe(TENANT_A);
118
+ });
119
+
120
+ it('SECURITY: X-Forwarded-Host pointing at a known tenant cannot rescue an unknown raw Host', async () => {
121
+ const app = buildApp();
122
+ const r = await get(app, '/api/whoami', { Host: 'unknown.example.com', 'X-Forwarded-Host': 'a.example.com' });
123
+ expect(r.status).toBe(400);
124
+ });
125
+
126
+ // Data loss — a 4xx-rejected request must never reach the route handler
127
+ it('a 4xx-rejected request never runs the handler (no write)', async () => {
128
+ const app = new Hono();
129
+ app.use('/api/*', tenantMiddleware());
130
+ let handlerRuns = 0;
131
+ app.get('/api/whoami', (c) => {
132
+ handlerRuns += 1;
133
+ return c.json({ tenant: getInstanceDid() });
134
+ });
135
+ const r = await get(app, '/api/whoami', { Host: 'unknown.example.com' });
136
+ expect(r.status).toBe(400);
137
+ expect(handlerRuns).toBe(0);
138
+ });
139
+
140
+ // ALS propagation — the handler runs INSIDE the withTenant context
141
+ it('the handler observes the resolved tenant via context ALS', async () => {
142
+ const app = new Hono();
143
+ app.use('/api/*', tenantMiddleware());
144
+ let seen: string | undefined;
145
+ app.get('/api/whoami', (c) => {
146
+ seen = context.getInstanceDid();
147
+ return c.json({ ok: true });
148
+ });
149
+ await get(app, '/api/whoami', { Host: 'b.example.com' });
150
+ expect(seen).toBe(TENANT_B);
151
+ });
152
+
153
+ // /health is outside /api/* — tenant middleware must not gate it
154
+ it('/health is reachable without a Host even in multi mode', async () => {
155
+ const app = buildApp();
156
+ const r = await get(app, '/health');
157
+ expect(r.status).toBe(200);
158
+ expect(r.body.status).toBe('ok');
159
+ });
160
+ });
@@ -0,0 +1,44 @@
1
+ // Phase 12c HARD GATE (scan): the CF worker is a host adapter. It must mount the
2
+ // embedded service's resource-route surface (svc.http.resourceRoutes) and NEVER
3
+ // access svc.handler — whose lazy getter builds the node-only Express app shell
4
+ // that does not belong under workerd. It also must not import the deleted legacy
5
+ // shims/queue.ts duplicate engine. This statically scans the worker source so a
6
+ // regression fails the suite (the runtime Proxy in ensurePaymentService is the
7
+ // second line of defense).
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+
11
+ const workerSrc = fs.readFileSync(path.join(__dirname, '../worker.ts'), 'utf8');
12
+
13
+ describe('Phase 12c — worker host-adapter hard gate', () => {
14
+ it('mounts the resource-route surface (svc.http.resourceRoutes)', () => {
15
+ expect(workerSrc).toMatch(/service\.http\.resourceRoutes/);
16
+ });
17
+
18
+ it('never reads .handler on the payment service', () => {
19
+ const offending = workerSrc
20
+ .split('\n')
21
+ .map((line, n) => ({ line: line.trim(), n: n + 1 }))
22
+ // ignore comment lines — only actual code may not read svc.handler
23
+ .filter(({ line }) => !line.startsWith('//') && !line.startsWith('*') && !line.startsWith('/*'))
24
+ .filter(({ line }) => /\b(service|paymentService|svc)\.handler\b/.test(line))
25
+ // the hard-gate trap string + the Proxy guard line are the gate itself
26
+ .filter(({ line }) => !/forbidden in the CF worker/.test(line) && !/prop === 'handler'/.test(line));
27
+ expect(offending).toEqual([]);
28
+ });
29
+
30
+ it('installs the runtime hard-gate Proxy that throws on handler access', () => {
31
+ expect(workerSrc).toMatch(/new Proxy\(svc,/);
32
+ expect(workerSrc).toMatch(/svc\.handler is forbidden in the CF worker/);
33
+ });
34
+
35
+ it('does not import the deleted legacy shims/queue engine', () => {
36
+ expect(workerSrc).not.toMatch(/from '\.\/shims\/queue'/);
37
+ });
38
+
39
+ it('drives the core queue runtime surface instead', () => {
40
+ expect(workerSrc).toMatch(/from '\.\.\/api\/src\/libs\/queue\/runtime'/);
41
+ expect(workerSrc).toMatch(/dispatchDueJobs\(\)/);
42
+ expect(workerSrc).toMatch(/flushQueueWork\(\)/);
43
+ });
44
+ });