payment-kit 1.29.1 → 1.29.3

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 (343) 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 +47 -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 +41 -37
  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/crons/tenant-fanout.ts +82 -0
  12. package/api/src/host-node/did-connect-runtime-node.ts +33 -0
  13. package/api/src/host-node/serve-static-arc.ts +68 -0
  14. package/api/src/host-node/serve-static.ts +41 -0
  15. package/api/src/index.ts +22 -161
  16. package/api/src/integrations/app-store/client.ts +3 -4
  17. package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
  18. package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
  19. package/api/src/integrations/arcblock/token.ts +21 -7
  20. package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
  21. package/api/src/integrations/google-play/handlers/voided.ts +2 -2
  22. package/api/src/integrations/google-play/verify.ts +3 -2
  23. package/api/src/integrations/iap-reconcile.ts +3 -5
  24. package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
  25. package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
  26. package/api/src/libs/archive/query.ts +19 -0
  27. package/api/src/libs/audit.ts +61 -4
  28. package/api/src/libs/auth.ts +247 -47
  29. package/api/src/libs/context.ts +89 -1
  30. package/api/src/libs/currency.ts +2 -2
  31. package/api/src/libs/dayjs.ts +8 -2
  32. package/api/src/libs/did-connect/runtime-did-connect-js.ts +88 -0
  33. package/api/src/libs/did-connect/tenant-identity.ts +221 -0
  34. package/api/src/libs/drivers/auth-storage.ts +118 -0
  35. package/api/src/libs/drivers/cron.ts +264 -0
  36. package/api/src/libs/drivers/db.ts +170 -0
  37. package/api/src/libs/drivers/identity.ts +142 -0
  38. package/api/src/libs/drivers/index.ts +40 -0
  39. package/api/src/libs/drivers/locks.ts +226 -0
  40. package/api/src/libs/drivers/migrate-runner.ts +70 -0
  41. package/api/src/libs/drivers/queue.ts +104 -0
  42. package/api/src/libs/drivers/secrets.ts +194 -0
  43. package/api/src/libs/env.ts +170 -54
  44. package/api/src/libs/exchange-rate/service.ts +7 -6
  45. package/api/src/libs/http-fetch-adapter.ts +60 -0
  46. package/api/src/libs/invoice.ts +1 -1
  47. package/api/src/libs/lock.ts +51 -47
  48. package/api/src/libs/logger.ts +48 -8
  49. package/api/src/libs/notification/index.ts +1 -1
  50. package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
  51. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
  52. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
  53. package/api/src/libs/overdraft-protection.ts +1 -1
  54. package/api/src/libs/payout.ts +1 -1
  55. package/api/src/libs/queue/index.ts +271 -52
  56. package/api/src/libs/queue/runtime.ts +175 -0
  57. package/api/src/libs/resource.ts +3 -3
  58. package/api/src/libs/secrets.ts +38 -0
  59. package/api/src/libs/session.ts +3 -2
  60. package/api/src/libs/subscription.ts +5 -5
  61. package/api/src/libs/tenant.ts +92 -0
  62. package/api/src/libs/url.ts +3 -3
  63. package/api/src/libs/util.ts +21 -13
  64. package/api/src/middlewares/hono/cdn.ts +63 -0
  65. package/api/src/middlewares/hono/context.ts +80 -0
  66. package/api/src/middlewares/hono/csrf.ts +83 -0
  67. package/api/src/middlewares/hono/fallback.ts +194 -0
  68. package/api/src/middlewares/hono/pipeline.ts +73 -0
  69. package/api/src/middlewares/hono/resource-mount.ts +42 -0
  70. package/api/src/middlewares/hono/resource.ts +63 -0
  71. package/api/src/middlewares/hono/security.ts +209 -0
  72. package/api/src/middlewares/hono/session.ts +114 -0
  73. package/api/src/middlewares/hono/xss.ts +61 -0
  74. package/api/src/queues/auto-recharge.ts +12 -10
  75. package/api/src/queues/checkout-session.ts +38 -21
  76. package/api/src/queues/credit-consume.ts +40 -36
  77. package/api/src/queues/credit-grant.ts +25 -18
  78. package/api/src/queues/credit-reconciliation.ts +7 -5
  79. package/api/src/queues/discount-status.ts +9 -6
  80. package/api/src/queues/event.ts +41 -11
  81. package/api/src/queues/exchange-rate-health.ts +49 -30
  82. package/api/src/queues/invoice.ts +18 -15
  83. package/api/src/queues/notification.ts +14 -7
  84. package/api/src/queues/payment.ts +64 -37
  85. package/api/src/queues/payout.ts +37 -21
  86. package/api/src/queues/refund.ts +36 -18
  87. package/api/src/queues/subscription.ts +83 -53
  88. package/api/src/queues/token-transfer.ts +15 -10
  89. package/api/src/queues/usage-record.ts +8 -5
  90. package/api/src/queues/vendors/commission.ts +7 -5
  91. package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
  92. package/api/src/queues/vendors/fulfillment.ts +4 -2
  93. package/api/src/queues/vendors/return-processor.ts +5 -3
  94. package/api/src/queues/vendors/return-scanner.ts +5 -4
  95. package/api/src/queues/vendors/status-check.ts +10 -7
  96. package/api/src/queues/webhook.ts +60 -32
  97. package/api/src/routes/connect/shared.ts +1 -2
  98. package/api/src/routes/connect/subscribe.ts +3 -3
  99. package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
  100. package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
  101. package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
  102. package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
  103. package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
  104. package/api/src/routes/hono/credit-tokens.ts +43 -0
  105. package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
  106. package/api/src/routes/{customers.ts → hono/customers.ts} +199 -224
  107. package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
  108. package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
  109. package/api/src/routes/{events.ts → hono/events.ts} +107 -71
  110. package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
  111. package/api/src/routes/hono/exchange-rates.ts +77 -0
  112. package/api/src/routes/hono/index.ts +115 -0
  113. package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
  114. package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
  115. package/api/src/routes/hono/integrations/stripe.ts +74 -0
  116. package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
  117. package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
  118. package/api/src/routes/hono/meters.ts +288 -0
  119. package/api/src/routes/hono/passports.ts +73 -0
  120. package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
  121. package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
  122. package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
  123. package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
  124. package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
  125. package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
  126. package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
  127. package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
  128. package/api/src/routes/{products.ts → hono/products.ts} +172 -159
  129. package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
  130. package/api/src/routes/hono/redirect.ts +24 -0
  131. package/api/src/routes/{refunds.ts → hono/refunds.ts} +98 -83
  132. package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
  133. package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
  134. package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
  135. package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
  136. package/api/src/routes/hono/tool.ts +69 -0
  137. package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
  138. package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
  139. package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
  140. package/api/src/routes/hono/webhook-endpoints.ts +126 -0
  141. package/api/src/service.ts +814 -0
  142. package/api/src/store/migrations/20230911-seeding.ts +2 -1
  143. package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
  144. package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
  145. package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
  146. package/api/src/store/models/auto-recharge-config.ts +22 -10
  147. package/api/src/store/models/checkout-session.ts +15 -14
  148. package/api/src/store/models/coupon.ts +29 -20
  149. package/api/src/store/models/credit-grant.ts +38 -29
  150. package/api/src/store/models/credit-transaction.ts +32 -21
  151. package/api/src/store/models/customer.ts +19 -17
  152. package/api/src/store/models/discount.ts +11 -2
  153. package/api/src/store/models/entitlement-grant.ts +21 -9
  154. package/api/src/store/models/entitlement-product.ts +21 -9
  155. package/api/src/store/models/entitlement.ts +19 -10
  156. package/api/src/store/models/event.ts +18 -9
  157. package/api/src/store/models/exchange-rate-provider.ts +17 -4
  158. package/api/src/store/models/invoice-item.ts +18 -9
  159. package/api/src/store/models/invoice.ts +16 -8
  160. package/api/src/store/models/meter-event.ts +27 -9
  161. package/api/src/store/models/meter.ts +31 -22
  162. package/api/src/store/models/payment-currency.ts +25 -8
  163. package/api/src/store/models/payment-intent.ts +15 -6
  164. package/api/src/store/models/payment-link.ts +15 -6
  165. package/api/src/store/models/payment-method.ts +38 -22
  166. package/api/src/store/models/payment-stat.ts +18 -9
  167. package/api/src/store/models/payout.ts +15 -6
  168. package/api/src/store/models/price-quote.ts +17 -8
  169. package/api/src/store/models/price.ts +24 -12
  170. package/api/src/store/models/pricing-table.ts +29 -20
  171. package/api/src/store/models/product-vendor.ts +20 -10
  172. package/api/src/store/models/product.ts +15 -6
  173. package/api/src/store/models/promotion-code.ts +14 -6
  174. package/api/src/store/models/refund.ts +15 -6
  175. package/api/src/store/models/revenue-snapshot.ts +21 -9
  176. package/api/src/store/models/setting.ts +18 -9
  177. package/api/src/store/models/setup-intent.ts +36 -27
  178. package/api/src/store/models/subscription-item.ts +21 -9
  179. package/api/src/store/models/subscription-schedule.ts +21 -9
  180. package/api/src/store/models/subscription.ts +21 -10
  181. package/api/src/store/models/tax-rate.ts +29 -21
  182. package/api/src/store/models/usage-record.ts +11 -2
  183. package/api/src/store/models/webhook-attempt.ts +18 -9
  184. package/api/src/store/models/webhook-endpoint.ts +18 -9
  185. package/api/src/store/scoped-core.ts +55 -0
  186. package/api/src/store/scoped.ts +247 -0
  187. package/api/src/store/sequelize.ts +82 -23
  188. package/api/src/store/sql-migrations.ts +20 -0
  189. package/api/src/store/tenant-backfill.ts +260 -0
  190. package/api/src/store/tenant-model.ts +124 -0
  191. package/api/src/store/tenant-tables.ts +50 -0
  192. package/api/tests/bootstrap/bootstrap.spec.ts +162 -0
  193. package/api/tests/crons/tenant-fanout.spec.ts +158 -0
  194. package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
  195. package/api/tests/fixtures/bare-query-violation.ts +13 -0
  196. package/api/tests/fixtures/core-env-violation.ts +10 -0
  197. package/api/tests/fixtures/host-read-violation.ts +19 -0
  198. package/api/tests/fixtures/tenants.ts +4 -0
  199. package/api/tests/integrations/iap-tenant.spec.ts +284 -0
  200. package/api/tests/libs/archive-query.spec.ts +26 -0
  201. package/api/tests/libs/audit-tenant.spec.ts +153 -0
  202. package/api/tests/libs/context.spec.ts +204 -0
  203. package/api/tests/libs/core-config.spec.ts +115 -0
  204. package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
  205. package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
  206. package/api/tests/libs/did-connect-runtime-js.spec.ts +98 -0
  207. package/api/tests/libs/did-connect-tenant-identity.spec.ts +159 -0
  208. package/api/tests/libs/lock-tenant.spec.ts +66 -0
  209. package/api/tests/libs/scoped.spec.ts +222 -0
  210. package/api/tests/libs/secrets-facade.spec.ts +52 -0
  211. package/api/tests/libs/service-host.spec.ts +37 -0
  212. package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
  213. package/api/tests/libs/tenant-middleware.spec.ts +42 -0
  214. package/api/tests/libs/tenant-scanner.spec.ts +120 -0
  215. package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
  216. package/api/tests/middlewares/hono/context.spec.ts +113 -0
  217. package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
  218. package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
  219. package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
  220. package/api/tests/middlewares/hono/security.spec.ts +181 -0
  221. package/api/tests/middlewares/hono/session.spec.ts +42 -0
  222. package/api/tests/middlewares/hono/xss.spec.ts +81 -0
  223. package/api/tests/models/tenant-backfill.spec.ts +287 -0
  224. package/api/tests/models/tenant-columns-model.spec.ts +46 -0
  225. package/api/tests/models/tenant-columns.spec.ts +161 -0
  226. package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
  227. package/api/tests/queues/credit-consume.spec.ts +8 -1
  228. package/api/tests/queues/event-tenant.spec.ts +292 -0
  229. package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
  230. package/api/tests/queues/queue-parity.spec.ts +249 -0
  231. package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
  232. package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
  233. package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
  234. package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
  235. package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
  236. package/api/tests/service/collapse.spec.ts +96 -0
  237. package/api/tests/service/didconnect-storage-slot.spec.ts +60 -0
  238. package/api/tests/service/fail-closed-http.spec.ts +79 -0
  239. package/api/tests/service/static-arc-handler.spec.ts +101 -0
  240. package/api/tests/service/static-externalized.spec.ts +48 -0
  241. package/api/tests/store/tenant-crosscut.spec.ts +202 -0
  242. package/api/tests/store/tenant-model-spike.spec.ts +177 -0
  243. package/api/tests/store/tenant-model.spec.ts +162 -0
  244. package/api/tests/store/tenant-residual.spec.ts +196 -0
  245. package/api/third.d.ts +4 -0
  246. package/blocklet.yml +1 -1
  247. package/cloudflare/MIGRATION-RUNBOOK.md +3 -8
  248. package/cloudflare/README.md +34 -27
  249. package/cloudflare/STAGING-MIGRATION-GUIDE.md +3 -15
  250. package/cloudflare/build.ts +33 -13
  251. package/cloudflare/cf-adapter.ts +419 -0
  252. package/cloudflare/did-connect-runtime.ts +96 -0
  253. package/cloudflare/did-connect-token-storage.ts +151 -0
  254. package/cloudflare/esbuild-cf-config.cjs +407 -0
  255. package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
  256. package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
  257. package/cloudflare/migrations/0008_schema_parity.sql +16 -0
  258. package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
  259. package/cloudflare/queue-runtime-mode.ts +13 -0
  260. package/cloudflare/run-build.js +33 -403
  261. package/cloudflare/scripts/cf-package-import-probe.mjs +90 -0
  262. package/cloudflare/scripts/didconnect-mock-smoke.mjs +140 -0
  263. package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
  264. package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
  265. package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
  266. package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
  267. package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
  268. package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
  269. package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +16 -1
  270. package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +18 -3
  271. package/cloudflare/shims/cron.ts +38 -158
  272. package/cloudflare/shims/events.ts +124 -0
  273. package/cloudflare/shims/fastq.ts +15 -1
  274. package/cloudflare/shims/nedb-storage.ts +16 -8
  275. package/cloudflare/shims/xss.ts +8 -0
  276. package/cloudflare/tenant-middleware.ts +36 -0
  277. package/cloudflare/tests/cf-adapter.spec.ts +244 -0
  278. package/cloudflare/tests/did-connect-token-storage.spec.ts +105 -0
  279. package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
  280. package/cloudflare/tests/worker-handler-gate.spec.ts +69 -0
  281. package/cloudflare/vite.config.ts +53 -45
  282. package/cloudflare/worker.ts +261 -448
  283. package/cloudflare/wrangler.json +0 -6
  284. package/cloudflare/wrangler.jsonc +0 -6
  285. package/cloudflare/wrangler.local-e2e.jsonc +25 -0
  286. package/cloudflare/wrangler.staging.json +0 -6
  287. package/jest.config.js +3 -1
  288. package/package.json +33 -38
  289. package/scripts/bootstrap-inject.ts +166 -0
  290. package/scripts/core-env-whitelist.json +1 -0
  291. package/scripts/e2e-12b-runtime.ts +149 -0
  292. package/scripts/e2e-core-config.ts +125 -0
  293. package/scripts/e2e-d1-tenancy.ts +116 -0
  294. package/scripts/e2e-d2-cron-queue.ts +139 -0
  295. package/scripts/e2e-d3-embedded-multi.ts +171 -0
  296. package/scripts/e2e-hono-s2.ts +125 -0
  297. package/scripts/e2e-hono-s3e.ts +135 -0
  298. package/scripts/e2e-hono-s4.ts +114 -0
  299. package/scripts/e2e-migration-contract.ts +100 -0
  300. package/scripts/e2e-s0.ts +61 -0
  301. package/scripts/e2e-s1.ts +107 -0
  302. package/scripts/e2e-s2.ts +178 -0
  303. package/scripts/e2e-s3.ts +110 -0
  304. package/scripts/e2e-s4.ts +191 -0
  305. package/scripts/e2e-s5.ts +139 -0
  306. package/scripts/e2e-s6.ts +127 -0
  307. package/scripts/e2e-tenant-model.ts +119 -0
  308. package/scripts/e2e-tenant-worker.ts +199 -0
  309. package/scripts/gen-sql-migrations.js +46 -0
  310. package/scripts/phase8-codemod.js +219 -0
  311. package/scripts/phase9a-env-getters-codemod.js +82 -0
  312. package/scripts/scan-core-env.js +109 -0
  313. package/scripts/scan-tenant-queries.js +235 -0
  314. package/scripts/schema-drift-guard.ts +210 -0
  315. package/scripts/tenant-scan-whitelist.json +1 -0
  316. package/src/app.tsx +2 -1
  317. package/src/env.d.ts +13 -1
  318. package/src/libs/service-host.ts +13 -0
  319. package/tsconfig.json +1 -1
  320. package/vite.arc.config.ts +159 -0
  321. package/api/src/libs/did-space.ts +0 -235
  322. package/api/src/libs/middleware.ts +0 -50
  323. package/api/src/libs/security.ts +0 -192
  324. package/api/src/queues/space.ts +0 -662
  325. package/api/src/routes/credit-tokens.ts +0 -38
  326. package/api/src/routes/exchange-rates.ts +0 -87
  327. package/api/src/routes/index.ts +0 -142
  328. package/api/src/routes/integrations/stripe.ts +0 -61
  329. package/api/src/routes/meters.ts +0 -274
  330. package/api/src/routes/passports.ts +0 -68
  331. package/api/src/routes/redirect.ts +0 -20
  332. package/api/src/routes/tool.ts +0 -65
  333. package/api/src/routes/webhook-endpoints.ts +0 -126
  334. package/api/tests/routes/credit-grants.spec.ts +0 -1261
  335. package/cloudflare/did-connect-auth.ts +0 -527
  336. package/cloudflare/shims/did-space-js.ts +0 -17
  337. package/cloudflare/shims/did-space.ts +0 -11
  338. package/cloudflare/shims/express-compat/index.ts +0 -80
  339. package/cloudflare/shims/express-compat/types.ts +0 -41
  340. package/cloudflare/shims/lock.ts +0 -115
  341. package/cloudflare/shims/queue.ts +0 -611
  342. package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
  343. package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * S3-CF (DID convergence) — local mock-AUTH_SERVICE smoke (TEST HARNESS ONLY).
