payment-kit 1.29.0 → 1.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/api/dev.ts +41 -2
  2. package/api/hono.d.ts +42 -0
  3. package/api/node-sqlite.d.ts +12 -0
  4. package/api/src/bootstrap.ts +36 -0
  5. package/api/src/crons/base.ts +3 -3
  6. package/api/src/crons/currency.ts +1 -1
  7. package/api/src/crons/index.ts +27 -24
  8. package/api/src/crons/metering-subscription-detection.ts +1 -1
  9. package/api/src/crons/overdue-detection.ts +2 -2
  10. package/api/src/crons/retry-pending-events.ts +6 -0
  11. package/api/src/index.ts +22 -161
  12. package/api/src/integrations/app-store/client.ts +3 -4
  13. package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
  14. package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
  15. package/api/src/integrations/arcblock/token.ts +21 -7
  16. package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
  17. package/api/src/integrations/google-play/handlers/voided.ts +2 -2
  18. package/api/src/integrations/google-play/verify.ts +3 -2
  19. package/api/src/integrations/iap-reconcile.ts +3 -5
  20. package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
  21. package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
  22. package/api/src/libs/archive/query.ts +19 -0
  23. package/api/src/libs/audit.ts +61 -4
  24. package/api/src/libs/auth.ts +99 -38
  25. package/api/src/libs/context.ts +78 -1
  26. package/api/src/libs/currency.ts +2 -2
  27. package/api/src/libs/dayjs.ts +8 -2
  28. package/api/src/libs/drivers/auth-storage.ts +118 -0
  29. package/api/src/libs/drivers/cron.ts +264 -0
  30. package/api/src/libs/drivers/db.ts +170 -0
  31. package/api/src/libs/drivers/identity.ts +81 -0
  32. package/api/src/libs/drivers/index.ts +40 -0
  33. package/api/src/libs/drivers/locks.ts +226 -0
  34. package/api/src/libs/drivers/migrate-runner.ts +70 -0
  35. package/api/src/libs/drivers/queue.ts +104 -0
  36. package/api/src/libs/drivers/secrets.ts +194 -0
  37. package/api/src/libs/env.ts +170 -54
  38. package/api/src/libs/exchange-rate/service.ts +7 -6
  39. package/api/src/libs/http-fetch-adapter.ts +50 -0
  40. package/api/src/libs/invoice.ts +1 -1
  41. package/api/src/libs/lock.ts +51 -47
  42. package/api/src/libs/logger.ts +48 -8
  43. package/api/src/libs/notification/index.ts +1 -1
  44. package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
  45. package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
  46. package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
  47. package/api/src/libs/overdraft-protection.ts +1 -1
  48. package/api/src/libs/payout.ts +1 -1
  49. package/api/src/libs/queue/index.ts +259 -52
  50. package/api/src/libs/queue/runtime.ts +175 -0
  51. package/api/src/libs/resource.ts +3 -3
  52. package/api/src/libs/secrets.ts +38 -0
  53. package/api/src/libs/session.ts +3 -2
  54. package/api/src/libs/subscription.ts +5 -5
  55. package/api/src/libs/tenant.ts +92 -0
  56. package/api/src/libs/url.ts +3 -3
  57. package/api/src/libs/util.ts +21 -13
  58. package/api/src/middlewares/hono/cdn.ts +63 -0
  59. package/api/src/middlewares/hono/context.ts +73 -0
  60. package/api/src/middlewares/hono/csrf.ts +72 -0
  61. package/api/src/middlewares/hono/fallback.ts +194 -0
  62. package/api/src/middlewares/hono/pipeline.ts +73 -0
  63. package/api/src/middlewares/hono/resource-mount.ts +42 -0
  64. package/api/src/middlewares/hono/resource.ts +63 -0
  65. package/api/src/middlewares/hono/security.ts +214 -0
  66. package/api/src/middlewares/hono/session.ts +114 -0
  67. package/api/src/middlewares/hono/xss.ts +61 -0
  68. package/api/src/queues/auto-recharge.ts +12 -10
  69. package/api/src/queues/checkout-session.ts +17 -12
  70. package/api/src/queues/credit-consume.ts +40 -36
  71. package/api/src/queues/credit-grant.ts +25 -18
  72. package/api/src/queues/credit-reconciliation.ts +7 -5
  73. package/api/src/queues/discount-status.ts +9 -6
  74. package/api/src/queues/event.ts +12 -4
  75. package/api/src/queues/exchange-rate-health.ts +49 -30
  76. package/api/src/queues/invoice.ts +18 -15
  77. package/api/src/queues/notification.ts +14 -7
  78. package/api/src/queues/payment.ts +41 -28
  79. package/api/src/queues/payout.ts +9 -5
  80. package/api/src/queues/refund.ts +18 -12
  81. package/api/src/queues/subscription.ts +83 -53
  82. package/api/src/queues/token-transfer.ts +15 -10
  83. package/api/src/queues/usage-record.ts +8 -5
  84. package/api/src/queues/vendors/commission.ts +7 -5
  85. package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
  86. package/api/src/queues/vendors/fulfillment.ts +4 -2
  87. package/api/src/queues/vendors/return-processor.ts +5 -3
  88. package/api/src/queues/vendors/return-scanner.ts +5 -4
  89. package/api/src/queues/vendors/status-check.ts +10 -7
  90. package/api/src/queues/webhook.ts +60 -32
  91. package/api/src/routes/connect/shared.ts +1 -2
  92. package/api/src/routes/connect/subscribe.ts +3 -3
  93. package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
  94. package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
  95. package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
  96. package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
  97. package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
  98. package/api/src/routes/hono/credit-tokens.ts +43 -0
  99. package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
  100. package/api/src/routes/{customers.ts → hono/customers.ts} +193 -223
  101. package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
  102. package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
  103. package/api/src/routes/{events.ts → hono/events.ts} +107 -71
  104. package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
  105. package/api/src/routes/hono/exchange-rates.ts +77 -0
  106. package/api/src/routes/hono/index.ts +115 -0
  107. package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
  108. package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
  109. package/api/src/routes/hono/integrations/stripe.ts +74 -0
  110. package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
  111. package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
  112. package/api/src/routes/hono/meters.ts +288 -0
  113. package/api/src/routes/hono/passports.ts +73 -0
  114. package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
  115. package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
  116. package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
  117. package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
  118. package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
  119. package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
  120. package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
  121. package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
  122. package/api/src/routes/{products.ts → hono/products.ts} +172 -159
  123. package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
  124. package/api/src/routes/hono/redirect.ts +24 -0
  125. package/api/src/routes/{refunds.ts → hono/refunds.ts} +96 -80
  126. package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
  127. package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
  128. package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
  129. package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
  130. package/api/src/routes/hono/tool.ts +69 -0
  131. package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
  132. package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
  133. package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
  134. package/api/src/routes/hono/webhook-endpoints.ts +126 -0
  135. package/api/src/service.ts +667 -0
  136. package/api/src/store/migrations/20230911-seeding.ts +2 -1
  137. package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
  138. package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
  139. package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
  140. package/api/src/store/models/auto-recharge-config.ts +22 -10
  141. package/api/src/store/models/checkout-session.ts +15 -14
  142. package/api/src/store/models/coupon.ts +29 -20
  143. package/api/src/store/models/credit-grant.ts +38 -29
  144. package/api/src/store/models/credit-transaction.ts +32 -21
  145. package/api/src/store/models/customer.ts +19 -17
  146. package/api/src/store/models/discount.ts +11 -2
  147. package/api/src/store/models/entitlement-grant.ts +21 -9
  148. package/api/src/store/models/entitlement-product.ts +21 -9
  149. package/api/src/store/models/entitlement.ts +19 -10
  150. package/api/src/store/models/event.ts +18 -9
  151. package/api/src/store/models/exchange-rate-provider.ts +17 -4
  152. package/api/src/store/models/invoice-item.ts +18 -9
  153. package/api/src/store/models/invoice.ts +16 -8
  154. package/api/src/store/models/meter-event.ts +27 -9
  155. package/api/src/store/models/meter.ts +31 -22
  156. package/api/src/store/models/payment-currency.ts +25 -8
  157. package/api/src/store/models/payment-intent.ts +15 -6
  158. package/api/src/store/models/payment-link.ts +15 -6
  159. package/api/src/store/models/payment-method.ts +38 -22
  160. package/api/src/store/models/payment-stat.ts +18 -9
  161. package/api/src/store/models/payout.ts +15 -6
  162. package/api/src/store/models/price-quote.ts +17 -8
  163. package/api/src/store/models/price.ts +24 -12
  164. package/api/src/store/models/pricing-table.ts +29 -20
  165. package/api/src/store/models/product-vendor.ts +20 -10
  166. package/api/src/store/models/product.ts +15 -6
  167. package/api/src/store/models/promotion-code.ts +14 -6
  168. package/api/src/store/models/refund.ts +15 -6
  169. package/api/src/store/models/revenue-snapshot.ts +21 -9
  170. package/api/src/store/models/setting.ts +18 -9
  171. package/api/src/store/models/setup-intent.ts +36 -27
  172. package/api/src/store/models/subscription-item.ts +21 -9
  173. package/api/src/store/models/subscription-schedule.ts +21 -9
  174. package/api/src/store/models/subscription.ts +21 -10
  175. package/api/src/store/models/tax-rate.ts +29 -21
  176. package/api/src/store/models/usage-record.ts +11 -2
  177. package/api/src/store/models/webhook-attempt.ts +18 -9
  178. package/api/src/store/models/webhook-endpoint.ts +18 -9
  179. package/api/src/store/scoped-core.ts +55 -0
  180. package/api/src/store/scoped.ts +247 -0
  181. package/api/src/store/sequelize.ts +66 -22
  182. package/api/src/store/sql-migrations.ts +20 -0
  183. package/api/src/store/tenant-backfill.ts +260 -0
  184. package/api/src/store/tenant-model.ts +124 -0
  185. package/api/src/store/tenant-tables.ts +50 -0
  186. package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
  187. package/api/tests/fixtures/bare-query-violation.ts +13 -0
  188. package/api/tests/fixtures/core-env-violation.ts +10 -0
  189. package/api/tests/fixtures/host-read-violation.ts +19 -0
  190. package/api/tests/fixtures/tenants.ts +4 -0
  191. package/api/tests/integrations/iap-tenant.spec.ts +284 -0
  192. package/api/tests/libs/archive-query.spec.ts +26 -0
  193. package/api/tests/libs/audit-tenant.spec.ts +153 -0
  194. package/api/tests/libs/context.spec.ts +204 -0
  195. package/api/tests/libs/core-config.spec.ts +115 -0
  196. package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
  197. package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
  198. package/api/tests/libs/lock-tenant.spec.ts +66 -0
  199. package/api/tests/libs/scoped.spec.ts +222 -0
  200. package/api/tests/libs/secrets-facade.spec.ts +52 -0
  201. package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
  202. package/api/tests/libs/tenant-middleware.spec.ts +42 -0
  203. package/api/tests/libs/tenant-scanner.spec.ts +120 -0
  204. package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
  205. package/api/tests/middlewares/hono/context.spec.ts +113 -0
  206. package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
  207. package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
  208. package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
  209. package/api/tests/middlewares/hono/security.spec.ts +181 -0
  210. package/api/tests/middlewares/hono/session.spec.ts +42 -0
  211. package/api/tests/middlewares/hono/xss.spec.ts +81 -0
  212. package/api/tests/models/tenant-backfill.spec.ts +287 -0
  213. package/api/tests/models/tenant-columns-model.spec.ts +46 -0
  214. package/api/tests/models/tenant-columns.spec.ts +161 -0
  215. package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
  216. package/api/tests/queues/credit-consume.spec.ts +8 -1
  217. package/api/tests/queues/event-tenant.spec.ts +236 -0
  218. package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
  219. package/api/tests/queues/queue-parity.spec.ts +249 -0
  220. package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
  221. package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
  222. package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
  223. package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
  224. package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
  225. package/api/tests/service/collapse.spec.ts +96 -0
  226. package/api/tests/store/tenant-crosscut.spec.ts +202 -0
  227. package/api/tests/store/tenant-model-spike.spec.ts +177 -0
  228. package/api/tests/store/tenant-model.spec.ts +162 -0
  229. package/api/tests/store/tenant-residual.spec.ts +196 -0
  230. package/api/third.d.ts +4 -0
  231. package/blocklet.yml +1 -1
  232. package/cloudflare/README.md +26 -6
  233. package/cloudflare/build.ts +28 -13
  234. package/cloudflare/did-connect-auth.ts +0 -217
  235. package/cloudflare/docs/2026-06-10-bundle-size-analysis.md +288 -0
  236. package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
  237. package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
  238. package/cloudflare/migrations/0008_schema_parity.sql +16 -0
  239. package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
  240. package/cloudflare/queue-runtime-mode.ts +13 -0
  241. package/cloudflare/run-build.js +31 -56
  242. package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
  243. package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
  244. package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
  245. package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
  246. package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
  247. package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
  248. package/cloudflare/shims/blocklet-sdk/util-csrf.ts +13 -0
  249. package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
  250. package/cloudflare/shims/cron.ts +38 -158
  251. package/cloudflare/shims/events.ts +124 -0
  252. package/cloudflare/shims/fastq.ts +15 -1
  253. package/cloudflare/shims/nedb-storage.ts +16 -8
  254. package/cloudflare/shims/node-fetch.ts +35 -0
  255. package/cloudflare/shims/xss.ts +8 -0
  256. package/cloudflare/tenant-middleware.ts +36 -0
  257. package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
  258. package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
  259. package/cloudflare/worker.ts +204 -433
  260. package/cloudflare/wrangler.local-e2e.jsonc +26 -0
  261. package/jest.config.js +3 -1
  262. package/package.json +33 -38
  263. package/scripts/core-env-whitelist.json +1 -0
  264. package/scripts/e2e-12b-runtime.ts +149 -0
  265. package/scripts/e2e-core-config.ts +125 -0
  266. package/scripts/e2e-d1-tenancy.ts +116 -0
  267. package/scripts/e2e-d2-cron-queue.ts +139 -0
  268. package/scripts/e2e-d3-embedded-multi.ts +171 -0
  269. package/scripts/e2e-hono-s2.ts +125 -0
  270. package/scripts/e2e-hono-s3e.ts +135 -0
  271. package/scripts/e2e-hono-s4.ts +114 -0
  272. package/scripts/e2e-migration-contract.ts +100 -0
  273. package/scripts/e2e-s0.ts +61 -0
  274. package/scripts/e2e-s1.ts +107 -0
  275. package/scripts/e2e-s2.ts +178 -0
  276. package/scripts/e2e-s3.ts +110 -0
  277. package/scripts/e2e-s4.ts +191 -0
  278. package/scripts/e2e-s5.ts +139 -0
  279. package/scripts/e2e-s6.ts +127 -0
  280. package/scripts/e2e-tenant-model.ts +119 -0
  281. package/scripts/e2e-tenant-worker.ts +199 -0
  282. package/scripts/gen-sql-migrations.js +46 -0
  283. package/scripts/phase8-codemod.js +219 -0
  284. package/scripts/phase9a-env-getters-codemod.js +82 -0
  285. package/scripts/scan-core-env.js +109 -0
  286. package/scripts/scan-tenant-queries.js +235 -0
  287. package/scripts/schema-drift-guard.ts +210 -0
  288. package/scripts/tenant-scan-whitelist.json +1 -0
  289. package/src/env.d.ts +13 -1
  290. package/tsconfig.json +1 -1
  291. package/api/src/libs/did-space.ts +0 -235
  292. package/api/src/libs/middleware.ts +0 -50
  293. package/api/src/libs/security.ts +0 -192
  294. package/api/src/queues/space.ts +0 -662
  295. package/api/src/routes/credit-tokens.ts +0 -38
  296. package/api/src/routes/exchange-rates.ts +0 -87
  297. package/api/src/routes/index.ts +0 -142
  298. package/api/src/routes/integrations/stripe.ts +0 -61
  299. package/api/src/routes/meters.ts +0 -274
  300. package/api/src/routes/passports.ts +0 -68
  301. package/api/src/routes/redirect.ts +0 -20
  302. package/api/src/routes/tool.ts +0 -65
  303. package/api/src/routes/webhook-endpoints.ts +0 -126
  304. package/api/tests/routes/credit-grants.spec.ts +0 -1261
  305. package/cloudflare/shims/did-space-js.ts +0 -17
  306. package/cloudflare/shims/did-space.ts +0 -11
  307. package/cloudflare/shims/express-compat/index.ts +0 -80
  308. package/cloudflare/shims/express-compat/types.ts +0 -41
  309. package/cloudflare/shims/lock.ts +0 -115
  310. package/cloudflare/shims/queue.ts +0 -611
  311. package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
  312. package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
