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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
|
2
|
-
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes
|
|
3
|
-
|
|
2
|
+
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes } from 'sequelize';
|
|
4
3
|
import { BN } from '@ocap/util';
|
|
4
|
+
import { TenantModel } from '../tenant-model';
|
|
5
|
+
import { getInstanceDid } from '../../libs/context';
|
|
5
6
|
|
|
6
7
|
import { createIdGenerator } from '../../libs/util';
|
|
7
8
|
import logger from '../../libs/logger';
|
|
@@ -9,9 +10,10 @@ import logger from '../../libs/logger';
|
|
|
9
10
|
const nextTaxRateId = createIdGenerator('txr', 16);
|
|
10
11
|
|
|
11
12
|
// eslint-disable-next-line prettier/prettier
|
|
12
|
-
export class TaxRate extends
|
|
13
|
+
export class TaxRate extends TenantModel<InferAttributes<TaxRate>, InferCreationAttributes<TaxRate>> {
|
|
13
14
|
declare id: CreationOptional<string>;
|
|
14
15
|
declare livemode: boolean;
|
|
16
|
+
declare instance_did?: string; // tenant column (Phase 1, W1-1a), nullable until Phase 2 backfill; scoped queries land in Phase 3
|
|
15
17
|
declare active: boolean;
|
|
16
18
|
|
|
17
19
|
declare country: string; // ISO 3166-1 alpha-2 country code
|
|
@@ -89,24 +91,30 @@ export class TaxRate extends Model<InferAttributes<TaxRate>, InferCreationAttrib
|
|
|
89
91
|
};
|
|
90
92
|
|
|
91
93
|
public static initialize(sequelize: any) {
|
|
92
|
-
this.init(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
94
|
+
this.init(
|
|
95
|
+
{
|
|
96
|
+
...TaxRate.GENESIS_ATTRIBUTES,
|
|
97
|
+
instance_did: { type: DataTypes.STRING(64), allowNull: true, defaultValue: () => getInstanceDid() },
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
sequelize,
|
|
101
|
+
modelName: 'TaxRate',
|
|
102
|
+
tableName: 'tax_rates',
|
|
103
|
+
createdAt: 'created_at',
|
|
104
|
+
updatedAt: 'updated_at',
|
|
105
|
+
indexes: [
|
|
106
|
+
{
|
|
107
|
+
fields: ['country'],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
fields: ['country', 'state'],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
fields: ['country', 'state', 'postal_code'],
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
}
|
|
117
|
+
);
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
public static associate() {}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes,
|
|
1
|
+
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Op } from 'sequelize';
|
|
2
2
|
import type { LiteralUnion } from 'type-fest';
|
|
3
|
+
import { TenantModel } from '../tenant-model';
|
|
4
|
+
import { getInstanceDid } from '../../libs/context';
|
|
3
5
|
|
|
4
6
|
import { createIdGenerator } from '../../libs/util';
|
|
5
7
|
|
|
6
8
|
const nextId = createIdGenerator('mbur', 24);
|
|
7
9
|
|
|
8
10
|
// eslint-disable-next-line prettier/prettier
|
|
9
|
-
export class UsageRecord extends
|
|
11
|
+
export class UsageRecord extends TenantModel<InferAttributes<UsageRecord>, InferCreationAttributes<UsageRecord>> {
|
|
10
12
|
declare id: CreationOptional<string>;
|
|
11
13
|
|
|
12
14
|
declare livemode: boolean;
|
|
15
|
+
declare instance_did?: string; // tenant column (Phase 1, W1-1a), nullable until Phase 2 backfill; scoped queries land in Phase 3
|
|
13
16
|
declare billed: boolean;
|
|
14
17
|
|
|
15
18
|
// The timestamp when this usage occurred.
|
|
@@ -72,6 +75,12 @@ export class UsageRecord extends Model<InferAttributes<UsageRecord>, InferCreati
|
|
|
72
75
|
this.init(
|
|
73
76
|
{
|
|
74
77
|
...UsageRecord.GENESIS_ATTRIBUTES,
|
|
78
|
+
instance_did: {
|
|
79
|
+
type: DataTypes.STRING(64),
|
|
80
|
+
allowNull: true,
|
|
81
|
+
// bare creates must still land in the active tenant (single mode = app DID)
|
|
82
|
+
defaultValue: () => getInstanceDid(),
|
|
83
|
+
},
|
|
75
84
|
billed: {
|
|
76
85
|
type: DataTypes.BOOLEAN,
|
|
77
86
|
defaultValue: false,
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes
|
|
1
|
+
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes } from 'sequelize';
|
|
2
2
|
import type { LiteralUnion } from 'type-fest';
|
|
3
|
+
import { TenantModel } from '../tenant-model';
|
|
4
|
+
import { getInstanceDid } from '../../libs/context';
|
|
3
5
|
|
|
4
6
|
import { createIdGenerator } from '../../libs/util';
|
|
5
7
|
|
|
6
8
|
const nextId = createIdGenerator('wa', 24);
|
|
7
9
|
|
|
8
10
|
// eslint-disable-next-line prettier/prettier
|
|
9
|
-
export class WebhookAttempt extends
|
|
11
|
+
export class WebhookAttempt extends TenantModel<InferAttributes<WebhookAttempt>, InferCreationAttributes<WebhookAttempt>> {
|
|
10
12
|
declare id: CreationOptional<string>;
|
|
11
13
|
declare livemode: boolean;
|
|
14
|
+
declare instance_did?: string; // tenant column (Phase 1, W1-1a), nullable until Phase 2 backfill; scoped queries land in Phase 3
|
|
12
15
|
|
|
13
16
|
declare event_id: string;
|
|
14
17
|
declare webhook_endpoint_id: string;
|
|
@@ -70,13 +73,19 @@ export class WebhookAttempt extends Model<InferAttributes<WebhookAttempt>, Infer
|
|
|
70
73
|
};
|
|
71
74
|
|
|
72
75
|
public static initialize(sequelize: any) {
|
|
73
|
-
this.init(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
this.init(
|
|
77
|
+
{
|
|
78
|
+
...WebhookAttempt.GENESIS_ATTRIBUTES,
|
|
79
|
+
instance_did: { type: DataTypes.STRING(64), allowNull: true, defaultValue: () => getInstanceDid() },
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
sequelize,
|
|
83
|
+
modelName: 'WebhookAttempt',
|
|
84
|
+
tableName: 'webhook_attempts',
|
|
85
|
+
createdAt: 'created_at',
|
|
86
|
+
updatedAt: 'updated_at',
|
|
87
|
+
}
|
|
88
|
+
);
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
public static associate(models: any) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes
|
|
1
|
+
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes } from 'sequelize';
|
|
2
2
|
import type { LiteralUnion } from 'type-fest';
|
|
3
|
+
import { TenantModel } from '../tenant-model';
|
|
4
|
+
import { getInstanceDid } from '../../libs/context';
|
|
3
5
|
|
|
4
6
|
import { createIdGenerator } from '../../libs/util';
|
|
5
7
|
import type { EventType } from './types';
|
|
@@ -7,9 +9,10 @@ import type { EventType } from './types';
|
|
|
7
9
|
const nextId = createIdGenerator('we', 24);
|
|
8
10
|
|
|
9
11
|
// eslint-disable-next-line prettier/prettier
|
|
10
|
-
export class WebhookEndpoint extends
|
|
12
|
+
export class WebhookEndpoint extends TenantModel<InferAttributes<WebhookEndpoint>, InferCreationAttributes<WebhookEndpoint>> {
|
|
11
13
|
declare id: CreationOptional<string>;
|
|
12
14
|
declare livemode: boolean;
|
|
15
|
+
declare instance_did?: string; // tenant column (Phase 1, W1-1a), nullable until Phase 2 backfill; scoped queries land in Phase 3
|
|
13
16
|
declare api_version: string;
|
|
14
17
|
|
|
15
18
|
declare url: string;
|
|
@@ -75,13 +78,19 @@ export class WebhookEndpoint extends Model<InferAttributes<WebhookEndpoint>, Inf
|
|
|
75
78
|
};
|
|
76
79
|
|
|
77
80
|
public static initialize(sequelize: any) {
|
|
78
|
-
this.init(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
this.init(
|
|
82
|
+
{
|
|
83
|
+
...WebhookEndpoint.GENESIS_ATTRIBUTES,
|
|
84
|
+
instance_did: { type: DataTypes.STRING(64), allowNull: true, defaultValue: () => getInstanceDid() },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
sequelize,
|
|
88
|
+
modelName: 'WebhookEndpoint',
|
|
89
|
+
tableName: 'webhook_endpoints',
|
|
90
|
+
createdAt: 'created_at',
|
|
91
|
+
updatedAt: 'updated_at',
|
|
92
|
+
}
|
|
93
|
+
);
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
public static associate(models: any) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Tenant scope primitives — the single source for tenant-scoping logic shared
|
|
2
|
+
// by:
|
|
3
|
+
// - scoped.ts (the opt-in scoped*/system* helper functions)
|
|
4
|
+
// - tenant-model.ts (the TenantModel base class)
|
|
5
|
+
//
|
|
6
|
+
// Before this module each of those carried its own inline copy of scopeWhere /
|
|
7
|
+
// stampTenant / the tenant-table check (W1′ task 3 de-dup). The semantics are
|
|
8
|
+
// identical and load-bearing, so they live in exactly one place now.
|
|
9
|
+
//
|
|
10
|
+
// IMPORTANT: this module must stay free of Node-only / workerd-only imports so
|
|
11
|
+
// the worker shim engine can import it too (Phase 3 cross-engine assertion).
|
|
12
|
+
// Its only ambient dependency is the request-context ALS in libs/context,
|
|
13
|
+
// which is async_hooks on Node and nodejs_compat on the worker — the same ALS
|
|
14
|
+
// the whole isolation design rides on.
|
|
15
|
+
import { getInstanceDid } from '../libs/context';
|
|
16
|
+
import { TENANT_MISMATCH, TenantError } from '../libs/tenant';
|
|
17
|
+
import { TENANT_TABLES } from './tenant-tables';
|
|
18
|
+
|
|
19
|
+
const TENANT_TABLE_SET: ReadonlySet<string> = new Set(TENANT_TABLES);
|
|
20
|
+
|
|
21
|
+
/** Is this table one of the 38 tenant-scoped tables? */
|
|
22
|
+
export function isTenantTable(tableName: string): boolean {
|
|
23
|
+
return TENANT_TABLE_SET.has(tableName);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Merge the active tenant into a where clause.
|
|
28
|
+
*
|
|
29
|
+
* - fail-closed: an explicit caller-provided `where.instance_did` that disagrees
|
|
30
|
+
* with the active tenant throws TENANT_MISMATCH — never silently overridden.
|
|
31
|
+
* - idempotent: an equal `instance_did` is a no-op, so the double injection from
|
|
32
|
+
* internal delegation (findByPk -> findOne -> findAll) is absorbed cleanly.
|
|
33
|
+
*/
|
|
34
|
+
export function scopeWhere(where: Record<string, any> | undefined): Record<string, any> {
|
|
35
|
+
const instanceDid = getInstanceDid();
|
|
36
|
+
if (where && 'instance_did' in where && where.instance_did !== instanceDid) {
|
|
37
|
+
throw new TenantError(
|
|
38
|
+
TENANT_MISMATCH,
|
|
39
|
+
`explicit where.instance_did (${JSON.stringify(where.instance_did)}) conflicts with the active tenant`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return { ...where, instance_did: instanceDid };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Stamp the active tenant onto create values. An explicit mismatched tenant is
|
|
47
|
+
* refused (writes never cross tenants); an equal one is a no-op.
|
|
48
|
+
*/
|
|
49
|
+
export function stampTenant(values: Record<string, any>): Record<string, any> {
|
|
50
|
+
const instanceDid = getInstanceDid();
|
|
51
|
+
if (values && 'instance_did' in values && values.instance_did !== instanceDid) {
|
|
52
|
+
throw new TenantError(TENANT_MISMATCH, 'explicit instance_did conflicts with the active tenant');
|
|
53
|
+
}
|
|
54
|
+
return { ...values, instance_did: instanceDid };
|
|
55
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/* eslint-disable max-classes-per-file */
|
|
2
|
+
import type {
|
|
3
|
+
Attributes,
|
|
4
|
+
CountOptions,
|
|
5
|
+
CreateOptions,
|
|
6
|
+
DestroyOptions,
|
|
7
|
+
FindOptions,
|
|
8
|
+
Model,
|
|
9
|
+
ModelStatic,
|
|
10
|
+
Sequelize,
|
|
11
|
+
UpdateOptions,
|
|
12
|
+
} from 'sequelize';
|
|
13
|
+
|
|
14
|
+
import { context } from '../libs/context';
|
|
15
|
+
import { INVALID_TENANT_TABLE, TENANT_MISMATCH, TenantError } from '../libs/tenant';
|
|
16
|
+
import { isTenantTable, scopeWhere } from './scoped-core';
|
|
17
|
+
|
|
18
|
+
// Phase 3 (W1-2): tenant-scoped query wrappers.
|
|
19
|
+
//
|
|
20
|
+
// Explicit wrapper functions (no global sequelize hooks) so every scoped call
|
|
21
|
+
// is visible and auditable at the call site. The CI scanner
|
|
22
|
+
// (scripts/scan-tenant-queries.js) rejects bare findByPk/findOne/findAll/
|
|
23
|
+
// findAndCountAll/sequelize.query on tenant models outside this module;
|
|
24
|
+
// the 38-table list is shared via ./tenant-tables (single source).
|
|
25
|
+
|
|
26
|
+
export { TENANT_TABLES } from './tenant-tables';
|
|
27
|
+
|
|
28
|
+
// scopeWhere / isTenantTable now live in ./scoped-core (shared with the
|
|
29
|
+
// TenantModel base class — one copy of the tenant-scoping logic).
|
|
30
|
+
|
|
31
|
+
function assertTenantModel(model: ModelStatic<any>) {
|
|
32
|
+
if (!isTenantTable(model.tableName)) {
|
|
33
|
+
throw new TenantError(
|
|
34
|
+
INVALID_TENANT_TABLE,
|
|
35
|
+
`${model.tableName} is not a tenant table — use the model API directly (exempt: jobs/locks/archive/exchange_rate_providers)`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// validation errors must surface as rejections (callers use .catch / await),
|
|
41
|
+
// even though the checks themselves are synchronous
|
|
42
|
+
function asAsync<T>(fn: () => Promise<T> | T): Promise<T> {
|
|
43
|
+
try {
|
|
44
|
+
return Promise.resolve(fn());
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return Promise.reject(err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function scopedFindAll<M extends Model>(
|
|
51
|
+
model: ModelStatic<M>,
|
|
52
|
+
options: FindOptions<Attributes<M>> = {}
|
|
53
|
+
): Promise<M[]> {
|
|
54
|
+
return asAsync(() => {
|
|
55
|
+
assertTenantModel(model);
|
|
56
|
+
return model.findAll({ ...options, where: scopeWhere(options.where as any) as any });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function scopedFindOne<M extends Model>(
|
|
61
|
+
model: ModelStatic<M>,
|
|
62
|
+
options: FindOptions<Attributes<M>> = {}
|
|
63
|
+
): Promise<M | null> {
|
|
64
|
+
return asAsync(() => {
|
|
65
|
+
assertTenantModel(model);
|
|
66
|
+
return model.findOne({ ...options, where: scopeWhere(options.where as any) as any });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function scopedFindAndCountAll<M extends Model>(
|
|
71
|
+
model: ModelStatic<M>,
|
|
72
|
+
options: FindOptions<Attributes<M>> = {}
|
|
73
|
+
): Promise<{ rows: M[]; count: number }> {
|
|
74
|
+
return asAsync(() => {
|
|
75
|
+
assertTenantModel(model);
|
|
76
|
+
return (model as any).findAndCountAll({ ...options, where: scopeWhere(options.where as any) });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Primary keys are globally unique, so a bare findByPk could load another
|
|
82
|
+
* tenant's row. The row is loaded first, then its tenant is enforced:
|
|
83
|
+
* mismatch -> TENANT_MISMATCH (no data returned, nothing written).
|
|
84
|
+
*/
|
|
85
|
+
export async function scopedFindByPk<M extends Model>(
|
|
86
|
+
model: ModelStatic<M>,
|
|
87
|
+
pk: string | number | undefined | null,
|
|
88
|
+
options: Omit<FindOptions<Attributes<M>>, 'where'> = {}
|
|
89
|
+
): Promise<M | null> {
|
|
90
|
+
assertTenantModel(model);
|
|
91
|
+
if (pk === undefined || pk === null) return null;
|
|
92
|
+
const instanceDid = context.getInstanceDid();
|
|
93
|
+
const row: any = await model.findByPk(pk as any, options as any);
|
|
94
|
+
if (!row) return null;
|
|
95
|
+
if (row.instance_did !== instanceDid) {
|
|
96
|
+
throw new TenantError(TENANT_MISMATCH, `${model.tableName}#${String(pk)} belongs to another tenant`);
|
|
97
|
+
}
|
|
98
|
+
return row;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function scopedCount<M extends Model>(
|
|
102
|
+
model: ModelStatic<M>,
|
|
103
|
+
options: CountOptions<Attributes<M>> = {}
|
|
104
|
+
): Promise<number> {
|
|
105
|
+
return asAsync(() => {
|
|
106
|
+
assertTenantModel(model);
|
|
107
|
+
return model.count({ ...options, where: scopeWhere(options.where as any) as any });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function scopedSum<M extends Model>(
|
|
112
|
+
model: ModelStatic<M>,
|
|
113
|
+
field: keyof Attributes<M>,
|
|
114
|
+
options: { where?: Record<string, any> } = {}
|
|
115
|
+
): Promise<number> {
|
|
116
|
+
return asAsync(() => {
|
|
117
|
+
assertTenantModel(model);
|
|
118
|
+
return (model as any).sum(field, { ...options, where: scopeWhere(options.where) });
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function scopedAggregate<M extends Model>(
|
|
123
|
+
model: ModelStatic<M>,
|
|
124
|
+
field: keyof Attributes<M> | '*',
|
|
125
|
+
aggregateFunction: string,
|
|
126
|
+
options: { where?: Record<string, any> } = {}
|
|
127
|
+
): Promise<unknown> {
|
|
128
|
+
return asAsync(() => {
|
|
129
|
+
assertTenantModel(model);
|
|
130
|
+
return (model as any).aggregate(field, aggregateFunction, { ...options, where: scopeWhere(options.where) });
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Create with the active tenant; an explicit mismatched tenant is refused. */
|
|
135
|
+
export function scopedCreate<M extends Model>(
|
|
136
|
+
model: ModelStatic<M>,
|
|
137
|
+
values: Record<string, any>,
|
|
138
|
+
options?: CreateOptions<Attributes<M>>
|
|
139
|
+
): Promise<M> {
|
|
140
|
+
return asAsync(() => {
|
|
141
|
+
assertTenantModel(model);
|
|
142
|
+
const instanceDid = context.getInstanceDid();
|
|
143
|
+
if ('instance_did' in values && values.instance_did !== instanceDid) {
|
|
144
|
+
throw new TenantError(TENANT_MISMATCH, 'explicit instance_did conflicts with the active tenant');
|
|
145
|
+
}
|
|
146
|
+
return model.create({ ...values, instance_did: instanceDid } as any, options) as Promise<M>;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function scopedUpdate<M extends Model>(
|
|
151
|
+
model: ModelStatic<M>,
|
|
152
|
+
values: Record<string, any>,
|
|
153
|
+
options: UpdateOptions<Attributes<M>>
|
|
154
|
+
): Promise<[affectedCount: number]> {
|
|
155
|
+
return asAsync(() => {
|
|
156
|
+
assertTenantModel(model);
|
|
157
|
+
if ('instance_did' in values) {
|
|
158
|
+
throw new TenantError(TENANT_MISMATCH, 'instance_did is immutable — rows never change tenant');
|
|
159
|
+
}
|
|
160
|
+
return model.update(values as any, { ...options, where: scopeWhere(options.where as any) as any });
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function scopedDestroy<M extends Model>(
|
|
165
|
+
model: ModelStatic<M>,
|
|
166
|
+
options: DestroyOptions<Attributes<M>> = {}
|
|
167
|
+
): Promise<number> {
|
|
168
|
+
return asAsync(() => {
|
|
169
|
+
assertTenantModel(model);
|
|
170
|
+
return model.destroy({ ...options, where: scopeWhere(options.where as any) as any });
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Raw SQL with mandatory tenant binding: the statement must reference
|
|
176
|
+
* $instance_did (or :instance_did) and the bind value is supplied here from
|
|
177
|
+
* the context — callers cannot forget or override it.
|
|
178
|
+
*/
|
|
179
|
+
export function scopedQuery(sequelize: Sequelize, sql: string, options: { bind?: Record<string, any> } = {}) {
|
|
180
|
+
if (!/[$:]instance_did\b/.test(sql)) {
|
|
181
|
+
throw new TenantError(
|
|
182
|
+
TENANT_MISMATCH,
|
|
183
|
+
'raw SQL on tenant tables must bind $instance_did — add it to the WHERE clause or use scopedQuery on a scoped view'
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const instanceDid = context.getInstanceDid();
|
|
187
|
+
const bind = { ...options.bind, instance_did: instanceDid };
|
|
188
|
+
return sequelize.query(sql, { ...options, bind });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// system* family — EXPLICIT cross-tenant reads for system paths.
|
|
193
|
+
//
|
|
194
|
+
// Queue handlers load rows by globally-unique primary key from a
|
|
195
|
+
// tenant-stamped payload and then enforce row tenant via
|
|
196
|
+
// assertJobObjectTenant (libs/queue); dispatch/recovery sweeps and
|
|
197
|
+
// fanout-by-row-tenant (queues/event.ts) legitimately read across tenants.
|
|
198
|
+
// Naming these calls system* keeps every query auditable: anything not
|
|
199
|
+
// scoped* or system* is a scanner violation.
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
// Each system* call runs inside context.runAsSystem so the TenantModel base
|
|
203
|
+
// class bypasses scoping for the read — otherwise the model's overridden
|
|
204
|
+
// findByPk/findAll/... would re-scope to the active tenant and a legitimate
|
|
205
|
+
// cross-tenant read (queue dispatch, IAP reverse-lookup, fan-out) would resolve
|
|
206
|
+
// to null. The caller still enforces the row's tenant afterwards.
|
|
207
|
+
|
|
208
|
+
export function systemFindByPk<M extends Model>(
|
|
209
|
+
model: ModelStatic<M>,
|
|
210
|
+
pk: string | number | undefined | null,
|
|
211
|
+
options?: Omit<FindOptions<Attributes<M>>, 'where'>
|
|
212
|
+
): Promise<M | null> {
|
|
213
|
+
if (pk === undefined || pk === null) return Promise.resolve(null);
|
|
214
|
+
// forward options only when provided — keeps call shapes identical for
|
|
215
|
+
// existing spies/assertions on model.findByPk
|
|
216
|
+
return context.runAsSystem(() =>
|
|
217
|
+
options ? model.findByPk(pk as any, options as any) : model.findByPk(pk as any)
|
|
218
|
+
) as Promise<M | null>;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function systemFindAll<M extends Model>(
|
|
222
|
+
model: ModelStatic<M>,
|
|
223
|
+
options: FindOptions<Attributes<M>> = {}
|
|
224
|
+
): Promise<M[]> {
|
|
225
|
+
return context.runAsSystem(() => model.findAll(options));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function systemFindOne<M extends Model>(
|
|
229
|
+
model: ModelStatic<M>,
|
|
230
|
+
options: FindOptions<Attributes<M>> = {}
|
|
231
|
+
): Promise<M | null> {
|
|
232
|
+
return context.runAsSystem(() => model.findOne(options));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function systemFindAndCountAll<M extends Model>(
|
|
236
|
+
model: ModelStatic<M>,
|
|
237
|
+
options: FindOptions<Attributes<M>> = {}
|
|
238
|
+
): Promise<{ rows: M[]; count: number }> {
|
|
239
|
+
return context.runAsSystem(() => (model as any).findAndCountAll(options));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function systemCount<M extends Model>(
|
|
243
|
+
model: ModelStatic<M>,
|
|
244
|
+
options: CountOptions<Attributes<M>> = {}
|
|
245
|
+
): Promise<number> {
|
|
246
|
+
return context.runAsSystem(() => model.count(options));
|
|
247
|
+
}
|
|
@@ -6,31 +6,90 @@ import { join } from 'path';
|
|
|
6
6
|
|
|
7
7
|
import CLS from 'cls-hooked';
|
|
8
8
|
import { Sequelize } from 'sequelize';
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
import env, {
|
|
10
|
+
sqlLog,
|
|
11
|
+
sqlBenchmark,
|
|
12
|
+
sequelizeOptionsPoolIdle,
|
|
13
|
+
sequelizeOptionsPoolMax,
|
|
14
|
+
sequelizeOptionsPoolMin,
|
|
15
|
+
} from '../libs/env';
|
|
11
16
|
|
|
12
17
|
const namespace = CLS.createNamespace('payment-kit');
|
|
13
18
|
|
|
14
19
|
Sequelize.useCLS(namespace);
|
|
15
20
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
// Phase 13b: the blocklet-server's default sqlite instance is built from
|
|
22
|
+
// `env.dataDir`, which is absent in a bare host (arc embedding before its blocklet
|
|
23
|
+
// runtime is wired). In that mode the host injects its OWN sequelize via the db
|
|
24
|
+
// slot and this default is never used — so importing the models must not eagerly
|
|
25
|
+
// construct it (it would crash on join(undefined, …)). Construct EAGERLY when a
|
|
26
|
+
// data dir is present (blocklet server: BLOCKLET_DATA_DIR; CF worker: shim '/tmp')
|
|
27
|
+
// so their behavior and hot path are byte-for-byte unchanged — no proxy on the
|
|
28
|
+
// query path, CLS/transaction identity preserved. Otherwise defer behind a proxy
|
|
29
|
+
// that builds on first access (only ever hit by code that uses this default).
|
|
30
|
+
function buildSequelize(): Sequelize {
|
|
31
|
+
const seq = new Sequelize({
|
|
32
|
+
dialect: 'sqlite',
|
|
33
|
+
logging: sqlLog(),
|
|
34
|
+
benchmark: sqlLog() && sqlBenchmark(),
|
|
35
|
+
storage: join(env.dataDir, 'payment-kit.db'),
|
|
36
|
+
pool: {
|
|
37
|
+
min: sequelizeOptionsPoolMin(),
|
|
38
|
+
max: sequelizeOptionsPoolMax(),
|
|
39
|
+
idle: sequelizeOptionsPoolIdle(),
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// SQLite PRAGMAs — run once at startup on the single reused connection.
|
|
44
|
+
// Note: SQLite in Sequelize reuses a cached connection (connection-manager.js:43-44),
|
|
45
|
+
// so these fire-and-forget calls reliably apply to all subsequent queries.
|
|
46
|
+
// Failures are non-fatal (pragmas are perf tuning) and must not crash the
|
|
47
|
+
// process via unhandledRejection, e.g. when jest teardown removes the data dir.
|
|
48
|
+
const pragma = (sql: string) => seq.query(sql).catch(() => {});
|
|
49
|
+
pragma('PRAGMA journal_mode = WAL');
|
|
50
|
+
pragma('PRAGMA synchronous = NORMAL');
|
|
51
|
+
pragma('PRAGMA journal_size_limit = 67108864');
|
|
52
|
+
pragma('PRAGMA busy_timeout = 5000');
|
|
53
|
+
pragma('PRAGMA cache_size = -16000'); // 16MB page cache (default ~2MB)
|
|
54
|
+
|
|
55
|
+
return seq;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let instance: Sequelize | undefined;
|
|
59
|
+
const getSequelize = (): Sequelize => {
|
|
60
|
+
instance ??= buildSequelize();
|
|
61
|
+
return instance;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Point the default-export `sequelize` at the host-injected instance. In a bare
|
|
66
|
+
* host (arc embedding, no BLOCKLET_DATA_DIR) the default export is a deferred
|
|
67
|
+
* Proxy whose `buildSequelize()` would crash on `join(undefined, …)`; the host
|
|
68
|
+
* binds every model to ITS OWN sequelize via `initialize(slots.db.sequelize)`.
|
|
69
|
+
* Code that imports this default directly (e.g. `sequelize.models.Product` in
|
|
70
|
+
* Price.expand) must therefore resolve to that SAME instance — otherwise it
|
|
71
|
+
* builds the broken default. The factory calls this at the single bind point so
|
|
72
|
+
* the imported default and the model-bound instance are always one object.
|
|
73
|
+
* Equivalent to a no-op for blocklet-server/CF, where the default is already the
|
|
74
|
+
* bind target.
|
|
75
|
+
*/
|
|
76
|
+
export function setDefaultSequelize(seq: Sequelize): void {
|
|
77
|
+
instance = seq;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const sequelize: Sequelize = env.dataDir
|
|
81
|
+
? getSequelize()
|
|
82
|
+
: new Proxy({} as Sequelize, {
|
|
83
|
+
get(_t, prop) {
|
|
84
|
+
const seq = getSequelize();
|
|
85
|
+
const value: any = (seq as any)[prop];
|
|
86
|
+
return typeof value === 'function' ? value.bind(seq) : value;
|
|
87
|
+
},
|
|
88
|
+
set(_t, prop, value) {
|
|
89
|
+
(getSequelize() as any)[prop] = value;
|
|
90
|
+
return true;
|
|
91
|
+
},
|
|
92
|
+
has(_t, prop) {
|
|
93
|
+
return prop in (getSequelize() as any);
|
|
94
|
+
},
|
|
95
|
+
});
|