4
+ *
5
+ * Runs the REAL built worker (dist/worker.js) in Miniflare with a MOCK AUTH_SERVICE
6
+ * that implements the production RPC contract (getInstanceAppIdentity) returning a
7
+ * deterministic TEST-ONLY appSk — no real secret, never in prod/wrangler config.
8
+ *
9
+ * The smoke goes through the REAL path (no handler/authenticator bypass):
10
+ * Host → tenant context → CF IdentityDriver → AUTH_SERVICE.getInstanceAppIdentity
11
+ * → resolveTenantIdentity → real @arcblock/did-connect-js authenticator
12
+ * → core buildConnectRoutesHono → tokenStorage.create (CF D1).
13
+ *
14
+ * Asserts the locally-verifiable integration facts:
15
+ * - /api/did/<action>/token is registered (not 404) and, WITH the mock, no longer
16
+ * fail-closes on getInstanceAppIdentity (the mock RPC is reached + appSk used);
17
+ * - a token row lands in the CF D1 _did_connect_tokens table, stamped with the
18
+ * tenant instanceDid.
19
+ * (The full wallet scan/auth handshake against a REAL AUTH_SERVICE stays a
20
+ * deploy/staging gate — see cloudflare/README.md.)
21
+ *
22
+ * node scripts/didconnect-mock-smoke.mjs
23
+ */
24
+ import { existsSync, readFileSync } from "node:fs";
25
+ import { dirname, join } from "node:path";
26
+ import { fileURLToPath } from "node:url";
27
+
28
+ import { Miniflare } from "miniflare";
29
+
30
+ const __dirname = dirname(fileURLToPath(import.meta.url));
31
+ const cfDir = join(__dirname, "..");
32
+ const workerPath = join(cfDir, "dist", "worker.js");
33
+
34
+ if (!existsSync(workerPath)) {
35
+ console.error(`mock-smoke: ${workerPath} not found — run \`node run-build.js\` first.`);
36
+ process.exit(2);
37
+ }
38
+
39
+ // Deterministic TEST-ONLY app signing key (ROLE_APPLICATION/ED25519/SHA3). NOT a
40
+ // real secret — generated once for the harness; isolation uses a second key.
41
+ const TEST_APP_SK =
42
+ "0xe86ad1043f2de80374ea9eb2ca5a0cdf0111126804cfb128e9e658cc44ae47694497dddaacf925929a19e8bc84a5059569983f307a4c67a694cca79c2001e634";
43
+ const INSTANCE_DID = "zMOCK_APP_INSTANCE";
44
+
45
+ const mockAuthScript = `
46
+ import { WorkerEntrypoint } from 'cloudflare:workers';
47
+ // Mock of the did-connect-service@4.0.3 AUTH_SERVICE RPC surface used on the DID path.
48
+ export class MockAuth extends WorkerEntrypoint {
49
+ async getInstanceAppIdentity(instanceDid) {
50
+ return { appSk: ${JSON.stringify(TEST_APP_SK)}, appInfo: { name: 'Mock Tenant', description: 'mock', icon: 'https://x/i.png' } };
51
+ }
52
+ async resolveInstanceDidForHost(host) { return null; }
53
+ async getAppEk(instanceDid) { return null; }
54
+ async resolveIdentity() { return null; }
55
+ }
56
+ export default { fetch() { return new Response('mock-auth'); } };
57
+ `;
58
+
59
+ const mf = new Miniflare({
60
+ workers: [
61
+ {
62
+ name: "payment",
63
+ modules: true,
64
+ modulesRoot: join(cfDir, "dist"),
65
+ scriptPath: workerPath,
66
+ compatibilityDate: "2024-12-01",
67
+ compatibilityFlags: ["nodejs_compat"],
68
+ d1Databases: { DB: "payment-mock-smoke" },
69
+ serviceBindings: { AUTH_SERVICE: { name: "mock-auth", entrypoint: "MockAuth" } },
70
+ bindings: {
71
+ APP_NAME: "Payment Kit",
72
+ APP_PID: INSTANCE_DID,
73
+ APP_URL: "http://localhost",
74
+ PAYMENT_LIVEMODE: "false",
75
+ },
76
+ },
77
+ {
78
+ name: "mock-auth",
79
+ modules: true,
80
+ script: mockAuthScript,
81
+ compatibilityDate: "2024-12-01",
82
+ compatibilityFlags: ["nodejs_compat"],
83
+ },
84
+ ],
85
+ });
86
+
87
+ let failed = false;
88
+ const log = (...a) => console.log(...a);
89
+
90
+ try {
91
+ const db = await mf.getD1Database("DB", "payment");
92
+ await db.exec(
93
+ "CREATE TABLE IF NOT EXISTS _did_connect_tokens (token TEXT PRIMARY KEY, data TEXT NOT NULL, expires_at INTEGER NOT NULL)",
94
+ );
95
+
96
+ // 1) healthz
97
+ const health = await mf.dispatchFetch("http://localhost/api/healthz");
98
+ log("=== healthz ===", health.status, await health.text());
99
+
100
+ // 2) /api/did/payment/token through the mock AUTH_SERVICE
101
+ const res = await mf.dispatchFetch("http://localhost/api/did/payment/token", {
102
+ method: "POST",
103
+ headers: { "content-type": "application/json" },
104
+ body: "{}",
105
+ });
106
+ const body = await res.text();
107
+ log("=== POST /api/did/payment/token ===", res.status);
108
+ log(body.slice(0, 800));
109
+
110
+ if (body.includes("getInstanceAppIdentity unavailable")) {
111
+ console.error("FAIL: still fail-closed — the mock AUTH_SERVICE RPC was not reached");
112
+ failed = true;
113
+ } else {
114
+ log("PASS: past the fail-closed gate — mock getInstanceAppIdentity reached + appSk resolved");
115
+ }
116
+
117
+ // 3) a token row landed in D1, stamped with the tenant instanceDid
118
+ const { results } = await db.prepare("SELECT token, data FROM _did_connect_tokens").all();
119
+ log("=== _did_connect_tokens rows ===", JSON.stringify(results, null, 2));
120
+ const tagged = (results || []).some((r) => {
121
+ try {
122
+ return JSON.parse(r.data).instanceDid === INSTANCE_DID;
123
+ } catch {
124
+ return false;
125
+ }
126
+ });
127
+ if (tagged) {
128
+ log(`PASS: a token row is stamped with instanceDid=${INSTANCE_DID}`);
129
+ } else {
130
+ console.error("FAIL: no token row stamped with the tenant instanceDid");
131
+ failed = true;
132
+ }
133
+ } catch (err) {
134
+ console.error("mock-smoke error:", err?.stack || err?.message || err);
135
+ failed = true;
136
+ } finally {
137
+ await mf.dispose();
138
+ }
139
+
140
+ process.exit(failed ? 1 : 0);
@@ -0,0 +1,20 @@
1
+ // CF shim for @blocklet/sdk/lib/util/asset-host-transformer.
2
+ //
3
+ // DEAD on the CF path: the only importer is the cdn middleware (node-shell only —
4
+ // HTML CDN-URL rewriting on the full node app shell). The worker serves JSON API
5
+ // routes through a LITE app-shell, so cdn never executes. A pass-through stub is
6
+ // enough to resolve the bundled-but-unreachable node code.
7
+ export class AssetHostTransformer {
8
+ // eslint-disable-next-line @typescript-eslint/no-useless-constructor, no-empty-function
9
+ constructor(_assetHost?: string) {}
10
+
11
+ transform(html: string, _assetHost?: string): string {
12
+ return html;
13
+ }
14
+
15
+ transformBuffer(body: Buffer | Uint8Array, _assetHost?: string): Buffer | Uint8Array {
16
+ return body;
17
+ }
18
+ }
19
+
20
+ export default { AssetHostTransformer };
@@ -4,5 +4,12 @@ export { env };
4
4
  export const Events = {};