@@ -0,0 +1,65 @@
1
+ -- Payment Kit: tenant unique keys + indexes (Phase 2, W1-1b)
2
+ -- Mirrors the index/unique-key part of api/src/store/migrations/20260611-tenant-backfill.ts.
3
+ --
4
+ -- IMPORTANT: the instance_did BACKFILL is NOT here. Static migration SQL
5
+ -- cannot know the deployment app DID, so the backfill (and the table rebuilds
6
+ -- that drop old inline single-column UNIQUE constraints) runs through the
7
+ -- shared runtime routine `runTenantBackfill()` (api/src/store/tenant-backfill.ts),
8
+ -- invoked idempotently from the worker's scheduled() handler.
9
+ --
10
+ -- Everything below is NULL-safe before that backfill runs: SQLite treats each
11
+ -- NULL as distinct in unique indexes, and existing single-column uniqueness
12
+ -- (identifier / did / key / idempotency_key) guarantees no composite dupes.
13
+
14
+ -- composite tenant unique keys (W1 §2.1, decisions D1/D3)
15
+ CREATE UNIQUE INDEX IF NOT EXISTS `uq_customers_tenant_did` ON `customers` (`instance_did`, `did`);
16
+ CREATE UNIQUE INDEX IF NOT EXISTS `uq_meter_events_tenant_identifier` ON `meter_events` (`instance_did`, `identifier`);
17
+ CREATE UNIQUE INDEX IF NOT EXISTS `uq_meters_tenant_event_name` ON `meters` (`instance_did`, `event_name`);
18
+ CREATE UNIQUE INDEX IF NOT EXISTS `uq_entitlements_tenant_key` ON `entitlements` (`instance_did`, `key`);
19
+ CREATE UNIQUE INDEX IF NOT EXISTS `uq_price_quotes_tenant_idem` ON `price_quotes` (`instance_did`, `idempotency_key`);
20
+ CREATE UNIQUE INDEX IF NOT EXISTS `uq_promotion_codes_tenant_code` ON `promotion_codes` (`instance_did`, `livemode`, `code`);
21
+ CREATE UNIQUE INDEX IF NOT EXISTS `uq_product_vendors_tenant_key` ON `product_vendors` (`instance_did`, `vendor_key`);
22
+ CREATE UNIQUE INDEX IF NOT EXISTS `uq_revenue_snapshots_tenant` ON `revenue_snapshots` (`instance_did`, `timestamp`, `currency_id`, `livemode`, `period_type`);
23
+ DROP INDEX IF EXISTS `idx_revenue_snapshots_unique`;
24
+ DROP INDEX IF EXISTS `idx_entitlements_key`;
25
+ DROP INDEX IF EXISTS `idx_pq_idempotency`;
26
+
27
+ -- plain tenant indexes for scoped queries (Phase 3+); meter_events is served
28
+ -- by its composite unique above (high-write table, avoid a second index)
29
+ CREATE INDEX IF NOT EXISTS `idx_customers_instance_did` ON `customers` (`instance_did`);
30
+ CREATE INDEX IF NOT EXISTS `idx_products_instance_did` ON `products` (`instance_did`);
31
+ CREATE INDEX IF NOT EXISTS `idx_prices_instance_did` ON `prices` (`instance_did`);
32
+ CREATE INDEX IF NOT EXISTS `idx_pricing_tables_instance_did` ON `pricing_tables` (`instance_did`);
33
+ CREATE INDEX IF NOT EXISTS `idx_payment_methods_instance_did` ON `payment_methods` (`instance_did`);
34
+ CREATE INDEX IF NOT EXISTS `idx_checkout_sessions_instance_did` ON `checkout_sessions` (`instance_did`);
35
+ CREATE INDEX IF NOT EXISTS `idx_payment_intents_instance_did` ON `payment_intents` (`instance_did`);
36
+ CREATE INDEX IF NOT EXISTS `idx_payment_links_instance_did` ON `payment_links` (`instance_did`);
37
+ CREATE INDEX IF NOT EXISTS `idx_setup_intents_instance_did` ON `setup_intents` (`instance_did`);
38
+ CREATE INDEX IF NOT EXISTS `idx_price_quotes_instance_did` ON `price_quotes` (`instance_did`);
39
+ CREATE INDEX IF NOT EXISTS `idx_subscriptions_instance_did` ON `subscriptions` (`instance_did`);
40
+ CREATE INDEX IF NOT EXISTS `idx_subscription_items_instance_did` ON `subscription_items` (`instance_did`);
41
+ CREATE INDEX IF NOT EXISTS `idx_subscription_schedules_instance_did` ON `subscription_schedules` (`instance_did`);
42
+ CREATE INDEX IF NOT EXISTS `idx_invoices_instance_did` ON `invoices` (`instance_did`);
43
+ CREATE INDEX IF NOT EXISTS `idx_invoice_items_instance_did` ON `invoice_items` (`instance_did`);
44
+ CREATE INDEX IF NOT EXISTS `idx_refunds_instance_did` ON `refunds` (`instance_did`);
45
+ CREATE INDEX IF NOT EXISTS `idx_credit_grants_instance_did` ON `credit_grants` (`instance_did`);
46
+ CREATE INDEX IF NOT EXISTS `idx_credit_transactions_instance_did` ON `credit_transactions` (`instance_did`);
47
+ CREATE INDEX IF NOT EXISTS `idx_meters_instance_did` ON `meters` (`instance_did`);
48
+ CREATE INDEX IF NOT EXISTS `idx_usage_records_instance_did` ON `usage_records` (`instance_did`);
49
+ CREATE INDEX IF NOT EXISTS `idx_coupons_instance_did` ON `coupons` (`instance_did`);
50
+ CREATE INDEX IF NOT EXISTS `idx_promotion_codes_instance_did` ON `promotion_codes` (`instance_did`);
51
+ CREATE INDEX IF NOT EXISTS `idx_discounts_instance_did` ON `discounts` (`instance_did`);
52
+ CREATE INDEX IF NOT EXISTS `idx_entitlements_instance_did` ON `entitlements` (`instance_did`);
53
+ CREATE INDEX IF NOT EXISTS `idx_entitlement_grants_instance_did` ON `entitlement_grants` (`instance_did`);
54
+ CREATE INDEX IF NOT EXISTS `idx_entitlement_products_instance_did` ON `entitlement_products` (`instance_did`);
55
+ CREATE INDEX IF NOT EXISTS `idx_events_instance_did` ON `events` (`instance_did`);
56
+ CREATE INDEX IF NOT EXISTS `idx_webhook_endpoints_instance_did` ON `webhook_endpoints` (`instance_did`);
57
+ CREATE INDEX IF NOT EXISTS `idx_webhook_attempts_instance_did` ON `webhook_attempts` (`instance_did`);
58
+ CREATE INDEX IF NOT EXISTS `idx_payouts_instance_did` ON `payouts` (`instance_did`);
59
+ CREATE INDEX IF NOT EXISTS `idx_payment_stats_instance_did` ON `payment_stats` (`instance_did`);
60
+ CREATE INDEX IF NOT EXISTS `idx_revenue_snapshots_instance_did` ON `revenue_snapshots` (`instance_did`);
61
+ CREATE INDEX IF NOT EXISTS `idx_auto_recharge_configs_instance_did` ON `auto_recharge_configs` (`instance_did`);
62
+ CREATE INDEX IF NOT EXISTS `idx_tax_rates_instance_did` ON `tax_rates` (`instance_did`);
63
+ CREATE INDEX IF NOT EXISTS `idx_settings_instance_did` ON `settings` (`instance_did`);
64
+ CREATE INDEX IF NOT EXISTS `idx_payment_currencies_instance_did` ON `payment_currencies` (`instance_did`);
65
+ CREATE INDEX IF NOT EXISTS `idx_product_vendors_instance_did` ON `product_vendors` (`instance_did`);
@@ -0,0 +1,16 @@
1
+ -- Phase 11 (W2′) schema parity: close the D1-lineage gaps the schema-drift guard
2
+ -- (scripts/schema-drift-guard.ts) found versus the Umzug canonical schema.
3
+ -- Additive only — safe to apply on a deployed D1 (no drops, no rewrites).
4
+
5
+ -- events: the Umzug genesis creates `events` with api_version (NOT NULL) and
6
+ -- metadata (JSON); the D1 0001 events table predates both, so the worker could
7
+ -- not write them. Backfill api_version with the app constant
8
+ -- (api/src/libs/audit.ts API_VERSION = '2023-09-05'); new rows carry the same.
9
+ ALTER TABLE `events` ADD COLUMN `api_version` VARCHAR(16) NOT NULL DEFAULT '2023-09-05';
10
+ ALTER TABLE `events` ADD COLUMN `metadata` JSON;
11
+
12
+ -- Indexes present in the canonical but missing in the D1 lineage (perf parity;
13
+ -- names match the Umzug migrations 20250904-discount / 20251007-relate-tax-rate).
14
+ CREATE INDEX IF NOT EXISTS `idx_discounts_customer_id` ON `discounts` (`customer_id`);
15
+ CREATE INDEX IF NOT EXISTS `idx_invoice_items_tax_rate_id` ON `invoice_items` (`tax_rate_id`);
16
+ CREATE INDEX IF NOT EXISTS `idx_promotion_codes_verification_type_coupon_id` ON `promotion_codes` (`verification_type`, `coupon_id`);
@@ -0,0 +1,5 @@
1
+ -- The DID Space billing-mirror integration was removed (libs/did-space.ts,
2
+ -- queues/space.ts). Rows in `jobs` with queue='did-space' can never be picked
3
+ -- up again (the scheduled scan only matches registered queue names), so they
4
+ -- would sit in the table forever. Delete them. Idempotent.
5
+ DELETE FROM jobs WHERE queue = 'did-space';
@@ -0,0 +1,13 @@
1
+ // Phase 12b (W2′): pin the core queue engine to 'workerd' mode.
2
+ //
3
+ // This MUST be imported before any business queue module (api/src/queues/*),
4
+ // because createQueue runs at import time and reads the mode to decide whether
5
+ // to start the node background poll loop(). A frozen CF isolate cannot run a
6
+ // background timer, so the worker disables the loop and drives due-job
7
+ // re-dispatch from scheduled() via the core dispatchDueJobs() surface instead.
8
+ //
9
+ // ESM executes imported module bodies in source order, so importing this first
10
+ // in worker.ts guarantees the mode is set before the engine is constructed.
11
+ import { setQueueRuntimeMode } from '../api/src/libs/queue/runtime';
12
+
13
+ setQueueRuntimeMode('workerd');
@@ -3,59 +3,15 @@ const path = require("path");
3
3
  const cfDir = __dirname;
