payment-kit 1.29.0 → 1.29.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/dev.ts +41 -2
- package/api/hono.d.ts +42 -0
- package/api/node-sqlite.d.ts +12 -0
- package/api/src/bootstrap.ts +36 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +27 -24
- package/api/src/crons/metering-subscription-detection.ts +1 -1
- package/api/src/crons/overdue-detection.ts +2 -2
- package/api/src/crons/retry-pending-events.ts +6 -0
- package/api/src/index.ts +22 -161
- package/api/src/integrations/app-store/client.ts +3 -4
- package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
- package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +21 -7
- package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
- package/api/src/integrations/google-play/handlers/voided.ts +2 -2
- package/api/src/integrations/google-play/verify.ts +3 -2
- package/api/src/integrations/iap-reconcile.ts +3 -5
- package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
- package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
- package/api/src/libs/archive/query.ts +19 -0
- package/api/src/libs/audit.ts +61 -4
- package/api/src/libs/auth.ts +99 -38
- package/api/src/libs/context.ts +78 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- package/api/src/libs/drivers/auth-storage.ts +118 -0
- package/api/src/libs/drivers/cron.ts +264 -0
- package/api/src/libs/drivers/db.ts +170 -0
- package/api/src/libs/drivers/identity.ts +81 -0
- package/api/src/libs/drivers/index.ts +40 -0
- package/api/src/libs/drivers/locks.ts +226 -0
- package/api/src/libs/drivers/migrate-runner.ts +70 -0
- package/api/src/libs/drivers/queue.ts +104 -0
- package/api/src/libs/drivers/secrets.ts +194 -0
- package/api/src/libs/env.ts +170 -54
- package/api/src/libs/exchange-rate/service.ts +7 -6
- package/api/src/libs/http-fetch-adapter.ts +50 -0
- package/api/src/libs/invoice.ts +1 -1
- package/api/src/libs/lock.ts +51 -47
- package/api/src/libs/logger.ts +48 -8
- package/api/src/libs/notification/index.ts +1 -1
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
- package/api/src/libs/overdraft-protection.ts +1 -1
- package/api/src/libs/payout.ts +1 -1
- package/api/src/libs/queue/index.ts +259 -52
- package/api/src/libs/queue/runtime.ts +175 -0
- package/api/src/libs/resource.ts +3 -3
- package/api/src/libs/secrets.ts +38 -0
- package/api/src/libs/session.ts +3 -2
- package/api/src/libs/subscription.ts +5 -5
- package/api/src/libs/tenant.ts +92 -0
- package/api/src/libs/url.ts +3 -3
- package/api/src/libs/util.ts +21 -13
- package/api/src/middlewares/hono/cdn.ts +63 -0
- package/api/src/middlewares/hono/context.ts +73 -0
- package/api/src/middlewares/hono/csrf.ts +72 -0
- package/api/src/middlewares/hono/fallback.ts +194 -0
- package/api/src/middlewares/hono/pipeline.ts +73 -0
- package/api/src/middlewares/hono/resource-mount.ts +42 -0
- package/api/src/middlewares/hono/resource.ts +63 -0
- package/api/src/middlewares/hono/security.ts +214 -0
- package/api/src/middlewares/hono/session.ts +114 -0
- package/api/src/middlewares/hono/xss.ts +61 -0
- package/api/src/queues/auto-recharge.ts +12 -10
- package/api/src/queues/checkout-session.ts +17 -12
- package/api/src/queues/credit-consume.ts +40 -36
- package/api/src/queues/credit-grant.ts +25 -18
- package/api/src/queues/credit-reconciliation.ts +7 -5
- package/api/src/queues/discount-status.ts +9 -6
- package/api/src/queues/event.ts +12 -4
- package/api/src/queues/exchange-rate-health.ts +49 -30
- package/api/src/queues/invoice.ts +18 -15
- package/api/src/queues/notification.ts +14 -7
- package/api/src/queues/payment.ts +41 -28
- package/api/src/queues/payout.ts +9 -5
- package/api/src/queues/refund.ts +18 -12
- package/api/src/queues/subscription.ts +83 -53
- package/api/src/queues/token-transfer.ts +15 -10
- package/api/src/queues/usage-record.ts +8 -5
- package/api/src/queues/vendors/commission.ts +7 -5
- package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
- package/api/src/queues/vendors/fulfillment.ts +4 -2
- package/api/src/queues/vendors/return-processor.ts +5 -3
- package/api/src/queues/vendors/return-scanner.ts +5 -4
- package/api/src/queues/vendors/status-check.ts +10 -7
- package/api/src/queues/webhook.ts +60 -32
- package/api/src/routes/connect/shared.ts +1 -2
- package/api/src/routes/connect/subscribe.ts +3 -3
- package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
- package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
- package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
- package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
- package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
- package/api/src/routes/hono/credit-tokens.ts +43 -0
- package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
- package/api/src/routes/{customers.ts → hono/customers.ts} +193 -223
- package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
- package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
- package/api/src/routes/{events.ts → hono/events.ts} +107 -71
- package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
- package/api/src/routes/hono/exchange-rates.ts +77 -0
- package/api/src/routes/hono/index.ts +115 -0
- package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
- package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
- package/api/src/routes/hono/integrations/stripe.ts +74 -0
- package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
- package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
- package/api/src/routes/hono/meters.ts +288 -0
- package/api/src/routes/hono/passports.ts +73 -0
- package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
- package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
- package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
- package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
- package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
- package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
- package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
- package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
- package/api/src/routes/{products.ts → hono/products.ts} +172 -159
- package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
- package/api/src/routes/hono/redirect.ts +24 -0
- package/api/src/routes/{refunds.ts → hono/refunds.ts} +96 -80
- package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
- package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
- package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
- package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
- package/api/src/routes/hono/tool.ts +69 -0
- package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
- package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
- package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
- package/api/src/routes/hono/webhook-endpoints.ts +126 -0
- package/api/src/service.ts +667 -0
- package/api/src/store/migrations/20230911-seeding.ts +2 -1
- package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
- package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
- package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
- package/api/src/store/models/auto-recharge-config.ts +22 -10
- package/api/src/store/models/checkout-session.ts +15 -14
- package/api/src/store/models/coupon.ts +29 -20
- package/api/src/store/models/credit-grant.ts +38 -29
- package/api/src/store/models/credit-transaction.ts +32 -21
- package/api/src/store/models/customer.ts +19 -17
- package/api/src/store/models/discount.ts +11 -2
- package/api/src/store/models/entitlement-grant.ts +21 -9
- package/api/src/store/models/entitlement-product.ts +21 -9
- package/api/src/store/models/entitlement.ts +19 -10
- package/api/src/store/models/event.ts +18 -9
- package/api/src/store/models/exchange-rate-provider.ts +17 -4
- package/api/src/store/models/invoice-item.ts +18 -9
- package/api/src/store/models/invoice.ts +16 -8
- package/api/src/store/models/meter-event.ts +27 -9
- package/api/src/store/models/meter.ts +31 -22
- package/api/src/store/models/payment-currency.ts +25 -8
- package/api/src/store/models/payment-intent.ts +15 -6
- package/api/src/store/models/payment-link.ts +15 -6
- package/api/src/store/models/payment-method.ts +38 -22
- package/api/src/store/models/payment-stat.ts +18 -9
- package/api/src/store/models/payout.ts +15 -6
- package/api/src/store/models/price-quote.ts +17 -8
- package/api/src/store/models/price.ts +24 -12
- package/api/src/store/models/pricing-table.ts +29 -20
- package/api/src/store/models/product-vendor.ts +20 -10
- package/api/src/store/models/product.ts +15 -6
- package/api/src/store/models/promotion-code.ts +14 -6
- package/api/src/store/models/refund.ts +15 -6
- package/api/src/store/models/revenue-snapshot.ts +21 -9
- package/api/src/store/models/setting.ts +18 -9
- package/api/src/store/models/setup-intent.ts +36 -27
- package/api/src/store/models/subscription-item.ts +21 -9
- package/api/src/store/models/subscription-schedule.ts +21 -9
- package/api/src/store/models/subscription.ts +21 -10
- package/api/src/store/models/tax-rate.ts +29 -21
- package/api/src/store/models/usage-record.ts +11 -2
- package/api/src/store/models/webhook-attempt.ts +18 -9
- package/api/src/store/models/webhook-endpoint.ts +18 -9
- package/api/src/store/scoped-core.ts +55 -0
- package/api/src/store/scoped.ts +247 -0
- package/api/src/store/sequelize.ts +66 -22
- package/api/src/store/sql-migrations.ts +20 -0
- package/api/src/store/tenant-backfill.ts +260 -0
- package/api/src/store/tenant-model.ts +124 -0
- package/api/src/store/tenant-tables.ts +50 -0
- package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
- package/api/tests/fixtures/bare-query-violation.ts +13 -0
- package/api/tests/fixtures/core-env-violation.ts +10 -0
- package/api/tests/fixtures/host-read-violation.ts +19 -0
- package/api/tests/fixtures/tenants.ts +4 -0
- package/api/tests/integrations/iap-tenant.spec.ts +284 -0
- package/api/tests/libs/archive-query.spec.ts +26 -0
- package/api/tests/libs/audit-tenant.spec.ts +153 -0
- package/api/tests/libs/context.spec.ts +204 -0
- package/api/tests/libs/core-config.spec.ts +115 -0
- package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
- package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
- package/api/tests/libs/lock-tenant.spec.ts +66 -0
- package/api/tests/libs/scoped.spec.ts +222 -0
- package/api/tests/libs/secrets-facade.spec.ts +52 -0
- package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
- package/api/tests/libs/tenant-middleware.spec.ts +42 -0
- package/api/tests/libs/tenant-scanner.spec.ts +120 -0
- package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
- package/api/tests/middlewares/hono/context.spec.ts +113 -0
- package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
- package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
- package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
- package/api/tests/middlewares/hono/security.spec.ts +181 -0
- package/api/tests/middlewares/hono/session.spec.ts +42 -0
- package/api/tests/middlewares/hono/xss.spec.ts +81 -0
- package/api/tests/models/tenant-backfill.spec.ts +287 -0
- package/api/tests/models/tenant-columns-model.spec.ts +46 -0
- package/api/tests/models/tenant-columns.spec.ts +161 -0
- package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
- package/api/tests/queues/credit-consume.spec.ts +8 -1
- package/api/tests/queues/event-tenant.spec.ts +236 -0
- package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
- package/api/tests/queues/queue-parity.spec.ts +249 -0
- package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
- package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
- package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
- package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
- package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
- package/api/tests/service/collapse.spec.ts +96 -0
- package/api/tests/store/tenant-crosscut.spec.ts +202 -0
- package/api/tests/store/tenant-model-spike.spec.ts +177 -0
- package/api/tests/store/tenant-model.spec.ts +162 -0
- package/api/tests/store/tenant-residual.spec.ts +196 -0
- package/api/third.d.ts +4 -0
- package/blocklet.yml +1 -1
- package/cloudflare/README.md +26 -6
- package/cloudflare/build.ts +28 -13
- package/cloudflare/did-connect-auth.ts +0 -217
- package/cloudflare/docs/2026-06-10-bundle-size-analysis.md +288 -0
- package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
- package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
- package/cloudflare/migrations/0008_schema_parity.sql +16 -0
- package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
- package/cloudflare/queue-runtime-mode.ts +13 -0
- package/cloudflare/run-build.js +31 -56
- package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
- package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
- package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/util-csrf.ts +13 -0
- package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
- package/cloudflare/shims/cron.ts +38 -158
- package/cloudflare/shims/events.ts +124 -0
- package/cloudflare/shims/fastq.ts +15 -1
- package/cloudflare/shims/nedb-storage.ts +16 -8
- package/cloudflare/shims/node-fetch.ts +35 -0
- package/cloudflare/shims/xss.ts +8 -0
- package/cloudflare/tenant-middleware.ts +36 -0
- package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
- package/cloudflare/worker.ts +204 -433
- package/cloudflare/wrangler.local-e2e.jsonc +26 -0
- package/jest.config.js +3 -1
- package/package.json +33 -38
- package/scripts/core-env-whitelist.json +1 -0
- package/scripts/e2e-12b-runtime.ts +149 -0
- package/scripts/e2e-core-config.ts +125 -0
- package/scripts/e2e-d1-tenancy.ts +116 -0
- package/scripts/e2e-d2-cron-queue.ts +139 -0
- package/scripts/e2e-d3-embedded-multi.ts +171 -0
- package/scripts/e2e-hono-s2.ts +125 -0
- package/scripts/e2e-hono-s3e.ts +135 -0
- package/scripts/e2e-hono-s4.ts +114 -0
- package/scripts/e2e-migration-contract.ts +100 -0
- package/scripts/e2e-s0.ts +61 -0
- package/scripts/e2e-s1.ts +107 -0
- package/scripts/e2e-s2.ts +178 -0
- package/scripts/e2e-s3.ts +110 -0
- package/scripts/e2e-s4.ts +191 -0
- package/scripts/e2e-s5.ts +139 -0
- package/scripts/e2e-s6.ts +127 -0
- package/scripts/e2e-tenant-model.ts +119 -0
- package/scripts/e2e-tenant-worker.ts +199 -0
- package/scripts/gen-sql-migrations.js +46 -0
- package/scripts/phase8-codemod.js +219 -0
- package/scripts/phase9a-env-getters-codemod.js +82 -0
- package/scripts/scan-core-env.js +109 -0
- package/scripts/scan-tenant-queries.js +235 -0
- package/scripts/schema-drift-guard.ts +210 -0
- package/scripts/tenant-scan-whitelist.json +1 -0
- package/src/env.d.ts +13 -1
- package/tsconfig.json +1 -1
- package/api/src/libs/did-space.ts +0 -235
- package/api/src/libs/middleware.ts +0 -50
- package/api/src/libs/security.ts +0 -192
- package/api/src/queues/space.ts +0 -662
- package/api/src/routes/credit-tokens.ts +0 -38
- package/api/src/routes/exchange-rates.ts +0 -87
- package/api/src/routes/index.ts +0 -142
- package/api/src/routes/integrations/stripe.ts +0 -61
- package/api/src/routes/meters.ts +0 -274
- package/api/src/routes/passports.ts +0 -68
- package/api/src/routes/redirect.ts +0 -20
- package/api/src/routes/tool.ts +0 -65
- package/api/src/routes/webhook-endpoints.ts +0 -126
- package/api/tests/routes/credit-grants.spec.ts +0 -1261
- package/cloudflare/shims/did-space-js.ts +0 -17
- package/cloudflare/shims/did-space.ts +0 -11
- package/cloudflare/shims/express-compat/index.ts +0 -80
- package/cloudflare/shims/express-compat/types.ts +0 -41
- package/cloudflare/shims/lock.ts +0 -115
- package/cloudflare/shims/queue.ts +0 -611
- package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
// W1′ Phase 5: structural tenant-isolation backstop scanner.
|
|
4
|
+
//
|
|
5
|
+
// Phase 3 made the 38 tenant models `extends TenantModel`, so a bare
|
|
6
|
+
// `Coupon.findAll()` is now tenant-scoped BY CONSTRUCTION — the old denylist
|
|
7
|
+
// ("bare model query = violation, 151-file whitelist") is obsolete and
|
|
8
|
+
// fail-open. This scanner replaces it with two STRUCTURAL invariants that make
|
|
9
|
+
// the safety load-bearing, plus the single-Host-reader rule:
|
|
10
|
+
//
|
|
11
|
+
// Assertion ① every tenant-table model `extends TenantModel` (not `Model`).
|
|
12
|
+
// If a tenant model regresses to `extends Model`, every query on
|
|
13
|
+
// it silently stops being scoped — caught here.
|
|
14
|
+
// Assertion ② a raw `.query(...)` whose SQL touches a tenant TABLE (DML, not
|
|
15
|
+
// DDL) must bind a tenant via `$instance_did` / `:instance_did`.
|
|
16
|
+
// Host rule the raw Host header is read at exactly one place (middleware).
|
|
17
|
+
//
|
|
18
|
+
// The whitelist collapses from 151 files to a handful of genuine raw/system
|
|
19
|
+
// exceptions (see tenant-scan-whitelist.json, reasons in three classes).
|
|
20
|
+
//
|
|
21
|
+
// Usage: node scripts/scan-tenant-queries.js [--json] [extra files...]
|
|
22
|
+
// Exit 1 when violations outside the whitelist are found (or whitelist is stale).
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
const ROOT = path.join(__dirname, '..');
|
|
28
|
+
const SRC_DIR = path.join(ROOT, 'api/src');
|
|
29
|
+
const MODELS_DIR = path.join(ROOT, 'api/src/store/models');
|
|
30
|
+
// TENANT_SCAN_WHITELIST overrides the whitelist path (tests point it at a temp
|
|
31
|
+
// file to prove an entry can never wildcard-swallow a directory).
|
|
32
|
+
const WHITELIST_FILE = process.env.TENANT_SCAN_WHITELIST || path.join(__dirname, 'tenant-scan-whitelist.json');
|
|
33
|
+
|
|
34
|
+
// canonical single source (scoped.ts re-exports this same constant)
|
|
35
|
+
const { TENANT_TABLES } = require(path.join(ROOT, 'api/src/store/tenant-tables.ts'));
|
|
36
|
+
const TENANT_TABLE_SET = new Set(TENANT_TABLES);
|
|
37
|
+
|
|
38
|
+
// table name -> model class name (customers -> Customer, payment_currencies -> PaymentCurrency)
|
|
39
|
+
function modelNameFor(table) {
|
|
40
|
+
const singular = table.endsWith('ies') ? `${table.slice(0, -3)}y` : table.replace(/s$/, '');
|
|
41
|
+
return singular
|
|
42
|
+
.split('_')
|
|
43
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
44
|
+
.join('');
|
|
45
|
+
}
|
|
46
|
+
const TENANT_MODEL_NAMES = new Set(TENANT_TABLES.map(modelNameFor));
|
|
47
|
+
|
|
48
|
+
// The raw-query exemptions encode the THREE reason classes that used to live as
|
|
49
|
+
// 151 whole-file whitelist entries (Phase 1 declared them; Phase 5 makes them
|
|
50
|
+
// structural). The whitelist file itself is now empty — these are the only
|
|
51
|
+
// exceptions, and they are by-design, not "unscheduled".
|
|
52
|
+
//
|
|
53
|
+
// class ① design-permanent : migrations + tenant-backfill cross tenants by
|
|
54
|
+
// design (RAW_EXEMPT_PATH).
|
|
55
|
+
// class ② entry-validated / : the scopedQuery guard helper (scoped.ts), the
|
|
56
|
+
// contract D1/SQLite executor whose callers own scoping
|
|
57
|
+
// (drivers/db.ts), startup pragmas (sequelize.ts),
|
|
58
|
+
// and DDL/schema statements (DDL regex below).
|
|
59
|
+
// class ③ unscheduled route : ELIMINATED — the route read surface is now
|
|
60
|
+
// tenant-scoped by construction (TenantModel),
|
|
61
|
+
// so it needs no exception at all.
|
|
62
|
+
const RAW_EXEMPT_FILES = new Set([
|
|
63
|
+
path.join('api', 'src', 'store', 'scoped.ts'),
|
|
64
|
+
path.join('api', 'src', 'libs', 'drivers', 'db.ts'),
|
|
65
|
+
path.join('api', 'src', 'store', 'sequelize.ts'),
|
|
66
|
+
]);
|
|
67
|
+
const RAW_EXEMPT_PATH = /(^|\/)(store\/migrations|store\/tenant-backfill)/;
|
|
68
|
+
|
|
69
|
+
const ANY_QUERY = /\.query\s*\(/g;
|
|
70
|
+
// The STATEMENT VERB — the first keyword inside the query's string literal —
|
|
71
|
+
// decides DDL vs DML, so a DML SELECT can't hide behind a later `/* CREATE */`.
|
|
72
|
+
const QUERY_VERB = /\.query\s*\(\s*[`'"]\s*(\w+)/;
|
|
73
|
+
const DDL_VERBS = new Set(['PRAGMA', 'CREATE', 'ALTER', 'DROP', 'ATTACH', 'DETACH', 'REINDEX', 'VACUUM']);
|
|
74
|
+
const TENANT_BIND = /[$:]instance_did\b/;
|
|
75
|
+
const TENANT_TABLE_IN_SQL = new RegExp(`\\b(${TENANT_TABLES.join('|')})\\b`);
|
|
76
|
+
|
|
77
|
+
const HOST_READ =
|
|
78
|
+
/\breq\s*\.\s*(?:headers\s*(?:\.\s*host\b|\[\s*['"]host['"]\s*\])|hostname\b|get\s*\(\s*['"]host['"])/gi;
|
|
79
|
+
const HOST_READ_EXEMPT = [path.join('api', 'src', 'libs', 'middleware.ts')];
|
|
80
|
+
|
|
81
|
+
function listFiles(dir) {
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
84
|
+
const full = path.join(dir, entry.name);
|
|
85
|
+
if (entry.isDirectory()) out.push(...listFiles(full));
|
|
86
|
+
else if (entry.isFile() && full.endsWith('.ts')) out.push(full);
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function stripLineComments(source) {
|
|
92
|
+
return source.replace(/\/\/[^\n]*/g, (m) => ' '.repeat(m.length));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function lineOf(source, index) {
|
|
96
|
+
return source.slice(0, index).split('\n').length;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Assertion ① — scan model files: a tenant-table model must extend TenantModel.
|
|
100
|
+
function scanModelExtends() {
|
|
101
|
+
const violations = [];
|
|
102
|
+
for (const file of listFiles(MODELS_DIR)) {
|
|
103
|
+
const source = stripLineComments(fs.readFileSync(file, 'utf8'));
|
|
104
|
+
const rel = path.relative(ROOT, file);
|
|
105
|
+
const re = /export\s+class\s+(\w+)\s+extends\s+(\w+)\s*</g;
|
|
106
|
+
let m;
|
|
107
|
+
// eslint-disable-next-line no-cond-assign
|
|
108
|
+
while ((m = re.exec(source))) {
|
|
109
|
+
const [, className, base] = m;
|
|
110
|
+
if (TENANT_MODEL_NAMES.has(className) && base !== 'TenantModel') {
|
|
111
|
+
violations.push({
|
|
112
|
+
file: rel,
|
|
113
|
+
line: lineOf(source, m.index),
|
|
114
|
+
call: `${className} extends ${base}`,
|
|
115
|
+
kind: 'model-not-tenant',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return violations;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Assertion ② (+ Host rule) — per-file scan.
|
|
124
|
+
function scanFile(file) {
|
|
125
|
+
const rel = path.relative(ROOT, file);
|
|
126
|
+
// Bad input: a file we cannot read/decode must FAIL LOUDLY, never be silently
|
|
127
|
+
// skipped (a silent skip would let an unscannable file hide a violation).
|
|
128
|
+
let raw;
|
|
129
|
+
try {
|
|
130
|
+
raw = fs.readFileSync(file, 'utf8');
|
|
131
|
+
} catch (err) {
|
|
132
|
+
return [{ file: rel, line: 0, call: `unreadable file: ${err.message}`, kind: 'unreadable' }];
|
|
133
|
+
}
|
|
134
|
+
if (raw.includes('\x00') || raw.includes('\uFFFD')) {
|
|
135
|
+
return [{ file: rel, line: 0, call: 'unparseable (binary/non-utf8) file', kind: 'unreadable' }];
|
|
136
|
+
}
|
|
137
|
+
const source = stripLineComments(raw);
|
|
138
|
+
const violations = [];
|
|
139
|
+
|
|
140
|
+
const rawExempt = RAW_EXEMPT_FILES.has(rel) || RAW_EXEMPT_PATH.test(rel.replace(/\\/g, '/'));
|
|
141
|
+
if (!rawExempt) {
|
|
142
|
+
ANY_QUERY.lastIndex = 0;
|
|
143
|
+
let m;
|
|
144
|
+
// eslint-disable-next-line no-cond-assign
|
|
145
|
+
while ((m = ANY_QUERY.exec(source))) {
|
|
146
|
+
// Skip the HTTP query accessor `req.query(...)` / `c.req.query(...)` — it
|
|
147
|
+
// reads the request query string, not SQL. hono resource routes (Phase 3
|
|
148
|
+
// express→hono) call `c.req.query()` pervasively; without this guard the
|
|
149
|
+
// 600-char SQL window below catches a nearby tenant-model name and false-
|
|
150
|
+
// positives. A raw `sequelize.query(` is never preceded by `req`.
|
|
151
|
+
if (source.slice(m.index - 3, m.index) === 'req') continue;
|
|
152
|
+
// inspect the SQL window after the `.query(` opening
|
|
153
|
+
const sql = source.slice(m.index, m.index + 600);
|
|
154
|
+
const verb = (sql.match(QUERY_VERB)?.[1] || '').toUpperCase();
|
|
155
|
+
if (DDL_VERBS.has(verb)) continue; // schema op, not a tenant-row read
|
|
156
|
+
if (TENANT_TABLE_IN_SQL.test(sql) && !TENANT_BIND.test(sql)) {
|
|
157
|
+
violations.push({
|
|
158
|
+
file: rel,
|
|
159
|
+
line: lineOf(source, m.index),
|
|
160
|
+
call: 'raw .query on tenant table without $instance_did',
|
|
161
|
+
kind: 'raw-unguarded',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!HOST_READ_EXEMPT.includes(rel)) {
|
|
168
|
+
HOST_READ.lastIndex = 0;
|
|
169
|
+
let m;
|
|
170
|
+
// eslint-disable-next-line no-cond-assign
|
|
171
|
+
while ((m = HOST_READ.exec(source))) {
|
|
172
|
+
violations.push({ file: rel, line: lineOf(source, m.index), call: m[0].replace(/\s*\($/, ''), kind: 'host' });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return violations;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function main() {
|
|
179
|
+
const args = process.argv.slice(2);
|
|
180
|
+
const json = args.includes('--json');
|
|
181
|
+
const extraFiles = args.filter((a) => !a.startsWith('--'));
|
|
182
|
+
|
|
183
|
+
const whitelist = JSON.parse(fs.readFileSync(WHITELIST_FILE, 'utf8'));
|
|
184
|
+
const whitelisted = new Set(whitelist.map((entry) => entry.file));
|
|
185
|
+
|
|
186
|
+
const files = extraFiles.length ? extraFiles.map((f) => path.resolve(f)) : listFiles(SRC_DIR);
|
|
187
|
+
|
|
188
|
+
let violations = [];
|
|
189
|
+
let whitelistedCount = 0;
|
|
190
|
+
for (const file of files) {
|
|
191
|
+
const rel = path.relative(ROOT, file);
|
|
192
|
+
const found = scanFile(file);
|
|
193
|
+
if (!found.length) continue;
|
|
194
|
+
if (whitelisted.has(rel)) whitelistedCount += found.length;
|
|
195
|
+
else violations = violations.concat(found);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Assertion ① runs over the canonical model dir (skip when scanning explicit
|
|
199
|
+
// extra files, e.g. the scanner self-test fixture).
|
|
200
|
+
if (!extraFiles.length) {
|
|
201
|
+
for (const v of scanModelExtends()) {
|
|
202
|
+
if (whitelisted.has(v.file)) whitelistedCount += 1;
|
|
203
|
+
else violations.push(v);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// a whitelist entry is stale when its file is clean (or gone) under the NEW rules
|
|
208
|
+
const stale = whitelist
|
|
209
|
+
.map((entry) => entry.file)
|
|
210
|
+
.filter((file) => {
|
|
211
|
+
const full = path.join(ROOT, file);
|
|
212
|
+
if (!fs.existsSync(full)) return true;
|
|
213
|
+
return scanFile(full).length === 0;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const result = { violations, whitelisted: whitelistedCount, staleWhitelistEntries: stale };
|
|
217
|
+
if (json) {
|
|
218
|
+
console.log(JSON.stringify(result));
|
|
219
|
+
} else {
|
|
220
|
+
for (const v of violations) {
|
|
221
|
+
const label =
|
|
222
|
+
v.kind === 'host'
|
|
223
|
+
? 'host read outside tenant middleware'
|
|
224
|
+
: v.kind === 'model-not-tenant'
|
|
225
|
+
? 'tenant model not extending TenantModel'
|
|
226
|
+
: 'raw query on tenant table missing $instance_did';
|
|
227
|
+
console.error(`${label}: ${v.file}:${v.line} ${v.call}`);
|
|
228
|
+
}
|
|
229
|
+
if (stale.length) console.error(`stale whitelist entries (remove them): ${stale.join(', ')}`);
|
|
230
|
+
console.log(`tenant-scan: ${violations.length} violations, ${whitelistedCount} whitelisted`);
|
|
231
|
+
}
|
|
232
|
+
process.exit(violations.length > 0 || stale.length > 0 ? 1 : 0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
main();
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
// Phase 11 (W2′): schema-drift guard — the "done" gate for the D1 SQL migration
|
|
3
|
+
// lineage (Path A). It builds two schemas from scratch and diffs them:
|
|
4
|
+
// - CANONICAL: the 75 Umzug TS migrations (api/src/store/migrations) run via
|
|
5
|
+
// api/src/store/migrate.ts — the authoritative blocklet-server SQLite schema.
|
|
6
|
+
// - D1 LINEAGE: the 7 cloudflare/migrations/*.sql applied to a fresh SQLite.
|
|
7
|
+
// If the D1 lineage drifts (missing/extra tables, columns, or indexes), the SQL
|
|
8
|
+
// lineage must be fixed — NOT switched to Umzug-on-D1.
|
|
9
|
+
//
|
|
10
|
+
// npx tsx scripts/schema-drift-guard.ts
|
|
11
|
+
// Exits non-zero on table/column drift (index drift is reported, warn-only).
|
|
12
|
+
|
|
13
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import os from 'os';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { fromRandom } from '@ocap/wallet';
|
|
18
|
+
import { types } from '@ocap/mcrypto';
|
|
19
|
+
|
|
20
|
+
// Self-build a throwaway test blocklet env BEFORE importing the core: the
|
|
21
|
+
// canonical (Umzug) import graph pulls auth/wallet (libs/auth.ts initializes a
|
|
22
|
+
// wallet at import). This gate must NOT depend on the developer's machine env —
|
|
23
|
+
// it provisions its own, like the e2e scripts.
|
|
24
|
+
function setupTestEnv(): string {
|
|
25
|
+
const w = fromRandom({ role: types.RoleType.ROLE_APPLICATION });
|
|
26
|
+
const a = w.address;
|
|
27
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'drift-env-'));
|
|
28
|
+
Object.assign(process.env, {
|
|
29
|
+
ABT_NODE_DID: a,
|
|
30
|
+
ABT_NODE_PK: w.publicKey,
|
|
31
|
+
ABT_NODE_PORT: '8089',
|
|
32
|
+
ABT_NODE_SERVICE_PORT: '40406',
|
|
33
|
+
BLOCKLET_MODE: 'test',
|
|
34
|
+
BLOCKLET_DID: a,
|
|
35
|
+
BLOCKLET_COMPONENT_DID: a,
|
|
36
|
+
BLOCKLET_APP_SK: w.secretKey,
|
|
37
|
+
BLOCKLET_APP_PSK: w.secretKey,
|
|
38
|
+
BLOCKLET_APP_PK: w.publicKey,
|
|
39
|
+
BLOCKLET_APP_EK: w.secretKey,
|
|
40
|
+
BLOCKLET_APP_ID: a,
|
|
41
|
+
BLOCKLET_APP_PID: a,
|
|
42
|
+
BLOCKLET_APP_IDS: a,
|
|
43
|
+
BLOCKLET_APP_NAME: 'payment-kit',
|
|
44
|
+
BLOCKLET_APP_DESCRIPTION: 'payment-kit',
|
|
45
|
+
BLOCKLET_APP_URL: 'http://127.0.0.1:3030',
|
|
46
|
+
BLOCKLET_DATA_DIR: dir,
|
|
47
|
+
BLOCKLET_LOG_DIR: dir,
|
|
48
|
+
BLOCKLET_APP_DIR: dir,
|
|
49
|
+
BLOCKLET_MOUNT_POINTS: '[]',
|
|
50
|
+
});
|
|
51
|
+
return dir;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type Column = { name: string; type: string; notnull: number; pk: number };
|
|
55
|
+
type TableSchema = { columns: Column[]; indexes: string[] };
|
|
56
|
+
type Schema = Record<string, TableSchema>;
|
|
57
|
+
|
|
58
|
+
const META_TABLES = new Set(['SequelizeMeta', 'd1_migrations', 'sqlite_sequence']);
|
|
59
|
+
// D1-only infrastructure tables: the standalone worker owns its locks + DID-Connect
|
|
60
|
+
// token storage as real tables; the blocklet server gets those from the blocklet
|
|
61
|
+
// runtime, so they never appear in the Umzug canonical. Not business-schema drift.
|
|
62
|
+
const D1_INFRA_TABLES = new Set(['_locks', '_did_connect_tokens']);
|
|
63
|
+
// `*_tmp` tables are transient SQLite rebuild scaffolding (ALTER-via-recreate);
|
|
64
|
+
// if one survives in a final schema it's a migration leftover, not real schema.
|
|
65
|
+
const isArtifact = (n: string) => n.endsWith('_tmp');
|
|
66
|
+
const isUserTable = (n: string) =>
|
|
67
|
+
!n.startsWith('sqlite_') && !META_TABLES.has(n) && !D1_INFRA_TABLES.has(n) && !isArtifact(n);
|
|
68
|
+
|
|
69
|
+
// Normalize index columns into a stable signature so the two engines compare
|
|
70
|
+
// equal regardless of index NAME (the SQL lineage and Umzug may name them
|
|
71
|
+
// differently); we compare the set of indexed-column-tuples per table.
|
|
72
|
+
function indexSignature(cols: string[]): string {
|
|
73
|
+
return cols.join(',');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function dump(query: (sql: string) => any[]): Schema {
|
|
77
|
+
const tables = query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
|
78
|
+
.map((r: any) => r.name)
|
|
79
|
+
.filter(isUserTable);
|
|
80
|
+
const schema: Schema = {};
|
|
81
|
+
for (const t of tables) {
|
|
82
|
+
const columns: Column[] = query(`PRAGMA table_info("${t}")`)
|
|
83
|
+
.map((c: any) => ({ name: c.name, type: String(c.type || '').toUpperCase(), notnull: c.notnull, pk: c.pk }))
|
|
84
|
+
.sort((a: Column, b: Column) => a.name.localeCompare(b.name));
|
|
85
|
+
const idxList = query(`PRAGMA index_list("${t}")`);
|
|
86
|
+
const indexes: string[] = [];
|
|
87
|
+
for (const idx of idxList) {
|
|
88
|
+
const info = query(`PRAGMA index_info("${idx.name}")`);
|
|
89
|
+
indexes.push(indexSignature(info.map((i: any) => i.name)));
|
|
90
|
+
}
|
|
91
|
+
schema[t] = { columns, indexes: [...new Set(indexes)].sort() };
|
|
92
|
+
}
|
|
93
|
+
return schema;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function canonicalSchema(): Promise<Schema> {
|
|
97
|
+
// run the 75 Umzug TS migrations against a throwaway temp SQLite
|
|
98
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'drift-canon-'));
|
|
99
|
+
process.env.BLOCKLET_DATA_DIR = tmp;
|
|
100
|
+
process.env.BLOCKLET_LOG_DIR = tmp;
|
|
101
|
+
// eslint-disable-next-line global-require, import/no-dynamic-require
|
|
102
|
+
const { sequelize } = require('../api/src/store/sequelize');
|
|
103
|
+
// eslint-disable-next-line global-require
|
|
104
|
+
const migrate = require('../api/src/store/migrate').default;
|
|
105
|
+
await migrate();
|
|
106
|
+
// dump via a direct sync handle to the same file the sequelize migrations wrote
|
|
107
|
+
const dbFile = path.join(tmp, 'payment-kit.db');
|
|
108
|
+
await sequelize.close();
|
|
109
|
+
const db = new DatabaseSync(dbFile);
|
|
110
|
+
const schema = dump((sql) => db.prepare(sql).all() as any[]);
|
|
111
|
+
db.close();
|
|
112
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
113
|
+
return schema;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function d1Schema(): Schema {
|
|
117
|
+
const db = new DatabaseSync(':memory:');
|
|
118
|
+
const dir = path.resolve(__dirname, '../cloudflare/migrations');
|
|
119
|
+
const files = fs
|
|
120
|
+
.readdirSync(dir)
|
|
121
|
+
.filter((f) => f.endsWith('.sql'))
|
|
122
|
+
.sort();
|
|
123
|
+
for (const f of files) {
|
|
124
|
+
const sql = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
125
|
+
db.exec(sql); // node:sqlite exec runs a multi-statement script
|
|
126
|
+
}
|
|
127
|
+
const schema = dump((sql) => db.prepare(sql).all() as any[]);
|
|
128
|
+
db.close();
|
|
129
|
+
return schema;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function diff(canon: Schema, d1: Schema) {
|
|
133
|
+
const drift: { tables: any; columns: any; indexes: any } = { tables: {}, columns: {}, indexes: {} };
|
|
134
|
+
const canonTables = new Set(Object.keys(canon));
|
|
135
|
+
const d1Tables = new Set(Object.keys(d1));
|
|
136
|
+
const missingInD1 = [...canonTables].filter((t) => !d1Tables.has(t));
|
|
137
|
+
const extraInD1 = [...d1Tables].filter((t) => !canonTables.has(t));
|
|
138
|
+
if (missingInD1.length) drift.tables.missing_in_d1 = missingInD1;
|
|
139
|
+
if (extraInD1.length) drift.tables.extra_in_d1 = extraInD1;
|
|
140
|
+
|
|
141
|
+
for (const t of [...canonTables].filter((x) => d1Tables.has(x))) {
|
|
142
|
+
const cCols = new Map(canon[t]!.columns.map((c) => [c.name, c]));
|
|
143
|
+
const dCols = new Map(d1[t]!.columns.map((c) => [c.name, c]));
|
|
144
|
+
const missingCols = [...cCols.keys()].filter((c) => !dCols.has(c));
|
|
145
|
+
const extraCols = [...dCols.keys()].filter((c) => !cCols.has(c));
|
|
146
|
+
if (missingCols.length || extraCols.length) {
|
|
147
|
+
drift.columns[t] = {
|
|
148
|
+
...(missingCols.length ? { missing_in_d1: missingCols } : {}),
|
|
149
|
+
...(extraCols.length ? { extra_in_d1: extraCols } : {}),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
// index drift (warn-only): compare the set of indexed-column signatures
|
|
153
|
+
const cIdx = new Set(canon[t]!.indexes);
|
|
154
|
+
const dIdx = new Set(d1[t]!.indexes);
|
|
155
|
+
const missingIdx = [...cIdx].filter((i) => !dIdx.has(i) && i !== '');
|
|
156
|
+
if (missingIdx.length) drift.indexes[t] = { missing_in_d1: missingIdx };
|
|
157
|
+
}
|
|
158
|
+
return drift;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function main() {
|
|
162
|
+
setupTestEnv();
|
|
163
|
+
const canon = await canonicalSchema();
|
|
164
|
+
const d1 = d1Schema();
|
|
165
|
+
console.log(JSON.stringify({ canonical_tables: Object.keys(canon).length, d1_tables: Object.keys(d1).length }));
|
|
166
|
+
const drift = diff(canon, d1);
|
|
167
|
+
|
|
168
|
+
// HARD GATE = anything the canonical has that D1 is MISSING (the worker would
|
|
169
|
+
// fail to write it). Extra-in-D1 (rebuild leftovers) and index diffs are
|
|
170
|
+
// WARN-only: inert for model reads/writes, and dropping columns on a deployed
|
|
171
|
+
// D1 is destructive — cleaned up separately if ever needed.
|
|
172
|
+
const missingTables = drift.tables.missing_in_d1 || [];
|
|
173
|
+
const missingCols = Object.entries(drift.columns).filter(([, v]: any) => v.missing_in_d1);
|
|
174
|
+
const extraTables = drift.tables.extra_in_d1 || [];
|
|
175
|
+
const extraCols = Object.entries(drift.columns).filter(([, v]: any) => v.extra_in_d1);
|
|
176
|
+
|
|
177
|
+
const hardFail = missingTables.length > 0 || missingCols.length > 0;
|
|
178
|
+
|
|
179
|
+
if (missingTables.length) console.log(JSON.stringify({ HARD_missing_tables_in_d1: missingTables }, null, 2));
|
|
180
|
+
if (missingCols.length)
|
|
181
|
+
console.log(
|
|
182
|
+
JSON.stringify(
|
|
183
|
+
{ HARD_missing_columns_in_d1: Object.fromEntries(missingCols.map(([t, v]: any) => [t, v.missing_in_d1])) },
|
|
184
|
+
null,
|
|
185
|
+
2
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
if (extraTables.length) console.log(JSON.stringify({ WARN_extra_tables_in_d1: extraTables }));
|
|
189
|
+
if (extraCols.length)
|
|
190
|
+
console.log(
|
|
191
|
+
JSON.stringify({
|
|
192
|
+
WARN_extra_columns_in_d1: Object.fromEntries(extraCols.map(([t, v]: any) => [t, v.extra_in_d1])),
|
|
193
|
+
})
|
|
194
|
+
);
|
|
195
|
+
if (Object.keys(drift.indexes).length) console.log(JSON.stringify({ WARN_missing_indexes_in_d1: drift.indexes }));
|
|
196
|
+
|
|
197
|
+
console.log(
|
|
198
|
+
JSON.stringify({
|
|
199
|
+
success: !hardFail,
|
|
200
|
+
hard_gate: 'no canonical table/column missing in D1',
|
|
201
|
+
warns: { extra_in_d1: 'inert leftovers', indexes: 'perf-only' },
|
|
202
|
+
})
|
|
203
|
+
);
|
|
204
|
+
if (hardFail) process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
main().catch((err) => {
|
|
208
|
+
console.error(JSON.stringify({ success: false, error: String(err?.stack || err) }));
|
|
209
|
+
process.exit(1);
|
|
210
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
package/src/env.d.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
declare var blocklet: {
|
|
2
|
+
appId: string;
|
|
3
|
+
appPk: string;
|
|
2
4
|
appName: string;
|
|
3
5
|
appLogo: string;
|
|
4
6
|
appUrl: string;
|
|
5
7
|
prefix: string;
|
|
6
8
|
languages: { code: string; name: string }[];
|
|
7
|
-
preferences: { overdraftProtectionMaxCount: number };
|
|
9
|
+
preferences: { overdraftProtectionMaxCount: number; [key: string]: any };
|
|
10
|
+
componentMountPoints: Array<{
|
|
11
|
+
appId?: string;
|
|
12
|
+
did?: string;
|
|
13
|
+
appName?: string;
|
|
14
|
+
appLogo?: string;
|
|
15
|
+
appUrl?: string;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}>;
|
|
18
|
+
PAYMENT_CHANGE_LOCKED_PRICE?: string;
|
|
19
|
+
[key: string]: any;
|
|
8
20
|
};
|
|
9
21
|
|
|
10
22
|
declare module '*.svg';
|
package/tsconfig.json
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
|
34
34
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
|
35
35
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
|
36
|
-
"types": ["node", "react", "react-dom", "
|
|
36
|
+
"types": ["node", "react", "react-dom", "debug", "dotenv-flow", "jest"], /* Only include types explicitly used by this package */
|
|
37
37
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
|
38
38
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
|
39
39
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|