5
5
  export const events = { on: () => {}, emit: () => {} };
6
6
 
7
- const config = { env, Events, events };
7
+ // Phase 4 (express→hono): the fallback middleware (SPA serving) reads these. It is
8
+ // node-shell only — DEAD on the CF worker (which never serves the SPA) — so a
9
+ // resolving stub is enough. getBlockletSettings also backs sessionMiddleware's
10
+ // blacklist check, which is gated off on CF (enableBlacklist: false).
11
+ export const getBlockletSettings = (): any => ({ enableBlacklist: false, theme: {} });
12
+ export const getBlockletJs = (..._args: any[]): string => '';
13
+
14
+ const config = { env, Events, events, getBlockletSettings, getBlockletJs };
8
15
  export default config;
@@ -0,0 +1,12 @@
1
+ // CF shim for @blocklet/sdk/lib/util/login.
2
+ //
3
+ // Reached on the LIVE CF path: sessionMiddleware (used by many resource routes)
4
+ // calls isLoginToken/isAccessKey to classify the bearer token. These are pure
5
+ // string-format checks — copied verbatim from the upstream so behavior matches.
6
+ export const isLoginToken = (token: unknown): boolean =>
7
+ typeof token === 'string' && token.split('.').length === 3;
8
+
9
+ export const isAccessKey = (token: unknown): boolean =>
10
+ typeof token === 'string' && token.split('.').length === 1 && token.startsWith('blocklet-');
11
+
12
+ export default { isLoginToken, isAccessKey };
@@ -0,0 +1,14 @@
1
+ // CF shim for @blocklet/sdk/lib/util/service-api.
2
+ //
3
+ // The only caller on the CF path is sessionMiddleware's login-token blacklist
4
+ // check, which is gated by `blockletSettings.enableBlacklist` (off on the CF
5
+ // worker — the worker resolves identity via AUTH_SERVICE RPC, not the blacklist
6
+ // endpoint). The old express-compat CF path never ran this check at all, so a
7
+ // stub that reports "valid" preserves the worker's (no-blacklist) behavior and
8
+ // never wrongly blocks a token if the setting is somehow on.
9
+ const serviceApi = {
10
+ post: async (_url: string, _body?: unknown) => ({ data: { valid: true } }),
11
+ get: async (_url: string) => ({ data: {} }),
12
+ };
13
+
14
+ export default serviceApi;
@@ -1,6 +1,8 @@
1
1
  // @blocklet/sdk/lib/middlewares/session shim