4
4
  const s = (f) => path.resolve(cfDir, f);
5
5
 
6
- // Resolve the absolute path of the original queue module so we can intercept it
7
- const queueModulePath = path.resolve(cfDir, "../api/src/libs/queue/index.ts");
8
- const queueShimPath = s("shims/queue.ts");
9
-
10
- // Resolve the absolute path of the original lock module so we can intercept it
11
- const lockModulePath = path.resolve(cfDir, "../api/src/libs/lock.ts");
12
- const lockShimPath = s("shims/lock.ts");
13
-
14
- // Plugin to redirect libs/queue to our CF Workers shim
15
- const queueNormalizedTarget = queueModulePath.replace(/\/index\.ts$/, '').replace(/\/index$/, '');
16
- const queueShimPlugin = {
17
- name: 'queue-shim',
18
- setup(build) {
19
- // Intercept any import that resolves to the original queue module
20
- build.onResolve({ filter: /libs\/queue/ }, (args) => {
21
- const resolveDir = args.resolveDir;
22
- if (!resolveDir) return undefined;
23
-
24
- // Resolve the relative import to an absolute path
25
- const resolved = path.resolve(resolveDir, args.path).replace(/\/index(\.ts)?$/, '');
26
- const match = resolved === queueNormalizedTarget;
27
- if (args.path.includes('queue')) {
28
- console.log(`[queue-shim] ${match ? 'MATCH' : 'skip'}: ${args.path} -> ${resolved} (target: ${queueNormalizedTarget})`);
29
- }
30
- if (match) {
31
- return { path: queueShimPath };
32
- }
33
- return undefined;
34
- });
35
- },
36
- };
37
-
38
- // Plugin to redirect libs/lock to our CF Workers D1-based lock shim
39
- const lockNormalizedTarget = lockModulePath.replace(/\.ts$/, '');
40
- const lockShimPlugin = {
41
- name: 'lock-shim',
42
- setup(build) {
43
- build.onResolve({ filter: /libs\/lock/ }, (args) => {
44
- const resolveDir = args.resolveDir;
45
- if (!resolveDir) return undefined;
46
-
47
- const resolved = path.resolve(resolveDir, args.path).replace(/\.ts$/, '');
48
- const match = resolved === lockNormalizedTarget;
49
- if (args.path.includes('lock')) {
50
- console.log(`[lock-shim] ${match ? 'MATCH' : 'skip'}: ${args.path} -> ${resolved} (target: ${lockNormalizedTarget})`);
51
- }
52
- if (match) {
53
- return { path: lockShimPath };
54
- }
55
- return undefined;
56
- });
57
- },
58
- };
6
+ // Phase 12b/12c: the queue-shim (libs/queue -> shims/queue.ts) and lock-shim
7
+ // (libs/lock -> shims/lock.ts) plugins were REMOVED.
8
+ // - queue-shim: option A makes api/src/libs/queue the ONE queue engine for all
9
+ // runtimes (the worker drives it via api/src/libs/queue/runtime.ts); the
10
+ // duplicate shims/queue.ts engine is deleted, so there is nothing to redirect.
11
+ // - lock-shim: lock became a Phase 8 driver (libs/drivers/locks) and the old
12
+ // shims/lock.ts no longer exists; the redirect pointed at a deleted file and
13
+ // was the only thing that broke this script.
14
+ // Everything else below is the original CF deploy-build optimization config.
59
15
 
