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.
- package/api/dev.ts +41 -2
- package/api/hono.d.ts +42 -0
- package/api/node-sqlite.d.ts +12 -0
- package/api/src/bootstrap.ts +36 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +27 -24
- package/api/src/crons/metering-subscription-detection.ts +1 -1
- package/api/src/crons/overdue-detection.ts +2 -2
- package/api/src/crons/retry-pending-events.ts +6 -0
- package/api/src/index.ts +22 -161
- package/api/src/integrations/app-store/client.ts +3 -4
- package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
- package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +21 -7
- package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
- package/api/src/integrations/google-play/handlers/voided.ts +2 -2
- package/api/src/integrations/google-play/verify.ts +3 -2
- package/api/src/integrations/iap-reconcile.ts +3 -5
- package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
- package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
- package/api/src/libs/archive/query.ts +19 -0
- package/api/src/libs/audit.ts +61 -4
- package/api/src/libs/auth.ts +99 -38
- package/api/src/libs/context.ts +78 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- package/api/src/libs/drivers/auth-storage.ts +118 -0
- package/api/src/libs/drivers/cron.ts +264 -0
- package/api/src/libs/drivers/db.ts +170 -0
- package/api/src/libs/drivers/identity.ts +81 -0
- package/api/src/libs/drivers/index.ts +40 -0
- package/api/src/libs/drivers/locks.ts +226 -0
- package/api/src/libs/drivers/migrate-runner.ts +70 -0
- package/api/src/libs/drivers/queue.ts +104 -0
- package/api/src/libs/drivers/secrets.ts +194 -0
- package/api/src/libs/env.ts +170 -54
- package/api/src/libs/exchange-rate/service.ts +7 -6
- package/api/src/libs/http-fetch-adapter.ts +50 -0
- package/api/src/libs/invoice.ts +1 -1
- package/api/src/libs/lock.ts +51 -47
- package/api/src/libs/logger.ts +48 -8
- package/api/src/libs/notification/index.ts +1 -1
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
- package/api/src/libs/overdraft-protection.ts +1 -1
- package/api/src/libs/payout.ts +1 -1
- package/api/src/libs/queue/index.ts +259 -52
- package/api/src/libs/queue/runtime.ts +175 -0
- package/api/src/libs/resource.ts +3 -3
- package/api/src/libs/secrets.ts +38 -0
- package/api/src/libs/session.ts +3 -2
- package/api/src/libs/subscription.ts +5 -5
- package/api/src/libs/tenant.ts +92 -0
- package/api/src/libs/url.ts +3 -3
- package/api/src/libs/util.ts +21 -13
- package/api/src/middlewares/hono/cdn.ts +63 -0
- package/api/src/middlewares/hono/context.ts +73 -0
- package/api/src/middlewares/hono/csrf.ts +72 -0
- package/api/src/middlewares/hono/fallback.ts +194 -0
- package/api/src/middlewares/hono/pipeline.ts +73 -0
- package/api/src/middlewares/hono/resource-mount.ts +42 -0
- package/api/src/middlewares/hono/resource.ts +63 -0
- package/api/src/middlewares/hono/security.ts +214 -0
- package/api/src/middlewares/hono/session.ts +114 -0
- package/api/src/middlewares/hono/xss.ts +61 -0
- package/api/src/queues/auto-recharge.ts +12 -10
- package/api/src/queues/checkout-session.ts +17 -12
- package/api/src/queues/credit-consume.ts +40 -36
- package/api/src/queues/credit-grant.ts +25 -18
- package/api/src/queues/credit-reconciliation.ts +7 -5
- package/api/src/queues/discount-status.ts +9 -6
- package/api/src/queues/event.ts +12 -4
- package/api/src/queues/exchange-rate-health.ts +49 -30
- package/api/src/queues/invoice.ts +18 -15
- package/api/src/queues/notification.ts +14 -7
- package/api/src/queues/payment.ts +41 -28
- package/api/src/queues/payout.ts +9 -5
- package/api/src/queues/refund.ts +18 -12
- package/api/src/queues/subscription.ts +83 -53
- package/api/src/queues/token-transfer.ts +15 -10
- package/api/src/queues/usage-record.ts +8 -5
- package/api/src/queues/vendors/commission.ts +7 -5
- package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
- package/api/src/queues/vendors/fulfillment.ts +4 -2
- package/api/src/queues/vendors/return-processor.ts +5 -3
- package/api/src/queues/vendors/return-scanner.ts +5 -4
- package/api/src/queues/vendors/status-check.ts +10 -7
- package/api/src/queues/webhook.ts +60 -32
- package/api/src/routes/connect/shared.ts +1 -2
- package/api/src/routes/connect/subscribe.ts +3 -3
- package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
- package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
- package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
- package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
- package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
- package/api/src/routes/hono/credit-tokens.ts +43 -0
- package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
- package/api/src/routes/{customers.ts → hono/customers.ts} +193 -223
- package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
- package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
- package/api/src/routes/{events.ts → hono/events.ts} +107 -71
- package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
- package/api/src/routes/hono/exchange-rates.ts +77 -0
- package/api/src/routes/hono/index.ts +115 -0
- package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
- package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
- package/api/src/routes/hono/integrations/stripe.ts +74 -0
- package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
- package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
- package/api/src/routes/hono/meters.ts +288 -0
- package/api/src/routes/hono/passports.ts +73 -0
- package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
- package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
- package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
- package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
- package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
- package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
- package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
- package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
- package/api/src/routes/{products.ts → hono/products.ts} +172 -159
- package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
- package/api/src/routes/hono/redirect.ts +24 -0
- package/api/src/routes/{refunds.ts → hono/refunds.ts} +96 -80
- package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
- package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
- package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
- package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
- package/api/src/routes/hono/tool.ts +69 -0
- package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
- package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
- package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
- package/api/src/routes/hono/webhook-endpoints.ts +126 -0
- package/api/src/service.ts +667 -0
- package/api/src/store/migrations/20230911-seeding.ts +2 -1
- package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
- package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
- package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
- package/api/src/store/models/auto-recharge-config.ts +22 -10
- package/api/src/store/models/checkout-session.ts +15 -14
- package/api/src/store/models/coupon.ts +29 -20
- package/api/src/store/models/credit-grant.ts +38 -29
- package/api/src/store/models/credit-transaction.ts +32 -21
- package/api/src/store/models/customer.ts +19 -17
- package/api/src/store/models/discount.ts +11 -2
- package/api/src/store/models/entitlement-grant.ts +21 -9
- package/api/src/store/models/entitlement-product.ts +21 -9
- package/api/src/store/models/entitlement.ts +19 -10
- package/api/src/store/models/event.ts +18 -9
- package/api/src/store/models/exchange-rate-provider.ts +17 -4
- package/api/src/store/models/invoice-item.ts +18 -9
- package/api/src/store/models/invoice.ts +16 -8
- package/api/src/store/models/meter-event.ts +27 -9
- package/api/src/store/models/meter.ts +31 -22
- package/api/src/store/models/payment-currency.ts +25 -8
- package/api/src/store/models/payment-intent.ts +15 -6
- package/api/src/store/models/payment-link.ts +15 -6
- package/api/src/store/models/payment-method.ts +38 -22
- package/api/src/store/models/payment-stat.ts +18 -9
- package/api/src/store/models/payout.ts +15 -6
- package/api/src/store/models/price-quote.ts +17 -8
- package/api/src/store/models/price.ts +24 -12
- package/api/src/store/models/pricing-table.ts +29 -20
- package/api/src/store/models/product-vendor.ts +20 -10
- package/api/src/store/models/product.ts +15 -6
- package/api/src/store/models/promotion-code.ts +14 -6
- package/api/src/store/models/refund.ts +15 -6
- package/api/src/store/models/revenue-snapshot.ts +21 -9
- package/api/src/store/models/setting.ts +18 -9
- package/api/src/store/models/setup-intent.ts +36 -27
- package/api/src/store/models/subscription-item.ts +21 -9
- package/api/src/store/models/subscription-schedule.ts +21 -9
- package/api/src/store/models/subscription.ts +21 -10
- package/api/src/store/models/tax-rate.ts +29 -21
- package/api/src/store/models/usage-record.ts +11 -2
- package/api/src/store/models/webhook-attempt.ts +18 -9
- package/api/src/store/models/webhook-endpoint.ts +18 -9
- package/api/src/store/scoped-core.ts +55 -0
- package/api/src/store/scoped.ts +247 -0
- package/api/src/store/sequelize.ts +66 -22
- package/api/src/store/sql-migrations.ts +20 -0
- package/api/src/store/tenant-backfill.ts +260 -0
- package/api/src/store/tenant-model.ts +124 -0
- package/api/src/store/tenant-tables.ts +50 -0
- package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
- package/api/tests/fixtures/bare-query-violation.ts +13 -0
- package/api/tests/fixtures/core-env-violation.ts +10 -0
- package/api/tests/fixtures/host-read-violation.ts +19 -0
- package/api/tests/fixtures/tenants.ts +4 -0
- package/api/tests/integrations/iap-tenant.spec.ts +284 -0
- package/api/tests/libs/archive-query.spec.ts +26 -0
- package/api/tests/libs/audit-tenant.spec.ts +153 -0
- package/api/tests/libs/context.spec.ts +204 -0
- package/api/tests/libs/core-config.spec.ts +115 -0
- package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
- package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
- package/api/tests/libs/lock-tenant.spec.ts +66 -0
- package/api/tests/libs/scoped.spec.ts +222 -0
- package/api/tests/libs/secrets-facade.spec.ts +52 -0
- package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
- package/api/tests/libs/tenant-middleware.spec.ts +42 -0
- package/api/tests/libs/tenant-scanner.spec.ts +120 -0
- package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
- package/api/tests/middlewares/hono/context.spec.ts +113 -0
- package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
- package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
- package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
- package/api/tests/middlewares/hono/security.spec.ts +181 -0
- package/api/tests/middlewares/hono/session.spec.ts +42 -0
- package/api/tests/middlewares/hono/xss.spec.ts +81 -0
- package/api/tests/models/tenant-backfill.spec.ts +287 -0
- package/api/tests/models/tenant-columns-model.spec.ts +46 -0
- package/api/tests/models/tenant-columns.spec.ts +161 -0
- package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
- package/api/tests/queues/credit-consume.spec.ts +8 -1
- package/api/tests/queues/event-tenant.spec.ts +236 -0
- package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
- package/api/tests/queues/queue-parity.spec.ts +249 -0
- package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
- package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
- package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
- package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
- package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
- package/api/tests/service/collapse.spec.ts +96 -0
- package/api/tests/store/tenant-crosscut.spec.ts +202 -0
- package/api/tests/store/tenant-model-spike.spec.ts +177 -0
- package/api/tests/store/tenant-model.spec.ts +162 -0
- package/api/tests/store/tenant-residual.spec.ts +196 -0
- package/api/third.d.ts +4 -0
- package/blocklet.yml +1 -1
- package/cloudflare/README.md +26 -6
- package/cloudflare/build.ts +28 -13
- package/cloudflare/did-connect-auth.ts +0 -217
- package/cloudflare/docs/2026-06-10-bundle-size-analysis.md +288 -0
- package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
- package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
- package/cloudflare/migrations/0008_schema_parity.sql +16 -0
- package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
- package/cloudflare/queue-runtime-mode.ts +13 -0
- package/cloudflare/run-build.js +31 -56
- package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
- package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
- package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/util-csrf.ts +13 -0
- package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
- package/cloudflare/shims/cron.ts +38 -158
- package/cloudflare/shims/events.ts +124 -0
- package/cloudflare/shims/fastq.ts +15 -1
- package/cloudflare/shims/nedb-storage.ts +16 -8
- package/cloudflare/shims/node-fetch.ts +35 -0
- package/cloudflare/shims/xss.ts +8 -0
- package/cloudflare/tenant-middleware.ts +36 -0
- package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
- package/cloudflare/worker.ts +204 -433
- package/cloudflare/wrangler.local-e2e.jsonc +26 -0
- package/jest.config.js +3 -1
- package/package.json +33 -38
- package/scripts/core-env-whitelist.json +1 -0
- package/scripts/e2e-12b-runtime.ts +149 -0
- package/scripts/e2e-core-config.ts +125 -0
- package/scripts/e2e-d1-tenancy.ts +116 -0
- package/scripts/e2e-d2-cron-queue.ts +139 -0
- package/scripts/e2e-d3-embedded-multi.ts +171 -0
- package/scripts/e2e-hono-s2.ts +125 -0
- package/scripts/e2e-hono-s3e.ts +135 -0
- package/scripts/e2e-hono-s4.ts +114 -0
- package/scripts/e2e-migration-contract.ts +100 -0
- package/scripts/e2e-s0.ts +61 -0
- package/scripts/e2e-s1.ts +107 -0
- package/scripts/e2e-s2.ts +178 -0
- package/scripts/e2e-s3.ts +110 -0
- package/scripts/e2e-s4.ts +191 -0
- package/scripts/e2e-s5.ts +139 -0
- package/scripts/e2e-s6.ts +127 -0
- package/scripts/e2e-tenant-model.ts +119 -0
- package/scripts/e2e-tenant-worker.ts +199 -0
- package/scripts/gen-sql-migrations.js +46 -0
- package/scripts/phase8-codemod.js +219 -0
- package/scripts/phase9a-env-getters-codemod.js +82 -0
- package/scripts/scan-core-env.js +109 -0
- package/scripts/scan-tenant-queries.js +235 -0
- package/scripts/schema-drift-guard.ts +210 -0
- package/scripts/tenant-scan-whitelist.json +1 -0
- package/src/env.d.ts +13 -1
- package/tsconfig.json +1 -1
- package/api/src/libs/did-space.ts +0 -235
- package/api/src/libs/middleware.ts +0 -50
- package/api/src/libs/security.ts +0 -192
- package/api/src/queues/space.ts +0 -662
- package/api/src/routes/credit-tokens.ts +0 -38
- package/api/src/routes/exchange-rates.ts +0 -87
- package/api/src/routes/index.ts +0 -142
- package/api/src/routes/integrations/stripe.ts +0 -61
- package/api/src/routes/meters.ts +0 -274
- package/api/src/routes/passports.ts +0 -68
- package/api/src/routes/redirect.ts +0 -20
- package/api/src/routes/tool.ts +0 -65
- package/api/src/routes/webhook-endpoints.ts +0 -126
- package/api/tests/routes/credit-grants.spec.ts +0 -1261
- package/cloudflare/shims/did-space-js.ts +0 -17
- package/cloudflare/shims/did-space.ts +0 -11
- package/cloudflare/shims/express-compat/index.ts +0 -80
- package/cloudflare/shims/express-compat/types.ts +0 -41
- package/cloudflare/shims/lock.ts +0 -115
- package/cloudflare/shims/queue.ts +0 -611
- package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
- 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');
|
package/cloudflare/run-build.js
CHANGED
|
@@ -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
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
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,
|
|
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
|
-
|
|
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
|
-
//
|
|
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 };
|
package/cloudflare/shims/cron.ts
CHANGED
|
@@ -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({
|
|
4
|
-
// In CF Workers
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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:
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
139
|
-
//
|
|
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
|
|
145
|
-
|
|
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
|
-
|
|
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
|
|
59
|
+
return getCronDriver().getJobNames();
|
|
180
60
|
}
|
|
181
61
|
|
|
182
62
|
// Export matching @abtnode/cron API: default export is { init }
|