2
- // Auth is resolved in Hono middleware layer via AUTH_SERVICE RPC.
3
- // req.user is already populated by mountExpressRoutes().
2
+ // Auth is resolved in the Hono middleware layer via AUTH_SERVICE RPC, then injected
3
+ // as x-user-* request headers by the worker's /api/* dispatcher (worker.ts) — the
4
+ // native authenticate()/sessionMiddleware read those. This express-middleware shim
5
+ // is inert (the old express-compat mountExpressRoutes path it served is gone).
4
6
  export default function sessionMiddleware(_options?: any) {
5
7
  return (_req: any, _res: any, next: any) => next();
6
8
  }
@@ -0,0 +1,8 @@
1
+ // CF shim for @blocklet/sdk/lib/util/constants.
2
+ //
3
+ // Reached via the fallback middleware (node-shell SPA serving — DEAD on the CF
4
+ // worker, which never serves the SPA). SERVICE_PREFIX carries its real upstream
5
+ // value so the bundled-but-unreachable node code is byte-faithful.
6
+ export const SERVICE_PREFIX = '/.well-known/service';
7
+
8
+ export default { SERVICE_PREFIX };
@@ -1,3 +1,18 @@
1
+ // CF FAIL-FAST shim for @blocklet/sdk/lib/wallet-authenticator.
2
+ //
3
+ // S3-CF (DID convergence): see wallet-handler.ts. On workerd the @blocklet/sdk
4
+ // WalletAuthenticator is a no-op — it cannot sign DID-Connect sessions/certs. CF
5
+ // (and arc-node embedded) MUST inject the real @arcblock/did-connect-js runtime.
6
+ // Throw on construct so a host that forgot to inject fails loudly instead of
7
+ // producing an authenticator that silently signs nothing.
8
+ const FORBIDDEN =
9
+ 'CF must inject a DID-Connect runtime via setDidConnectRuntime(createCloudflareDidConnectRuntime); ' +
10
+ 'the @blocklet/sdk wallet-authenticator shim is forbidden on workerd';
11
+
1
12
  export class WalletAuthenticator {
2
- constructor(_opts?: any) {}
13
+ constructor(_opts?: any) {
14
+ throw new Error(`[did-connect] WalletAuthenticator: ${FORBIDDEN}`);
15
+ }
3
16
  }
