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