payment-kit 1.29.1 → 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/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 +10 -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/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,81 @@
|
|
|
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 { getDefaultInstanceDid, getTenantMode, TenantError, TENANT_HOST_UNRESOLVED } from '../tenant';
|
|
17
|
+
|
|
18
|
+
export interface IdentityDriver {
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a request Host to its tenant instanceDid, or null/undefined when
|
|
21
|
+
* the host is unknown (multi-tenant fail-closed). The host value is the raw
|
|
22
|
+
* Host header — never a proxy header.
|
|
23
|
+
*/
|
|
24
|
+
resolveInstanceDidForHost(host: string | undefined): Promise<string | null> | string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Phase 11: the tenant's encryption key (EK). Required by the keyring secrets
|
|
27
|
+
* driver; optional on the contract because the single-tenant default secrets
|
|
28
|
+
* driver uses the process key and never calls it.
|
|
29
|
+
*/
|
|
30
|
+
getAppEk?(instanceDid: string): Promise<string> | string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Single-tenant default: every host maps to the deployment app DID. Used by
|
|
35
|
+
* Blocklet Server and the standalone worker's single-mode transition.
|
|
36
|
+
*/
|
|
37
|
+
export function createDefaultIdentityDriver(): IdentityDriver {
|
|
38
|
+
return {
|
|
39
|
+
resolveInstanceDidForHost() {
|
|
40
|
+
return getDefaultInstanceDid();
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let activeIdentityDriver: IdentityDriver = createDefaultIdentityDriver();
|
|
46
|
+
|
|
47
|
+
/** Inject the identity driver (multi-tenant hosts wire a Host->tenant map here). */
|
|
48
|
+
export function setIdentityDriver(driver: IdentityDriver): void {
|
|
49
|
+
activeIdentityDriver = driver;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getIdentityDriver(): IdentityDriver {
|
|
53
|
+
return activeIdentityDriver;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a request's raw Host header to a tenant instanceDid. SINGLE POINT of
|
|
58
|
+
* Host -> tenant resolution — both host adapters funnel through here (the
|
|
59
|
+
* Express `contextMiddleware` and the CF worker `tenantMiddleware`), so neither
|
|
60
|
+
* reinvents Host parsing (the tenant-scan rule forbids any route/queue/cron
|
|
61
|
+
* from reading Host directly).
|
|
62
|
+
*
|
|
63
|
+
* single mode: always the deployment app DID (Blocklet Server / standalone
|
|
64
|
+
* worker unchanged). multi mode: the identity driver maps Host -> instanceDid;
|
|
65
|
+
* an unknown/missing host throws TENANT_HOST_UNRESOLVED so the caller fails
|
|
66
|
+
* closed 4xx with no default-tenant fallback.
|
|
67
|
+
*
|
|
68
|
+
* The host MUST be the raw Host header — never a proxy header
|
|
69
|
+
* (X-Forwarded-Host / X-Real-IP). The runtime host (CF route / blocklet server /
|
|
70
|
+
* reverse proxy) is responsible for ensuring it cannot be forged by the client.
|
|
71
|
+
*/
|
|
72
|
+
export async function resolveTenantForHost(host: string | undefined): Promise<string> {
|
|
73
|
+
if (getTenantMode() === 'single') {
|
|
74
|
+
return getDefaultInstanceDid();
|
|
75
|
+
}
|
|
76
|
+
const resolved = await activeIdentityDriver.resolveInstanceDidForHost(host);
|
|
77
|
+
if (!resolved) {
|
|
78
|
+
throw new TenantError(TENANT_HOST_UNRESOLVED, `no tenant resolved for host "${host ?? ''}"`);
|
|
79
|
+
}
|
|
80
|
+
return resolved;
|
|
81
|
+
}
|
|
@@ -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 } from './identity';
|
|
37
|
+
export { createDefaultIdentityDriver, setIdentityDriver, getIdentityDriver } from './identity';
|
|
38
|
+
|
|
39
|
+
export type { SecretsDriver } from './secrets';
|
|
40
|
+
export { createDefaultSecretsDriver, createKeyringSecretsDriver, setSecretsDriver, getSecretsDriver } from './secrets';
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Phase 8 (W2-1a): locks slot driver contract.
|
|
2
|
+
//
|
|
3
|
+
// W1 §2.2 exempts the `locks` table from an instance_did column: tenant
|
|
4
|
+
// isolation for locks is achieved by prefixing the lock NAME, not by a column.
|
|
5
|
+
// `scopedLockName` is the single place that prefix is applied, so both drivers
|
|
6
|
+
// (and the libs/lock facade) stay consistent.
|
|
7
|
+
//
|
|
8
|
+
// Two implementations conform to the contract:
|
|
9
|
+
// - memory driver: the original in-process EventEmitter lock (Blocklet Server
|
|
10
|
+
// single-process Node.js). Semantics unchanged — Phase 8 wraps, never
|
|
11
|
+
// rewrites.
|
|
12
|
+
// - d1 driver: the Cloudflare D1-backed lock (atomic INSERT OR IGNORE + TTL
|
|
13
|
+
// expiry + owner token), relocated here from the previously-orphaned
|
|
14
|
+
// cloudflare/shims/lock.ts so the worker wires it through the locks slot.
|
|
15
|
+
|
|
16
|
+
// the two lock classes are the two driver implementations — cohesive in one module
|
|
17
|
+
/* eslint-disable max-classes-per-file */
|
|
18
|
+
|
|
19
|
+
import type { D1Binding } from './db';
|
|
20
|
+
|
|
21
|
+
export interface LockHandle {
|
|
22
|
+
name: string;
|
|
23
|
+
/** whether this handle currently holds the lock — used by singleton start guards */
|
|
24
|
+
locked: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Acquire the lock. `maxWaitMs` is a bounded-wait hint: the d1 driver enforces
|
|
27
|
+
* it as a hard timeout (and rejects on expiry) because a holder lives in a
|
|
28
|
+
* different isolate and may crash; the in-process memory driver waits until
|
|
29
|
+
* release and does not time out — single process means there is no
|
|
30
|
+
* crashed-holder / cross-isolate-wait scenario. The shared parity cases
|
|
31
|
+
* (acquire-when-free, block-until-release) hold on both; TTL-expiry and
|
|
32
|
+
* timeout are d1-only capabilities (see drivers/locks.spec.ts).
|
|
33
|
+
*/
|
|
34
|
+
acquire(maxWaitMs?: number): Promise<true>;
|
|
35
|
+
release(): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type LocksDriverKind = 'memory' | 'd1';
|
|
39
|
+
|
|
40
|
+
export interface LocksDriver {
|
|
41
|
+
kind: LocksDriverKind;
|
|
42
|
+
getLock(name: string, options?: { ttl?: number }): LockHandle;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type LockScope = 'tenant' | 'global';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Apply the tenant prefix to a lock name. Tenant-scoped locks (the default for
|
|
49
|
+
* per-resource locks) are isolated per deployment/tenant; global-scoped locks
|
|
50
|
+
* (process-level singleton guards like queue start guards) are intentionally
|
|
51
|
+
* shared and keep their bare name. In single-tenant mode the prefix is the
|
|
52
|
+
* constant deployment app DID, so lock identity is unchanged in practice.
|
|
53
|
+
*/
|
|
54
|
+
export function scopedLockName(name: string, instanceDid: string | null, scope: LockScope): string {
|
|
55
|
+
if (scope === 'global') return name;
|
|
56
|
+
if (!instanceDid) {
|
|
57
|
+
// tenant scope requires a tenant — callers resolve it fail-closed before
|
|
58
|
+
// reaching here (see libs/lock.ts). Guard anyway so a programming error is
|
|
59
|
+
// loud rather than silently producing a cross-tenant-shared lock.
|
|
60
|
+
throw new Error('scopedLockName: tenant-scoped lock requires a non-empty instanceDid');
|
|
61
|
+
}
|
|
62
|
+
return `tenant:${instanceDid}::${name}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// memory driver — original in-process lock semantics
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
const { EventEmitter } = require('events');
|
|
70
|
+
|
|
71
|
+
export class MemoryLock implements LockHandle {
|
|
72
|
+
name: string;
|
|
73
|
+
locked: boolean;
|
|
74
|
+
private events: any;
|
|
75
|
+
|
|
76
|
+
constructor(name: string) {
|
|
77
|
+
this.name = name;
|
|
78
|
+
this.locked = false;
|
|
79
|
+
this.events = new EventEmitter();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
acquire(): Promise<true> {
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
if (this.locked) {
|
|
85
|
+
const tryAcquire = () => {
|
|
86
|
+
if (!this.locked) {
|
|
87
|
+
this.locked = true;
|
|
88
|
+
this.events.removeListener('release', tryAcquire);
|
|
89
|
+
resolve(true);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
this.events.on('release', tryAcquire);
|
|
93
|
+
} else {
|
|
94
|
+
this.locked = true;
|
|
95
|
+
resolve(true);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
release(): void {
|
|
101
|
+
this.locked = false;
|
|
102
|
+
setImmediate(() => this.events.emit('release'));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function createMemoryLocksDriver(): LocksDriver {
|
|
107
|
+
const locks = new Map<string, MemoryLock>();
|
|
108
|
+
return {
|
|
109
|
+
kind: 'memory',
|
|
110
|
+
getLock(name: string): LockHandle {
|
|
111
|
+
const exist = locks.get(name);
|
|
112
|
+
if (exist instanceof MemoryLock) return exist;
|
|
113
|
+
const lock = new MemoryLock(name);
|
|
114
|
+
locks.set(name, lock);
|
|
115
|
+
return lock;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// d1 driver — D1-backed cross-isolate lock (relocated from shims/lock.ts)
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
let nanoidCounter = 0;
|
|
125
|
+
function simpleId(): string {
|
|
126
|
+
nanoidCounter += 1;
|
|
127
|
+
return `${Date.now().toString(36)}-${nanoidCounter.toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
class D1Lock implements LockHandle {
|
|
131
|
+
name: string;
|
|
132
|
+
owner: string;
|
|
133
|
+
locked: boolean;
|
|
134
|
+
ttl: number;
|
|
135
|
+
private getBinding: () => D1Binding;
|
|
136
|
+
|
|
137
|
+
constructor(getBinding: () => D1Binding, name: string, options?: { ttl?: number }) {
|
|
138
|
+
this.getBinding = getBinding;
|
|
139
|
+
this.name = name;
|
|
140
|
+
this.owner = simpleId();
|
|
141
|
+
this.locked = false;
|
|
142
|
+
this.ttl = options?.ttl || 5000;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async acquire(maxWaitMs = 10000): Promise<true> {
|
|
146
|
+
const db: any = this.getBinding();
|
|
147
|
+
const deadline = Date.now() + maxWaitMs;
|
|
148
|
+
let delay = 30;
|
|
149
|
+
|
|
150
|
+
while (Date.now() < deadline) {
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
try {
|
|
153
|
+
// eslint-disable-next-line no-await-in-loop -- polling retry until acquired or timed out
|
|
154
|
+
const batchResult = await db.batch([
|
|
155
|
+
db.prepare('DELETE FROM _locks WHERE name = ? AND expires_at < ?').bind(this.name, now),
|
|
156
|
+
db
|
|
157
|
+
.prepare('INSERT OR IGNORE INTO _locks (name, owner, expires_at) VALUES (?, ?, ?)')
|
|
158
|
+
.bind(this.name, this.owner, now + this.ttl),
|
|
159
|
+
db.prepare('SELECT owner FROM _locks WHERE name = ?').bind(this.name),
|
|
160
|
+
]);
|
|
161
|
+
const row = batchResult[2]?.results?.[0] as { owner: string } | undefined;
|
|
162
|
+
if (row?.owner === this.owner) {
|
|
163
|
+
this.locked = true;
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
} catch (err: any) {
|
|
167
|
+
// eslint-disable-next-line no-console
|
|
168
|
+
console.error(`[D1Lock] acquire error for "${this.name}":`, err?.message || err);
|
|
169
|
+
}
|
|
170
|
+
// backoff with jitter, capped — captured into a const so the closure does
|
|
171
|
+
// not reference the mutated `delay`
|
|
172
|
+
const waitMs = delay + Math.random() * delay * 0.3;
|
|
173
|
+
// eslint-disable-next-line no-await-in-loop, no-promise-executor-return
|
|
174
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
175
|
+
delay = Math.min(delay * 2, 500);
|
|
176
|
+
}
|
|
177
|
+
throw new Error(`[D1Lock] Failed to acquire lock "${this.name}" within ${maxWaitMs}ms`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
release(): void {
|
|
181
|
+
if (!this.locked) return;
|
|
182
|
+
this.locked = false;
|
|
183
|
+
try {
|
|
184
|
+
const db: any = this.getBinding();
|
|
185
|
+
const promise = db
|
|
186
|
+
.prepare('DELETE FROM _locks WHERE name = ? AND owner = ?')
|
|
187
|
+
.bind(this.name, this.owner)
|
|
188
|
+
.run()
|
|
189
|
+
// eslint-disable-next-line no-console
|
|
190
|
+
.catch((err: any) => console.error(`[D1Lock] release error for "${this.name}":`, err?.message || err));
|
|
191
|
+
|
|
192
|
+
// release is fire-and-forget; the worker shell flushes the pending delete
|
|
193
|
+
// before responding. This depends on ambient worker globals
|
|
194
|
+
// (__cfHttpContext__ / __cfWaitUntil__ / __cfPendingJobs__) set by the CF
|
|
195
|
+
// request handler — a host injecting the d1 locks driver must provide the
|
|
196
|
+
// same flush hooks (Phase 9 formalizes the flush contract). Outside a
|
|
197
|
+
// worker (e.g. the consistency suite) the delete still runs; it is simply
|
|
198
|
+
// not registered for flushing.
|
|
199
|
+
const isHttp = (globalThis as any).__cfHttpContext__;
|
|
200
|
+
if (isHttp) {
|
|
201
|
+
const waitUntil = (globalThis as any).__cfWaitUntil__;
|
|
202
|
+
if (typeof waitUntil === 'function') waitUntil(promise);
|
|
203
|
+
} else {
|
|
204
|
+
const pending = (globalThis as any).__cfPendingJobs__;
|
|
205
|
+
if (Array.isArray(pending)) pending.push(promise);
|
|
206
|
+
}
|
|
207
|
+
} catch (err: any) {
|
|
208
|
+
// eslint-disable-next-line no-console
|
|
209
|
+
console.error(`[D1Lock] release setup error for "${this.name}":`, err?.message || err);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* D1-backed locks driver. `getBinding` is a getter (the binding is resolved
|
|
216
|
+
* lazily per request in the worker). Each call returns a fresh lock — isolates
|
|
217
|
+
* never share instances, matching the original shim's behavior.
|
|
218
|
+
*/
|
|
219
|
+
export function createD1LocksDriver(getBinding: () => D1Binding): LocksDriver {
|
|
220
|
+
return {
|
|
221
|
+
kind: 'd1',
|
|
222
|
+
getLock(name: string, options?: { ttl?: number }): LockHandle {
|
|
223
|
+
return new D1Lock(getBinding, name, options);
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Phase 11 (W2′): runtime-neutral SQL migration runner — the migration-driver
|
|
2
|
+
// boundary. The library provisions the embedded D1 schema by applying the D1 SQL
|
|
3
|
+
// migrations through the host's db driver (its `exec`), NEVER by shelling out to
|
|
4
|
+
// the wrangler CLI. `wrangler d1 migrations apply` stays the CF-native local-dev
|
|
5
|
+
// / deploy provisioning mechanism for the SAME .sql files; this runner is the
|
|
6
|
+
// in-process path arc-node (and any non-CF host) uses via lifecycle/host wiring.
|
|
7
|
+
//
|
|
8
|
+
// Idempotent: applied migrations are tracked in `_sql_migrations`, so re-running
|
|
9
|
+
// is a no-op (the .sql contain non-IF-NOT-EXISTS DDL like ALTER ... ADD COLUMN).
|
|
10
|
+
//
|
|
11
|
+
// NOTE: this tracker (`_sql_migrations`) is independent of wrangler's own
|
|
12
|
+
// `d1_migrations` table. That is safe because the two provisioning paths target
|
|
13
|
+
// DIFFERENT databases — arc-node's embedded SQLite (this runner) vs the CF
|
|
14
|
+
// worker's bound D1 (wrangler) — and never share one. Do NOT run both paths
|
|
15
|
+
// against the SAME database: the runner is blind to `d1_migrations`, so it would
|
|
16
|
+
// re-apply everything and the non-idempotent ALTER ... ADD COLUMN would fail.
|
|
17
|
+
|
|
18
|
+
import type { DbDriver, DbBatchOp } from './db';
|
|
19
|
+
|
|
20
|
+
export interface SqlMigration {
|
|
21
|
+
/** stable id (the migration filename) — the idempotency key */
|
|
22
|
+
name: string;
|
|
23
|
+
/** the migration body; may contain multiple `;`-separated statements */
|
|
24
|
+
sql: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Strip line comments and split a migration body into individual statements. */
|
|
28
|
+
export function splitStatements(sql: string): string[] {
|
|
29
|
+
return sql
|
|
30
|
+
.split('\n')
|
|
31
|
+
.filter((line) => !line.trim().startsWith('--'))
|
|
32
|
+
.join('\n')
|
|
33
|
+
.split(';')
|
|
34
|
+
.map((s) => s.trim())
|
|
35
|
+
.filter((s) => s.length > 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Apply pending SQL migrations through the db driver, in order. Returns the
|
|
40
|
+
* names actually applied this run (empty on a fully-migrated db). No wrangler.
|
|
41
|
+
*
|
|
42
|
+
* Each migration is applied ATOMICALLY: all of its statements PLUS the tracker
|
|
43
|
+
* insert run in a single `driver.batch(...)` (the contract's all-or-nothing
|
|
44
|
+
* transactional primitive). So a mid-migration failure rolls the whole migration
|
|
45
|
+
* back AND leaves the tracker unwritten — a re-run retries that migration from a
|
|
46
|
+
* clean slate, never replaying half-applied (non-IF-NOT-EXISTS) DDL like
|
|
47
|
+
* `ALTER ... ADD COLUMN`. This satisfies "中断后重跑不丢已迁移状态(幂等 + 事务边界)".
|
|
48
|
+
*/
|
|
49
|
+
export async function applySqlMigrations(driver: DbDriver, migrations: SqlMigration[]): Promise<string[]> {
|
|
50
|
+
await driver.exec('CREATE TABLE IF NOT EXISTS _sql_migrations (name TEXT PRIMARY KEY, applied_at TEXT NOT NULL)');
|
|
51
|
+
const rows = await driver.all<{ name: string }>('SELECT name FROM _sql_migrations');
|
|
52
|
+
const applied = new Set(rows.map((r) => r.name));
|
|
53
|
+
|
|
54
|
+
const ran: string[] = [];
|
|
55
|
+
const pending = migrations.filter((m) => !applied.has(m.name));
|
|
56
|
+
/* eslint-disable no-await-in-loop -- migrations are ordered + must apply sequentially */
|
|
57
|
+
for (const m of pending) {
|
|
58
|
+
const ops: DbBatchOp[] = splitStatements(m.sql).map((sql) => ({ sql }));
|
|
59
|
+
// tracker insert is the LAST op in the same batch — committed iff every
|
|
60
|
+
// schema statement of this migration committed (atomic boundary).
|
|
61
|
+
ops.push({
|
|
62
|
+
sql: 'INSERT INTO _sql_migrations (name, applied_at) VALUES (?, ?)',
|
|
63
|
+
params: [m.name, new Date().toISOString()],
|
|
64
|
+
});
|
|
65
|
+
await driver.batch(ops);
|
|
66
|
+
ran.push(m.name);
|
|
67
|
+
}
|
|
68
|
+
/* eslint-enable no-await-in-loop */
|
|
69
|
+
return ran;
|
|
70
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Phase 9 (W2-1b): queue slot driver contract.
|
|
2
|
+
//
|
|
3
|
+
// The queue contract is the WHOLE semantics the two engines share, not just
|
|
4
|
+
// enqueue:
|
|
5
|
+
// - jobs table (createQueueStore) = persistent scheduler / source of truth
|
|
6
|
+
// - immediate vs delayed dispatch (delayed rows wait for the due-poll)
|
|
7
|
+
// - consumer ack + failure re-delivery (retry_count up to maxRetries, then
|
|
8
|
+
// failed; nonRetryable errors fail immediately)
|
|
9
|
+
// - pushAndWait inline execution (caller awaits the result)
|
|
10
|
+
// - host flush hook (CF flushes pending push/timer work before responding;
|
|
11
|
+
// the Node long-lived process is a no-op)
|
|
12
|
+
// - tenant is carried in the payload (Phase 5/6); the contract layer passes
|
|
13
|
+
// it through and NEVER resolves Host on the background path
|
|
14
|
+
//
|
|
15
|
+
// ONE engine (api/src/libs/queue) serves every runtime (Phase 12b, option A):
|
|
16
|
+
// the host swaps only the EXECUTOR (real fastq on node, cloudflare/shims/fastq
|
|
17
|
+
// in the worker) and the TRIGGER (in-process poll loop on node, CF scheduled()
|
|
18
|
+
// calling the engine's dispatchDueJobs() in the worker — see
|
|
19
|
+
// api/src/libs/queue/runtime.ts). Persistence, retry policy and tenant handling
|
|
20
|
+
// are shared because there is no second engine. The old cloudflare/shims/queue.ts
|
|
21
|
+
// duplicate engine was removed in Phase 12c.
|
|
22
|
+
|
|
23
|
+
import type EventEmitter from 'events';
|
|
24
|
+
|
|
25
|
+
export interface QueueOptions<T> {
|
|
26
|
+
id?: (job: T) => string;
|
|
27
|
+
concurrency?: number;
|
|
28
|
+
maxRetries?: number;
|
|
29
|
+
maxTimeout?: number;
|
|
30
|
+
retryDelay?: number;
|
|
31
|
+
enableScheduledJob?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PushParams<T> {
|
|
35
|
+
job: T;
|
|
36
|
+
id?: string;
|
|
37
|
+
persist?: boolean;
|
|
38
|
+
/** seconds */
|
|
39
|
+
delay?: number;
|
|
40
|
+
/** unix timestamp in seconds */
|
|
41
|
+
runAt?: number;
|
|
42
|
+
skipDuplicateCheck?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Internal re-delivery flag for rows already persisted (startup / scheduled
|
|
45
|
+
* recovery). Skips the enqueue tenant gate so the execution-side legacy
|
|
46
|
+
* strategy applies. NEVER set from application code.
|
|
47
|
+
*/
|
|
48
|
+
fromStore?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** the per-job event channel returned by push (emits queued/finished/failed/retry/cancelled) */
|
|
52
|
+
export type JobEvents = EventEmitter & { id?: string };
|
|
53
|
+
|
|
54
|
+
export interface QueueHandle<T = any> {
|
|
55
|
+
push(params: PushParams<T>): JobEvents;
|
|
56
|
+
pushAndWait(params: PushParams<T>): Promise<{ id: string; job: T; result: any }>;
|
|
57
|
+
get(id: string): Promise<T | null>;
|
|
58
|
+
delete(id: string, knownExists?: boolean): Promise<boolean>;
|
|
59
|
+
cancel(id: string): Promise<T | null>;
|
|
60
|
+
update(id: string, updates: any): Promise<any>;
|
|
61
|
+
/**
|
|
62
|
+
* D2 teardown: stop this queue's node poll loop (clears its sleep timer).
|
|
63
|
+
* No-op when the queue is not scheduled or on a workerd host (no loop runs).
|
|
64
|
+
* The Node host tears every queue down via lifecycle.stop() → stopAllQueues().
|
|
65
|
+
*/
|
|
66
|
+
stop?(): void;
|
|
67
|
+
options: Required<Omit<QueueOptions<T>, 'id'>>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** factory shape both engines export as default */
|
|
71
|
+
export type QueueFactory = <T = any>(params: {
|
|
72
|
+
name: string;
|
|
73
|
+
onJob: (job: T) => Promise<any>;
|
|
74
|
+
options?: QueueOptions<T>;
|
|
75
|
+
}) => QueueHandle<T> & EventEmitter;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Host flush hook. CF must flush pending push/timer work before returning the
|
|
79
|
+
* response (the isolate is torn down after); a long-lived Node process never
|
|
80
|
+
* needs to and uses the no-op below.
|
|
81
|
+
*/
|
|
82
|
+
export interface QueueHostHooks {
|
|
83
|
+
flush(): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Node host: long-lived process, nothing to flush before a response. */
|
|
87
|
+
export const nodeQueueHostHooks: QueueHostHooks = {
|
|
88
|
+
async flush() {
|
|
89
|
+
/* no-op — the process stays alive, in-flight jobs continue */
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Active host hooks — injectable by the factory's `queue` slot; defaults to the
|
|
94
|
+
// Node no-op. The worker shell injects hooks that flush pending push/timer work
|
|
95
|
+
// before responding.
|
|
96
|
+
let activeQueueHostHooks: QueueHostHooks = nodeQueueHostHooks;
|
|
97
|
+
|
|
98
|
+
export function setQueueHostHooks(hooks: QueueHostHooks): void {
|
|
99
|
+
activeQueueHostHooks = hooks;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getQueueHostHooks(): QueueHostHooks {
|
|
103
|
+
return activeQueueHostHooks;
|
|
104
|
+
}
|