17
+
18
+ export default { WalletAuthenticator };
@@ -1,6 +1,21 @@
1
+ // CF FAIL-FAST shim for @blocklet/sdk/lib/wallet-handler.
2
+ //
3
+ // S3-CF (DID convergence): on workerd the @blocklet/sdk wallet wrapper is NOT a
4
+ // working DID-Connect implementation. CF (and arc-node embedded) MUST inject the
5
+ // real @arcblock/did-connect-js runtime via setDidConnectRuntime
6
+ // (createCloudflareDidConnectRuntime). This used to be a silent no-op stub whose
7
+ // `attach()` registered ZERO routes — a CF host that forgot to inject would serve
8
+ // no DID-Connect routes and fail only at request time. It now throws on construct
9
+ // so the misconfiguration fails loudly (a spec asserts the AUTH_SERVICE runtime
10
+ // never reaches this path).
11
+ const FORBIDDEN =
12
+ 'CF must inject a DID-Connect runtime via setDidConnectRuntime(createCloudflareDidConnectRuntime); ' +
13
+ 'the @blocklet/sdk wallet-handler shim is forbidden on workerd';
14
+
1
15
  export class WalletHandlers {
2
- constructor(_opts?: any) {}
3
- attach(_opts: any) {
4
- // TODO: implement DID Connect for CF
16
+ constructor(_opts?: any) {
17
+ throw new Error(`[did-connect] WalletHandlers: ${FORBIDDEN}`);
5
18
  }
6
19
  }
20
+
21
+ export default { WalletHandlers };
@@ -1,182 +1,62 @@
1
- // @abtnode/cron shim for CF Cron Triggers
1
+ // @abtnode/cron shim for CF Cron Triggers (Phase 9, W2-1b).
2
2
  //
3
- // The real @abtnode/cron exports { init } where init({ context, jobs, onError }) registers jobs.
4
- // In CF Workers, we store the jobs and execute them via the scheduled() handler.
5
- //
6
- // The shim parses 6-field cron expressions (sec min hour day month weekday)
7
- // and only runs jobs whose schedule matches the current 5-minute window.
8
-
9
- type CronJob = {
10
- name: string;
11
- time: string;
12
- fn: () => Promise<any> | any;
13
- options?: { runOnInit?: boolean };
14
- };
3
+ // The real @abtnode/cron exports { init } where init({ jobs, onError }) registers
4
+ // jobs. In CF Workers we store the jobs and execute due ones from scheduled().
5
+ // The cron-expression matcher + registry now live in the shared cron driver
6
+ // (api/src/libs/drivers/cron.ts) so embedded and worker agree on when a job is
7
+ // due; this file is the thin worker adapter that keeps the @abtnode/cron API.
8
+
9
+ import {
10
+ createCronRegistry,
11
+ setCronDriver,
12
+ getCronDriver,
13
+ matchesCron,
14
+ shouldRunInWindow,
15
+ } from '../../api/src/libs/drivers/cron';
16
+
17
+ // D2: crons/index.ts now registers through getCronDriver() instead of importing
18
+ // @abtnode/cron directly. On CF this shim is the active cron driver, so make the
19
+ // cf-cron registry the global driver at module load — BEFORE worker.ts calls
20
+ // crons.init(). That keeps crons.init()'s register() and this shim's runAll() on
21
+ // the SAME passive cf-cron registry (host drives runDue from scheduled(); no
22
+ // @abtnode/cron self-scheduling timer is ever created in the frozen isolate).
23
+ const registry = createCronRegistry('cf-cron');
24
+ setCronDriver(registry);
15
25
 
16
26
  type InitOptions = {
17
27
  context?: any;
18
- jobs: CronJob[];
28
+ jobs: Array<{ name: string; time: string; fn: () => Promise<any> | any; options?: { runOnInit?: boolean } }>;
19
29
  onError?: (error: Error, name: string) => void;
20
30
  };
21
31
 
22
- // Singleton storage for registered cron jobs
23
- const registeredJobs: CronJob[] = [];
24
- let onErrorHandler: ((error: Error, name: string) => void) | undefined;
25
-
26
- // --- Cron expression matcher ---
27
- // Supports 6-field format: second minute hour dayOfMonth month dayOfWeek
28
- // Supports: numbers, *, */N, ranges (1-5), lists (1,3,5)
29
-
30
- function parseField(field: string, min: number, max: number): number[] | null {
31
- // null means "match all"
32
- if (field === '*') return null;
33
-
34
- const values = new Set<number>();
35
-
36
- for (const part of field.split(',')) {
37
- // */N — every N
38
- const stepMatch = part.match(/^\*\/(\d+)$/);
39
- if (stepMatch) {
40
- const step = parseInt(stepMatch[1], 10);
41
- for (let i = min; i <= max; i += step) {
42
- values.add(i);
43
- }
44
- continue;
45
- }
46
-
47
- // N-M — range
48
- const rangeMatch = part.match(/^(\d+)-(\d+)$/);
49
- if (rangeMatch) {
50
- const from = parseInt(rangeMatch[1], 10);
51
- const to = parseInt(rangeMatch[2], 10);
52
- for (let i = from; i <= to; i++) {
53
- values.add(i);
54
- }
55
- continue;
56
- }
57
-
58
- // N — single value
59
- const num = parseInt(part, 10);
60
- if (!isNaN(num)) {
61
- values.add(num);
62
- }
63
- }
64
-
65
- return values.size > 0 ? Array.from(values) : null;
66
- }
67
-
68
- /**
69
- * Check if a date matches a 6-field cron expression.
70
- * Returns true if the date's minute/hour/day/month/weekday match.
71
- * Seconds field is ignored (CF triggers are minute-level).
72
- */
73
- function matchesCron(cronExpr: string, date: Date): boolean {
74
- const fields = cronExpr.trim().split(/\s+/);
75
- if (fields.length < 5) return true; // Can't parse — run it
76
-
77
- // 6-field: sec min hour dom month dow
78
- // 5-field: min hour dom month dow
79
- const offset = fields.length >= 6 ? 1 : 0;
80
-
81
- const minuteField = parseField(fields[offset], 0, 59);
82
- const hourField = parseField(fields[offset + 1], 0, 23);
83
- const domField = parseField(fields[offset + 2], 1, 31);
84
- const monthField = parseField(fields[offset + 3], 0, 11); // cron months are 1-12, JS is 0-11
85
- const dowField = parseField(fields[offset + 4], 0, 6);
86
-
87
- const m = date.getUTCMinutes();
88
- const h = date.getUTCHours();
89
- const dom = date.getUTCDate();
90
- const month = date.getUTCMonth() + 1; // JS 0-based → cron 1-based
91
- const dow = date.getUTCDay(); // 0=Sunday
92
-
93
- if (minuteField && !minuteField.includes(m)) return false;
94
- if (hourField && !hourField.includes(h)) return false;
95
- if (domField && !domField.includes(dom)) return false;
96
- if (monthField && !monthField.includes(month)) return false;
97
- if (dowField && !dowField.includes(dow)) return false;
98
-
99
- return true;
100
- }
101
-
102
- // Check if a cron expression should fire at the given date (minute-level match).
103
- //
104
- // History: this helper previously used a 5-minute look-ahead window, under the
105
- // assumption that CF Cron Triggers fire every 5 minutes. Once the deploy config
106
- // switched to every-minute cron, that window made every stepped expression fire
107
- // 5x more often than intended, driving CF Queues past the free-tier daily cap
108
- // (2026-04-17 incident). With CF Scheduled firing every minute, we only check
109
- // the current minute — each cron expression triggers at its designed frequency.
110
- // See docs/cf-queues-ops-alert-analysis.md § 改动 B.
111
- function shouldRunInWindow(cronExpr: string, date: Date): boolean {
112
- return matchesCron(cronExpr, date);
113
- }
114
-
115
- // --- Cron shim API ---
116
-
117
32
  function init(options: InitOptions) {
118
- registeredJobs.length = 0;
119
- onErrorHandler = options.onError;
120
-
121
- for (const job of options.jobs || []) {
122
- if (job.name && job.time && typeof job.fn === 'function') {
123
- registeredJobs.push(job);
124
- }
125
- }
126
-
127
- // Skip runOnInit jobs in CF Workers — they'll run on the next matching trigger
128
- // (running them at module init time would block the request and may exceed CPU limits)
129
-
33
+ registry.register(options.jobs || [], options.onError);
34
+ // runOnInit jobs are skipped in CF Workers — they run on the next matching
35
+ // trigger (running them at module init would block the request / risk CPU limits)
130
36
  return {
131
37
  addJob(name: string, time: string, fn: Function, opts?: any) {
132
- registeredJobs.push({ name, time, fn: fn as any, options: opts });
38
+ registry.addJob(name, time, fn as any, opts);
39
+ },
40
+ start() {
41
+ /* no-op — CF scheduled() drives runAll */
133
42
  },
134
- start() { /* no-op */ },
135
43
  };
136
44
  }
137
45
 
138
- // Called by worker.ts scheduled handler only runs jobs matching the trigger time.
139
- // Accepts an optional `now` so the caller can pass `event.scheduledTime` (the
140
- // intended trigger minute) instead of relying on wall-clock at execution time.
141
- // CF may deliver scheduled events with a small delay that crosses a minute
142
- // boundary; matching on `scheduledTime` keeps exact-minute cron reliable.
46
+ // Called by worker.ts scheduled handler. Accepts an optional `now` so the caller
47
+ // can pass `event.scheduledTime` (the intended trigger minute).
143
48
  async function runAll(now: Date = new Date()) {
144
- const matched: string[] = [];
145
- const skipped: string[] = [];
146
-
147
- for (const job of registeredJobs) {
148
- if (shouldRunInWindow(job.time, now)) {
149
- matched.push(job.name);
150
- try {
151
- await job.fn();
152
- } catch (err: any) {
153
- console.error(`[Cron] ${job.name} failed:`, err?.message || err);
154
- onErrorHandler?.(err, job.name);
155
- }
156
- } else {
157
- skipped.push(job.name);
158
- }
159
- }
160
-
161
- console.log(`[Cron] Ran ${matched.length} jobs: [${matched.join(', ')}]. Skipped ${skipped.length}.`);
49
+ const { ran, skipped } = await getCronDriver().runDue(now);
50
+ // eslint-disable-next-line no-console
51
+ console.log(`[Cron] Ran ${ran.length} jobs: [${ran.join(', ')}]. Skipped ${skipped.length}.`);
162
52
  }
163
53
 
164
54
  async function runJob(name: string) {
165
- const job = registeredJobs.find((j) => j.name === name);
166
- if (job) {
167
- try {
168
- await job.fn();
169
- } catch (err: any) {
170
- console.error(`[Cron] ${name} failed:`, err?.message || err);
171
- onErrorHandler?.(err, name);
172
- }
173
- } else {
174
- console.warn(`[Cron] Job ${name} not found`);
175
- }
55
+ await getCronDriver().runJob(name);
176
56
  }
177
57
 
178
58
  function getJobNames(): string[] {
179
- return registeredJobs.map((j) => `${j.name} (${j.time})`);
59
+ return getCronDriver().getJobNames();
180
60
  }
181
61
 
182
62
  // Export matching @abtnode/cron API: default export is { init }
@@ -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
  }