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,264 @@
1
+ // Phase 9 (W2-1b): cron slot driver contract.
2
+ //
3
+ // The cron contract is: register jobs + a due-poll dispatch entry. The HOST
4
+ // provides the trigger — CF `scheduled()` calls runDue() every minute; the Node
5
+ // host uses @abtnode/cron's own scheduler. Both share ONE cron-expression
6
+ // matcher (previously duplicated in cloudflare/shims/cron.ts), so embedded and
7
+ // worker agree on exactly when a job is due.
8
+
9
+ export interface CronJob {
10
+ name: string;
11
+ time: string;
12
+ fn: () => Promise<any> | any;
13
+ /**
14
+ * runOnInit: honored by the node host scheduler (@abtnode/cron) which runs
15
+ * the job once at registration. The shared registry's runDue (used by the
16
+ * cf-cron host) does NOT auto-run it — CF deliberately skips runOnInit and
17
+ * lets the next matching trigger fire it (running at module-init would block
18
+ * the request / risk CPU limits). It is a host-trigger concern, not a matcher
19
+ * concern.
20
+ */
21
+ options?: { runOnInit?: boolean };
22
+ }
23
+
24
+ export interface CronDriver {
25
+ kind: 'node-cron' | 'cf-cron';
26
+ /** register jobs (idempotent: clears + re-adds) */
27
+ register(jobs: CronJob[], onError?: (err: Error, name: string) => void): void;
28
+ /** add a single job after init */
29
+ addJob(name: string, time: string, fn: CronJob['fn'], options?: CronJob['options']): void;
30
+ /** due-poll dispatch entry — runs every job whose schedule matches `now` */
31
+ runDue(now?: Date): Promise<{ ran: string[]; skipped: string[] }>;
32
+ /** run one named job regardless of schedule (manual trigger) */
33
+ runJob(name: string): Promise<void>;
34
+ /** registered job descriptions (name + schedule) */
35
+ getJobNames(): string[];
36
+ /**
37
+ * D2 teardown surface. Stop all live timers (the node-cron driver tears down
38
+ * its @abtnode/cron scheduler so the process has no dangling cron timer); the
39
+ * registry is preserved so a later start() can re-register. No-op for the
40
+ * passive cf-cron driver (it never owns a timer — the host drives runDue).
41
+ */
42
+ stop(): void;
43
+ /** stop + clear the registry (a full reset; start() re-registers from scratch). */
44
+ dispose(): void;
45
+ }
46
+
47
+ // @abtnode/cron's CronScheduler shape we drive (lib/scheduler.js): addJob wires
48
+ // a self-scheduling `cron` CronJob (start=true) and exposes the live jobs map.
49
+ interface AbtnodeCronScheduler {
50
+ jobs: Record<string, { start(): void; stop(): void }>;
51
+ addJob(name: string, time: string, fn: (...args: any[]) => any, options?: Record<string, any>): unknown;
52
+ }
53
+
54
+ // --- shared cron-expression matcher ---
55
+ // 6-field: second minute hour dayOfMonth month dayOfWeek (seconds ignored —
56
+ // CF triggers are minute-level). Supports numbers, *, */N, ranges, lists.
57
+
58
+ function parseField(field: string, min: number, max: number): number[] | null {
59
+ // null means "match all"
60
+ if (field === '*') return null;
61
+
62
+ const values = new Set<number>();
63
+
64
+ for (const part of field.split(',')) {
65
+ const stepMatch = part.match(/^\*\/(\d+)$/);
66
+ const rangeMatch = part.match(/^(\d+)-(\d+)$/);
67
+ if (stepMatch) {
68
+ const step = parseInt(stepMatch[1]!, 10);
69
+ for (let i = min; i <= max; i += step) values.add(i);
70
+ } else if (rangeMatch) {
71
+ const from = parseInt(rangeMatch[1]!, 10);
72
+ const to = parseInt(rangeMatch[2]!, 10);
73
+ for (let i = from; i <= to; i += 1) values.add(i);
74
+ } else {
75
+ const num = parseInt(part, 10);
76
+ if (!Number.isNaN(num)) values.add(num);
77
+ }
78
+ }
79
+
80
+ return values.size > 0 ? Array.from(values) : null;
81
+ }
82
+
83
+ /**
84
+ * Whether `date` matches a 5- or 6-field cron expression (minute granularity).
85
+ */
86
+ export function matchesCron(cronExpr: string, date: Date): boolean {
87
+ const fields = cronExpr.trim().split(/\s+/);
88
+ if (fields.length < 5) return true; // can't parse — run it
89
+
90
+ const offset = fields.length >= 6 ? 1 : 0;
91
+ // length >= 5 guaranteed above, so offset..offset+4 are present
92
+ const minuteField = parseField(fields[offset]!, 0, 59);
93
+ const hourField = parseField(fields[offset + 1]!, 0, 23);
94
+ const domField = parseField(fields[offset + 2]!, 1, 31);
95
+ // cron months are 1-12 (matched against getUTCMonth()+1); a 0-11 range here
96
+ // made step patterns like */3 generate {0,3,6,9} and never match real months.
97
+ const monthField = parseField(fields[offset + 3]!, 1, 12);
98
+ const dowField = parseField(fields[offset + 4]!, 0, 6);
99
+
100
+ const m = date.getUTCMinutes();
101
+ const h = date.getUTCHours();
102
+ const dom = date.getUTCDate();
103
+ const month = date.getUTCMonth() + 1; // JS 0-based → cron 1-based
104
+ const dow = date.getUTCDay();
105
+
106
+ if (minuteField && !minuteField.includes(m)) return false;
107
+ if (hourField && !hourField.includes(h)) return false;
108
+ if (domField && !domField.includes(dom)) return false;
109
+ if (monthField && !monthField.includes(month)) return false;
110
+ if (dowField && !dowField.includes(dow)) return false;
111
+
112
+ return true;
113
+ }
114
+
115
+ /**
116
+ * Whether a cron expression should fire at `date`. Minute-level match — the
117
+ * trigger source (CF scheduled / node cron) fires every minute, so each
118
+ * expression triggers at its designed frequency (see the 2026-04-17 incident
119
+ * note in the prior cloudflare/shims/cron.ts history).
120
+ */
121
+ export function shouldRunInWindow(cronExpr: string, date: Date): boolean {
122
+ return matchesCron(cronExpr, date);
123
+ }
124
+
125
+ /**
126
+ * A cron registry implementing the due-poll dispatch entry on top of the shared
127
+ * matcher. Both the node-cron and cf-cron drivers are built from this — the
128
+ * only difference is who calls runDue (node scheduler vs CF scheduled()).
129
+ */
130
+ export function createCronRegistry(kind: CronDriver['kind']): CronDriver {
131
+ const jobs: CronJob[] = [];
132
+ let onErrorHandler: ((err: Error, name: string) => void) | undefined;
133
+
134
+ // node-cron self-schedules through @abtnode/cron; cf-cron stays a passive
135
+ // matcher registry whose runDue() is driven by the host's scheduled().
136
+ const selfSchedule = kind === 'node-cron';
137
+ let scheduler: AbtnodeCronScheduler | null = null;
138
+
139
+ // Tear down every live @abtnode/cron timer (each scheduler.jobs[*] is a `cron`
140
+ // CronJob with its own setTimeout). Clears the scheduler so the process has no
141
+ // dangling cron handle and a later start() rebuilds from scratch.
142
+ const stopScheduler = (): void => {
143
+ if (!scheduler) return;
144
+ for (const job of Object.values(scheduler.jobs)) {
145
+ try {
146
+ job.stop();
147
+ } catch {
148
+ /* a job already stopped — ignore */
149
+ }
150
+ }
151
+ scheduler = null;
152
+ };
153
+
154
+ // (Re)build the live scheduler from the current jobs array. Always stops the
155
+ // previous scheduler first so register()/start() is idempotent: no double
156
+ // timers, no double registration. Lazy-requires @abtnode/cron so the cf-cron
157
+ // path and pure imports never load the Node scheduler.
158
+ const startScheduler = (): void => {
159
+ stopScheduler();
160
+ if (!selfSchedule || jobs.length === 0) return;
161
+ // eslint-disable-next-line global-require
162
+ const mod = require('@abtnode/cron');
163
+ // real package (CJS): module.exports = { init }; the CF shim (ESM default)
164
+ // resolves to { default: { init } } under the bundler interop.
165
+ type AbtnodeInit = (params: {
166
+ context: any;
167
+ jobs: any[];
168
+ onError: (e: Error, n: string) => void;
169
+ }) => AbtnodeCronScheduler;
170
+ const init: AbtnodeInit = mod.init ?? mod.default?.init;
171
+ // D2/multi: skip runOnInit in multi-tenant mode — same as the CF host
172
+ // (cloudflare/shims/cron.ts). A runOnInit job fires immediately at register,
173
+ // before any request, so it has NO tenant context; in multi mode that makes
174
+ // a tenant-requiring startup push (e.g. deposit.vault / credit.consumption)
175
+ // throw TENANT_CONTEXT_MISSING and abort lifecycle.start(). The job still
176
+ // runs on its next scheduled tick (where the handler resolves its own
177
+ // tenant). Single mode keeps runOnInit (the default tenant is always present).
178
+ // eslint-disable-next-line global-require
179
+ const { getTenantMode } = require('../tenant');
180
+ const isMulti = getTenantMode() === 'multi';
181
+ scheduler = init({
182
+ context: {},
183
+ jobs: jobs.map((j) => ({
184
+ name: j.name,
185
+ time: j.time,
186
+ fn: j.fn,
187
+ options: isMulti ? { ...(j.options || {}), runOnInit: false } : j.options || {},
188
+ })),
189
+ onError: onErrorHandler ?? (() => {}),
190
+ });
191
+ };
192
+
193
+ return {
194
+ kind,
195
+ register(next, onError) {
196
+ jobs.length = 0;
197
+ onErrorHandler = onError;
198
+ for (const job of next || []) {
199
+ if (job.name && job.time && typeof job.fn === 'function') jobs.push(job);
200
+ }
201
+ startScheduler();
202
+ },
203
+ addJob(name, time, fn, options) {
204
+ jobs.push({ name, time, fn, options });
205
+ // keep the live scheduler in sync when one is already running
206
+ if (selfSchedule && scheduler) {
207
+ try {
208
+ scheduler.addJob(name, time, fn, options || {});
209
+ } catch (err: any) {
210
+ onErrorHandler?.(err, name);
211
+ }
212
+ }
213
+ },
214
+ stop() {
215
+ stopScheduler();
216
+ },
217
+ dispose() {
218
+ stopScheduler();
219
+ jobs.length = 0;
220
+ },
221
+ async runDue(now = new Date()) {
222
+ const ran: string[] = [];
223
+ const skipped: string[] = [];
224
+ for (const job of jobs) {
225
+ if (shouldRunInWindow(job.time, now)) {
226
+ ran.push(job.name);
227
+ try {
228
+ // eslint-disable-next-line no-await-in-loop -- jobs run sequentially within a tick
229
+ await job.fn();
230
+ } catch (err: any) {
231
+ onErrorHandler?.(err, job.name);
232
+ }
233
+ } else {
234
+ skipped.push(job.name);
235
+ }
236
+ }
237
+ return { ran, skipped };
238
+ },
239
+ async runJob(name) {
240
+ const job = jobs.find((j) => j.name === name);
241
+ if (!job) return;
242
+ try {
243
+ await job.fn();
244
+ } catch (err: any) {
245
+ onErrorHandler?.(err, name);
246
+ }
247
+ },
248
+ getJobNames() {
249
+ return jobs.map((j) => `${j.name} (${j.time})`);
250
+ },
251
+ };
252
+ }
253
+
254
+ // Active cron driver — injectable by the factory's `cron` slot; defaults to a
255
+ // node-cron registry. The worker shell injects the cf-cron driver.
256
+ let activeCronDriver: CronDriver = createCronRegistry('node-cron');
257
+
258
+ export function setCronDriver(driver: CronDriver): void {
259
+ activeCronDriver = driver;
260
+ }
261
+
262
+ export function getCronDriver(): CronDriver {
263
+ return activeCronDriver;
264
+ }
@@ -0,0 +1,170 @@
1
+ // Phase 8 (W2-1a): db slot driver contract.
2
+ //
3
+ // The db slot carries the SQL layer. Two implementations conform to the same
4
+ // contract:
5
+ // - node driver: wraps the existing Sequelize instance (raw helpers go
6
+ // through `sequelize.query`); `sequelize` is exposed so the factory can
7
+ // bind models to it (unchanged from Phase 7).
8
+ // - d1 driver: wraps a Cloudflare D1 binding (`prepare().bind().run()/all()/
9
+ // first()`), the worker-side implementation that `cloudflare/shims/
10
+ // sequelize-d1` is the model layer for. This phase formalizes the
11
+ // D1Binding contract beneath that model layer; physically relocating the
12
+ // sequelize-d1 files out of cloudflare/shims is part of the Phase 12 §3.1
13
+ // shim cleanup (the small, build-orphan shims — lock.ts, nedb-storage — are
14
+ // handled here since they carried no build-alias risk).
15
+ //
16
+ // The minimal raw surface (`exec/all/get`) is what tenant-agnostic
17
+ // infrastructure (e.g. the did-connect AuthStorage) builds on so a single
18
+ // implementation runs identically on both backends — see auth-storage.ts and
19
+ // the driver consistency suite.
20
+
21
+ export type DbDriverKind = 'node' | 'd1';
22
+
23
+ export interface DbExecResult {
24
+ /** rows affected by an INSERT/UPDATE/DELETE */
25
+ changes: number;
26
+ }
27
+
28
+ /** a single statement in a batch (the shared transactional primitive) */
29
+ export interface DbBatchOp {
30
+ sql: string;
31
+ params?: any[];
32
+ }
33
+
34
+ export interface DbDriver {
35
+ kind: DbDriverKind;
36
+ /** run a write statement; returns affected row count */
37
+ exec(sql: string, params?: any[]): Promise<DbExecResult>;
38
+ /** run a SELECT; returns all rows */
39
+ all<T = any>(sql: string, params?: any[]): Promise<T[]>;
40
+ /** run a SELECT; returns the first row or null */
41
+ get<T = any>(sql: string, params?: any[]): Promise<T | null>;
42
+ /**
43
+ * Run statements atomically. This is the contract's transactional primitive:
44
+ * D1 offers no interactive transactions, only batch-level atomicity, so the
45
+ * shared surface is "all-or-nothing batch" rather than an interactive
46
+ * transaction(fn). Any statement failing rolls the whole batch back. The node
47
+ * driver wraps a real Sequelize transaction; the d1 driver uses binding.batch.
48
+ */
49
+ batch(ops: DbBatchOp[]): Promise<any[]>;
50
+ /**
51
+ * The ORM instance, when the backend is Sequelize-based. Present on the node
52
+ * driver (model binding target); absent on the raw d1 binding driver.
53
+ */
54
+ sequelize?: any;
55
+ }
56
+
57
+ /** D1 binding shape (subset actually used). */
58
+ export interface D1Binding {
59
+ prepare(sql: string): {
60
+ bind(...params: any[]): {
61
+ run(): Promise<{ success?: boolean; meta?: { changes?: number; rows_written?: number } }>;
62
+ all(): Promise<{ results?: any[] }>;
63
+ first(): Promise<any | null>;
64
+ };
65
+ };
66
+ batch(stmts: any[]): Promise<any[]>;
67
+ }
68
+
69
+ /**
70
+ * Node driver — wraps an existing Sequelize instance. Raw helpers route through
71
+ * `sequelize.query` with bind replacements (`$1, $2, ...` style); no string
72
+ * interpolation of values, matching the d1 driver's parameter binding.
73
+ */
74
+ export function createNodeDbDriver(sequelize: any): DbDriver {
75
+ if (!sequelize) throw new Error('createNodeDbDriver: sequelize instance is required');
76
+ // Sequelize positional bind uses `$1`-style markers; callers pass `?` which we
77
+ // translate so the same SQL string works on both drivers.
78
+ const toBind = (sql: string) => {
79
+ let i = 0;
80
+ return sql.replace(/\?/g, () => {
81
+ i += 1;
82
+ return `$${i}`;
83
+ });
84
+ };
85
+ return {
86
+ kind: 'node',
87
+ sequelize,
88
+ async exec(sql, params = []) {
89
+ const [, meta] = await sequelize.query(toBind(sql), { bind: params });
90
+ // sqlite returns affected rows on meta.changes; fall back to 0
91
+ const changes = (meta && (meta.changes ?? meta.rowCount)) ?? 0;
92
+ return { changes };
93
+ },
94
+ async all(sql, params = []) {
95
+ const rows = await sequelize.query(toBind(sql), {
96
+ bind: params,
97
+ type: sequelize.QueryTypes ? sequelize.QueryTypes.SELECT : 'SELECT',
98
+ });
99
+ return rows as any[];
100
+ },
101
+ async get(sql, params = []) {
102
+ const rows = await sequelize.query(toBind(sql), {
103
+ bind: params,
104
+ type: sequelize.QueryTypes ? sequelize.QueryTypes.SELECT : 'SELECT',
105
+ });
106
+ return (rows as any[])[0] ?? null;
107
+ },
108
+ // eslint-disable-next-line require-await -- async contract; returns the transaction promise
109
+ async batch(ops) {
110
+ // real Sequelize transaction — any statement throwing rolls everything back
111
+ return sequelize.transaction(async (t: any) => {
112
+ const results: any[] = [];
113
+ for (const op of ops) {
114
+ // eslint-disable-next-line no-await-in-loop -- ordered within one transaction
115
+ const rows = await sequelize.query(toBind(op.sql), { bind: op.params ?? [], transaction: t });
116
+ results.push(rows);
117
+ }
118
+ return results;
119
+ });
120
+ },
121
+ };
122
+ }
123
+
124
+ /**
125
+ * D1 driver — wraps a Cloudflare D1 binding. This is the worker-side db driver;
126
+ * the sequelize-d1 model layer is built on the same binding. Accepts the
127
+ * binding directly or a lazy getter (the worker resolves the binding per
128
+ * request, so it may be absent at construction time).
129
+ */
130
+ export function createD1DbDriver(binding: D1Binding | (() => D1Binding)): DbDriver {
131
+ if (!binding) throw new Error('createD1DbDriver: D1 binding (or getter) is required');
132
+ const resolve = (): D1Binding => {
133
+ const b = typeof binding === 'function' ? (binding as () => D1Binding)() : binding;
134
+ if (!b) throw new Error('createD1DbDriver: D1 binding is not available');
135
+ return b;
136
+ };
137
+ return {
138
+ kind: 'd1',
139
+ async exec(sql, params = []) {
140
+ const res = await resolve()
141
+ .prepare(sql)
142
+ .bind(...params)
143
+ .run();
144
+ const changes = res?.meta?.changes ?? res?.meta?.rows_written ?? 0;
145
+ return { changes };
146
+ },
147
+ async all(sql, params = []) {
148
+ const res = await resolve()
149
+ .prepare(sql)
150
+ .bind(...params)
151
+ .all();
152
+ return (res?.results ?? []) as any[];
153
+ },
154
+ async get(sql, params = []) {
155
+ const row = await resolve()
156
+ .prepare(sql)
157
+ .bind(...params)
158
+ .first();
159
+ return (row ?? null) as any;
160
+ },
161
+ // eslint-disable-next-line require-await -- async contract; resolve() throws surface as rejection
162
+ async batch(ops) {
163
+ const b = resolve();
164
+ // D1 batch is atomic (implicitly transactional) — a failing statement
165
+ // rolls the batch back, matching the node driver's transaction semantics
166
+ const stmts = ops.map((op) => b.prepare(op.sql).bind(...(op.params ?? [])));
167
+ return b.batch(stmts);
168
+ },
169
+ };
170
+ }
@@ -0,0 +1,142 @@
1
+ // Phase 10 (W2-2): identity slot driver contract.
2
+ //
3
+ // The identity slot resolves a request Host to a tenant (instanceDid) and, from
4
+ // Phase 11, supplies per-tenant encryption keys. It mirrors the relevant slice
5
+ // of BlockletServiceRPCInterface (resolveInstanceDidForHost). The CF identity
6
+ // shims (blocklet-sdk/{auth-service,session,verify-session,verify-sign}) sit
7
+ // behind this contract.
8
+ //
9
+ // Resolution is single-point: ONLY the tenant-resolving middleware calls
10
+ // resolveInstanceDidForHost (the scanner enforces that no route/queue/cron
11
+ // reads Host directly). In single-tenant mode the default driver ignores the
12
+ // host and returns the deployment app DID, so Blocklet Server behavior is
13
+ // unchanged; multi-tenant hosts inject a driver that maps Host -> instanceDid
14
+ // and returns null for unknown hosts (the middleware then fails closed 4xx).
15
+
16
+ import type { WalletObject } from '@ocap/wallet';
17
+
18
+ import { getDefaultInstanceDid, getTenantMode, TenantError, TENANT_HOST_UNRESOLVED } from '../tenant';
19
+
20
+ /** Branding/profile used by DID-Connect prompts (mirrors did-connect-service InstanceAppInfoDTO). */
21
+ export interface InstanceAppInfo {
22
+ name?: string;
23
+ description?: string;
24
+ icon?: string;
25
+ link?: string;
26
+ }
27
+
28
+ /**
29
+ * Per-instance app signing identity used to build the DID-Connect authenticator
30
+ * (mirrors did-connect-service@4.0.3 InstanceAppIdentityDTO). The AUTH_SERVICE
31
+ * RPC `getInstanceAppIdentity(instanceDid)` already resolves the arc run-mode
32
+ * (instance `app:sk` → instance identity; else auth-service root `APP_SK/APP_PSK`;
33
+ * else fail-closed), so the payment runtime never reimplements "instance ?? root".
34
+ */
35
+ export interface InstanceAppIdentity {
36
+ /** Current app signing key for this instance. */
37
+ appSk: string;
38
+ /** Permanent app signing key, present when the instance has rotated keys. */
39
+ appPsk?: string;
40
+ /** Branding/profile for DID-Connect prompts. */
41
+ appInfo?: InstanceAppInfo;
42
+ }
43
+
44
+ export interface IdentityDriver {
45
+ /**
46
+ * Resolve a request Host to its tenant instanceDid, or null/undefined when
47
+ * the host is unknown (multi-tenant fail-closed). The host value is the raw
48
+ * Host header — never a proxy header.
49
+ */
50
+ resolveInstanceDidForHost(host: string | undefined): Promise<string | null> | string | null;
51
+ /**
52
+ * Phase 11: the tenant's encryption key (EK). Required by the keyring secrets
53
+ * driver; optional on the contract because the single-tenant default secrets
54
+ * driver uses the process key and never calls it.
55
+ */
56
+ getAppEk?(instanceDid: string): Promise<string> | string;
57
+ /**
58
+ * S3-CF (DID convergence): the per-instance app signing identity backing the
59
+ * DID-Connect authenticator. The AUTH_SERVICE-backed driver (CF + arc-node
60
+ * embedded) implements this via `AUTH_SERVICE.getInstanceAppIdentity`; the
61
+ * blocklet-server runtime uses the @blocklet/sdk wallet wrapper and never calls
62
+ * it. Optional on the contract for that reason — `resolveTenantIdentity` throws
63
+ * a clear error if a non-SDK runtime reaches a driver that lacks it.
64
+ */
65
+ getInstanceAppIdentity?(instanceDid: string): Promise<InstanceAppIdentity> | InstanceAppIdentity;
66
+ /**
67
+ * The current tenant's business chain wallet (the receiving address + on-chain
68
+ * tx/refund/payout signer). SYNCHRONOUS — `wallet.address` is read inline on hot
69
+ * paths — so it reads the warmed per-tenant cache (see warmTenantIdentity), never
70
+ * an RPC. Present on AUTH_SERVICE-backed drivers (arc-node + CF, derived from
71
+ * getInstanceAppIdentity); ABSENT on the blocklet-server default, where auth.ts
72
+ * falls back to the @blocklet/sdk env wallet (which alone handles the remote-sign
73
+ * / delegation / migration cases a bare appSk cannot). This is the single seam:
74
+ * a consumer never branches on the runtime, only on "does the driver provide it".
75
+ */
76
+ getBusinessWallet?(chain: 'arcblock' | 'ethereum'): WalletObject;
77
+ /**
78
+ * The host user directory (`blocklet.getUser` etc.). Present on the arc-node
79
+ * embedded driver (a DID-echo: the DID-Connect DID IS the wallet DID); ABSENT on
80
+ * blocklet-server (real BlockletService) and on CF (its build-alias shim). auth.ts
81
+ * uses `driver.directory?.() ?? <real BlockletService>` — same one-seam pattern.
82
+ */
83
+ directory?(): BlockletDirectory;
84
+ }
85
+
86
+ /** The subset of @blocklet/sdk BlockletService the payment runtime calls. */
87
+ export interface BlockletDirectory {
88
+ getUser(did: string, opts?: any): Promise<{ user: any }> | { user: any };
89
+ getUsers(params?: any): Promise<{ users: any[] }> | { users: any[] };
90
+ getVault(): Promise<any> | any;
91
+ getBlocklet(): Promise<any> | any;
92
+ }
93
+
94
+ /**
95
+ * Single-tenant default: every host maps to the deployment app DID. Used by
96
+ * Blocklet Server and the standalone worker's single-mode transition.
97
+ */
98
+ export function createDefaultIdentityDriver(): IdentityDriver {
99
+ return {
100
+ resolveInstanceDidForHost() {
101
+ return getDefaultInstanceDid();
102
+ },
103
+ };
104
+ }
105
+
106
+ let activeIdentityDriver: IdentityDriver = createDefaultIdentityDriver();
107
+
108
+ /** Inject the identity driver (multi-tenant hosts wire a Host->tenant map here). */
109
+ export function setIdentityDriver(driver: IdentityDriver): void {
110
+ activeIdentityDriver = driver;
111
+ }
112
+
113
+ export function getIdentityDriver(): IdentityDriver {
114
+ return activeIdentityDriver;
115
+ }
116
+
117
+ /**
118
+ * Resolve a request's raw Host header to a tenant instanceDid. SINGLE POINT of
119
+ * Host -> tenant resolution — both host adapters funnel through here (the
120
+ * Express `contextMiddleware` and the CF worker `tenantMiddleware`), so neither
121
+ * reinvents Host parsing (the tenant-scan rule forbids any route/queue/cron
122
+ * from reading Host directly).
123
+ *
124
+ * single mode: always the deployment app DID (Blocklet Server / standalone
125
+ * worker unchanged). multi mode: the identity driver maps Host -> instanceDid;
126
+ * an unknown/missing host throws TENANT_HOST_UNRESOLVED so the caller fails
127
+ * closed 4xx with no default-tenant fallback.
128
+ *
129
+ * The host MUST be the raw Host header — never a proxy header
130
+ * (X-Forwarded-Host / X-Real-IP). The runtime host (CF route / blocklet server /
131
+ * reverse proxy) is responsible for ensuring it cannot be forged by the client.
132
+ */
133
+ export async function resolveTenantForHost(host: string | undefined): Promise<string> {
134
+ if (getTenantMode() === 'single') {
135
+ return getDefaultInstanceDid();
136
+ }
137
+ const resolved = await activeIdentityDriver.resolveInstanceDidForHost(host);
138
+ if (!resolved) {
139
+ throw new TenantError(TENANT_HOST_UNRESOLVED, `no tenant resolved for host "${host ?? ''}"`);
140
+ }
141
+ return resolved;
142
+ }
@@ -0,0 +1,40 @@
1
+ // Phase 8 (W2-1a): db / locks driver contracts barrel.
2
+ export type { DbDriver, DbDriverKind, DbExecResult, D1Binding } from './db';
3
+ export { createNodeDbDriver, createD1DbDriver } from './db';
4
+
5
+ export type { LockHandle, LocksDriver, LocksDriverKind, LockScope } from './locks';
6
+ export { createMemoryLocksDriver, createD1LocksDriver, scopedLockName, MemoryLock } from './locks';
7
+
8
+ export type { AuthRecord } from './auth-storage';
9
+ export { DbAuthStorage, createAuthStorage } from './auth-storage';
10
+
11
+ export type { QueueOptions, PushParams, JobEvents, QueueHandle, QueueFactory, QueueHostHooks } from './queue';
12
+ export { nodeQueueHostHooks, setQueueHostHooks, getQueueHostHooks } from './queue';
13
+
14
+ export type { CronJob, CronDriver } from './cron';
15
+ export { matchesCron, shouldRunInWindow, createCronRegistry, setCronDriver, getCronDriver } from './cron';
16
+
17
+ export type { SqlMigration } from './migrate-runner';
18
+ export { applySqlMigrations, splitStatements } from './migrate-runner';
19
+
20
+ // The embedded D1 SQL lineage, inlined (store/sql-migrations is generated from
21
+ // cloudflare/migrations/*.sql) so it ships INSIDE the @arcblock/payment-service
22
+ // bundle. A host provisions the embedded schema with applyPaymentCoreMigrations
23
+ // alone — no repo paths, no wrangler. See migrate-runner.ts.
24
+ // eslint-disable-next-line import/first
25
+ import type { DbDriver as DbDriverForMigrations } from './db';
26
+ // eslint-disable-next-line import/first
27
+ import { applySqlMigrations as applySql } from './migrate-runner';
28
+ // eslint-disable-next-line import/first
29
+ import { paymentCoreSqlMigrations } from '../../store/sql-migrations';
30
+
31
+ export { paymentCoreSqlMigrations };
32
+ export function applyPaymentCoreMigrations(driver: DbDriverForMigrations): Promise<string[]> {
33
+ return applySql(driver, paymentCoreSqlMigrations);
34
+ }
35
+
36
+ export type { IdentityDriver, InstanceAppIdentity, InstanceAppInfo, BlockletDirectory } from './identity';
37
+ export { createDefaultIdentityDriver, setIdentityDriver, getIdentityDriver } from './identity';
38
+
39
+ export type { SecretsDriver } from './secrets';
40
+ export { createDefaultSecretsDriver, createKeyringSecretsDriver, setSecretsDriver, getSecretsDriver } from './secrets';