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
package/api/dev.ts CHANGED
@@ -1,10 +1,49 @@
1
+ // Dev shell — express→hono Phase 4. Dev-only (NOT bundled into production:
2
+ // tsconfig.api excludes api/dev.ts; the production entry is ./src → index.ts).
3
+ //
4
+ // Hosts the Vite client + HMR on a `connect` app and routes backend paths to the
5
+ // hono service. `vite-plugin-blocklet`'s setupClient only knows `app.use(vite
6
+ // .middlewares)`, so the dev shell hosts Vite on connect (NOT express — core is
7
+ // express-free). Backend paths are routed to service.fetch EXPLICITLY by prefix:
8
+ // a Web-Fetch 404 cannot call connect next(), so hono must never be the trailing
9
+ // catch-all (that would swallow client routes the SPA fallback should serve).
10
+ import { createServer } from 'http';
11
+
12
+ import { getRequestListener } from '@hono/node-server';
13
+ // eslint-disable-next-line import/no-extraneous-dependencies
14
+ import connect from 'connect';
1
15
  // eslint-disable-next-line import/no-extraneous-dependencies
2
16
  import { setupClient } from 'vite-plugin-blocklet';
3
17
 
4
- import { app, server } from './src';
18
+ import { buildService, onListening } from './src/bootstrap';
19
+
20
+ const { service, port } = buildService();
21
+
22
+ // Hand backend paths to the hono app; everything else falls through to the Vite
23
+ // middleware setupClient() mounts after this router.
24
+ const honoListener = getRequestListener(service.fetch);
25
+ const isBackendPath = (url: string): boolean => {
26
+ const pathname = url.split('?')[0] || '';
27
+ return pathname === '/api' || pathname.startsWith('/api/') || pathname.startsWith('/.well-known/');
28
+ };
5
29
 
30
+ const app = connect();
31
+ app.use((req, res, next) => {
32
+ if (isBackendPath(req.url || '')) {
33
+ honoListener(req, res);
34
+ return;
35
+ }
36
+ next();
37
+ });
38
+
39
+ const server = createServer(app);
40
+
41
+ // setupClient appends the Vite connect middleware (client assets + HMR + SPA
42
+ // fallback) AFTER our backend router, and wires the HMR ws upgrade onto `server`.
6
43
  setupClient(app, {
7
44
  server,
8
- // @ts-ignore
45
+ // @ts-ignore — import.meta.hot is injected by tsx/vite-node at runtime
9
46
  importMetaHot: import.meta.hot,
10
47
  });
