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
package/cloudflare/wrangler.json
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Stripped LOCAL-ONLY config for the express→hono Phase 4 CF runtime E2E.
|
|
3
|
+
// Drops the external service bindings (AUTH_SERVICE / MEDIA_KIT — separate
|
|
4
|
+
// workers not in this repo), HYPERDRIVE, queues, assets, and crons so the worker
|
|
5
|
+
// boots under `wrangler dev` with just a local D1 + KV. Auth is therefore absent
|
|
6
|
+
// (caller resolves to null) — only PUBLIC read routes + the 404 path are testable.
|
|
7
|
+
"name": "payment-kit-local-e2e",
|
|
8
|
+
"main": "dist/worker.js",
|
|
9
|
+
"compatibility_date": "2024-12-01",
|
|
10
|
+
"compatibility_flags": ["nodejs_compat"],
|
|
11
|
+
"d1_databases": [
|
|
12
|
+
{
|
|
13
|
+
"binding": "DB",
|
|
14
|
+
"database_name": "payment-kit-prod",
|
|
15
|
+
"database_id": "ea6c75d0-39b8-40cd-a0ae-a2062e77c4b9",
|
|
16
|
+
"migrations_dir": "migrations"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"vars": {
|
|
20
|
+
"APP_NAME": "Payment Kit",
|
|
21
|
+
"APP_PID": "payment-kit-dev",
|
|
22
|
+
"APP_URL": "http://localhost:8787",
|
|
23
|
+
"PAYMENT_LIVEMODE": "false"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/jest.config.js
CHANGED
|
@@ -13,7 +13,9 @@ module.exports = {
|
|
|
13
13
|
'^.+\\.js$': ['babel-jest', { presets: [['@babel/preset-env', { targets: { node: 'current' } }]] }],
|
|
14
14
|
},
|
|
15
15
|
transformIgnorePatterns: [
|
|
16
|
-
|
|
16
|
+
// `validator` ships an ESM build under validator/es/* that resource routes
|
|
17
|
+
// (customers) import directly; transform it so jest can load those routes.
|
|
18
|
+
'node_modules/(?!.*(@noble|@scure|string-width|strip-ansi|ansi-regex|p-retry|is-network-error|validator|uuid)/)',
|
|
17
19
|
],
|
|
18
20
|
testMatch: ['**/tests/**/*.spec.ts'],
|
|
19
21
|
collectCoverageFrom: ['api/src/**/*.ts'],
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.3",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"prelint": "npm run types",
|
|
7
|
-
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
7
|
+
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx && node scripts/scan-tenant-queries.js && node scripts/scan-core-env.js",
|
|
8
8
|
"lint:fix": "pnpm run lint --fix",
|
|
9
9
|
"format": "prettier -w src",
|
|
10
10
|
"test": "node scripts/jest.js",
|
|
@@ -46,60 +46,55 @@
|
|
|
46
46
|
]
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@abtnode/cron": "^1.17.
|
|
49
|
+
"@abtnode/cron": "^1.17.13-beta-20260613-094425-b81920c8",
|
|
50
50
|
"@apple/app-store-server-library": "^3.1.0",
|
|
51
|
-
"@arcblock/did": "^1.30.
|
|
52
|
-
"@arcblock/did-connect-js": "4.0.
|
|
53
|
-
"@arcblock/did-connect-react": "^3.5.
|
|
51
|
+
"@arcblock/did": "^1.30.24",
|
|
52
|
+
"@arcblock/did-connect-js": "^4.0.5",
|
|
53
|
+
"@arcblock/did-connect-react": "^3.5.4",
|
|
54
54
|
"@arcblock/did-connect-storage-nedb": "^1.8.0",
|
|
55
|
-
"@arcblock/did-util": "^1.30.
|
|
56
|
-
"@arcblock/jwt": "^1.30.
|
|
57
|
-
"@arcblock/react-hooks": "^3.5.
|
|
58
|
-
"@arcblock/ux": "^3.5.
|
|
59
|
-
"@arcblock/validator": "^1.30.
|
|
60
|
-
"@arcblock/vc": "^1.30.
|
|
61
|
-
"@blocklet/did-space-js": "^1.2.23",
|
|
55
|
+
"@arcblock/did-util": "^1.30.24",
|
|
56
|
+
"@arcblock/jwt": "^1.30.24",
|
|
57
|
+
"@arcblock/react-hooks": "^3.5.4",
|
|
58
|
+
"@arcblock/ux": "^3.5.4",
|
|
59
|
+
"@arcblock/validator": "^1.30.24",
|
|
60
|
+
"@arcblock/vc": "^1.30.24",
|
|
62
61
|
"@blocklet/error": "^0.3.5",
|
|
63
|
-
"@blocklet/js-sdk": "^1.17.
|
|
64
|
-
"@blocklet/logger": "^1.17.
|
|
65
|
-
"@blocklet/payment-broker-client": "1.29.
|
|
66
|
-
"@blocklet/payment-react": "1.29.
|
|
67
|
-
"@blocklet/payment-vendor": "1.29.
|
|
68
|
-
"@blocklet/sdk": "^1.17.
|
|
69
|
-
"@blocklet/ui-react": "^3.5.
|
|
62
|
+
"@blocklet/js-sdk": "^1.17.13-beta-20260613-094425-b81920c8",
|
|
63
|
+
"@blocklet/logger": "^1.17.13-beta-20260613-094425-b81920c8",
|
|
64
|
+
"@blocklet/payment-broker-client": "1.29.3",
|
|
65
|
+
"@blocklet/payment-react": "1.29.3",
|
|
66
|
+
"@blocklet/payment-vendor": "1.29.3",
|
|
67
|
+
"@blocklet/sdk": "^1.17.13-beta-20260613-094425-b81920c8",
|
|
68
|
+
"@blocklet/ui-react": "^3.5.4",
|
|
70
69
|
"@blocklet/uploader": "^0.3.20",
|
|
71
70
|
"@blocklet/xss": "^0.3.16",
|
|
71
|
+
"@hono/node-server": "2.0.4",
|
|
72
72
|
"@mui/icons-material": "^7.1.2",
|
|
73
73
|
"@mui/lab": "7.0.0-beta.14",
|
|
74
74
|
"@mui/material": "^7.1.2",
|
|
75
75
|
"@mui/system": "^7.1.1",
|
|
76
|
-
"@ocap/asset": "^1.30.
|
|
77
|
-
"@ocap/client": "^1.30.
|
|
78
|
-
"@ocap/mcrypto": "^1.30.
|
|
79
|
-
"@ocap/util": "^1.30.
|
|
80
|
-
"@ocap/wallet": "^1.30.
|
|
76
|
+
"@ocap/asset": "^1.30.24",
|
|
77
|
+
"@ocap/client": "^1.30.24",
|
|
78
|
+
"@ocap/mcrypto": "^1.30.24",
|
|
79
|
+
"@ocap/util": "^1.30.24",
|
|
80
|
+
"@ocap/wallet": "^1.30.24",
|
|
81
81
|
"@stripe/react-stripe-js": "^2.9.0",
|
|
82
82
|
"@stripe/stripe-js": "^2.4.0",
|
|
83
83
|
"ahooks": "^3.8.5",
|
|
84
84
|
"axios": "^1.15.2",
|
|
85
85
|
"bignumber.js": "^9.3.1",
|
|
86
|
-
"body-parser": "^1.20.3",
|
|
87
86
|
"cls-hooked": "^4.2.2",
|
|
88
|
-
"cookie-parser": "^1.4.7",
|
|
89
87
|
"copy-to-clipboard": "^3.3.3",
|
|
90
|
-
"cors": "^2.8.5",
|
|
91
88
|
"date-fns": "^3.6.0",
|
|
92
89
|
"dayjs": "^1.11.13",
|
|
93
90
|
"debug": "^4.4.1",
|
|
94
91
|
"dotenv-flow": "^3.3.0",
|
|
95
92
|
"ethers": "^6.14.4",
|
|
96
|
-
"express": "^4.21.2",
|
|
97
|
-
"express-async-errors": "^3.1.1",
|
|
98
|
-
"express-history-api-fallback": "^2.2.1",
|
|
99
93
|
"fastq": "^1.19.1",
|
|
100
94
|
"flat": "^5.0.2",
|
|
101
95
|
"google-libphonenumber": "^3.2.42",
|
|
102
96
|
"google-play-billing-validator": "^2.1.3",
|
|
97
|
+
"hono": "4.12.12",
|
|
103
98
|
"html2canvas": "^1.4.1",
|
|
104
99
|
"iframe-resizer-react": "^1.1.1",
|
|
105
100
|
"joi": "17.12.2",
|
|
@@ -125,7 +120,7 @@
|
|
|
125
120
|
"react-router-dom": "^6.30.3",
|
|
126
121
|
"recharts": "^2.15.4",
|
|
127
122
|
"rimraf": "^3.0.2",
|
|
128
|
-
"sequelize": "
|
|
123
|
+
"sequelize": "~6.37.8",
|
|
129
124
|
"sql-where-parser": "^2.2.1",
|
|
130
125
|
"sqlite3": "^5.1.7",
|
|
131
126
|
"stripe": "^13.11.0",
|
|
@@ -138,14 +133,12 @@
|
|
|
138
133
|
"web3": "^4.16.0"
|
|
139
134
|
},
|
|
140
135
|
"devDependencies": {
|
|
141
|
-
"@abtnode/types": "^1.17.
|
|
136
|
+
"@abtnode/types": "^1.17.13-beta-20260613-094425-b81920c8",
|
|
142
137
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
143
|
-
"@blocklet/payment-types": "1.29.
|
|
144
|
-
"@types/
|
|
145
|
-
"@types/cors": "^2.8.19",
|
|
138
|
+
"@blocklet/payment-types": "1.29.3",
|
|
139
|
+
"@types/connect": "^3.4.38",
|
|
146
140
|
"@types/debug": "^4.1.12",
|
|
147
141
|
"@types/dotenv-flow": "^3.3.3",
|
|
148
|
-
"@types/express": "^4.17.23",
|
|
149
142
|
"@types/node": "^18.19.112",
|
|
150
143
|
"@types/node-apple-receipt-verify": "^1.7.5",
|
|
151
144
|
"@types/react": "^18.3.23",
|
|
@@ -154,6 +147,7 @@
|
|
|
154
147
|
"babel-plugin-lodash": "^3.3.4",
|
|
155
148
|
"bumpp": "^8.2.1",
|
|
156
149
|
"code-inspector-plugin": "^1.2.3",
|
|
150
|
+
"connect": "^3.7.0",
|
|
157
151
|
"cross-env": "^7.0.3",
|
|
158
152
|
"eslint": "^8.57.1",
|
|
159
153
|
"import-sort-style-module": "^6.0.0",
|
|
@@ -176,6 +170,7 @@
|
|
|
176
170
|
"vite-plugin-node-polyfills": "^0.23.0",
|
|
177
171
|
"vite-plugin-svgr": "^4.3.0",
|
|
178
172
|
"vite-tsconfig-paths": "^5.1.4",
|
|
173
|
+
"wrangler": "^4.100.0",
|
|
179
174
|
"zx": "^8.8.5"
|
|
180
175
|
},
|
|
181
176
|
"importSort": {
|
|
@@ -188,5 +183,5 @@
|
|
|
188
183
|
"parser": "typescript"
|
|
189
184
|
}
|
|
190
185
|
},
|
|
191
|
-
"gitHead": "
|
|
186
|
+
"gitHead": "0bd49f9b9586e307b51d1f5a6b042e82c39366ef"
|
|
192
187
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// P2 (README D2 / F2) — runtime-neutral bootstrap helper.
|
|
2
|
+
//
|
|
3
|
+
// Parameterizes the formerly CF-only `cf-inject-blocklet` logic
|
|
4
|
+
// (cloudflare/vite.config.ts) so the node arc build (vite.arc.config.ts) and the
|
|
5
|
+
// CF build share ONE source for the build-time HTML bootstrap. The pure
|
|
6
|
+
// functions `buildBootstrap` / `mergeRemote` are jest-assertable
|
|
7
|
+
// (api/tests/bootstrap/bootstrap.spec.ts); `buildBootstrapScript` serializes
|
|
8
|
+
// `mergeRemote` (via .toString()) into the inline <script> so the browser runs
|
|
9
|
+
// the identical merge at runtime.
|
|
10
|
+
//
|
|
11
|
+
// D-3 (user-confirmed 2026-06-14) put this under blocklets/core/build/. That
|
|
12
|
+
// path is .gitignored (`blocklets/core/.gitignore:13` — `build` = production
|
|
13
|
+
// outputs), so the file could never be tracked there. It lives in scripts/
|
|
14
|
+
// instead — same intent (under blocklets/core, adjacent to both vite configs,
|
|
15
|
+
// zero cross-package dep, where build tooling already lives: jest.js,
|
|
16
|
+
// build-clean.js) but tracked. Extension is `.ts` (not `.mjs`) so ts-jest can
|
|
17
|
+
// unit-test the pure functions directly; both vite configs are `.ts` and import
|
|
18
|
+
// it natively.
|
|
19
|
+
|
|
20
|
+
// Source of truth: packages/react/src/libs/util.ts:33. Re-declared here to keep
|
|
21
|
+
// this build helper free of the heavy browser util graph (the only thing the
|
|
22
|
+
// build needs from it is the bare DID literal).
|
|
23
|
+
export const PAYMENT_KIT_DID = 'z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk';
|
|
24
|
+
|
|
25
|
+
export interface BootstrapOptions {
|
|
26
|
+
/** the host's UI mount prefix (node arc = '/.well-known/payment'; cf custom). */
|
|
27
|
+
uiPrefix: string;
|
|
28
|
+
/** PAYMENT_KIT_DID so getPrefix() (packages/react/src/libs/util.ts:87) takes
|
|
29
|
+
* the `componentId === PAYMENT_KIT_DID` branch and resolves to uiPrefix. */
|
|
30
|
+
componentId: string;
|
|
31
|
+
/** root-exact `/__blocklet__.js?type=json` — NEVER under uiPrefix (T2.3). */
|
|
32
|
+
remoteBlockletUrl: string;
|
|
33
|
+
/** session service host (node arc = arc '/.well-known/service'); CF root-deploy
|
|
34
|
+
* omits it so window.blocklet.serviceHost stays undefined (app.tsx falls back
|
|
35
|
+
* to prefix — P5 T5.1). */
|
|
36
|
+
serviceHost?: string;
|
|
37
|
+
/** baked appUrl; defaults to window.location.origin at runtime when omitted. */
|
|
38
|
+
appUrl?: string;
|
|
39
|
+
/** keys the remote __blocklet__.js must NOT clobber (G3 — `prefix` included). */
|
|
40
|
+
localOnly?: string[];
|
|
41
|
+
/** host-owned extras merged into the base bootstrap (navigation, mountpoints). */
|
|
42
|
+
extra?: Record<string, any>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// G3: `prefix` MUST be local-only so the app-root prefix from the remote
|
|
46
|
+
// __blocklet__.js never overwrites the payment reserved prefix.
|
|
47
|
+
const DEFAULT_LOCAL_ONLY = ['prefix', 'navigation', 'componentMountPoints'];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Build the synchronous window.blocklet skeleton. Pure — no IO, no globals.
|
|
51
|
+
* Throws (T2.3 regression lock) when remoteBlockletUrl is not root-exact, so a
|
|
52
|
+
* misconfigured build fails loudly at build time instead of silently fetching
|
|
53
|
+
* `/.well-known/payment/__blocklet__.js` (inside the reserved prefix).
|
|
54
|
+
*/
|
|
55
|
+
export function buildBootstrap(opts: BootstrapOptions): Record<string, any> {
|
|
56
|
+
const { uiPrefix, componentId, remoteBlockletUrl, serviceHost, appUrl, extra } = opts;
|
|
57
|
+
const localOnly = opts.localOnly || DEFAULT_LOCAL_ONLY;
|
|
58
|
+
|
|
59
|
+
if (!remoteBlockletUrl.startsWith('/__blocklet__')) {
|
|
60
|
+
throw new Error(`remoteBlockletUrl must be root-exact (start with /__blocklet__), got: ${remoteBlockletUrl}`);
|
|
61
|
+
}
|
|
62
|
+
if (uiPrefix !== '/' && remoteBlockletUrl.startsWith(uiPrefix)) {
|
|
63
|
+
throw new Error(`remoteBlockletUrl must not fall under uiPrefix ${uiPrefix}: ${remoteBlockletUrl}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
prefix: uiPrefix,
|
|
68
|
+
groupPrefix: uiPrefix,
|
|
69
|
+
componentId,
|
|
70
|
+
serviceHost,
|
|
71
|
+
appUrl: appUrl || '',
|
|
72
|
+
remoteBlockletUrl,
|
|
73
|
+
localOnly,
|
|
74
|
+
// @blocklet/ui-react nav/dashboard iterate these with .forEach — default to []
|
|
75
|
+
// so a host that doesn't override via `extra` never crashes on undefined. They
|
|
76
|
+
// are in localOnly, so the remote __blocklet__.js merge can't clobber them
|
|
77
|
+
// (AUTH_SERVICE doesn't know this blocklet's nav / mount points).
|
|
78
|
+
navigation: [],
|
|
79
|
+
componentMountPoints: [],
|
|
80
|
+
...(extra || {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Merge the remote per-app __blocklet__.js JSON onto the local bootstrap.
|
|
86
|
+
*
|
|
87
|
+
* SELF-CONTAINED (no module-scope refs) so `buildBootstrapScript` can embed it
|
|
88
|
+
* via `.toString()` and the browser runs the identical merge. Guards:
|
|
89
|
+
* - localOnly keys (prefix/navigation/componentMountPoints) are never clobbered
|
|
90
|
+
* - __proto__/constructor/prototype keys are dropped (prototype-pollution)
|
|
91
|
+
* - string values are angle-bracket escaped (<,> → entities) — blocks tag
|
|
92
|
+
* injection without corrupting URL query strings (& preserved)
|
|
93
|
+
* - payloads > 256KB are refused (resource exhaustion)
|
|
94
|
+
*/
|
|
95
|
+
export function mergeRemote(
|
|
96
|
+
wb: Record<string, any>,
|
|
97
|
+
remote: Record<string, any>,
|
|
98
|
+
localOnlyArg?: string[]
|
|
99
|
+
): Record<string, any> {
|
|
100
|
+
var localOnly = localOnlyArg || (wb && wb.localOnly) || ['prefix', 'navigation', 'componentMountPoints'];
|
|
101
|
+
|
|
102
|
+
if (remote && typeof remote === 'object') {
|
|
103
|
+
var size = 0;
|
|
104
|
+
try {
|
|
105
|
+
size = JSON.stringify(remote).length;
|
|
106
|
+
} catch (e) {
|
|
107
|
+
size = Infinity;
|
|
108
|
+
}
|
|
109
|
+
if (size > 256 * 1024) {
|
|
110
|
+
throw new Error('remote bootstrap payload too large (>256KB), refusing merge');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
var out: Record<string, any> = {};
|
|
115
|
+
Object.keys(wb || {}).forEach(function (k) {
|
|
116
|
+
out[k] = wb[k];
|
|
117
|
+
});
|
|
118
|
+
Object.keys(remote || {}).forEach(function (k) {
|
|
119
|
+
if (k === '__proto__' || k === 'constructor' || k === 'prototype') return;
|
|
120
|
+
// componentId is a structural invariant (getPrefix's PAYMENT_KIT_DID branch),
|
|
121
|
+
// protected independently of the configurable localOnly list.
|
|
122
|
+
if (k === 'componentId') return;
|
|
123
|
+
if (localOnly.indexOf(k) !== -1) return;
|
|
124
|
+
var v = remote[k];
|
|
125
|
+
if (typeof v === 'string') {
|
|
126
|
+
v = v.replace(/</g, '<').replace(/>/g, '>');
|
|
127
|
+
}
|
|
128
|
+
out[k] = v;
|
|
129
|
+
});
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Produce the inline <script> for transformIndexHtml. Bakes the validated
|
|
135
|
+
* buildBootstrap() result as the synchronous window.blocklet, then embeds
|
|
136
|
+
* mergeRemote (serialized) so the runtime XHR merge is the same code jest tests.
|
|
137
|
+
*/
|
|
138
|
+
export function buildBootstrapScript(opts: BootstrapOptions): string {
|
|
139
|
+
const wb = buildBootstrap(opts);
|
|
140
|
+
const initial = JSON.stringify(wb);
|
|
141
|
+
const remoteUrl = JSON.stringify(wb.remoteBlockletUrl);
|
|
142
|
+
return `<script>
|
|
143
|
+
window.global = globalThis;
|
|
144
|
+
if (!window.blocklet) {
|
|
145
|
+
window.blocklet = ${initial};
|
|
146
|
+
window.blocklet.appUrl = window.blocklet.appUrl || window.location.origin;
|
|
147
|
+
}
|
|
148
|
+
(function () {
|
|
149
|
+
try {
|
|
150
|
+
var xhr = new XMLHttpRequest();
|
|
151
|
+
var url = ${remoteUrl};
|
|
152
|
+
xhr.open('GET', url + (url.indexOf('?') === -1 ? '?' : '&') + '_t=' + Date.now(), false);
|
|
153
|
+
xhr.send();
|
|
154
|
+
if (xhr.status === 200) {
|
|
155
|
+
var remote = JSON.parse(xhr.responseText);
|
|
156
|
+
window.blocklet = (${mergeRemote.toString()})(window.blocklet, remote);
|
|
157
|
+
if (!window.blocklet.env) window.blocklet.env = {};
|
|
158
|
+
window.blocklet.env.appName = window.blocklet.appName || '';
|
|
159
|
+
window.blocklet.env.appDescription = window.blocklet.appDescription || '';
|
|
160
|
+
window.blocklet.env.appLogo = window.blocklet.appLogo || '';
|
|
161
|
+
window.blocklet.env.appUrl = window.blocklet.appUrl || '';
|
|
162
|
+
}
|
|
163
|
+
} catch (e) { /* ignore — fall back to sync bootstrap */ }
|
|
164
|
+
})();
|
|
165
|
+
</script>`;
|
|
166
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Phase 12b E2E harness — drives the core queue RUNTIME surface against a real
|
|
2
|
+
// sqlite DB (outside jest) and prints JSON-shaped results for the build-phases
|
|
3
|
+
// E2E hard gate. Proves worker(workerd) ↔ node parity for delay/cancel and that
|
|
4
|
+
// the host-driven dispatchDueJobs()/flushQueueWork() seam runs the SAME engine.
|
|
5
|
+
//
|
|
6
|
+
// Run: npx tsx scripts/e2e-12b-runtime.ts (cwd = blocklets/core)
|
|
7
|
+
/* eslint-disable no-console, global-require, @typescript-eslint/no-var-requires */
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { Sequelize } from 'sequelize';
|
|
12
|
+
import { SequelizeStorage, Umzug } from 'umzug';
|
|
13
|
+
|
|
14
|
+
const TENANT_A = 'did:abt:zE2E12BA';
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
const STORE_DIR = path.join(process.cwd(), 'api/src/store');
|
|
18
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'e2e-12b-'));
|
|
19
|
+
// store/migrate.ts eagerly builds a sequelize at module-load (needs a data
|
|
20
|
+
// dir). We bind models to our OWN sqlite below; this just satisfies the eager
|
|
21
|
+
// path. Set BEFORE any in-repo require.
|
|
22
|
+
process.env.BLOCKLET_DATA_DIR = dir;
|
|
23
|
+
process.env.NODE_ENV = process.env.NODE_ENV || 'test';
|
|
24
|
+
// single-mode default tenant for the tenant-backfill migration + config gate
|
|
25
|
+
process.env.BLOCKLET_APP_PID = process.env.BLOCKLET_APP_PID || TENANT_A;
|
|
26
|
+
process.env.BLOCKLET_APP_ID = process.env.BLOCKLET_APP_ID || TENANT_A;
|
|
27
|
+
const sequelize = new Sequelize({ dialect: 'sqlite', storage: path.join(dir, 'e2e.db'), logging: false });
|
|
28
|
+
const qi = sequelize.getQueryInterface();
|
|
29
|
+
const migDir = path.join(STORE_DIR, 'migrations');
|
|
30
|
+
const migrations = fs
|
|
31
|
+
.readdirSync(migDir)
|
|
32
|
+
.filter((f) => f.endsWith('.ts'))
|
|
33
|
+
.sort()
|
|
34
|
+
.map((f) => {
|
|
35
|
+
const mod = require(path.join(migDir, f));
|
|
36
|
+
return { name: f, up: async () => mod.up({ context: qi }), down: async () => mod.down({ context: qi }) };
|
|
37
|
+
});
|
|
38
|
+
const umzug = new Umzug({ migrations, storage: new SequelizeStorage({ sequelize }), logger: undefined });
|
|
39
|
+
await umzug.up();
|
|
40
|
+
require('../api/src/store/models').initialize(sequelize);
|
|
41
|
+
|
|
42
|
+
const createQueue = require('../api/src/libs/queue').default;
|
|
43
|
+
const {
|
|
44
|
+
setQueueRuntimeMode,
|
|
45
|
+
getAllQueueNames,
|
|
46
|
+
dispatchDueJobs,
|
|
47
|
+
flushQueueWork,
|
|
48
|
+
} = require('../api/src/libs/queue/runtime');
|
|
49
|
+
|
|
50
|
+
const out: any[] = [];
|
|
51
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
52
|
+
|
|
53
|
+
// --- S12b.1 registry non-empty (canonical engine, not the dead shim) ---
|
|
54
|
+
setQueueRuntimeMode('workerd');
|
|
55
|
+
let ranDue = 0;
|
|
56
|
+
const q = createQueue({
|
|
57
|
+
name: 'e2e-due',
|
|
58
|
+
onJob: async () => {
|
|
59
|
+
ranDue += 1;
|
|
60
|
+
},
|
|
61
|
+
options: { enableScheduledJob: true },
|
|
62
|
+
});
|
|
63
|
+
out.push({ case: 'S12b.1 registry non-empty', input: 'createQueue(e2e-due)', output: { queues: getAllQueueNames() } });
|
|
64
|
+
|
|
65
|
+
// --- S12b.2 workerd disables the background loop (no auto-dispatch) ---
|
|
66
|
+
await q.store.addJob('due-1', { v: 1, instance_did: TENANT_A }, { delay: 5, will_run_at: Date.now() - 1000 });
|
|
67
|
+
await sleep(80);
|
|
68
|
+
out.push({ case: 'S12b.2 workerd loop disabled', input: 'due delayed row + wait 80ms', output: { ranBeforeDispatch: ranDue } });
|
|
69
|
+
|
|
70
|
+
// --- S12b.3 host-driven dispatchDueJobs executes the due delayed job once ---
|
|
71
|
+
const dispatchResult = await dispatchDueJobs();
|
|
72
|
+
await flushQueueWork();
|
|
73
|
+
out.push({
|
|
74
|
+
case: 'S12b.3 dispatchDueJobs executes due job once',
|
|
75
|
+
input: 'dispatchDueJobs() + flushQueueWork()',
|
|
76
|
+
output: { dispatched: dispatchResult.dispatched, ran: ranDue, rowCleared: (await q.store.getJob('due-1')) === null },
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// --- S12b.4 NEGATIVE: cancel an in-flight delayed job — no half-execution ---
|
|
80
|
+
let ranCancel = 0;
|
|
81
|
+
const qc = createQueue({
|
|
82
|
+
name: 'e2e-cancel',
|
|
83
|
+
onJob: async () => {
|
|
84
|
+
ranCancel += 1;
|
|
85
|
+
},
|
|
86
|
+
options: { enableScheduledJob: true },
|
|
87
|
+
});
|
|
88
|
+
await qc.store.addJob('cancel-1', { v: 1, instance_did: TENANT_A }, { delay: 5, will_run_at: Date.now() - 1000 });
|
|
89
|
+
await qc.cancel('cancel-1');
|
|
90
|
+
const cancelDispatch = await dispatchDueJobs();
|
|
91
|
+
await flushQueueWork();
|
|
92
|
+
out.push({
|
|
93
|
+
case: 'S12b.4 cancel in-flight leaves no residue',
|
|
94
|
+
input: 'cancel(cancel-1) before dispatchDueJobs()',
|
|
95
|
+
output: { dispatched: cancelDispatch.dispatched, ran: ranCancel },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// --- S12b.5 node entry parity: same redispatchDue body runs on a node loop ---
|
|
99
|
+
setQueueRuntimeMode('node');
|
|
100
|
+
let ranNode = 0;
|
|
101
|
+
const qn = createQueue({
|
|
102
|
+
name: 'e2e-node',
|
|
103
|
+
onJob: async () => {
|
|
104
|
+
ranNode += 1;
|
|
105
|
+
},
|
|
106
|
+
options: { enableScheduledJob: true, retryDelay: 1 },
|
|
107
|
+
});
|
|
108
|
+
await qn.store.addJob('node-1', { v: 1, instance_did: TENANT_A }, { delay: 2, will_run_at: Date.now() - 1000 });
|
|
109
|
+
// node loop polls every minDelay/2 (~1s in test) — wait for one cycle
|
|
110
|
+
await sleep(1500);
|
|
111
|
+
out.push({
|
|
112
|
+
case: 'S12b.5 node loop dispatches the same way',
|
|
113
|
+
input: 'node-mode delayed row + wait one poll cycle',
|
|
114
|
+
output: { ran: ranNode, rowCleared: (await qn.store.getJob('node-1')) === null },
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// --- S12b.6 retry parity (node engine): maxRetries attempts then failed ---
|
|
118
|
+
let attempts = 0;
|
|
119
|
+
const qr = createQueue({
|
|
120
|
+
name: 'e2e-retry',
|
|
121
|
+
onJob: async () => {
|
|
122
|
+
attempts += 1;
|
|
123
|
+
throw new Error('always');
|
|
124
|
+
},
|
|
125
|
+
options: { maxRetries: 3, retryDelay: 1 },
|
|
126
|
+
});
|
|
127
|
+
const settled: any = await new Promise((resolve) => {
|
|
128
|
+
const ev = qr.push({ job: { v: 1, instance_did: TENANT_A } });
|
|
129
|
+
ev.on('failed', (d: any) => resolve({ event: 'failed', id: d.id }));
|
|
130
|
+
ev.on('finished', (d: any) => resolve({ event: 'finished', id: d.id }));
|
|
131
|
+
});
|
|
132
|
+
out.push({
|
|
133
|
+
case: 'S12b.6 retry up to maxRetries then failed',
|
|
134
|
+
input: 'maxRetries:3 onJob always throws',
|
|
135
|
+
output: { event: settled.event, attempts },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
console.log(JSON.stringify({ phase: '12b', success: true, cases: out }, null, 2));
|
|
139
|
+
|
|
140
|
+
await sequelize.close();
|
|
141
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
main().catch((err) => {
|
|
146
|
+
console.log(JSON.stringify({ phase: '12b', success: false, error: err?.message || String(err) }));
|
|
147
|
+
console.error(err?.stack || err);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
// Phase 8 (W2′) deterministic E2E: prove the injected config slot is authoritative
|
|
3
|
+
// and fails closed on a missing required field. Emits raw JSON for each call.
|
|
4
|
+
//
|
|
5
|
+
// npx tsx scripts/e2e-core-config.ts
|
|
6
|
+
|
|
7
|
+
import { setCoreConfig, readConfig } from '../api/src/libs/env';
|
|
8
|
+
import { getTenantMode, getDefaultInstanceDid, setDefaultInstanceDid } from '../api/src/libs/tenant';
|
|
9
|
+
import { createEmbeddedPaymentService } from '../api/src/service';
|
|
10
|
+
|
|
11
|
+
function emit(label: string, input: any, output: any) {
|
|
12
|
+
console.log(`=== ${label} ===`);
|
|
13
|
+
console.log(`input: ${JSON.stringify(input)}`);
|
|
14
|
+
console.log(`output: ${JSON.stringify(output)}`);
|
|
15
|
+
console.log('');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function tryFactory(slots: any): { threw: boolean; name?: string; code?: string; field?: string; message?: string } {
|
|
19
|
+
try {
|
|
20
|
+
createEmbeddedPaymentService(slots);
|
|
21
|
+
return { threw: false };
|
|
22
|
+
} catch (err: any) {
|
|
23
|
+
return { threw: true, name: err?.name, code: err?.code, field: err?.field, message: err?.message };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function main() {
|
|
28
|
+
const checks: Array<[string, boolean]> = [];
|
|
29
|
+
|
|
30
|
+
// ---- Happy: injected config is authoritative, env is the fallback ----
|
|
31
|
+
process.env.E2E_KEY = 'from-env';
|
|
32
|
+
setCoreConfig({ E2E_KEY: 'from-config', PAYMENT_TENANT_MODE: 'multi' });
|
|
33
|
+
const injected = readConfig('E2E_KEY');
|
|
34
|
+
emit('S8.1 injected config wins over process.env', { env: 'from-env', config: 'from-config' }, { read: injected });
|
|
35
|
+
checks.push(['injected config wins', injected === 'from-config']);
|
|
36
|
+
|
|
37
|
+
setCoreConfig({ ONLY: 'x' });
|
|
38
|
+
const fallback = readConfig('E2E_KEY');
|
|
39
|
+
emit('S8.2 fallback to process.env when key absent from config', { configKeys: ['ONLY'] }, { read: fallback });
|
|
40
|
+
checks.push(['fallback to env', fallback === 'from-env']);
|
|
41
|
+
delete process.env.E2E_KEY;
|
|
42
|
+
|
|
43
|
+
// ---- getTenantMode reads mode from injected config (single source of truth) ----
|
|
44
|
+
delete process.env.PAYMENT_TENANT_MODE;
|
|
45
|
+
setCoreConfig({ PAYMENT_TENANT_MODE: 'multi' });
|
|
46
|
+
const modeMulti = getTenantMode();
|
|
47
|
+
setCoreConfig({ PAYMENT_TENANT_MODE: 'single' });
|
|
48
|
+
const modeSingle = getTenantMode();
|
|
49
|
+
emit('S8.3 getTenantMode from injected config', { config: ['multi', 'single'] }, { modeMulti, modeSingle });
|
|
50
|
+
checks.push(['mode multi from config', modeMulti === 'multi']);
|
|
51
|
+
checks.push(['mode single from config', modeSingle === 'single']);
|
|
52
|
+
|
|
53
|
+
// injected config wins over a conflicting env mirror
|
|
54
|
+
process.env.PAYMENT_TENANT_MODE = 'single';
|
|
55
|
+
setCoreConfig({ PAYMENT_TENANT_MODE: 'multi' });
|
|
56
|
+
const modeConflict = getTenantMode();
|
|
57
|
+
emit('S8.4 config wins over conflicting process.env', { env: 'single', config: 'multi' }, { mode: modeConflict });
|
|
58
|
+
checks.push(['config beats conflicting env', modeConflict === 'multi']);
|
|
59
|
+
delete process.env.PAYMENT_TENANT_MODE;
|
|
60
|
+
|
|
61
|
+
// app DID from injected config
|
|
62
|
+
setDefaultInstanceDid(undefined);
|
|
63
|
+
delete process.env.BLOCKLET_APP_PID;
|
|
64
|
+
setCoreConfig({ BLOCKLET_APP_PID: 'did:abt:zCONFIGAPP', PAYMENT_TENANT_MODE: 'single' });
|
|
65
|
+
const appDid = getDefaultInstanceDid();
|
|
66
|
+
emit('S8.5 getDefaultInstanceDid from injected config', { config: 'did:abt:zCONFIGAPP' }, { appDid });
|
|
67
|
+
checks.push(['app DID from config', appDid === 'did:abt:zCONFIGAPP']);
|
|
68
|
+
|
|
69
|
+
// ---- Negative: fail-fast on missing required field ----
|
|
70
|
+
setCoreConfig(undefined);
|
|
71
|
+
setDefaultInstanceDid(undefined);
|
|
72
|
+
delete process.env.BLOCKLET_APP_PID;
|
|
73
|
+
const failFast = tryFactory({ config: {}, db: { sequelize: {} } });
|
|
74
|
+
emit('S8.6 single-mode factory missing BLOCKLET_APP_PID -> fail-fast', { config: {}, tenancy: 'single(default)' }, failFast);
|
|
75
|
+
checks.push([
|
|
76
|
+
'fail-fast MissingConfigError(BLOCKLET_APP_PID)',
|
|
77
|
+
failFast.threw && failFast.code === 'MISSING_CONFIG_FIELD' && failFast.field === 'BLOCKLET_APP_PID',
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
// multi mode does not require BLOCKLET_APP_PID (tenant resolved per request)
|
|
81
|
+
setCoreConfig(undefined);
|
|
82
|
+
const multiNoPid = tryFactory({ config: { PAYMENT_TENANT_MODE: 'multi' }, db: { sequelize: {} }, tenancy: { mode: 'multi' } });
|
|
83
|
+
emit('S8.7 multi mode does NOT require BLOCKLET_APP_PID', { tenancy: 'multi' }, multiNoPid);
|
|
84
|
+
checks.push(['multi mode skips the requirement', !(multiNoPid.code === 'MISSING_CONFIG_FIELD')]);
|
|
85
|
+
|
|
86
|
+
// ---- Layer 3: adversarial ----
|
|
87
|
+
console.log('##### LAYER 3: ADVERSARIAL #####\n');
|
|
88
|
+
// a) prototype-pollution key in config must not poison readConfig or globals
|
|
89
|
+
setCoreConfig(JSON.parse('{"__proto__": {"polluted": true}, "SAFE": "ok"}'));
|
|
90
|
+
const polluted = ({} as any).polluted === true;
|
|
91
|
+
const safeRead = readConfig('SAFE');
|
|
92
|
+
emit('S8.A1 prototype-pollution config key', { config: '{"__proto__":{"polluted":true},"SAFE":"ok"}' }, { globalPolluted: polluted, safeRead });
|
|
93
|
+
checks.push(['no prototype pollution from config', polluted === false]);
|
|
94
|
+
checks.push(['benign keys still readable', safeRead === 'ok']);
|
|
95
|
+
|
|
96
|
+
// b) the fail-fast error message must not echo secret values
|
|
97
|
+
setCoreConfig(undefined);
|
|
98
|
+
setDefaultInstanceDid(undefined);
|
|
99
|
+
delete process.env.BLOCKLET_APP_PID;
|
|
100
|
+
const secretLeak = tryFactory({ config: { STRIPE_SECRET: 'sk_live_DEADBEEF_should_not_appear' }, db: { sequelize: {} } });
|
|
101
|
+
const leaks = (secretLeak.message || '').includes('sk_live_DEADBEEF');
|
|
102
|
+
emit('S8.A2 fail-fast error does not echo secret config values', { secretInConfig: 'sk_live_DEADBEEF...' }, { message: secretLeak.message, leaks });
|
|
103
|
+
checks.push(['error does not leak secret value', leaks === false]);
|
|
104
|
+
|
|
105
|
+
// c) numeric/garbage config coerced to string by the boundary (no crash)
|
|
106
|
+
setCoreConfig({ NUMERIC: 12345, BOOL: true });
|
|
107
|
+
const numeric = readConfig('NUMERIC');
|
|
108
|
+
const bool = readConfig('BOOL');
|
|
109
|
+
emit('S8.A3 non-string config values coerced safely', { NUMERIC: 12345, BOOL: true }, { numeric, bool });
|
|
110
|
+
checks.push(['non-string config coerced to string', numeric === '12345' && bool === 'true']);
|
|
111
|
+
|
|
112
|
+
// restore + report
|
|
113
|
+
setCoreConfig(undefined);
|
|
114
|
+
|
|
115
|
+
console.log('##### ASSERTIONS #####');
|
|
116
|
+
let allPass = true;
|
|
117
|
+
for (const [name, ok] of checks) {
|
|
118
|
+
if (!ok) allPass = false;
|
|
119
|
+
console.log(JSON.stringify({ check: name, pass: ok }));
|
|
120
|
+
}
|
|
121
|
+
console.log(JSON.stringify({ success: allPass, total: checks.length, passed: checks.filter(([, ok]) => ok).length }));
|
|
122
|
+
if (!allPass) process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
main();
|