60
16
  // Plugin to neutralize rolldown-generated `__require(import.meta.url)` helpers
61
17
  // that ship inside npm packages built with rolldown (e.g. @ocap/message,
@@ -216,13 +172,30 @@ const noopPackagesPlugin = {
216
172
  },
217
173
  };
218
174
 
175
+ // Plugin: drop ethers' non-English BIP39 wordlists (~70K dead weight). The payment
176
+ // worker never uses Mnemonic/HD wallets (0 source refs to Mnemonic/HDNode/wordlist),
177
+ // so the 8 non-English word tables never execute. ethers' own /dist build strips
178
+ // these too (~80kb). Keep LangEn (Mnemonic default). Stub the rest with a static
179
+ // wordlist() returning null so wordlists.js's top-level LangXx.wordlist() calls
180
+ // (run at module init) don't crash.
181
+ const dropEthersWordlistsPlugin = {
182
+ name: 'drop-ethers-wordlists',
183
+ setup(build) {
184
+ build.onLoad({ filter: /ethers\/lib\.esm\/wordlists\/lang-(cz|es|fr|ja|ko|it|pt|zh)\.js$/ }, (args) => {
185
+ const lang = /lang-(\w+)\.js$/.exec(args.path)[1];
186
+ const cls = 'Lang' + lang.charAt(0).toUpperCase() + lang.slice(1);
187
+ return { contents: `export class ${cls} { static wordlist() { return null; } }`, loader: 'js' };
188
+ });
189
+ },
190
+ };
191
+
219
192
  build({
220
193
  entryPoints: [s("worker.ts")],
221
194
  bundle: true, format: "esm", platform: "node", target: "esnext",
222
195
  outdir: s("dist"), minify: true, sourcemap: true, metafile: true,
223
196
  mainFields: ["module", "main"],
224
197
  plugins: [
225
- noopPackagesPlugin, queueShimPlugin, lockShimPlugin, rolldownRuntimeNoopPlugin, lodashSubpathPlugin,
198
+ noopPackagesPlugin, rolldownRuntimeNoopPlugin, lodashSubpathPlugin, dropEthersWordlistsPlugin,
226
199
  ],
227
200
  external: ["cloudflare:*", "__STATIC_CONTENT_MANIFEST"],
228
201
  // Give import.meta.url a stable fallback so bundled deps that call
@@ -265,6 +238,10 @@ build({
265
238
  // axios → lightweight fetch-based shim (115KB → ~2KB)
266
239
  "axios": s("shims/axios-lite.ts"),
267
240
 
241
+ // node-fetch → native fetch (drops encoding/tr46/whatwg-url polyfill ~754KB
242
+ // pulled in by @apple/app-store-server-library; dead weight on CF Workers)
243
+ "node-fetch": s("shims/node-fetch.ts"),
244
+
268
245
  // Stripe — wrap constructor to use fetch HTTP client in CF Workers
269
246
  "stripe": s("shims/stripe-cf.ts"),
270
247
  "__real_stripe__": require.resolve("stripe"),
@@ -282,7 +259,6 @@ build({
282
259
  "sqlite3": s("shims/noop.ts"),
283
260
  "cls-hooked": s("shims/noop.ts"),
284
261
  "express-async-errors": s("shims/noop.ts"),
285
- "express": s("shims/express-compat/index.ts"),
286
262
  "cors": s("shims/cors.ts"),
287
263
  "cookie-parser": s("shims/cookie-parser.ts"),
288
264
  "@blocklet/sdk/lib/middlewares/fallback": s("shims/blocklet-sdk/fallback.ts"),
@@ -310,7 +286,6 @@ build({
310
286
  "@blocklet/xss": s("shims/xss.ts"),
311
287
  "@blocklet/error": s("shims/error.ts"),
312
288
  "@blocklet/logger": s("shims/blocklet-sdk/logger.ts"),
313
- "@blocklet/did-space-js": s("shims/did-space.ts"),
314
289
  "@blocklet/payment-vendor": s("shims/payment-vendor.ts"),
315
290
  "@arcblock/did-connect-storage-nedb": s("shims/nedb-storage.ts"),
316
291
  "@abtnode/cron": s("shims/cron.ts"),
@@ -0,0 +1,20 @@
1
+ // CF shim for @blocklet/sdk/lib/util/asset-host-transformer.
2
+ //
3
+ // DEAD on the CF path: the only importer is the cdn middleware (node-shell only —
4
+ // HTML CDN-URL rewriting on the full node app shell). The worker serves JSON API
5
+ // routes through a LITE app-shell, so cdn never executes. A pass-through stub is
6
+ // enough to resolve the bundled-but-unreachable node code.
7
+ export class AssetHostTransformer {
8
+ // eslint-disable-next-line @typescript-eslint/no-useless-constructor, no-empty-function
9
+ constructor(_assetHost?: string) {}
10
+
11
+ transform(html: string, _assetHost?: string): string {
12
+ return html;
13
+ }
14
+
15
+ transformBuffer(body: Buffer | Uint8Array, _assetHost?: string): Buffer | Uint8Array {
16
+ return body;
17
+ }
18
+ }
19
+
20
+ export default { AssetHostTransformer };
@@ -4,5 +4,12 @@ export { env };
4
4
  export const Events = {};
5
5
  export const events = { on: () => {}, emit: () => {} };
6
6
 
7
- const config = { env, Events, events };
7
+ // Phase 4 (express→hono): the fallback middleware (SPA serving) reads these. It is
8
+ // node-shell only — DEAD on the CF worker (which never serves the SPA) — so a
9
+ // resolving stub is enough. getBlockletSettings also backs sessionMiddleware's
10
+ // blacklist check, which is gated off on CF (enableBlacklist: false).
11
+ export const getBlockletSettings = (): any => ({ enableBlacklist: false, theme: {} });
12
+ export const getBlockletJs = (..._args: any[]): string => '';
13
+
14
+ const config = { env, Events, events, getBlockletSettings, getBlockletJs };
8
15
  export default config;
@@ -0,0 +1,12 @@
1
+ // CF shim for @blocklet/sdk/lib/util/login.
2
+ //
3
+ // Reached on the LIVE CF path: sessionMiddleware (used by many resource routes)
4
+ // calls isLoginToken/isAccessKey to classify the bearer token. These are pure
5
+ // string-format checks — copied verbatim from the upstream so behavior matches.
6
+ export const isLoginToken = (token: unknown): boolean =>
7
+ typeof token === 'string' && token.split('.').length === 3;
8
+
9
+ export const isAccessKey = (token: unknown): boolean =>
10
+ typeof token === 'string' && token.split('.').length === 1 && token.startsWith('blocklet-');
11
+
12
+ export default { isLoginToken, isAccessKey };
@@ -0,0 +1,14 @@
1
+ // CF shim for @blocklet/sdk/lib/util/service-api.
2
+ //
3
+ // The only caller on the CF path is sessionMiddleware's login-token blacklist
4
+ // check, which is gated by `blockletSettings.enableBlacklist` (off on the CF
5
+ // worker — the worker resolves identity via AUTH_SERVICE RPC, not the blacklist
6
+ // endpoint). The old express-compat CF path never ran this check at all, so a
7
+ // stub that reports "valid" preserves the worker's (no-blacklist) behavior and
8
+ // never wrongly blocks a token if the setting is somehow on.
9
+ const serviceApi = {
10
+ post: async (_url: string, _body?: unknown) => ({ data: { valid: true } }),
11
+ get: async (_url: string) => ({ data: {} }),
12
+ };
13
+
14
+ export default serviceApi;
@@ -1,6 +1,8 @@
1
1
  // @blocklet/sdk/lib/middlewares/session shim
2
- // Auth is resolved in Hono middleware layer via AUTH_SERVICE RPC.
3
- // req.user is already populated by mountExpressRoutes().
2
+ // Auth is resolved in the Hono middleware layer via AUTH_SERVICE RPC, then injected
3
+ // as x-user-* request headers by the worker's /api/* dispatcher (worker.ts) — the
4
+ // native authenticate()/sessionMiddleware read those. This express-middleware shim
5
+ // is inert (the old express-compat mountExpressRoutes path it served is gone).
4
6
  export default function sessionMiddleware(_options?: any) {
5
7
  return (_req: any, _res: any, next: any) => next();
6
8
  }
@@ -0,0 +1,8 @@
1
+ // CF shim for @blocklet/sdk/lib/util/constants.
2
+ //
3
+ // Reached via the fallback middleware (node-shell SPA serving — DEAD on the CF
4
+ // worker, which never serves the SPA). SERVICE_PREFIX carries its real upstream
5
+ // value so the bundled-but-unreachable node code is byte-faithful.
6
+ export const SERVICE_PREFIX = '/.well-known/service';
7
+
8
+ export default { SERVICE_PREFIX };
@@ -0,0 +1,13 @@
1
+ // CF shim for @blocklet/sdk/lib/util/csrf.
2
+ //
3
+ // DEAD on the CF path: the csrf middleware (middlewares/hono/csrf) lives only on
4
+ // the NODE app-shell pipeline (service.ts buildHonoApp / configureNativePipeline,
5
+ // reached via getHonoApp). The CF worker mounts a LITE app-shell (xss only) and
6
+ // owns its own cors, so csrf never executes on the worker. These stubs exist only
7
+ // so esbuild can resolve the import in the bundled-but-unreachable node code.
8
+ export const getCsrfSecret = (): string => '';
9
+ export const sign = (_secret: string, _value: string): string => '';
10
+ export const verify = (_secret: string, _value: string, _token: string): boolean => false;
11
+ export const hmac = (_secret: string, _value: string): string => '';
12
+
13
+ export default { getCsrfSecret, sign, verify, hmac };
@@ -0,0 +1,8 @@
1
+ // CF shim for @blocklet/sdk/lib/util/wallet.
2
+ //
3
+ // DEAD on the CF path: the only importer is the csrf middleware (node-shell only;
4
+ // see util-csrf.ts), which reads isDidWalletConnect to skip csrf for DID-wallet
5
+ // requests. Never executes on the worker — a resolving stub is enough.
6
+ export const isDidWalletConnect = (_userAgent?: string): boolean => false;
7
+
8
+ export default { isDidWalletConnect };
@@ -1,182 +1,62 @@
1
- // @abtnode/cron shim for CF Cron Triggers
1
+ // @abtnode/cron shim for CF Cron Triggers (Phase 9, W2-1b).
2
2
  //
3
- // The real @abtnode/cron exports { init } where init({ context, jobs, onError }) registers jobs.
4
- // In CF Workers, we store the jobs and execute them via the scheduled() handler.
5
- //
6
- // The shim parses 6-field cron expressions (sec min hour day month weekday)
7
- // and only runs jobs whose schedule matches the current 5-minute window.
8
-
9
- type CronJob = {
10
- name: string;
11
- time: string;
12
- fn: () => Promise<any> | any;
13
- options?: { runOnInit?: boolean };
14
- };
3
+ // The real @abtnode/cron exports { init } where init({ jobs, onError }) registers
4
+ // jobs. In CF Workers we store the jobs and execute due ones from scheduled().
5
+ // The cron-expression matcher + registry now live in the shared cron driver
6
+ // (api/src/libs/drivers/cron.ts) so embedded and worker agree on when a job is
7
+ // due; this file is the thin worker adapter that keeps the @abtnode/cron API.
8
+
9
+ import {
10
+ createCronRegistry,
11
+ setCronDriver,
12
+ getCronDriver,
13
+ matchesCron,
14
+ shouldRunInWindow,
15
+ } from '../../api/src/libs/drivers/cron';
16
+
17
+ // D2: crons/index.ts now registers through getCronDriver() instead of importing
18
+ // @abtnode/cron directly. On CF this shim is the active cron driver, so make the
19
+ // cf-cron registry the global driver at module load — BEFORE worker.ts calls
20
+ // crons.init(). That keeps crons.init()'s register() and this shim's runAll() on
21
+ // the SAME passive cf-cron registry (host drives runDue from scheduled(); no
22
+ // @abtnode/cron self-scheduling timer is ever created in the frozen isolate).
23
+ const registry = createCronRegistry('cf-cron');
24
+ setCronDriver(registry);
15
25
 
16
26
  type InitOptions = {
17
27
  context?: any;
18
- jobs: CronJob[];
28
+ jobs: Array<{ name: string; time: string; fn: () => Promise<any> | any; options?: { runOnInit?: boolean } }>;
19
29
  onError?: (error: Error, name: string) => void;
20
30
  };
21
31
 
22
- // Singleton storage for registered cron jobs
23
- const registeredJobs: CronJob[] = [];
24
- let onErrorHandler: ((error: Error, name: string) => void) | undefined;
25
-
26
- // --- Cron expression matcher ---
27
- // Supports 6-field format: second minute hour dayOfMonth month dayOfWeek
28
- // Supports: numbers, *, */N, ranges (1-5), lists (1,3,5)
29
-
30
- function parseField(field: string, min: number, max: number): number[] | null {
31
- // null means "match all"
32
- if (field === '*') return null;
33
-
34
- const values = new Set<number>();
35
-
36
- for (const part of field.split(',')) {
37
- // */N — every N
38
- const stepMatch = part.match(/^\*\/(\d+)$/);
39
- if (stepMatch) {
40
- const step = parseInt(stepMatch[1], 10);
41
- for (let i = min; i <= max; i += step) {
42
- values.add(i);
43
- }
44
- continue;
45
- }
46
-
47
- // N-M — range
48
- const rangeMatch = part.match(/^(\d+)-(\d+)$/);
49
- if (rangeMatch) {
50
- const from = parseInt(rangeMatch[1], 10);
51
- const to = parseInt(rangeMatch[2], 10);
52
- for (let i = from; i <= to; i++) {
53
- values.add(i);
54
- }
55
- continue;
56
- }
57
-
58
- // N — single value
59
- const num = parseInt(part, 10);
60
- if (!isNaN(num)) {
61
- values.add(num);
62
- }
63
- }
64
-
65
- return values.size > 0 ? Array.from(values) : null;
66
- }
67
-
68
- /**
69
- * Check if a date matches a 6-field cron expression.
70
- * Returns true if the date's minute/hour/day/month/weekday match.
71
- * Seconds field is ignored (CF triggers are minute-level).
72
- */
73
- function matchesCron(cronExpr: string, date: Date): boolean {
74
- const fields = cronExpr.trim().split(/\s+/);
75
- if (fields.length < 5) return true; // Can't parse — run it
76
-
77
- // 6-field: sec min hour dom month dow
78
- // 5-field: min hour dom month dow
79
- const offset = fields.length >= 6 ? 1 : 0;
80
-
81
- const minuteField = parseField(fields[offset], 0, 59);
82
- const hourField = parseField(fields[offset + 1], 0, 23);
83
- const domField = parseField(fields[offset + 2], 1, 31);
84
- const monthField = parseField(fields[offset + 3], 0, 11); // cron months are 1-12, JS is 0-11
85
- const dowField = parseField(fields[offset + 4], 0, 6);
86
-
87
- const m = date.getUTCMinutes();
88
- const h = date.getUTCHours();
89
- const dom = date.getUTCDate();
90
- const month = date.getUTCMonth() + 1; // JS 0-based → cron 1-based
91
- const dow = date.getUTCDay(); // 0=Sunday
92
-
93
- if (minuteField && !minuteField.includes(m)) return false;
94
- if (hourField && !hourField.includes(h)) return false;
95
- if (domField && !domField.includes(dom)) return false;
96
- if (monthField && !monthField.includes(month)) return false;
97
- if (dowField && !dowField.includes(dow)) return false;
98
-
99
- return true;
100
- }
101
-
102
- // Check if a cron expression should fire at the given date (minute-level match).
103
- //
104
- // History: this helper previously used a 5-minute look-ahead window, under the
105
- // assumption that CF Cron Triggers fire every 5 minutes. Once the deploy config
106
- // switched to every-minute cron, that window made every stepped expression fire
107
- // 5x more often than intended, driving CF Queues past the free-tier daily cap
108
- // (2026-04-17 incident). With CF Scheduled firing every minute, we only check
109
- // the current minute — each cron expression triggers at its designed frequency.
110
- // See docs/cf-queues-ops-alert-analysis.md § 改动 B.
111
- function shouldRunInWindow(cronExpr: string, date: Date): boolean {
112
- return matchesCron(cronExpr, date);
113
- }
114
-
115
- // --- Cron shim API ---
116
-
117
32
  function init(options: InitOptions) {
118
- registeredJobs.length = 0;
119
- onErrorHandler = options.onError;
120
-
121
- for (const job of options.jobs || []) {
122
- if (job.name && job.time && typeof job.fn === 'function') {
123
- registeredJobs.push(job);
124
- }
125
- }
126
-
127
- // Skip runOnInit jobs in CF Workers — they'll run on the next matching trigger
128
- // (running them at module init time would block the request and may exceed CPU limits)
129
-
33
+ registry.register(options.jobs || [], options.onError);
34
+ // runOnInit jobs are skipped in CF Workers — they run on the next matching
35
+ // trigger (running them at module init would block the request / risk CPU limits)
130
36
  return {
131
37
  addJob(name: string, time: string, fn: Function, opts?: any) {
132
- registeredJobs.push({ name, time, fn: fn as any, options: opts });
38
+ registry.addJob(name, time, fn as any, opts);
39
+ },
40
+ start() {
41
+ /* no-op — CF scheduled() drives runAll */
133
42
  },
134
- start() { /* no-op */ },
135
43
  };
136
44
  }
137
45
 
138
- // Called by worker.ts scheduled handler only runs jobs matching the trigger time.
139
- // Accepts an optional `now` so the caller can pass `event.scheduledTime` (the
140
- // intended trigger minute) instead of relying on wall-clock at execution time.
141
- // CF may deliver scheduled events with a small delay that crosses a minute
142
- // boundary; matching on `scheduledTime` keeps exact-minute cron reliable.
46
+ // Called by worker.ts scheduled handler. Accepts an optional `now` so the caller
47
+ // can pass `event.scheduledTime` (the intended trigger minute).
143
48
  async function runAll(now: Date = new Date()) {
144
- const matched: string[] = [];
145
- const skipped: string[] = [];
146
-
147
- for (const job of registeredJobs) {
148
- if (shouldRunInWindow(job.time, now)) {
149
- matched.push(job.name);
150
- try {
151
- await job.fn();
152
- } catch (err: any) {
153
- console.error(`[Cron] ${job.name} failed:`, err?.message || err);
154
- onErrorHandler?.(err, job.name);
155
- }
156
- } else {
157
- skipped.push(job.name);
158
- }
159
- }
160
-
161
- console.log(`[Cron] Ran ${matched.length} jobs: [${matched.join(', ')}]. Skipped ${skipped.length}.`);
49
+ const { ran, skipped } = await getCronDriver().runDue(now);
50
+ // eslint-disable-next-line no-console
51
+ console.log(`[Cron] Ran ${ran.length} jobs: [${ran.join(', ')}]. Skipped ${skipped.length}.`);
162
52
  }
163
53
 
164
54
  async function runJob(name: string) {
165
- const job = registeredJobs.find((j) => j.name === name);
166
- if (job) {
167
- try {
168
- await job.fn();
169
- } catch (err: any) {
170
- console.error(`[Cron] ${name} failed:`, err?.message || err);
171
- onErrorHandler?.(err, name);
172
- }
173
- } else {
174
- console.warn(`[Cron] Job ${name} not found`);
175
- }
55
+ await getCronDriver().runJob(name);
176
56
  }
177
57
 
178
58
  function getJobNames(): string[] {
179
- return registeredJobs.map((j) => `${j.name} (${j.time})`);
59
+ return getCronDriver().getJobNames();
180
60
  }
181
61
 
182
62
  // Export matching @abtnode/cron API: default export is { init }