48
+
49
+ server.listen(port, () => onListening(service, port));
package/api/hono.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ // ContextVariableMap skeleton (Phase 0, express→hono migration).
2
+ //
3
+ // Mirrors the express `Request` augmentation in api/third.d.ts so that
4
+ // `c.set(...)` / `c.get(...)` are typed everywhere across the migration. This
5
+ // file only declares the key→type contract; the actual injection lands later:
6
+ // - user / customer / doc / customer_id → Phase 1 (libs/security.ts)
7
+ // - locale / t → Phase 1 (libs/middleware.ts)
8
+ // - sanitizedBody → Phase 1 (forked xss middleware)
9
+ // - livemode / baseCurrency → Phase 3 (routes/index.ts middleware)
10
+ // - stripeEvent / stripeClient → Phase 3e (stripe verifyWebhookSig)
11
+ //
12
+ // Two keys exist only on the hono side (no express equivalent), because hono's
13
+ // Request is immutable and cannot be rewritten in place the way express was:
14
+ // - sanitizedBody : xss is the single body read-point; routes read this, never
15
+ // c.req.json() (a re-read would return the UN-sanitized
16
+ // original — see design §7 security note).
17
+ // - customer_id : security "mine" mode injects the verified customer id here
18
+ // (express wrote req.query.customer_id =, impossible on hono).
19
+ import 'hono';
20
+
21
+ declare module 'hono' {
22
+ interface ContextVariableMap {
23
+ user: {
24
+ did: string;
25
+ role: string;
26
+ provider: string;
27
+ fullName: string;
28
+ walletOS: string;
29
+ via?: string;
30
+ };
31
+ livemode: boolean;
32
+ locale: string;
33
+ t: (key: string, ...args: any[]) => string;
34
+ doc: any;
35
+ customer: any;
36
+ baseCurrency: any;
37
+ customer_id: string;
38
+ sanitizedBody: any;
39
+ stripeEvent: any;
40
+ stripeClient: any;
41
+ }
42
+ }
@@ -0,0 +1,12 @@
1
+ // Ambient declaration for node:sqlite (experimental, Node 22+). It exists at
2
+ // runtime (the tests + schema-drift-guard run on a node that ships it) but
3
+ // @types/node does not yet declare it, so tsc --noEmit fails to resolve the import.
4
+ // Declared as a class so it works both as a value (`new DatabaseSync()`) and as a
5
+ // type (`let db: DatabaseSync`). Typed loosely — test/tooling-only consumers.
6
+ declare module 'node:sqlite' {
7
+ export class DatabaseSync {
8
+ constructor(path?: string, options?: any);
9
+
10
+ [key: string]: any;
11
+ }
12
+ }
@@ -0,0 +1,47 @@
1
+ // Blocklet server service bootstrap — shared by the production entry
2
+ // (./index.ts → api/dist/index.js) and the dev shell (../dev.ts).
3
+ //
4
+ // Phase 4 (express→hono): the listened surface is the hono app (service.fetch).
5
+ // Production serves it directly via @hono/node-server. Dev hosts the Vite client
6
+ // + HMR on a `connect` shell (../dev.ts) and routes backend paths to the same
7
+ // service.fetch — so both entries build the service identically here and only
8
+ // differ in HOW they put it behind a socket.
9
+
10
+ import logger from './libs/logger';
11
+ import { getDefaultInstanceDid, getTenantMode } from './libs/tenant';
12
+ import { createBlockletServerDidConnectRuntime } from './libs/auth';
13
+ import { createEmbeddedPaymentService, type EmbeddedPaymentService } from './service';
14
+ import { attachNodeStatic } from './host-node/serve-static';
15
+ import { sequelize } from './store/sequelize';
16
+ import { blockletPort } from './libs/env';
17
+
18
+ export function buildService(): { service: EmbeddedPaymentService; port: number } {
19
+ const tenancy =
20
+ getTenantMode() === 'multi'
21
+ ? ({ mode: 'multi' } as const)
22
+ : ({ mode: 'single', instanceDid: getDefaultInstanceDid() } as const);
23
+
24
+ const service = createEmbeddedPaymentService({
25
+ config: { ...process.env }, // Phase 12 narrows this to an explicit schema
26
+ db: { sequelize },
27
+ tenancy,
28
+ // S3-CF Phase 1 ①: the blocklet-server host owns static/SPA serving (moved out
29
+ // of the runtime-neutral buildHonoApp). attachNodeStatic is production-gated, so
30
+ // dev (this same bootstrap) is unaffected — identical to the prior behavior.
31
+ staticHandler: attachNodeStatic,
32
+ // S3-CF (DID convergence): blocklet-server is the ONE host that uses the
33
+ // @blocklet/sdk DID-Connect wrapper (autoConnect / notification relay /
34
+ // federated login). Injected explicitly via the same host-injection entry as
35
+ // CF/arc-node — not relied on as an implicit libs/auth default.
36
+ didConnectRuntime: createBlockletServerDidConnectRuntime(),
37
+ });
38
+
39
+ const port = parseInt(blockletPort()!, 10);
40
+ return { service, port };
41
+ }
42
+
43
+ /** Start background services once the socket is bound (the listen callback). */
44
+ export function onListening(service: EmbeddedPaymentService, port: number): void {
45
+ logger.info(`> payment-kit ready on ${port}`);
46
+ service.lifecycle.start().catch((startErr) => logger.error('failed to start background services', startErr));
47
+ }
@@ -175,7 +175,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
175
175
  await pAll(
176
176
  deleteTasks.map((task) => () => task()),
177
177
  {
178
- concurrency: notificationCronConcurrency,
178
+ concurrency: notificationCronConcurrency(),
179
179
  stopOnError: false,
180
180
  }
181
181
  );
@@ -192,7 +192,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
192
192
  };
193
193
  }),
194
194
  {
195
- concurrency: notificationCronConcurrency,
195
+ concurrency: notificationCronConcurrency(),
196
196
  stopOnError: false,
197
197
  }
198
198
  );
@@ -231,7 +231,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
231
231
  );
232
232
 
233
233
  await pAll(deletePromises, {
234
- concurrency: notificationCronConcurrency,
234
+ concurrency: notificationCronConcurrency(),
235
235
  stopOnError: false,
236
236
  });
237
237
  }
@@ -1,5 +1,5 @@
1
1
  import { Op } from 'sequelize';
2
- import { getUrl } from '@blocklet/sdk';
2
+ import { getUrl } from '@blocklet/sdk/lib/component';
3
3
  import { PaymentMethod, PaymentCurrency } from '../store/models';
4
4
  import logger from '../libs/logger';
5
5
 
@@ -1,4 +1,4 @@
1
- import Cron from '@abtnode/cron';
1
+ import { getCronDriver } from '../libs/drivers/cron';
2
2
 
3
3
  import { checkStakeRevokeTx } from '../integrations/arcblock/stake';
4
4
  import { runIapReconcile } from '../integrations/iap-reconcile';
@@ -34,6 +34,7 @@ import { CheckoutSession } from '../store/models';
34
34
  import { createOverdueDetection } from './overdue-detection';
35
35
  import { createPaymentStat } from './payment-stat';
36
36
  import { retryPendingEvents } from './retry-pending-events';
37
+ import { perTenant } from './tenant-fanout';
37
38
  import { SubscriptionTrialWillEndSchedule } from './subscription-trial-will-end';
38
39
  import { SubscriptionWillCanceledSchedule } from './subscription-will-canceled';
39
40
  import { SubscriptionWillRenewSchedule } from './subscription-will-renew';
@@ -63,30 +64,33 @@ function init() {
63
64
  },
64
65
  ]
65
66
  : [];
66
- Cron.init({
67
- context: {},
68
- jobs: [
67
+ // D2: register through the injected cron driver (no bare @abtnode/cron). On
68
+ // the node host the driver self-schedules via @abtnode/cron; on CF the host
69
+ // drives runDue() from scheduled(). register() is idempotent (clears + re-adds
70
+ // + restarts the scheduler), so a stop()/start() cycle never double-registers.
71
+ getCronDriver().register(
72
+ [
69
73
  {
70
74
  name: 'subscription.will.renew',
71
- time: notificationCronTime,
72
- fn: () => new SubscriptionWillRenewSchedule().run(),
75
+ time: notificationCronTime(),
76
+ fn: perTenant('subscription.will.renew', () => new SubscriptionWillRenewSchedule().run()),
73
77
  options: { runOnInit: true },
74
78
  },
75
79
  {
76
80
  name: 'subscription.trial.will.end',
77
- time: notificationCronTime,
78
- fn: () => new SubscriptionTrialWillEndSchedule().run(),
81
+ time: notificationCronTime(),
82
+ fn: perTenant('subscription.trial.will.end', () => new SubscriptionTrialWillEndSchedule().run()),
79
83
  options: { runOnInit: true },
80
84
  },
81
85
  {
82
86
  name: 'customer.subscription.will_canceled',
83
- time: notificationCronTime,
84
- fn: () => new SubscriptionWillCanceledSchedule().run(),
87
+ time: notificationCronTime(),
88
+ fn: perTenant('customer.subscription.will_canceled', () => new SubscriptionWillCanceledSchedule().run()),
85
89
  options: { runOnInit: true },
86
90
  },
87
91
  {
88
92
  name: 'subscription.schedule.retry',
89
- time: subscriptionCronTime,
93
+ time: subscriptionCronTime(),
90
94
  fn: startSubscriptionQueue,
91
95
  options: { runOnInit: false },
92
96
  },
@@ -101,35 +105,35 @@ function init() {
101
105
  },
102
106
  {
103
107
  name: 'checkoutSession.cleanup.expired',
104
- time: expiredSessionCleanupCronTime,
105
- fn: async () => {
108
+ time: expiredSessionCleanupCronTime(),
109
+ fn: perTenant('checkoutSession.cleanup.expired', async () => {
106
110
  const removedCount = await CheckoutSession.cleanupExpiredSessions();
107
111
  logger.info('CheckoutSession.cleanupExpiredSessions', { removedCount });
108
- },
112
+ }),
109
113
  options: { runOnInit: true },
110
114
  },
111
115
  {
112
116
  name: 'stripe.invoice.sync',
113
- time: stripeInvoiceCronTime,
114
- fn: batchHandleStripeInvoices,
117
+ time: stripeInvoiceCronTime(),
118
+ fn: perTenant('stripe.invoice.sync', batchHandleStripeInvoices),
115
119
  options: { runOnInit: false },
116
120
  },
117
121
  {
118
122
  name: 'stripe.payment.sync',
119
- time: stripePaymentCronTime,
120
- fn: batchHandleStripePayments,
123
+ time: stripePaymentCronTime(),
124
+ fn: perTenant('stripe.payment.sync', batchHandleStripePayments),
121
125
  options: { runOnInit: false },
122
126
  },
123
127
  {
124
128
  name: 'stripe.subscription.sync',
125
- time: stripeSubscriptionCronTime,
126
- fn: batchHandleStripeSubscriptions,
129
+ time: stripeSubscriptionCronTime(),
130
+ fn: perTenant('stripe.subscription.sync', batchHandleStripeSubscriptions),
127
131
  options: { runOnInit: false },
128
132
  },
129
133
  {
130
134
  name: 'customer.stake.revoked',
131
- time: revokeStakeCronTime,
132
- fn: checkStakeRevokeTx,
135
+ time: revokeStakeCronTime(),
136
+ fn: perTenant('customer.stake.revoked', checkStakeRevokeTx),
133
137
  options: { runOnInit: false },
134
138
  },
135
139
  {
@@ -137,8 +141,8 @@ function init() {
137
141
  // subscription state and patches local drift (refunds/renewals the
138
142
  // webhook missed). See blocklets/core/api/src/integrations/iap-reconcile.ts.
139
143
  name: 'iap.reconcile',
140
- time: iapReconcileCronTime,
141
- fn: () => runIapReconcile(),
144
+ time: iapReconcileCronTime(),
145
+ fn: perTenant('iap.reconcile', () => runIapReconcile()),
142
146
  options: { runOnInit: false },
143
147
  },
144
148
  {
@@ -146,52 +150,52 @@ function init() {
146
150
  // with pending_webhooks>0 older than 60s and re-invokes handleEvent.
147
151
  // See blocklets/core/api/src/crons/retry-pending-events.ts.
148
152
  name: 'event.retry',
149
- time: eventRetryCronTime,
150
- fn: () => retryPendingEvents(),
153
+ time: eventRetryCronTime(),
154
+ fn: perTenant('event.retry', () => retryPendingEvents()),
151
155
  options: { runOnInit: false },
152
156
  },
153
157
  {
154
158
  name: 'payment.stat',
155
- time: paymentStatCronTime,
156
- fn: () => createPaymentStat(),
159
+ time: paymentStatCronTime(),
160
+ fn: perTenant('payment.stat', () => createPaymentStat()),
157
161
  options: { runOnInit: false },
158
162
  },
159
163
  {
160
164
  name: 'payment.daily.report',
161
- time: overdueDetectionCronTime,
162
- fn: () => createOverdueDetection(),
165
+ time: overdueDetectionCronTime(),
166
+ fn: perTenant('payment.daily.report', () => createOverdueDetection()),
163
167
  options: { runOnInit: false },
164
168
  },
165
169
  {
166
170
  name: 'deposit.vault',
167
- time: depositVaultCronTime,
171
+ time: depositVaultCronTime(),
168
172
  fn: () => startDepositVaultQueue(),
169
173
  options: { runOnInit: true },
170
174
  },
171
175
  {
172
176
  name: 'credit.consumption',
173
- time: creditConsumptionCronTime,
177
+ time: creditConsumptionCronTime(),
174
178
  fn: () => startCreditConsumeQueue(),
175
179
  options: { runOnInit: true },
176
180
  },
177
181
  {
178
182
  name: 'vendor.status.check',
179
- time: vendorStatusCheckCronTime,
183
+ time: vendorStatusCheckCronTime(),
180
184
  fn: () => startVendorStatusCheckSchedule(),
181
185
  options: { runOnInit: false },
182
186
  },
183
187
  {
184
188
  name: 'vendor.return.scan',
185
- time: vendorReturnScanCronTime,
189
+ time: vendorReturnScanCronTime(),
186
190
  fn: () => scheduleVendorReturnScan(),
187
191
  options: { runOnInit: false },
188
192
  },
189
193
  ...archiveJobs,
190
194
  ],
191
- onError: (error: Error, name: string) => {
195
+ (error: Error, name: string) => {
192
196
  logger.error('run job failed', { name, error });
193
- },
194
- });
197
+ }
198
+ );
195
199
  }
196
200
 
197
201
  export default {
@@ -1,4 +1,4 @@
1
- import { Notification } from '@blocklet/sdk';
1
+ import Notification from '@blocklet/sdk/service/notification';
2
2
  import { Op } from 'sequelize';
3
3
  import { env } from '@blocklet/sdk/lib/config';
4
4
  import dayjs from '../libs/dayjs';
@@ -1,4 +1,4 @@
1
- import { Notification } from '@blocklet/sdk';
1
+ import Notification from '@blocklet/sdk/service/notification';
2
2
  import { getUrl } from '@blocklet/sdk/lib/component';
3
3
  import { env } from '@blocklet/sdk/lib/config';
4
4
  import { fromUnitToToken, BN } from '@ocap/util';
@@ -175,7 +175,7 @@ export class HealthReportTemplate implements BaseEmailTemplate<HealthReportConte
175
175
  currencyTotals.set(currencyId, currentTotal.add(group.total));
176
176
 
177
177
  // Check if this user's pending exceeds threshold for this currency
178
- const thresholdInUnit = new BN(overdueThreshold.toString()).mul(new BN(10).pow(new BN(currency.decimal)));
178
+ const thresholdInUnit = new BN(overdueThreshold().toString()).mul(new BN(10).pow(new BN(currency.decimal)));
179
179
  if (group.total.gt(thresholdInUnit)) {
180
180
  exceedsThreshold = true;
181
181
  }
@@ -33,6 +33,12 @@ const REALTIME_GRACE_SECONDS = 60;
33
33
  // generous headroom and we still drain the backlog over a few minutes.
34
34
  const BATCH_LIMIT = 5;
35
35
 
36
+ // Phase 4 (W1-3): this scan is deliberately tenant-agnostic — it only
37
+ // re-enqueues event IDs. Tenant enforcement happens downstream in
38
+ // handleEvent's fanout (endpoints filtered by the event's instance_did) and
39
+ // handleWebhook's event/endpoint tenant invariant. Events whose creation was
40
+ // tenant-rejected never produced a row, so this backstop cannot "rescue"
41
+ // them — permanent loss of rejected events is the intended semantic.
36
42
  export async function retryPendingEvents(): Promise<void> {
37
43
  const threshold = new Date(Date.now() - REALTIME_GRACE_SECONDS * 1000);
38
44
  const docs = await Event.findAll({
@@ -0,0 +1,82 @@
1
+ // Multi-tenant cron fan-out (S3 arc-payment-embed — Phase 7).
2
+ //
3
+ // Background crons that issue a top-level tenant-scoped model query
4
+ // (subscription schedules, reconciliation, payment stat, overdue detection,
5
+ // stake-revoke, expired-session cleanup, stripe sync, event retry) have NO
6
+ // request to carry a tenant. In multi mode `getInstanceDid()` throws
7
+ // TENANT_CONTEXT_MISSING and the whole pass does nothing. Single mode is
8
+ // unaffected — the default tenant auto-resolves.
9
+ //
10
+ // Tenant enumeration is SELF-CONTAINED (decision 2026-06-13): payment-core owns
11
+ // its own scope, so we iterate the tenants that have payment configuration in
12
+ // OUR OWN store (`provisionTenant` seeds a PaymentMethod row per tenant) rather
13
+ // than asking the host for a registry. A tenant with no payment rows has
14
+ // nothing for these crons to do, and this fits a lazily-provisioned host
15
+ // (arc-node) where a tenant appears in payment.db only after its first request.
16
+ //
17
+ // Queue-starter crons (startXQueue) are NOT wrapped: each persisted job already
18
+ // carries its own instance_did and runs inside withTenant(job.tenant) via the
19
+ // queue runtime (libs/queue runJobWithTenant). Wrapping them would re-scan and
20
+ // double-process.
21
+
22
+ import { withTenant } from '../libs/context';
23
+ import logger from '../libs/logger';
24
+ import { getTenantMode } from '../libs/tenant';
25
+ import { systemFindAll } from '../store/scoped';
26
+
27
+ /**
28
+ * Tenants that have payment configuration in payment-core's own store. Reads
29
+ * across tenants (systemFindAll runs inside runAsSystem so the TenantModel base
30
+ * does not re-scope to a — here absent — active tenant). DISTINCT is done in JS
31
+ * to stay compatible with both real Sequelize and the worker's sequelize-d1
32
+ * shim. PaymentMethod is the canonical "provisioned tenant" marker; a tenant
33
+ * that has any payment data necessarily has a payment method.
34
+ */
35
+ export async function listProvisionedTenants(): Promise<string[]> {
36
+ // eslint-disable-next-line global-require
37
+ const { PaymentMethod } = require('../store/models');
38
+ // raw:true returns plain rows, not Model instances — cast through unknown.
39
+ const rows = (await systemFindAll(PaymentMethod, {
40
+ attributes: ['instance_did'],
41
+ raw: true,
42
+ })) as unknown as Array<{ instance_did?: string | null }>;
43
+ const dids = new Set<string>();
44
+ for (const row of rows) {
45
+ if (row?.instance_did) dids.add(row.instance_did);
46
+ }
47
+ return [...dids];
48
+ }
49
+
50
+ /**
51
+ * Wrap a tenant-scoped cron fn so it runs once per provisioned tenant inside
52
+ * `withTenant`. Single mode runs the fn as-is (the default tenant auto-resolves
53
+ * in getInstanceDid). Multi mode enumerates provisioned tenants and runs the fn
54
+ * per tenant; per-tenant errors are ISOLATED (caught + logged) so one tenant's
55
+ * failure never aborts the rest of the pass.
56
+ *
57
+ * @param listTenants enumeration seam (defaults to listProvisionedTenants;
58
+ * injected in tests so the fan-out logic is exercised without a DB).
59
+ */
60
+ export function perTenant(
61
+ name: string,
62
+ fn: () => Promise<unknown> | unknown,
63
+ listTenants: () => Promise<string[]> = listProvisionedTenants
64
+ ): () => Promise<void> {
65
+ return async () => {
66
+ if (getTenantMode() === 'single') {
67
+ await fn();
68
+ return;
69
+ }
70
+ const dids = await listTenants();
71
+ if (dids.length === 0) return;
72
+ logger.info('cron.tenant.fanout', { cron: name, tenantCount: dids.length });
73
+ for (const instanceDid of dids) {
74
+ // async callback so a SYNCHRONOUS throw from fn surfaces as a rejection
75
+ // (and stays inside the tenant's ALS scope) — then .catch isolates it.
76
+ // eslint-disable-next-line no-await-in-loop, require-await -- sequential per-tenant pass; async wraps sync throws
77
+ await withTenant(instanceDid, async () => fn()).catch((error: unknown) =>
78
+ logger.error('cron tenant pass failed', { cron: name, instanceDid, error })
79
+ );
80
+ }
81
+ };
82
+ }
@@ -0,0 +1,33 @@
1
+ // Node host adapter — the AUTH_SERVICE-backed DID-Connect runtime for the
2
+ // arc-node embedded host. The node analog of cloudflare/did-connect-runtime.ts:
3
+ // the shared @arcblock/did-connect-js runtime whose signing wallet/appInfo are
4
+ // resolved per-tenant via resolveTenantIdentity (getInstanceAppIdentity), NOT a
5
+ // fixed isolate key.
6
+ //
7
+ // Without this, a bare host falls back to createBlockletServerDidConnectRuntime,
8
+ // whose WalletAuthenticator is constructed with `makeWallet()` (env BLOCKLET_APP_SK)
9
+ // — which arc never sets — so the first DID-Connect sign (e.g. /api/did/payment/token
10
+ // → generateSession) throws "Missing public key for SK wallet: BLOCKLET_APP_PK".
11
+ //
12
+ // Differences from the CF runtime are node-only:
13
+ // - txEncoder = the @ocap/client/encode CBOR encoder (same one the
14
+ // blocklet-server runtime passes), resolved from the host's node_modules.
15
+ // - tokenStorage omitted → buildTokenStorage's file-backed nedb default
16
+ // (os.tmpdir on a bare host; DID-Connect session tokens are ephemeral).
17
+ // - chainInfo omitted → the 14 payment DID handlers resolve it per-payment in
18
+ // onConnect, exactly as the blocklet-server runtime (which passes none).
19
+ import type { DidConnectRuntime, DidConnectTokenStorage } from '../libs/auth';
20
+ import { createDidConnectJsRuntime } from '../libs/did-connect/runtime-did-connect-js';
21
+
22
+ export function createNodeDidConnectRuntime(opts?: {
23
+ tokenStorage?: DidConnectTokenStorage;
24
+ timeout?: number;
25
+ }): DidConnectRuntime {
26
+ // eslint-disable-next-line global-require, import/no-extraneous-dependencies
27
+ const { createTxEncoder } = require('@ocap/client/encode');
28
+ return createDidConnectJsRuntime({
29
+ tokenStorage: opts?.tokenStorage,
30
+ txEncoder: createTxEncoder(),
31
+ timeout: opts?.timeout ?? 30000,
32
+ });
33
+ }
@@ -0,0 +1,68 @@
1
+ // P3b (README D3 / F3) — clean node static + SPA handler for embedded hosts (arc).
2
+ //
3
+ // Unlike `attachNodeStatic` (serve-static.ts), this is NOT blocklet-server bound:
4
+ // - no isProduction gate, no blockletAppDir (host passes an explicit webRoot)
5
+ // - does NOT reuse the @blocklet/sdk-bound `fallback` middleware, which injects
6
+ // getBlockletJs / theme server-side. This handler serves index.html VERBATIM
7
+ // — the window.blocklet bootstrap is already baked in at BUILD time (P2), so
8
+ // there is ZERO server-side injection (T3b.3).
9
+ //
10
+ // Wired via the host `staticHandler` slot (P3a / service.ts:81), AFTER the
11
+ // api/connect routes. Order (T3b.1): SPA fallback FIRST (html GET/HEAD that is
12
+ // not a RESOURCE_PATTERN asset → index.html), serveStatic AFTER (real asset
13
+ // files; a miss falls through to 404, never index.html — T3b.2).
14
+ import fs from 'fs';
15
+ import path from 'path';
16
+
17
+ import type { Hono } from 'hono';
18
+ // eslint-disable-next-line import/no-extraneous-dependencies
19
+ import { RESOURCE_PATTERN } from '@blocklet/constant';
20
+
21
+ function acceptsHtml(accept: string): boolean {
22
+ if (!accept) return true;
23
+ return accept.includes('text/html') || accept.includes('application/xhtml+xml') || accept.includes('*/*');
24
+ }
25
+
26
+ /**
27
+ * Build a host-injectable static/SPA handler over `webRoot` (the arc webRoot =
28
+ * `@arcblock/payment-service/web`). Returns a `(app) => void` for the
29
+ * staticHandler slot. Throws at construction if webRoot has no index.html
30
+ * (fail fast — a misconfigured webRoot must not silently 404 every navigation).
31
+ */
32
+ export function createNodeStaticHandler(webRoot: string): (app: Hono) => void {
33
+ const indexPath = path.join(webRoot, 'index.html');
34
+ if (!fs.existsSync(indexPath)) {
35
+ throw new Error(`createNodeStaticHandler: webRoot has no index.html: ${indexPath}`);
36
+ }
37
+ // index.html is an immutable build artifact for the process lifetime — read once.
38
+ let cachedIndex: string | null = null;
39
+
40
+ return (app: Hono) => {
41
+ // eslint-disable-next-line global-require, import/no-extraneous-dependencies
42
+ const { serveStatic } = require('@hono/node-server/serve-static');
43
+
44
+ // SPA fallback FIRST — html navigations (not assets) get index.html VERBATIM.
45
+ app.use('*', async (c, next) => {
46
+ const method = c.req.method.toUpperCase();
47
+ if (
48
+ (method !== 'GET' && method !== 'HEAD') ||
49
+ !acceptsHtml(c.req.header('accept') || '') ||
50
+ RESOURCE_PATTERN.test(c.req.path)
51
+ ) {
52
+ return next();
53
+ }
54
+ if (cachedIndex == null) cachedIndex = fs.readFileSync(indexPath, 'utf8');
55
+ return c.html(cachedIndex);
56
+ });
57
+
58
+ // Real asset files. Pass the ABSOLUTE webRoot: serveStatic does
59
+ // `join(root, filename)` + existsSync, so an absolute root resolves the file
60
+ // regardless of process.cwd(). (attachNodeStatic mapped to a cwd-relative
61
+ // path; that breaks in an embedded host whose cwd is not the app dir — e.g.
62
+ // the arc daemon, whose cwd ≠ the node_modules webRoot.) A miss falls
63
+ // through (→ 404).
64
+ app.use('*', serveStatic({ root: webRoot }));
65
+ };
66
+ }
67
+
68
+ export default createNodeStaticHandler;
@@ -0,0 +1,41 @@
1
+ // Node host (blocklet-server) static + SPA serving.
2
+ //
3
+ // S3-CF Phase 1 inversion ①: this is the node-only static/SPA shell that used to
4
+ // live INSIDE buildHonoApp (service.ts). It is moved here so the runtime-neutral
5
+ // hono app carries ZERO node:fs / @hono/node-server — that is what let `http.fetch`
6
+ // converge to a single surface shared by node, CF, and the standalone worker.
7
+ //
8
+ // The blocklet-server entry (bootstrap.ts) injects this as the `staticHandler`
9
+ // slot. It is a verbatim move of the old block (serveStatic + the node:fs
10
+ // fallback), still production-gated, so blocklet-server behavior is unchanged. The
11
+ // CF/standalone worker serves assets via env.ASSETS and injects nothing here, so
12
+ // this module is never reached by the worker bundle.
13
+ import path from 'path';
14
+
15
+ import type { Hono } from 'hono';
16
+
17
+ import { isProduction, blockletAppDir } from '../libs/env';
18
+
19
+ /**
20
+ * Wire production static + SPA fallback onto the full node hono app. No-op outside
21
+ * production (identical to the old `if (isProduction())` gate in buildHonoApp).
22
+ *
23
+ * `fallback` runs first and skips asset paths (RESOURCE_PATTERN) + non-html, so
24
+ * real files fall through to serveStatic and html navigations get the injected
25
+ * index.html. Both modules are required lazily so this file's import stays cheap.
26
+ */
27
+ export function attachNodeStatic(app: Hono): void {
28
+ if (!isProduction()) return;
29
+ // eslint-disable-next-line global-require, import/no-extraneous-dependencies
30
+ const { serveStatic } = require('@hono/node-server/serve-static');
31
+ // eslint-disable-next-line global-require
32
+ const { fallback } = require('../middlewares/hono/fallback');
33
+ const staticDir = path.resolve(blockletAppDir()!, 'dist');
34
+ // serveStatic resolves `root` relative to process.cwd(); map the absolute app
35
+ // dist dir back to a cwd-relative path so it resolves to the same place.
36
+ const staticRoot = path.relative(process.cwd(), staticDir) || '.';
37
+ app.use('*', fallback('index.html', { root: staticDir })); // injected index.html for html GET (skips assets)
38
+ app.use('*', serveStatic({ root: staticRoot })); // real asset files
39
+ }
40
+
41
+ export default attachNodeStatic;