@blamejs/blamejs-shop 0.0.44
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/CHANGELOG.md +87 -0
- package/LICENSE +17 -0
- package/README.md +117 -0
- package/SECURITY.md +139 -0
- package/lib/admin.js +952 -0
- package/lib/analytics.js +267 -0
- package/lib/cart.js +279 -0
- package/lib/catalog-import.js +344 -0
- package/lib/catalog.js +769 -0
- package/lib/checkout.js +320 -0
- package/lib/config.js +151 -0
- package/lib/customers.js +322 -0
- package/lib/email.js +242 -0
- package/lib/externaldb-d1.js +283 -0
- package/lib/index.js +57 -0
- package/lib/inventory-alerts.js +198 -0
- package/lib/newsletter.js +142 -0
- package/lib/order.js +380 -0
- package/lib/payment.js +318 -0
- package/lib/pricing.js +185 -0
- package/lib/r2-bridge.js +169 -0
- package/lib/shipping.js +185 -0
- package/lib/storefront.js +2160 -0
- package/lib/subscriptions.js +410 -0
- package/lib/tax.js +161 -0
- package/lib/theme.js +194 -0
- package/lib/vendor/MANIFEST.json +19 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +23 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +34 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/project.yaml +16 -0
- package/lib/vendor/blamejs/.dockerignore +45 -0
- package/lib/vendor/blamejs/.gitattributes +42 -0
- package/lib/vendor/blamejs/.github/CODEOWNERS +4 -0
- package/lib/vendor/blamejs/.github/FUNDING.yml +2 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/bug_report.md +58 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/feature_request.md +99 -0
- package/lib/vendor/blamejs/.github/PULL_REQUEST_TEMPLATE.md +77 -0
- package/lib/vendor/blamejs/.github/dependabot.yml +37 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +148 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +107 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +122 -0
- package/lib/vendor/blamejs/.github/workflows/ci.yml +511 -0
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +50 -0
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +655 -0
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +406 -0
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +101 -0
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +134 -0
- package/lib/vendor/blamejs/.gitignore +102 -0
- package/lib/vendor/blamejs/.gitleaks.toml +166 -0
- package/lib/vendor/blamejs/.hadolint.yaml +18 -0
- package/lib/vendor/blamejs/.npmrc +5 -0
- package/lib/vendor/blamejs/.pinact.yaml +17 -0
- package/lib/vendor/blamejs/ARCHITECTURE.md +158 -0
- package/lib/vendor/blamejs/CHANGELOG.md +1351 -0
- package/lib/vendor/blamejs/CODE_OF_CONDUCT.md +86 -0
- package/lib/vendor/blamejs/CONTRIBUTING.md +156 -0
- package/lib/vendor/blamejs/GOVERNANCE.md +201 -0
- package/lib/vendor/blamejs/LICENSE +201 -0
- package/lib/vendor/blamejs/LTS-CALENDAR.md +29 -0
- package/lib/vendor/blamejs/MIGRATING.md +29 -0
- package/lib/vendor/blamejs/NOTICE +81 -0
- package/lib/vendor/blamejs/README.md +304 -0
- package/lib/vendor/blamejs/SECURITY.md +432 -0
- package/lib/vendor/blamejs/api-snapshot.json +48709 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.png +0 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.svg +129 -0
- package/lib/vendor/blamejs/bench/README.md +77 -0
- package/lib/vendor/blamejs/bench/_helpers.js +70 -0
- package/lib/vendor/blamejs/bench/baseline.json +183 -0
- package/lib/vendor/blamejs/bench/crypto-hash.bench.js +19 -0
- package/lib/vendor/blamejs/bench/crypto-symmetric.bench.js +28 -0
- package/lib/vendor/blamejs/bench/run.js +140 -0
- package/lib/vendor/blamejs/bench/safe-json.bench.js +31 -0
- package/lib/vendor/blamejs/bin/blamejs.js +13 -0
- package/lib/vendor/blamejs/docker/caddy/Caddyfile +46 -0
- package/lib/vendor/blamejs/docker/coredns/Corefile +37 -0
- package/lib/vendor/blamejs/docker/haproxy/haproxy.cfg +52 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +118 -0
- package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +87 -0
- package/lib/vendor/blamejs/docker/mitmproxy/config.yaml +16 -0
- package/lib/vendor/blamejs/docker/mongo/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/mysql/my.cnf +12 -0
- package/lib/vendor/blamejs/docker/nats/nats.conf +33 -0
- package/lib/vendor/blamejs/docker/postgres/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/postgres/postgresql.conf +18 -0
- package/lib/vendor/blamejs/docker/rabbitmq/rabbitmq.conf +18 -0
- package/lib/vendor/blamejs/docker/redis/redis.conf +15 -0
- package/lib/vendor/blamejs/docker/squid/squid.conf +24 -0
- package/lib/vendor/blamejs/docker/syslog/syslog-ng.conf +34 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +545 -0
- package/lib/vendor/blamejs/docs/cis-postgres-crosswalk.md +102 -0
- package/lib/vendor/blamejs/docs/cis-sqlite-equivalent.md +92 -0
- package/lib/vendor/blamejs/eslint.config.mjs +204 -0
- package/lib/vendor/blamejs/examples/wiki/Caddyfile +40 -0
- package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +218 -0
- package/lib/vendor/blamejs/examples/wiki/Dockerfile +120 -0
- package/lib/vendor/blamejs/examples/wiki/README.md +157 -0
- package/lib/vendor/blamejs/examples/wiki/cli-snapshot.json +250 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +231 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +166 -0
- package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +217 -0
- package/lib/vendor/blamejs/examples/wiki/lib/auto-site-entries.js +139 -0
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +555 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-cli.js +507 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-env-vars.js +435 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-errors.js +282 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-vendored-deps.js +321 -0
- package/lib/vendor/blamejs/examples/wiki/lib/nav.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/lib/opts-resolver.js +75 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +508 -0
- package/lib/vendor/blamejs/examples/wiki/lib/section.js +276 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +587 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +318 -0
- package/lib/vendor/blamejs/examples/wiki/lib/symbol-index.js +122 -0
- package/lib/vendor/blamejs/examples/wiki/migrations/0001-pages-schema.js +74 -0
- package/lib/vendor/blamejs/examples/wiki/package.json +18 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.png +0 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.svg +129 -0
- package/lib/vendor/blamejs/examples/wiki/public/robots.txt +5 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/MANIFEST.json +30 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.css +1 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/public/wiki.css +1250 -0
- package/lib/vendor/blamejs/examples/wiki/routes/admin.js +366 -0
- package/lib/vendor/blamejs/examples/wiki/routes/integration.js +230 -0
- package/lib/vendor/blamejs/examples/wiki/routes/pages.js +266 -0
- package/lib/vendor/blamejs/examples/wiki/scripts/backfill-module-metadata.js +214 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/0001-default-pages.js +35 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/_index.js +34 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/api.js +76 -0
- package/lib/vendor/blamejs/examples/wiki/server.js +129 -0
- package/lib/vendor/blamejs/examples/wiki/site.config.js +197 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/README.md +38 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/auth/password-hash.example.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/src/editor.js +103 -0
- package/lib/vendor/blamejs/examples/wiki/src/wiki.js +349 -0
- package/lib/vendor/blamejs/examples/wiki/test/AUDIT.md +155 -0
- package/lib/vendor/blamejs/examples/wiki/test/codebase-patterns.test.js +594 -0
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +741 -0
- package/lib/vendor/blamejs/examples/wiki/test/find-missing-pages.js +254 -0
- package/lib/vendor/blamejs/examples/wiki/test/integration.js +391 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-cli-snapshot.js +379 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-env-snapshot.js +346 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-nav-coverage.js +212 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-site-coverage.js +252 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-source-comment-blocks.js +107 -0
- package/lib/vendor/blamejs/examples/wiki/views/_layout.html +115 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/api-keys.html +51 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/dashboard.html +22 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/edit.html +17 -0
- package/lib/vendor/blamejs/examples/wiki/views/home.html +85 -0
- package/lib/vendor/blamejs/examples/wiki/views/login.html +18 -0
- package/lib/vendor/blamejs/examples/wiki/views/page.html +5 -0
- package/lib/vendor/blamejs/examples/wiki/views/partials/nav.html +13 -0
- package/lib/vendor/blamejs/examples/wiki/views/search.html +19 -0
- package/lib/vendor/blamejs/examples/wiki/wiki.config.js +15 -0
- package/lib/vendor/blamejs/fuzz/README.md +137 -0
- package/lib/vendor/blamejs/fuzz/_expected.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-agent-registry.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-csv.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/01-basic.csv +3 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/02-formula.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/03-hyperlink.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-dsn.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-email.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-email_seed_corpus/01-basic.eml +5 -0
- package/lib/vendor/blamejs/fuzz/guard-envelope.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-payload.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-topic.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-html.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/01-basic.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/02-script.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/03-event.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/04-jsurl.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-idempotency-key.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-imap-command.fuzz.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-jmap.fuzz.js +41 -0
- package/lib/vendor/blamejs/fuzz/guard-json.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/01-basic.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/02-proto.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/03-dupkey.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/04-nan.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/05-bom.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-list-id.fuzz.js +21 -0
- package/lib/vendor/blamejs/fuzz/guard-list-unsubscribe.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-compose.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-move.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-query.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-reply.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-sieve.fuzz.js +36 -0
- package/lib/vendor/blamejs/fuzz/guard-managesieve-command.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/01-basic.md +2 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/02-jsurl.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/03-jsimg.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-message-id.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-pop3-command.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-posture-chain.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-saga-config.fuzz.js +32 -0
- package/lib/vendor/blamejs/fuzz/guard-smtp-command.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-snapshot-envelope.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-stream-args.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-svg.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/01-basic.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/02-script.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-tenant-id.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-trace-context.fuzz.js +30 -0
- package/lib/vendor/blamejs/fuzz/guard-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/02-xxe.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/01-basic.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/02-anchor.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/03-norway.yaml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/04-multidoc.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini_seed_corpus/01-basic.ini +2 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml_seed_corpus/01-basic.toml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml_seed_corpus/01-basic.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/safe-decompress.fuzz.js +49 -0
- package/lib/vendor/blamejs/fuzz/safe-dns.fuzz.js +29 -0
- package/lib/vendor/blamejs/fuzz/safe-ical.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-icap.fuzz.js +42 -0
- package/lib/vendor/blamejs/fuzz/safe-json.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/01-object.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/02-array.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/03-string.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/04-proto.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/05-deep.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/02-filter.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/03-deepscan.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/04-slice.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-mime.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/safe-mount-info.fuzz.js +33 -0
- package/lib/vendor/blamejs/fuzz/safe-sieve.fuzz.js +28 -0
- package/lib/vendor/blamejs/fuzz/safe-smtp.fuzz.js +64 -0
- package/lib/vendor/blamejs/fuzz/safe-url.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/02-userinfo.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/03-dangerous.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/04-data.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/05-ipv6.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/06-idn.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-vcard.fuzz.js +16 -0
- package/lib/vendor/blamejs/index.js +678 -0
- package/lib/vendor/blamejs/keys/release-pqc-pub.json +7 -0
- package/lib/vendor/blamejs/lib/_test/crypto-fixtures.js +67 -0
- package/lib/vendor/blamejs/lib/a2a-tasks.js +598 -0
- package/lib/vendor/blamejs/lib/a2a.js +407 -0
- package/lib/vendor/blamejs/lib/acme.js +1448 -0
- package/lib/vendor/blamejs/lib/agent-audit.js +45 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +382 -0
- package/lib/vendor/blamejs/lib/agent-idempotency.js +497 -0
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +717 -0
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +366 -0
- package/lib/vendor/blamejs/lib/agent-saga.js +321 -0
- package/lib/vendor/blamejs/lib/agent-snapshot.js +676 -0
- package/lib/vendor/blamejs/lib/agent-stream.js +269 -0
- package/lib/vendor/blamejs/lib/agent-tenant.js +632 -0
- package/lib/vendor/blamejs/lib/agent-trace.js +281 -0
- package/lib/vendor/blamejs/lib/ai-adverse-decision.js +184 -0
- package/lib/vendor/blamejs/lib/ai-content-detect.js +268 -0
- package/lib/vendor/blamejs/lib/ai-input.js +201 -0
- package/lib/vendor/blamejs/lib/ai-model-manifest.js +363 -0
- package/lib/vendor/blamejs/lib/ai-pref.js +340 -0
- package/lib/vendor/blamejs/lib/api-key.js +721 -0
- package/lib/vendor/blamejs/lib/api-snapshot.js +458 -0
- package/lib/vendor/blamejs/lib/app-shutdown.js +557 -0
- package/lib/vendor/blamejs/lib/app.js +365 -0
- package/lib/vendor/blamejs/lib/archive.js +547 -0
- package/lib/vendor/blamejs/lib/arg-parser.js +697 -0
- package/lib/vendor/blamejs/lib/argon2-builtin.js +173 -0
- package/lib/vendor/blamejs/lib/asn1-der.js +424 -0
- package/lib/vendor/blamejs/lib/asyncapi-bindings.js +160 -0
- package/lib/vendor/blamejs/lib/asyncapi-traits.js +143 -0
- package/lib/vendor/blamejs/lib/asyncapi.js +575 -0
- package/lib/vendor/blamejs/lib/atomic-file.js +1023 -0
- package/lib/vendor/blamejs/lib/audit-chain.js +266 -0
- package/lib/vendor/blamejs/lib/audit-daily-review.js +389 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +751 -0
- package/lib/vendor/blamejs/lib/audit-tools.js +1113 -0
- package/lib/vendor/blamejs/lib/audit.js +1671 -0
- package/lib/vendor/blamejs/lib/auth/aal.js +169 -0
- package/lib/vendor/blamejs/lib/auth/access-lock.js +220 -0
- package/lib/vendor/blamejs/lib/auth/acr-vocabulary.js +265 -0
- package/lib/vendor/blamejs/lib/auth/ato-kill-switch.js +112 -0
- package/lib/vendor/blamejs/lib/auth/auth-time-tracker.js +111 -0
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +573 -0
- package/lib/vendor/blamejs/lib/auth/ciba.js +637 -0
- package/lib/vendor/blamejs/lib/auth/dpop.js +516 -0
- package/lib/vendor/blamejs/lib/auth/elevation-grant.js +306 -0
- package/lib/vendor/blamejs/lib/auth/fal.js +229 -0
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +681 -0
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +519 -0
- package/lib/vendor/blamejs/lib/auth/jwt.js +430 -0
- package/lib/vendor/blamejs/lib/auth/lockout.js +449 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +2141 -0
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +657 -0
- package/lib/vendor/blamejs/lib/auth/oid4vp.js +531 -0
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +600 -0
- package/lib/vendor/blamejs/lib/auth/passkey.js +676 -0
- package/lib/vendor/blamejs/lib/auth/password.js +693 -0
- package/lib/vendor/blamejs/lib/auth/saml.js +2109 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-disclosure.js +95 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-holder.js +225 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-issuer.js +197 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +728 -0
- package/lib/vendor/blamejs/lib/auth/status-list.js +272 -0
- package/lib/vendor/blamejs/lib/auth/step-up-policy.js +335 -0
- package/lib/vendor/blamejs/lib/auth/step-up.js +454 -0
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +505 -0
- package/lib/vendor/blamejs/lib/auth-header.js +148 -0
- package/lib/vendor/blamejs/lib/backup/bundle.js +265 -0
- package/lib/vendor/blamejs/lib/backup/crypto.js +176 -0
- package/lib/vendor/blamejs/lib/backup/index.js +1001 -0
- package/lib/vendor/blamejs/lib/backup/manifest.js +443 -0
- package/lib/vendor/blamejs/lib/boot-gates.js +174 -0
- package/lib/vendor/blamejs/lib/breach-deadline.js +272 -0
- package/lib/vendor/blamejs/lib/break-glass.js +1753 -0
- package/lib/vendor/blamejs/lib/budr.js +205 -0
- package/lib/vendor/blamejs/lib/bundler.js +461 -0
- package/lib/vendor/blamejs/lib/cache-redis.js +256 -0
- package/lib/vendor/blamejs/lib/cache-status.js +288 -0
- package/lib/vendor/blamejs/lib/cache.js +1331 -0
- package/lib/vendor/blamejs/lib/calendar.js +1240 -0
- package/lib/vendor/blamejs/lib/canonical-json.js +143 -0
- package/lib/vendor/blamejs/lib/cdn-cache-control.js +473 -0
- package/lib/vendor/blamejs/lib/cert.js +763 -0
- package/lib/vendor/blamejs/lib/chain-writer.js +259 -0
- package/lib/vendor/blamejs/lib/circuit-breaker.js +101 -0
- package/lib/vendor/blamejs/lib/cli-helpers.js +237 -0
- package/lib/vendor/blamejs/lib/cli.js +2328 -0
- package/lib/vendor/blamejs/lib/client-hints.js +318 -0
- package/lib/vendor/blamejs/lib/cloud-events.js +277 -0
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +317 -0
- package/lib/vendor/blamejs/lib/cluster-storage.js +351 -0
- package/lib/vendor/blamejs/lib/cluster.js +1017 -0
- package/lib/vendor/blamejs/lib/cms-codec.js +826 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +262 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-logging.js +190 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +205 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-risk.js +189 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +200 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act.js +821 -0
- package/lib/vendor/blamejs/lib/compliance-eaa.js +204 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-aliases.js +167 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fetcher.js +206 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fuzzy.js +297 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions.js +569 -0
- package/lib/vendor/blamejs/lib/compliance.js +1558 -0
- package/lib/vendor/blamejs/lib/config-drift.js +426 -0
- package/lib/vendor/blamejs/lib/config.js +446 -0
- package/lib/vendor/blamejs/lib/consent.js +369 -0
- package/lib/vendor/blamejs/lib/constants.js +209 -0
- package/lib/vendor/blamejs/lib/content-credentials.js +704 -0
- package/lib/vendor/blamejs/lib/cookies.js +560 -0
- package/lib/vendor/blamejs/lib/cra-report.js +299 -0
- package/lib/vendor/blamejs/lib/credential-hash.js +394 -0
- package/lib/vendor/blamejs/lib/crypto-field.js +1017 -0
- package/lib/vendor/blamejs/lib/crypto-hpke-pq.js +187 -0
- package/lib/vendor/blamejs/lib/crypto-hpke.js +256 -0
- package/lib/vendor/blamejs/lib/crypto.js +1908 -0
- package/lib/vendor/blamejs/lib/csp.js +271 -0
- package/lib/vendor/blamejs/lib/csv.js +418 -0
- package/lib/vendor/blamejs/lib/daemon.js +481 -0
- package/lib/vendor/blamejs/lib/dark-patterns.js +488 -0
- package/lib/vendor/blamejs/lib/data-act.js +328 -0
- package/lib/vendor/blamejs/lib/db-collection.js +587 -0
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +267 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +420 -0
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +333 -0
- package/lib/vendor/blamejs/lib/db-query.js +802 -0
- package/lib/vendor/blamejs/lib/db-role-context.js +50 -0
- package/lib/vendor/blamejs/lib/db-schema.js +322 -0
- package/lib/vendor/blamejs/lib/db.js +3111 -0
- package/lib/vendor/blamejs/lib/dbsc.js +299 -0
- package/lib/vendor/blamejs/lib/ddl-change-control.js +523 -0
- package/lib/vendor/blamejs/lib/deprecate.js +377 -0
- package/lib/vendor/blamejs/lib/dev.js +405 -0
- package/lib/vendor/blamejs/lib/dora.js +402 -0
- package/lib/vendor/blamejs/lib/dr-runbook.js +368 -0
- package/lib/vendor/blamejs/lib/dsr.js +1188 -0
- package/lib/vendor/blamejs/lib/dual-control.js +526 -0
- package/lib/vendor/blamejs/lib/early-hints.js +212 -0
- package/lib/vendor/blamejs/lib/error-page.js +420 -0
- package/lib/vendor/blamejs/lib/events.js +214 -0
- package/lib/vendor/blamejs/lib/external-db-migrate.js +659 -0
- package/lib/vendor/blamejs/lib/external-db.js +1877 -0
- package/lib/vendor/blamejs/lib/fapi2.js +394 -0
- package/lib/vendor/blamejs/lib/fda-21cfr11.js +395 -0
- package/lib/vendor/blamejs/lib/fdx.js +370 -0
- package/lib/vendor/blamejs/lib/fedcm.js +264 -0
- package/lib/vendor/blamejs/lib/file-type.js +360 -0
- package/lib/vendor/blamejs/lib/file-upload.js +1256 -0
- package/lib/vendor/blamejs/lib/flag-cache.js +136 -0
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +135 -0
- package/lib/vendor/blamejs/lib/flag-providers.js +279 -0
- package/lib/vendor/blamejs/lib/flag-targeting.js +210 -0
- package/lib/vendor/blamejs/lib/flag.js +346 -0
- package/lib/vendor/blamejs/lib/forms.js +525 -0
- package/lib/vendor/blamejs/lib/framework-error.js +724 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +845 -0
- package/lib/vendor/blamejs/lib/framework-sha1-hibp.js +34 -0
- package/lib/vendor/blamejs/lib/fsm.js +469 -0
- package/lib/vendor/blamejs/lib/gate-contract.js +1661 -0
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +261 -0
- package/lib/vendor/blamejs/lib/graphql-federation.js +234 -0
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +179 -0
- package/lib/vendor/blamejs/lib/guard-all.js +555 -0
- package/lib/vendor/blamejs/lib/guard-archive.js +901 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +451 -0
- package/lib/vendor/blamejs/lib/guard-cidr.js +676 -0
- package/lib/vendor/blamejs/lib/guard-csv.js +1176 -0
- package/lib/vendor/blamejs/lib/guard-domain.js +814 -0
- package/lib/vendor/blamejs/lib/guard-dsn.js +382 -0
- package/lib/vendor/blamejs/lib/guard-email.js +951 -0
- package/lib/vendor/blamejs/lib/guard-envelope.js +294 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +217 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +150 -0
- package/lib/vendor/blamejs/lib/guard-filename.js +956 -0
- package/lib/vendor/blamejs/lib/guard-graphql.js +731 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-aria.js +164 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-forms.js +144 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tables.js +154 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tagwalk.js +44 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag.js +470 -0
- package/lib/vendor/blamejs/lib/guard-html.js +1209 -0
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +151 -0
- package/lib/vendor/blamejs/lib/guard-image.js +584 -0
- package/lib/vendor/blamejs/lib/guard-imap-command.js +337 -0
- package/lib/vendor/blamejs/lib/guard-jmap.js +321 -0
- package/lib/vendor/blamejs/lib/guard-json.js +935 -0
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +512 -0
- package/lib/vendor/blamejs/lib/guard-jwt.js +772 -0
- package/lib/vendor/blamejs/lib/guard-list-id.js +318 -0
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +412 -0
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +282 -0
- package/lib/vendor/blamejs/lib/guard-mail-move.js +202 -0
- package/lib/vendor/blamejs/lib/guard-mail-query.js +310 -0
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +172 -0
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +207 -0
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +566 -0
- package/lib/vendor/blamejs/lib/guard-markdown.js +768 -0
- package/lib/vendor/blamejs/lib/guard-message-id.js +267 -0
- package/lib/vendor/blamejs/lib/guard-mime.js +609 -0
- package/lib/vendor/blamejs/lib/guard-oauth.js +650 -0
- package/lib/vendor/blamejs/lib/guard-pdf.js +569 -0
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +317 -0
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +201 -0
- package/lib/vendor/blamejs/lib/guard-regex.js +632 -0
- package/lib/vendor/blamejs/lib/guard-saga-config.js +157 -0
- package/lib/vendor/blamejs/lib/guard-shell.js +522 -0
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +594 -0
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +168 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +166 -0
- package/lib/vendor/blamejs/lib/guard-svg.js +1163 -0
- package/lib/vendor/blamejs/lib/guard-template.js +490 -0
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +138 -0
- package/lib/vendor/blamejs/lib/guard-time.js +586 -0
- package/lib/vendor/blamejs/lib/guard-trace-context.js +172 -0
- package/lib/vendor/blamejs/lib/guard-uuid.js +548 -0
- package/lib/vendor/blamejs/lib/guard-xml.js +666 -0
- package/lib/vendor/blamejs/lib/guard-yaml.js +726 -0
- package/lib/vendor/blamejs/lib/hal.js +125 -0
- package/lib/vendor/blamejs/lib/handlers.js +350 -0
- package/lib/vendor/blamejs/lib/honeytoken.js +168 -0
- package/lib/vendor/blamejs/lib/html-balance.js +347 -0
- package/lib/vendor/blamejs/lib/http-client-cache.js +923 -0
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +519 -0
- package/lib/vendor/blamejs/lib/http-client.js +2152 -0
- package/lib/vendor/blamejs/lib/http-message-signature.js +589 -0
- package/lib/vendor/blamejs/lib/http2-teardown.js +34 -0
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +398 -0
- package/lib/vendor/blamejs/lib/i18n.js +931 -0
- package/lib/vendor/blamejs/lib/iab-mspa.js +257 -0
- package/lib/vendor/blamejs/lib/iab-tcf.js +461 -0
- package/lib/vendor/blamejs/lib/importmap-integrity.js +90 -0
- package/lib/vendor/blamejs/lib/inbox.js +435 -0
- package/lib/vendor/blamejs/lib/incident-report.js +314 -0
- package/lib/vendor/blamejs/lib/ip-utils.js +102 -0
- package/lib/vendor/blamejs/lib/jobs.js +185 -0
- package/lib/vendor/blamejs/lib/jose-jwe-experimental.js +228 -0
- package/lib/vendor/blamejs/lib/jsonapi.js +230 -0
- package/lib/vendor/blamejs/lib/keychain.js +865 -0
- package/lib/vendor/blamejs/lib/lazy-require.js +48 -0
- package/lib/vendor/blamejs/lib/legal-hold.js +374 -0
- package/lib/vendor/blamejs/lib/local-db-thin.js +321 -0
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +369 -0
- package/lib/vendor/blamejs/lib/log-stream-local.js +146 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +410 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +286 -0
- package/lib/vendor/blamejs/lib/log-stream-syslog.js +310 -0
- package/lib/vendor/blamejs/lib/log-stream-webhook.js +199 -0
- package/lib/vendor/blamejs/lib/log-stream.js +584 -0
- package/lib/vendor/blamejs/lib/log.js +625 -0
- package/lib/vendor/blamejs/lib/lro.js +200 -0
- package/lib/vendor/blamejs/lib/mail-agent.js +786 -0
- package/lib/vendor/blamejs/lib/mail-arc-sign.js +417 -0
- package/lib/vendor/blamejs/lib/mail-arf.js +343 -0
- package/lib/vendor/blamejs/lib/mail-auth.js +2144 -0
- package/lib/vendor/blamejs/lib/mail-bimi.js +1047 -0
- package/lib/vendor/blamejs/lib/mail-bounce.js +955 -0
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +1286 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +789 -0
- package/lib/vendor/blamejs/lib/mail-crypto.js +108 -0
- package/lib/vendor/blamejs/lib/mail-dav.js +1224 -0
- package/lib/vendor/blamejs/lib/mail-deploy.js +1119 -0
- package/lib/vendor/blamejs/lib/mail-dkim.js +1250 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +448 -0
- package/lib/vendor/blamejs/lib/mail-helo.js +473 -0
- package/lib/vendor/blamejs/lib/mail-journal.js +435 -0
- package/lib/vendor/blamejs/lib/mail-mdn.js +424 -0
- package/lib/vendor/blamejs/lib/mail-rbl.js +392 -0
- package/lib/vendor/blamejs/lib/mail-require-tls.js +198 -0
- package/lib/vendor/blamejs/lib/mail-scan.js +502 -0
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +629 -0
- package/lib/vendor/blamejs/lib/mail-server-imap.js +1858 -0
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +1565 -0
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +908 -0
- package/lib/vendor/blamejs/lib/mail-server-mx.js +969 -0
- package/lib/vendor/blamejs/lib/mail-server-pop3.js +915 -0
- package/lib/vendor/blamejs/lib/mail-server-rate-limit.js +315 -0
- package/lib/vendor/blamejs/lib/mail-server-registry.js +378 -0
- package/lib/vendor/blamejs/lib/mail-server-submission.js +1396 -0
- package/lib/vendor/blamejs/lib/mail-server-tls.js +445 -0
- package/lib/vendor/blamejs/lib/mail-sieve.js +557 -0
- package/lib/vendor/blamejs/lib/mail-spam-score.js +284 -0
- package/lib/vendor/blamejs/lib/mail-srs.js +248 -0
- package/lib/vendor/blamejs/lib/mail-store-fts.js +394 -0
- package/lib/vendor/blamejs/lib/mail-store.js +929 -0
- package/lib/vendor/blamejs/lib/mail-unsubscribe.js +400 -0
- package/lib/vendor/blamejs/lib/mail.js +1971 -0
- package/lib/vendor/blamejs/lib/mcp-tool-registry.js +473 -0
- package/lib/vendor/blamejs/lib/mcp.js +950 -0
- package/lib/vendor/blamejs/lib/metrics.js +1503 -0
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/ai-act-disclosure.js +203 -0
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +981 -0
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +137 -0
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +171 -0
- package/lib/vendor/blamejs/lib/middleware/attach-user.js +220 -0
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +293 -0
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +1519 -0
- package/lib/vendor/blamejs/lib/middleware/bot-disclose.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +217 -0
- package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +122 -0
- package/lib/vendor/blamejs/lib/middleware/compose-pipeline.js +355 -0
- package/lib/vendor/blamejs/lib/middleware/compression.js +489 -0
- package/lib/vendor/blamejs/lib/middleware/cookies.js +130 -0
- package/lib/vendor/blamejs/lib/middleware/cors.js +386 -0
- package/lib/vendor/blamejs/lib/middleware/csp-nonce.js +388 -0
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +499 -0
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +243 -0
- package/lib/vendor/blamejs/lib/middleware/db-role-for.js +304 -0
- package/lib/vendor/blamejs/lib/middleware/dpop.js +402 -0
- package/lib/vendor/blamejs/lib/middleware/error-handler.js +69 -0
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +168 -0
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +110 -0
- package/lib/vendor/blamejs/lib/middleware/gpc.js +153 -0
- package/lib/vendor/blamejs/lib/middleware/headers.js +242 -0
- package/lib/vendor/blamejs/lib/middleware/health.js +438 -0
- package/lib/vendor/blamejs/lib/middleware/host-allowlist.js +189 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +964 -0
- package/lib/vendor/blamejs/lib/middleware/index.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/nel.js +214 -0
- package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +237 -0
- package/lib/vendor/blamejs/lib/middleware/no-cache.js +106 -0
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/protected-resource-metadata.js +277 -0
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +556 -0
- package/lib/vendor/blamejs/lib/middleware/request-id.js +79 -0
- package/lib/vendor/blamejs/lib/middleware/request-log.js +205 -0
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +138 -0
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +144 -0
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +290 -0
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +113 -0
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +97 -0
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +212 -0
- package/lib/vendor/blamejs/lib/middleware/require-step-up.js +226 -0
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +375 -0
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +285 -0
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +170 -0
- package/lib/vendor/blamejs/lib/middleware/span-http-server.js +280 -0
- package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +323 -0
- package/lib/vendor/blamejs/lib/middleware/sse.js +200 -0
- package/lib/vendor/blamejs/lib/middleware/trace-log-correlation.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/trace-propagate.js +148 -0
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +749 -0
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +164 -0
- package/lib/vendor/blamejs/lib/migration-files.js +37 -0
- package/lib/vendor/blamejs/lib/migrations.js +385 -0
- package/lib/vendor/blamejs/lib/mime-parse.js +198 -0
- package/lib/vendor/blamejs/lib/money.js +699 -0
- package/lib/vendor/blamejs/lib/mtls-ca.js +572 -0
- package/lib/vendor/blamejs/lib/mtls-engine-default.js +501 -0
- package/lib/vendor/blamejs/lib/network-byte-quota.js +308 -0
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +533 -0
- package/lib/vendor/blamejs/lib/network-dns.js +1930 -0
- package/lib/vendor/blamejs/lib/network-heartbeat.js +425 -0
- package/lib/vendor/blamejs/lib/network-nts.js +574 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +265 -0
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +836 -0
- package/lib/vendor/blamejs/lib/network-tls.js +3126 -0
- package/lib/vendor/blamejs/lib/network.js +346 -0
- package/lib/vendor/blamejs/lib/nis2-report.js +181 -0
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +293 -0
- package/lib/vendor/blamejs/lib/nonce-store.js +177 -0
- package/lib/vendor/blamejs/lib/notify.js +683 -0
- package/lib/vendor/blamejs/lib/ntp-check.js +458 -0
- package/lib/vendor/blamejs/lib/numeric-bounds.js +111 -0
- package/lib/vendor/blamejs/lib/numeric-checks.js +40 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +349 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +488 -0
- package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +351 -0
- package/lib/vendor/blamejs/lib/object-store/gcs.js +515 -0
- package/lib/vendor/blamejs/lib/object-store/http-put.js +153 -0
- package/lib/vendor/blamejs/lib/object-store/http-request.js +38 -0
- package/lib/vendor/blamejs/lib/object-store/index.js +197 -0
- package/lib/vendor/blamejs/lib/object-store/local.js +163 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +1133 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +957 -0
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +420 -0
- package/lib/vendor/blamejs/lib/observability-tracer.js +395 -0
- package/lib/vendor/blamejs/lib/observability.js +720 -0
- package/lib/vendor/blamejs/lib/openapi-paths-builder.js +248 -0
- package/lib/vendor/blamejs/lib/openapi-schema-walk.js +192 -0
- package/lib/vendor/blamejs/lib/openapi-security.js +169 -0
- package/lib/vendor/blamejs/lib/openapi-yaml.js +154 -0
- package/lib/vendor/blamejs/lib/openapi.js +489 -0
- package/lib/vendor/blamejs/lib/otel-export.js +278 -0
- package/lib/vendor/blamejs/lib/outbox.js +547 -0
- package/lib/vendor/blamejs/lib/pagination.js +542 -0
- package/lib/vendor/blamejs/lib/parsers/index.js +91 -0
- package/lib/vendor/blamejs/lib/parsers/safe-env.js +642 -0
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +293 -0
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +784 -0
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +390 -0
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +1015 -0
- package/lib/vendor/blamejs/lib/permissions.js +793 -0
- package/lib/vendor/blamejs/lib/pick.js +105 -0
- package/lib/vendor/blamejs/lib/pqc-agent.js +351 -0
- package/lib/vendor/blamejs/lib/pqc-gate.js +279 -0
- package/lib/vendor/blamejs/lib/pqc-software.js +271 -0
- package/lib/vendor/blamejs/lib/problem-details.js +482 -0
- package/lib/vendor/blamejs/lib/process-spawn.js +196 -0
- package/lib/vendor/blamejs/lib/promise-pool.js +162 -0
- package/lib/vendor/blamejs/lib/protobuf-encoder.js +190 -0
- package/lib/vendor/blamejs/lib/protocol-dispatcher.js +161 -0
- package/lib/vendor/blamejs/lib/public-suffix.js +403 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +154 -0
- package/lib/vendor/blamejs/lib/pubsub-redis.js +167 -0
- package/lib/vendor/blamejs/lib/pubsub.js +463 -0
- package/lib/vendor/blamejs/lib/queue-local.js +476 -0
- package/lib/vendor/blamejs/lib/queue-redis.js +745 -0
- package/lib/vendor/blamejs/lib/queue-sqs.js +319 -0
- package/lib/vendor/blamejs/lib/queue.js +1016 -0
- package/lib/vendor/blamejs/lib/redact.js +1007 -0
- package/lib/vendor/blamejs/lib/redis-client.js +520 -0
- package/lib/vendor/blamejs/lib/render.js +285 -0
- package/lib/vendor/blamejs/lib/request-helpers.js +767 -0
- package/lib/vendor/blamejs/lib/resource-access-lock.js +116 -0
- package/lib/vendor/blamejs/lib/restore-bundle.js +340 -0
- package/lib/vendor/blamejs/lib/restore-rollback.js +365 -0
- package/lib/vendor/blamejs/lib/restore.js +409 -0
- package/lib/vendor/blamejs/lib/retention.js +640 -0
- package/lib/vendor/blamejs/lib/retry.js +523 -0
- package/lib/vendor/blamejs/lib/router.js +1289 -0
- package/lib/vendor/blamejs/lib/safe-async.js +1184 -0
- package/lib/vendor/blamejs/lib/safe-buffer.js +562 -0
- package/lib/vendor/blamejs/lib/safe-decompress.js +297 -0
- package/lib/vendor/blamejs/lib/safe-dns.js +665 -0
- package/lib/vendor/blamejs/lib/safe-ical.js +634 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +502 -0
- package/lib/vendor/blamejs/lib/safe-json.js +946 -0
- package/lib/vendor/blamejs/lib/safe-jsonpath.js +285 -0
- package/lib/vendor/blamejs/lib/safe-mime.js +831 -0
- package/lib/vendor/blamejs/lib/safe-mount-info.js +306 -0
- package/lib/vendor/blamejs/lib/safe-path.js +254 -0
- package/lib/vendor/blamejs/lib/safe-redirect.js +106 -0
- package/lib/vendor/blamejs/lib/safe-schema.js +1810 -0
- package/lib/vendor/blamejs/lib/safe-sieve.js +684 -0
- package/lib/vendor/blamejs/lib/safe-smtp.js +185 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +363 -0
- package/lib/vendor/blamejs/lib/safe-url.js +428 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +473 -0
- package/lib/vendor/blamejs/lib/sandbox-worker.js +135 -0
- package/lib/vendor/blamejs/lib/sandbox.js +358 -0
- package/lib/vendor/blamejs/lib/scheduler.js +827 -0
- package/lib/vendor/blamejs/lib/sd-notify.js +269 -0
- package/lib/vendor/blamejs/lib/sec-cyber.js +214 -0
- package/lib/vendor/blamejs/lib/security-assert.js +395 -0
- package/lib/vendor/blamejs/lib/seeders.js +620 -0
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +309 -0
- package/lib/vendor/blamejs/lib/self-update.js +804 -0
- package/lib/vendor/blamejs/lib/server-timing.js +174 -0
- package/lib/vendor/blamejs/lib/session-device-binding.js +431 -0
- package/lib/vendor/blamejs/lib/session-stores.js +138 -0
- package/lib/vendor/blamejs/lib/session.js +1162 -0
- package/lib/vendor/blamejs/lib/slug.js +381 -0
- package/lib/vendor/blamejs/lib/sse.js +349 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +792 -0
- package/lib/vendor/blamejs/lib/standard-webhooks.js +183 -0
- package/lib/vendor/blamejs/lib/static.js +1249 -0
- package/lib/vendor/blamejs/lib/storage.js +1272 -0
- package/lib/vendor/blamejs/lib/stream-throttle.js +235 -0
- package/lib/vendor/blamejs/lib/structured-fields.js +244 -0
- package/lib/vendor/blamejs/lib/subject.js +667 -0
- package/lib/vendor/blamejs/lib/tcpa-10dlc.js +175 -0
- package/lib/vendor/blamejs/lib/template.js +931 -0
- package/lib/vendor/blamejs/lib/tenant-quota.js +545 -0
- package/lib/vendor/blamejs/lib/test-harness.js +275 -0
- package/lib/vendor/blamejs/lib/testing.js +1185 -0
- package/lib/vendor/blamejs/lib/time.js +578 -0
- package/lib/vendor/blamejs/lib/tls-exporter.js +239 -0
- package/lib/vendor/blamejs/lib/totp.js +318 -0
- package/lib/vendor/blamejs/lib/tracing.js +546 -0
- package/lib/vendor/blamejs/lib/uuid.js +207 -0
- package/lib/vendor/blamejs/lib/validate-opts.js +381 -0
- package/lib/vendor/blamejs/lib/vault/index.js +638 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +311 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-source.js +198 -0
- package/lib/vendor/blamejs/lib/vault/rotate.js +803 -0
- package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +471 -0
- package/lib/vendor/blamejs/lib/vault/wrap.js +296 -0
- package/lib/vendor/blamejs/lib/vault-aad.js +259 -0
- package/lib/vendor/blamejs/lib/vendor/.vendor-data-pubkey +4 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +161 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.data.js +68 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.pem +33 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.data.js +1325 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.txt +10002 -0
- package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +9 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +18 -0
- package/lib/vendor/blamejs/lib/vendor/pki.cjs +181 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +16382 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5881 -0
- package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +328 -0
- package/lib/vendor/blamejs/lib/vendor/vendor-data-pubkey.js +16 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +520 -0
- package/lib/vendor/blamejs/lib/vex.js +630 -0
- package/lib/vendor/blamejs/lib/watcher.js +608 -0
- package/lib/vendor/blamejs/lib/web-push-vapid.js +322 -0
- package/lib/vendor/blamejs/lib/webhook.js +977 -0
- package/lib/vendor/blamejs/lib/websocket-channels.js +327 -0
- package/lib/vendor/blamejs/lib/websocket.js +1561 -0
- package/lib/vendor/blamejs/lib/wiki-concepts.js +338 -0
- package/lib/vendor/blamejs/lib/worker-pool.js +464 -0
- package/lib/vendor/blamejs/lib/ws-client.js +978 -0
- package/lib/vendor/blamejs/lib/xml-c14n.js +506 -0
- package/lib/vendor/blamejs/memory/specs/node-26-map-getorinsert-migration.md +164 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +19 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/README.md +88 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/build.sh +26 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/project.yaml +28 -0
- package/lib/vendor/blamejs/package.json +81 -0
- package/lib/vendor/blamejs/release-notes/v0.0.x.json +310 -0
- package/lib/vendor/blamejs/release-notes/v0.1.x.json +1798 -0
- package/lib/vendor/blamejs/release-notes/v0.10.x.json +1288 -0
- package/lib/vendor/blamejs/release-notes/v0.11.x.json +2551 -0
- package/lib/vendor/blamejs/release-notes/v0.12.0.json +64 -0
- package/lib/vendor/blamejs/release-notes/v0.12.1.json +32 -0
- package/lib/vendor/blamejs/release-notes/v0.12.2.json +45 -0
- package/lib/vendor/blamejs/release-notes/v0.2.x.json +706 -0
- package/lib/vendor/blamejs/release-notes/v0.3.x.json +786 -0
- package/lib/vendor/blamejs/release-notes/v0.4.x.json +588 -0
- package/lib/vendor/blamejs/release-notes/v0.5.x.json +390 -0
- package/lib/vendor/blamejs/release-notes/v0.6.x.json +1947 -0
- package/lib/vendor/blamejs/release-notes/v0.7.x.json +3811 -0
- package/lib/vendor/blamejs/release-notes/v0.8.x.json +3318 -0
- package/lib/vendor/blamejs/release-notes/v0.9.x.json +2257 -0
- package/lib/vendor/blamejs/scripts/build-vendored-sbom.js +325 -0
- package/lib/vendor/blamejs/scripts/check-api-snapshot.js +62 -0
- package/lib/vendor/blamejs/scripts/check-changelog-extract.js +108 -0
- package/lib/vendor/blamejs/scripts/check-pack-against-gitignore.js +83 -0
- package/lib/vendor/blamejs/scripts/check-services.js +483 -0
- package/lib/vendor/blamejs/scripts/check-vendor-currency.js +349 -0
- package/lib/vendor/blamejs/scripts/consolidate-release-notes.js +216 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +275 -0
- package/lib/vendor/blamejs/scripts/generate-changelog-entry.js +577 -0
- package/lib/vendor/blamejs/scripts/generate-release-signing-key.js +79 -0
- package/lib/vendor/blamejs/scripts/publish-dep-confusion-placeholder.sh +101 -0
- package/lib/vendor/blamejs/scripts/refresh-api-snapshot.js +31 -0
- package/lib/vendor/blamejs/scripts/refresh-vendor-manifest.js +132 -0
- package/lib/vendor/blamejs/scripts/release.js +652 -0
- package/lib/vendor/blamejs/scripts/sha3-digest.js +62 -0
- package/lib/vendor/blamejs/scripts/sign-release-artifact.js +92 -0
- package/lib/vendor/blamejs/scripts/test-integration.js +181 -0
- package/lib/vendor/blamejs/scripts/test-wiki-integration.js +126 -0
- package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +77 -0
- package/lib/vendor/blamejs/scripts/vendor-data-gen.js +186 -0
- package/lib/vendor/blamejs/scripts/vendor-data-keygen.js +101 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +278 -0
- package/lib/vendor/blamejs/test/00-primitives.js +19075 -0
- package/lib/vendor/blamejs/test/10-state.js +622 -0
- package/lib/vendor/blamejs/test/20-db.js +561 -0
- package/lib/vendor/blamejs/test/30-chain.js +2110 -0
- package/lib/vendor/blamejs/test/40-consumers.js +2453 -0
- package/lib/vendor/blamejs/test/50-integration.js +486 -0
- package/lib/vendor/blamejs/test/_helpers.js +10 -0
- package/lib/vendor/blamejs/test/_smoke-worker.js +69 -0
- package/lib/vendor/blamejs/test/fixtures/exploit-corpus/corpus.json +368 -0
- package/lib/vendor/blamejs/test/fixtures/http-client-stream-payload.txt +2 -0
- package/lib/vendor/blamejs/test/fixtures/worker-pool/echo.js +52 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle-worker.js +24 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle.js +203 -0
- package/lib/vendor/blamejs/test/helpers/_shape-match.js +513 -0
- package/lib/vendor/blamejs/test/helpers/check.js +36 -0
- package/lib/vendor/blamejs/test/helpers/cluster.js +70 -0
- package/lib/vendor/blamejs/test/helpers/db.js +143 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +207 -0
- package/lib/vendor/blamejs/test/helpers/fs-watch.js +101 -0
- package/lib/vendor/blamejs/test/helpers/http.js +14 -0
- package/lib/vendor/blamejs/test/helpers/index.js +93 -0
- package/lib/vendor/blamejs/test/helpers/json-round-trip.js +120 -0
- package/lib/vendor/blamejs/test/helpers/mocks.js +20 -0
- package/lib/vendor/blamejs/test/helpers/otel.js +13 -0
- package/lib/vendor/blamejs/test/helpers/services.js +380 -0
- package/lib/vendor/blamejs/test/helpers/wait.js +206 -0
- package/lib/vendor/blamejs/test/integration/cache.test.js +235 -0
- package/lib/vendor/blamejs/test/integration/cluster-provider-mysql.test.js +174 -0
- package/lib/vendor/blamejs/test/integration/federation-auth.test.js +611 -0
- package/lib/vendor/blamejs/test/integration/http-client.test.js +129 -0
- package/lib/vendor/blamejs/test/integration/log-stream.test.js +219 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +181 -0
- package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +152 -0
- package/lib/vendor/blamejs/test/integration/mail-smtp.test.js +161 -0
- package/lib/vendor/blamejs/test/integration/mtls-ca.test.js +289 -0
- package/lib/vendor/blamejs/test/integration/network-dns.test.js +123 -0
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/ntp-check.test.js +89 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +403 -0
- package/lib/vendor/blamejs/test/integration/pqc-pkcs8-forward-compat.test.js +271 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +137 -0
- package/lib/vendor/blamejs/test/integration/queue-redis.test.js +352 -0
- package/lib/vendor/blamejs/test/integration/redis-client-tls.test.js +96 -0
- package/lib/vendor/blamejs/test/integration/ssrf-guard.test.js +98 -0
- package/lib/vendor/blamejs/test/integration/websocket-permessage-deflate.test.js +261 -0
- package/lib/vendor/blamejs/test/integration/ws-client-roundtrip.test.js +230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +211 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/access-lock.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +219 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +262 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-orchestrator.test.js +390 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-posture-chain.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-stream.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-tenant.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-trace.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-adverse-decision.test.js +44 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-content-detect.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-input.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-model-manifest.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-pref.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +1080 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +311 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-zip-stream.test.js +291 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +108 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asyncapi.test.js +929 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-conflict-path.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-cve-defensive.test.js +176 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-export-cadf.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-framework-namespaces.test.js +141 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-segregation.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-ml-dsa-65.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-use-store.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge-verifier.test.js +485 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +331 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +352 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +572 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-password-audit.test.js +61 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-bucket-ops.test.js +258 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-worker.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bearer-auth.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-chunked-malformed.test.js +131 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-smuggling.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/boot-gates.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/breach-deadline.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +861 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/budr.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bundler-engine.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache-status.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +871 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +891 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/canonical-json-jcs.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cdn-cache-control.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +550 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-api-key.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-audit-verify-chain.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-backup.test.js +135 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-config-drift.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-file-type.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-helpers.test.js +145 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-mtls.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-password.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-restore.test.js +160 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-retention.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-security.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-vault.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/client-hints.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +237 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +9600 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-cascade.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-eaa.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +712 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config-drift.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config.test.js +424 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cra-report.test.js +31 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-base64url.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-envelope.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke-pq.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-mlkem768-x25519.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-namespace-hash.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-random-int.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-builder.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-nonce.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daily-byte-quota.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection-extensions.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-init-extensions.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-extensions.test.js +191 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-role-for.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-vacuum.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +184 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-row-policy.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-view.test.js +303 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-dnssec-algorithm.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-null-mx.test.js +39 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dr-runbook.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr-state-rules.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +786 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/early-hints.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/events.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/exploit-replay.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-routing.test.js +531 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fapi2.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +156 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fdx.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +434 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +432 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/file-type.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +887 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/forensic-snapshot.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fsm.test.js +375 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gcs-bucket-ops.test.js +321 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +41 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-agent-registry.test.js +87 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-all.test.js +328 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +694 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-dsn.test.js +296 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-email.test.js +234 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-envelope.test.js +192 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-payload.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-topic.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +386 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html-wcag.test.js +859 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +317 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-id.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-unsubscribe.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-compose.test.js +111 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-move.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-query.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-reply.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-sieve.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +301 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +265 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-message-id.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +161 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-posture-chain.test.js +100 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-saga-config.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-smtp-command.test.js +269 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-snapshot-envelope.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-stream-args.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +288 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-tenant-id.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-trace-context.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-xml.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-yaml.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/hal.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/honeytoken.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +692 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +225 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +991 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-mspa.test.js +63 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-tcf.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +612 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +166 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jose-jwe-experimental.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-api.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-round-trip-helper.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/local-db-thin.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-cloudwatch.test.js +489 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp-grpc.test.js +207 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/lro.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +417 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-arf.test.js +208 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +910 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +502 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +680 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-canspam.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +297 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +514 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy-tlsrpt.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +627 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-feedback-id.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-greylist.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-mdn.test.js +334 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-rbl.test.js +271 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-require-tls.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +215 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +336 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +732 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +840 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +285 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +394 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-tls.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-spam-score.test.js +204 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-srs.test.js +152 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-unsubscribe.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +439 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-tool-registry.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-shadow-registry.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +224 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-compose-pipeline.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/money.test.js +376 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-paths.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nel.test.js +200 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-byte-quota.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +372 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +635 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-heartbeat-passive.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-build-options.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-ct-inclusion.test.js +179 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +447 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nis2-report.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nist-crosswalk.test.js +42 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/no-cache.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/notify.test.js +707 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/numeric-bounds.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/openapi.test.js +877 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pagination.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +324 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +546 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-software.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/problem-details.test.js +195 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/process-spawn.test.js +62 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/promise-pool.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protected-resource-metadata.test.js +68 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protobuf-encoder.test.js +138 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protocol-dispatcher.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/public-suffix.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +232 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-dlq-extend-lease.test.js +178 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-priority-rate-progress.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-sqs.test.js +300 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-cluster.test.js +338 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-registry.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +335 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-log.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-auth-cache-control.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-mtls.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/resource-access-lock.test.js +52 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retry.test.js +535 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-cross-origin-redirect.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-loops.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-parallel.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +248 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +451 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +289 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-icap.test.js +206 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-jsonpath.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mount-info.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-sieve.test.js +123 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-smtp.test.js +95 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-idn-homograph.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-slo.test.js +249 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-exactly-once.test.js +238 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +700 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-notify.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sec-cyber.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +175 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/seeders.test.js +816 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update-standalone-verifier.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/server-timing.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +247 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +295 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/shape-match.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +952 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-multipart-sse.test.js +441 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/slug.test.js +330 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/smtp-policy.test.js +233 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/source-comment-blocks.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/speculation-rules.test.js +319 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse.test.js +148 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/standard-webhooks.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +487 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-chunk-scratch.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +773 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/stream-throttle.test.js +173 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/structured-fields.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tcpa-10dlc.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +571 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-harness.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing-request.test.js +119 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-exporter.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-ct.test.js +275 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-verify.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-pinset-drift.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-preferred-groups.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tracing.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/uuid.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-aad.test.js +277 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-seal-pem-file.test.js +252 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-data.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-manifest.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vex.test.js +661 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +308 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/web-push-vapid.test.js +144 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +674 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-channels.test.js +360 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +349 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +717 -0
- package/lib/vendor/blamejs/test/layer-5-integration/bundler-output.test.js +444 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-5-integration/security-chaos.test.js +308 -0
- package/lib/vendor/blamejs/test/smoke.js +431 -0
- package/lib/webhooks.js +305 -0
- package/package.json +43 -0
|
@@ -0,0 +1,1877 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.externalDb
|
|
4
|
+
* @nav Data
|
|
5
|
+
* @title External Database
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* External-database integration for app data — Postgres / MySQL /
|
|
9
|
+
* SQLite / MongoDB connection pooling, retry, circuit breaker,
|
|
10
|
+
* classification routing, residency enforcement, and audit hooks.
|
|
11
|
+
*
|
|
12
|
+
* Framework state (audit_log, consent_log, _blamejs_*) stays in the
|
|
13
|
+
* local SQLite via `b.db`. This module is for APP DATA — when an
|
|
14
|
+
* operator keeps domain tables in Postgres / MySQL / MongoDB / libsql,
|
|
15
|
+
* they configure a backend here and use `b.externalDb.query()` instead
|
|
16
|
+
* of `b.db.from()` for those tables. The same surface also serves
|
|
17
|
+
* cluster-mode coordination (leader election advisory locks,
|
|
18
|
+
* cross-replica routing) when the cluster provider points at the same
|
|
19
|
+
* backend.
|
|
20
|
+
*
|
|
21
|
+
* Bring-your-own-client design (per "zero npm runtime deps" rule):
|
|
22
|
+
* the operator supplies the actual DB driver via each backend's
|
|
23
|
+
* `connect` / `query` / `close` hooks. The framework layers
|
|
24
|
+
* connection pooling (lazy-create, idle reaping), transient-error
|
|
25
|
+
* retry, per-backend circuit breaker, classification routing
|
|
26
|
+
* (which backend serves which data class), residency enforcement
|
|
27
|
+
* against `db.getDataResidency().region`, and audit hooks
|
|
28
|
+
* (`system.externaldb.{query,transaction,read}`).
|
|
29
|
+
*
|
|
30
|
+
* Read-replica routing exposes `b.externalDb.read.query()` and
|
|
31
|
+
* `b.externalDb.write.query()` — reads weight-round-robin across
|
|
32
|
+
* declared replicas with health tracking and primary fallback;
|
|
33
|
+
* writes always route to primary.
|
|
34
|
+
*
|
|
35
|
+
* @card
|
|
36
|
+
* External-database integration for app data — Postgres / MySQL / SQLite / MongoDB connection pooling, retry, circuit breaker, classification routing, residency enforcement, and audit hooks.
|
|
37
|
+
*/
|
|
38
|
+
var retryHelper = require("./retry");
|
|
39
|
+
var bCrypto = require("./crypto");
|
|
40
|
+
var C = require("./constants");
|
|
41
|
+
var dbRoleContext = require("./db-role-context");
|
|
42
|
+
var externalDbMigrate = require("./external-db-migrate");
|
|
43
|
+
var lazyRequire = require("./lazy-require");
|
|
44
|
+
var { boot } = require("./log");
|
|
45
|
+
var safeAsync = require("./safe-async");
|
|
46
|
+
var safeSql = require("./safe-sql");
|
|
47
|
+
var { ExternalDbError } = require("./framework-error");
|
|
48
|
+
|
|
49
|
+
var log = boot("external-db");
|
|
50
|
+
|
|
51
|
+
var audit = lazyRequire(function () { return require("./audit"); });
|
|
52
|
+
var db = lazyRequire(function () { return require("./db"); });
|
|
53
|
+
var observability = lazyRequire(function () { return require("./observability"); });
|
|
54
|
+
|
|
55
|
+
function _emitMetric(name, value, labels) {
|
|
56
|
+
try { observability().event(name, value, labels || {}); }
|
|
57
|
+
catch (_e) { /* hot-path observability sink — drop silent by design */ }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Statement-class classifier for auth-failure forensics (D-M2). Inspects
|
|
61
|
+
// the leading keyword only so an attacker-controlled trailing fragment
|
|
62
|
+
// can't smuggle a false classification. Skips leading whitespace plus
|
|
63
|
+
// SQL line / block comments before reading the keyword.
|
|
64
|
+
var _STATEMENT_CLASS_RE = /^\s*(?:\/\*[\s\S]*?\*\/\s*|--[^\n]*\n\s*)*([A-Za-z]+)/;
|
|
65
|
+
var _STATEMENT_CLASS_MAP = Object.freeze({
|
|
66
|
+
SELECT: "SELECT", WITH: "SELECT", VALUES: "SELECT", TABLE: "SELECT",
|
|
67
|
+
INSERT: "DML", UPDATE: "DML", DELETE: "DML", MERGE: "DML", UPSERT: "DML",
|
|
68
|
+
CREATE: "DDL", DROP: "DDL", ALTER: "DDL", TRUNCATE: "DDL",
|
|
69
|
+
RENAME: "DDL", COMMENT: "DDL",
|
|
70
|
+
GRANT: "DCL", REVOKE: "DCL",
|
|
71
|
+
SET: "SESSION", RESET: "SESSION",
|
|
72
|
+
BEGIN: "TX", START: "TX", COMMIT: "TX", ROLLBACK: "TX",
|
|
73
|
+
SAVEPOINT: "TX", RELEASE: "TX",
|
|
74
|
+
CALL: "ROUTINE", EXECUTE: "ROUTINE",
|
|
75
|
+
COPY: "BULK",
|
|
76
|
+
EXPLAIN: "META", ANALYZE: "META", VACUUM: "META",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function _classifyStatement(sql) {
|
|
80
|
+
if (typeof sql !== "string" || sql.length === 0) return "UNKNOWN";
|
|
81
|
+
var m = _STATEMENT_CLASS_RE.exec(sql);
|
|
82
|
+
if (!m) return "UNKNOWN";
|
|
83
|
+
return _STATEMENT_CLASS_MAP[m[1].toUpperCase()] || "OTHER";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Postgres SQLSTATE classes that indicate authentication / authorization
|
|
87
|
+
// failure at the DB level. SOC2 forensic gap (D-M2) — every match emits
|
|
88
|
+
// db.auth.failed with the SQL identity attempted, the database, and
|
|
89
|
+
// the statement class.
|
|
90
|
+
var _AUTH_FAILURE_CODES = Object.freeze({
|
|
91
|
+
"28000": "invalid_authorization_specification",
|
|
92
|
+
"28P01": "invalid_password",
|
|
93
|
+
"42501": "insufficient_privilege",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
function _emitAuthFailureAudit(backend, role, sql, e) {
|
|
97
|
+
if (!e || !e.code) return;
|
|
98
|
+
var kind = _AUTH_FAILURE_CODES[e.code];
|
|
99
|
+
if (!kind) return;
|
|
100
|
+
audit().safeEmit({
|
|
101
|
+
action: "db.auth.failed",
|
|
102
|
+
actor: {},
|
|
103
|
+
resource: { kind: "db.backend", id: backend.name },
|
|
104
|
+
outcome: "denied",
|
|
105
|
+
reason: kind,
|
|
106
|
+
metadata: {
|
|
107
|
+
backend: backend.name,
|
|
108
|
+
dialect: backend.dialect,
|
|
109
|
+
sqlIdentity: role || null,
|
|
110
|
+
sqlstate: e.code,
|
|
111
|
+
statementClass: _classifyStatement(sql),
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
_emitMetric("db.auth.failed", 1, {
|
|
115
|
+
backend: backend.name,
|
|
116
|
+
sqlstate: e.code,
|
|
117
|
+
statementClass: _classifyStatement(sql),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Slow-query bucket emitter (D-L7). Single-shot per query — highest
|
|
122
|
+
// matched bucket wins. Operators dashboard on the `bucket` label
|
|
123
|
+
// rather than separate counters per threshold.
|
|
124
|
+
var _SLOW_QUERY_BUCKETS = Object.freeze([
|
|
125
|
+
{ ms: C.TIME.seconds(30), label: "30s" },
|
|
126
|
+
{ ms: C.TIME.seconds(5), label: "5s" },
|
|
127
|
+
{ ms: C.TIME.seconds(1), label: "1s" },
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
function _emitSlowQuery(backendName, role, durationMs, statementClass) {
|
|
131
|
+
if (typeof durationMs !== "number" || !isFinite(durationMs)) return;
|
|
132
|
+
for (var i = 0; i < _SLOW_QUERY_BUCKETS.length; i++) {
|
|
133
|
+
var bucket = _SLOW_QUERY_BUCKETS[i];
|
|
134
|
+
if (durationMs >= bucket.ms) {
|
|
135
|
+
_emitMetric("db.query.slow", durationMs, {
|
|
136
|
+
backend: backendName,
|
|
137
|
+
role: role || "(none)",
|
|
138
|
+
bucket: bucket.label,
|
|
139
|
+
statementClass: statementClass || "UNKNOWN",
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
var _err = ExternalDbError.factory;
|
|
147
|
+
|
|
148
|
+
var initialized = false;
|
|
149
|
+
var backends = {};
|
|
150
|
+
var defaultBackend = null;
|
|
151
|
+
// Operator-declared { role: backendName } map for request-time pool pick.
|
|
152
|
+
// Populated at init() from opts.dbRoleBackends. Read by _pickBackend
|
|
153
|
+
// when no explicit opts.backend is supplied AND the ALS scope has a role.
|
|
154
|
+
var dbRoleBackends = {};
|
|
155
|
+
|
|
156
|
+
// ---- Pool ----
|
|
157
|
+
//
|
|
158
|
+
// Per-backend pool with lazy creation + LRU-ish reuse. Connections returned
|
|
159
|
+
// to the pool when query/transaction completes; idle connections expire.
|
|
160
|
+
|
|
161
|
+
class Pool {
|
|
162
|
+
constructor(name, config) {
|
|
163
|
+
this.name = name;
|
|
164
|
+
this.config = Object.assign({ min: 1, max: 10, idleTimeoutMs: C.TIME.minutes(1) }, config.pool || {});
|
|
165
|
+
this.connect = config.connect;
|
|
166
|
+
this.close = config.close || function () { return Promise.resolve(); };
|
|
167
|
+
this.idle = []; // [{ client, lastUsedAt }]
|
|
168
|
+
this.active = 0; // count of in-use clients
|
|
169
|
+
this.waiters = []; // queued acquisitions when at max
|
|
170
|
+
this._reaper = safeAsync.repeating(this._reapIdle.bind(this),
|
|
171
|
+
C.TIME.seconds(10), { name: "external-db-reaper" });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async acquire() {
|
|
175
|
+
if (this.idle.length > 0) {
|
|
176
|
+
var entry = this.idle.pop();
|
|
177
|
+
this.active += 1;
|
|
178
|
+
return entry.client;
|
|
179
|
+
}
|
|
180
|
+
if (this.active < this.config.max) {
|
|
181
|
+
this.active += 1;
|
|
182
|
+
try {
|
|
183
|
+
return await this.connect();
|
|
184
|
+
} catch (e) {
|
|
185
|
+
this.active -= 1;
|
|
186
|
+
throw e;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// At max — wait for a release. The waiter's clock starts now;
|
|
190
|
+
// when release() resolves the waiter we emit the wait duration so
|
|
191
|
+
// operators can see backpressure on the pool.
|
|
192
|
+
var self = this;
|
|
193
|
+
var waitStartedAt = Date.now();
|
|
194
|
+
return new Promise(function (resolve, reject) {
|
|
195
|
+
self.waiters.push({
|
|
196
|
+
resolve: function (client) {
|
|
197
|
+
_emitMetric("externaldb.pool.acquire_wait", Date.now() - waitStartedAt,
|
|
198
|
+
{ backend: self.name });
|
|
199
|
+
resolve(client);
|
|
200
|
+
},
|
|
201
|
+
reject: reject,
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
release(client) {
|
|
207
|
+
this.active -= 1;
|
|
208
|
+
if (this.waiters.length > 0) {
|
|
209
|
+
var w = this.waiters.shift();
|
|
210
|
+
this.active += 1;
|
|
211
|
+
w.resolve(client);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
this.idle.push({ client: client, lastUsedAt: Date.now() });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async destroy(client) {
|
|
218
|
+
this.active -= 1;
|
|
219
|
+
try { await this.close(client); } catch (_e) { /* best effort */ }
|
|
220
|
+
if (this.waiters.length > 0) {
|
|
221
|
+
var w = this.waiters.shift();
|
|
222
|
+
this.acquire().then(w.resolve, w.reject);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
_reapIdle() {
|
|
227
|
+
var now = Date.now();
|
|
228
|
+
var keep = [];
|
|
229
|
+
var self = this;
|
|
230
|
+
this.idle.forEach(function (entry) {
|
|
231
|
+
if ((now - entry.lastUsedAt) >= self.config.idleTimeoutMs) {
|
|
232
|
+
Promise.resolve().then(function () { return self.close(entry.client); }).catch(function () {});
|
|
233
|
+
} else {
|
|
234
|
+
keep.push(entry);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
this.idle = keep;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async drain() {
|
|
241
|
+
if (this._reaper) { this._reaper.stop(); this._reaper = null; }
|
|
242
|
+
var idleClients = this.idle.map(function (e) { return e.client; });
|
|
243
|
+
this.idle = [];
|
|
244
|
+
var self = this;
|
|
245
|
+
await Promise.all(idleClients.map(function (c) {
|
|
246
|
+
return Promise.resolve().then(function () { return self.close(c); }).catch(function () {});
|
|
247
|
+
}));
|
|
248
|
+
this.waiters.forEach(function (w) { w.reject(_err("POOL_DRAINED", "pool is shutting down", true)); });
|
|
249
|
+
this.waiters = [];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
stats() {
|
|
253
|
+
return { active: this.active, idle: this.idle.length, waiters: this.waiters.length };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---- Init ----
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @primitive b.externalDb.init
|
|
261
|
+
* @signature b.externalDb.init(opts)
|
|
262
|
+
* @since 0.4.0
|
|
263
|
+
* @related b.externalDb.query, b.externalDb.shutdown, b.externalDb.adapters.connectAs
|
|
264
|
+
*
|
|
265
|
+
* Register one or more app-data backends. Each backend declares its
|
|
266
|
+
* `connect` / `query` driver hooks plus optional pooling, classification,
|
|
267
|
+
* residency, retry, and replica configuration. Throws synchronously on
|
|
268
|
+
* malformed input (missing hooks, unknown dialect, residency mismatch
|
|
269
|
+
* against `db.getDataResidency()`, dotted GUC names that fail
|
|
270
|
+
* identifier validation).
|
|
271
|
+
*
|
|
272
|
+
* Boot-time residency check: when `db.getDataResidency().region` is set,
|
|
273
|
+
* any backend serving `personal` (or `*`) data must carry a
|
|
274
|
+
* `residencyTag` in the allowed-region list — refused with
|
|
275
|
+
* `RESIDENCY_VIOLATION` when not.
|
|
276
|
+
*
|
|
277
|
+
* @opts
|
|
278
|
+
* backends: { [name]: BackendConfig }, // required; one or more named backends
|
|
279
|
+
* defaultBackend?: string, // pool used when no opts.backend / classification / role match (defaults to first)
|
|
280
|
+
* dbRoleBackends?: { [sqlRole]: backendName }, // request-time role → backend mapping for the dbRoleFor middleware
|
|
281
|
+
*
|
|
282
|
+
* // BackendConfig shape:
|
|
283
|
+
* // connect(): async () → driver client (required)
|
|
284
|
+
* // query(client, sql, p): async → { rows, rowCount } (required)
|
|
285
|
+
* // close(client): async → void (optional; default no-op)
|
|
286
|
+
* // ping(client): async → void (optional; default `SELECT 1`)
|
|
287
|
+
* // beginTx / commit / rollback(client): async → void (optional; default `BEGIN`/`COMMIT`/`ROLLBACK`)
|
|
288
|
+
* // dialect: "postgres" | "mysql" | "sqlite" | "mongodb" | "other" (default "postgres")
|
|
289
|
+
* // applicationName: string ≤ 63 bytes, no CR/LF/NUL (Postgres pg_stat_activity tag; default null)
|
|
290
|
+
* // pool: { min, max, idleTimeoutMs } (defaults: 1 / 10 / C.TIME.minutes(1))
|
|
291
|
+
* // classifications: string[] (defaults to ["*"])
|
|
292
|
+
* // residencyTag: "EU" | "US" | "unrestricted" | ... (defaults to "unrestricted")
|
|
293
|
+
* // retry, breaker: passthrough to b.retry / CircuitBreaker
|
|
294
|
+
* // replicas: [{ connect, query, weight?, residencyTag?, allowCrossBorder? }]
|
|
295
|
+
* // replicaFallbackToPrimary: boolean (default true)
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* var pg = require("pg");
|
|
299
|
+
* var pool = new pg.Pool({ connectionString: "postgres://app:pw@db.example.com/app" });
|
|
300
|
+
*
|
|
301
|
+
* b.externalDb.init({
|
|
302
|
+
* backends: {
|
|
303
|
+
* main: {
|
|
304
|
+
* dialect: "postgres",
|
|
305
|
+
* applicationName: "blamejs-app",
|
|
306
|
+
* connect: function () { return pool.connect(); },
|
|
307
|
+
* query: function (client, sql, params) { return client.query(sql, params); },
|
|
308
|
+
* close: function (client) { return client.release(); },
|
|
309
|
+
* classifications: ["personal", "operational"],
|
|
310
|
+
* residencyTag: "EU",
|
|
311
|
+
* pool: { min: 2, max: 20, idleTimeoutMs: 60000 },
|
|
312
|
+
* },
|
|
313
|
+
* },
|
|
314
|
+
* defaultBackend: "main",
|
|
315
|
+
* });
|
|
316
|
+
*/
|
|
317
|
+
function init(opts) {
|
|
318
|
+
if (initialized) return;
|
|
319
|
+
if (!opts || !opts.backends) throw new Error("externalDb.init({ backends }) is required");
|
|
320
|
+
|
|
321
|
+
backends = {};
|
|
322
|
+
dbRoleBackends = {};
|
|
323
|
+
for (var name in opts.backends) {
|
|
324
|
+
var cfg = opts.backends[name];
|
|
325
|
+
if (typeof cfg.connect !== "function") {
|
|
326
|
+
throw _err("INVALID_CONFIG", "backend '" + name + "' missing connect() function", true);
|
|
327
|
+
}
|
|
328
|
+
if (typeof cfg.query !== "function") {
|
|
329
|
+
throw _err("INVALID_CONFIG", "backend '" + name + "' missing query() function", true);
|
|
330
|
+
}
|
|
331
|
+
// dialect — informational marker so dialect-specific consumers
|
|
332
|
+
// (e.g. b.db.declareView) can fail-fast at apply time. Defaults to
|
|
333
|
+
// "postgres" because that's the dominant blamejs externalDb target;
|
|
334
|
+
// operators on SQLite/MySQL/etc. set this explicitly so downstream
|
|
335
|
+
// primitives surface NOT_SUPPORTED with a clear message instead of
|
|
336
|
+
// emitting Postgres-flavored DDL into the wrong dialect.
|
|
337
|
+
var dialect = (cfg.dialect || "postgres").toLowerCase();
|
|
338
|
+
if (["postgres", "mysql", "sqlite", "mongodb", "other"].indexOf(dialect) === -1) {
|
|
339
|
+
throw _err("INVALID_CONFIG",
|
|
340
|
+
"backend '" + name + "': dialect must be one of " +
|
|
341
|
+
"'postgres' | 'mysql' | 'sqlite' | 'mongodb' | 'other', got '" + dialect + "'", true);
|
|
342
|
+
}
|
|
343
|
+
// OWASP-3 — application_name normalization for Postgres backends.
|
|
344
|
+
// Always set on every fresh connection (not just connectAs branch)
|
|
345
|
+
// so pg_stat_activity / log_line_prefix / audit log surfaces show
|
|
346
|
+
// a stable identifier instead of falling back to the driver's
|
|
347
|
+
// bare process name. CR / LF / NUL refused at config-time —
|
|
348
|
+
// those characters terminate the SET statement early in some
|
|
349
|
+
// drivers and have no legitimate use in an application_name.
|
|
350
|
+
// OWASP-3 — application_name normalization for Postgres backends.
|
|
351
|
+
// Opt-in via `cfg.applicationName` to surface a stable identifier
|
|
352
|
+
// in pg_stat_activity / log_line_prefix / Postgres audit log
|
|
353
|
+
// surfaces. Default leaves application_name to the driver — issuing
|
|
354
|
+
// a SET on every fresh connection at framework default would
|
|
355
|
+
// double-count queries for operators counting per-pool query
|
|
356
|
+
// activity (and break test fakes that count tracker.query calls).
|
|
357
|
+
var applicationName = cfg.applicationName !== undefined ? cfg.applicationName : null;
|
|
358
|
+
if (applicationName !== null && (typeof applicationName !== "string" || applicationName.length === 0)) {
|
|
359
|
+
throw _err("INVALID_CONFIG",
|
|
360
|
+
"backend '" + name + "': applicationName must be a non-empty string", true);
|
|
361
|
+
}
|
|
362
|
+
if (applicationName !== null) {
|
|
363
|
+
// eslint-disable-next-line no-control-regex
|
|
364
|
+
if (/[\r\n\u0000]/.test(applicationName)) {
|
|
365
|
+
throw _err("INVALID_CONFIG",
|
|
366
|
+
"backend '" + name + "': applicationName must not contain CR, LF, or NUL characters", true);
|
|
367
|
+
}
|
|
368
|
+
if (applicationName.length > C.BYTES.bytes(63)) {
|
|
369
|
+
throw _err("INVALID_CONFIG",
|
|
370
|
+
"backend '" + name + "': applicationName exceeds Postgres 63-byte limit (got " +
|
|
371
|
+
applicationName.length + ")", true);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
var rawConnect = cfg.connect;
|
|
375
|
+
var rawQuery = cfg.query;
|
|
376
|
+
var connectFn = rawConnect;
|
|
377
|
+
if (dialect === "postgres" && applicationName !== null) {
|
|
378
|
+
// IIFE captures per-iteration rawConnect/rawQuery; without this
|
|
379
|
+
// the var-hoisted bindings are shared across the for-loop and
|
|
380
|
+
// every backend's connectFn ends up calling the LAST iteration's
|
|
381
|
+
// rawQuery (classic closure-in-loop bug).
|
|
382
|
+
connectFn = (function (cn, qn, appName) {
|
|
383
|
+
var quotedAppName = "'" + appName.replace(/'/g, "''") + "'";
|
|
384
|
+
return async function () {
|
|
385
|
+
var client = await cn();
|
|
386
|
+
try {
|
|
387
|
+
await qn(client, "SET application_name TO " + quotedAppName, []);
|
|
388
|
+
} catch (_e) {
|
|
389
|
+
// Best-effort. Real Postgres always supports SET
|
|
390
|
+
// application_name; a driver that refuses it is a shim
|
|
391
|
+
// (test fake / non-PG backend mislabeled "postgres") and
|
|
392
|
+
// there's nothing useful to surface — keep the connection
|
|
393
|
+
// and let the operator hit any real query failure
|
|
394
|
+
// immediately afterwards.
|
|
395
|
+
void _e;
|
|
396
|
+
}
|
|
397
|
+
return client;
|
|
398
|
+
};
|
|
399
|
+
})(rawConnect, rawQuery, applicationName);
|
|
400
|
+
}
|
|
401
|
+
var poolCfg = Object.assign({}, cfg, { connect: connectFn });
|
|
402
|
+
backends[name] = {
|
|
403
|
+
name: name,
|
|
404
|
+
dialect: dialect,
|
|
405
|
+
applicationName: applicationName,
|
|
406
|
+
pool: new Pool(name, poolCfg),
|
|
407
|
+
query: cfg.query,
|
|
408
|
+
ping: cfg.ping || null,
|
|
409
|
+
beginTx: cfg.beginTx || function (client) { return cfg.query(client, "BEGIN", []); },
|
|
410
|
+
commit: cfg.commit || function (client) { return cfg.query(client, "COMMIT", []); },
|
|
411
|
+
rollback: cfg.rollback || function (client) { return cfg.query(client, "ROLLBACK", []); },
|
|
412
|
+
classifications: Array.isArray(cfg.classifications) && cfg.classifications.length > 0
|
|
413
|
+
? cfg.classifications.slice()
|
|
414
|
+
: ["*"],
|
|
415
|
+
residencyTag: cfg.residencyTag || "unrestricted",
|
|
416
|
+
breaker: new retryHelper.CircuitBreaker("externalDb:" + name, cfg.breaker),
|
|
417
|
+
retryConfig: cfg.retry || null,
|
|
418
|
+
replicas: _buildReplicas(name, cfg),
|
|
419
|
+
replicaIdx: 0, // round-robin cursor
|
|
420
|
+
replicaFallbackToPrimary: cfg.replicaFallbackToPrimary !== false,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
defaultBackend = opts.defaultBackend || Object.keys(backends)[0];
|
|
425
|
+
|
|
426
|
+
// dbRoleBackends — request-time role → backend mapping. Each role name
|
|
427
|
+
// validates as a SQL identifier at init (matches the dbRoleFor
|
|
428
|
+
// middleware's runtime check) so a typo surfaces at boot rather than
|
|
429
|
+
// as a silent default-backend fallback at the first request.
|
|
430
|
+
if (opts.dbRoleBackends !== undefined && opts.dbRoleBackends !== null) {
|
|
431
|
+
if (typeof opts.dbRoleBackends !== "object" || Array.isArray(opts.dbRoleBackends)) {
|
|
432
|
+
throw _err("INVALID_CONFIG",
|
|
433
|
+
"dbRoleBackends must be an object map of role → backendName", true);
|
|
434
|
+
}
|
|
435
|
+
for (var role in opts.dbRoleBackends) {
|
|
436
|
+
if (!Object.prototype.hasOwnProperty.call(opts.dbRoleBackends, role)) continue;
|
|
437
|
+
try {
|
|
438
|
+
safeSql.validateIdentifier(role, { allowReserved: false });
|
|
439
|
+
} catch (e) {
|
|
440
|
+
throw _err("INVALID_CONFIG",
|
|
441
|
+
"dbRoleBackends: role '" + role + "' is not a valid SQL identifier: " +
|
|
442
|
+
((e && e.message) || String(e)), true);
|
|
443
|
+
}
|
|
444
|
+
var bn = opts.dbRoleBackends[role];
|
|
445
|
+
if (typeof bn !== "string" || bn.length === 0) {
|
|
446
|
+
throw _err("INVALID_CONFIG",
|
|
447
|
+
"dbRoleBackends['" + role + "']: backend name must be a non-empty string", true);
|
|
448
|
+
}
|
|
449
|
+
if (!Object.prototype.hasOwnProperty.call(backends, bn)) {
|
|
450
|
+
throw _err("INVALID_CONFIG",
|
|
451
|
+
"dbRoleBackends['" + role + "']: no backend named '" + bn + "' " +
|
|
452
|
+
"(declared backends: " + Object.keys(backends).join(", ") + ")", true);
|
|
453
|
+
}
|
|
454
|
+
dbRoleBackends[role] = bn;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
_validateResidency();
|
|
459
|
+
initialized = true;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function _validateResidency() {
|
|
463
|
+
var residency;
|
|
464
|
+
try { residency = db().getDataResidency(); } catch (_e) { residency = null; }
|
|
465
|
+
if (!residency || !residency.region) return;
|
|
466
|
+
|
|
467
|
+
var allowed = [residency.region].concat(residency.allowedStorageRegions || []);
|
|
468
|
+
for (var name in backends) {
|
|
469
|
+
var b = backends[name];
|
|
470
|
+
var serves = b.classifications.indexOf("*") !== -1 || b.classifications.indexOf("personal") !== -1;
|
|
471
|
+
if (!serves) continue;
|
|
472
|
+
if (allowed.indexOf(b.residencyTag) === -1) {
|
|
473
|
+
throw _err("RESIDENCY_VIOLATION",
|
|
474
|
+
"externalDb backend '" + name + "' serves 'personal' data with residencyTag '" +
|
|
475
|
+
b.residencyTag + "' but app's dataResidency.region is '" + residency.region + "'",
|
|
476
|
+
true);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ---- Backend selection ----
|
|
482
|
+
//
|
|
483
|
+
// Pick precedence:
|
|
484
|
+
// 1. opts.backend — explicit override always wins
|
|
485
|
+
// 2. opts.classification — first backend serving that class
|
|
486
|
+
// 3. ALS-bound dbRole + dbRoleBackends — request-time auto-pick
|
|
487
|
+
// 4. defaultBackend — final fallback
|
|
488
|
+
//
|
|
489
|
+
// The ALS path matches the dbRoleFor middleware shape: middleware sets
|
|
490
|
+
// the role; deep async reads pick up the matching backend without having
|
|
491
|
+
// to thread `req` through every call site.
|
|
492
|
+
|
|
493
|
+
function _pickBackend(opts) {
|
|
494
|
+
opts = opts || {};
|
|
495
|
+
if (opts.backend) {
|
|
496
|
+
var b = backends[opts.backend];
|
|
497
|
+
if (!b) throw _err("UNKNOWN_BACKEND", "no backend named '" + opts.backend + "'", true);
|
|
498
|
+
if (opts.classification && !_servesClassification(b, opts.classification)) {
|
|
499
|
+
throw _err("CLASSIFICATION_MISMATCH",
|
|
500
|
+
"backend '" + opts.backend + "' does not serve classification '" + opts.classification + "'", true);
|
|
501
|
+
}
|
|
502
|
+
return b;
|
|
503
|
+
}
|
|
504
|
+
var classification = opts.classification;
|
|
505
|
+
if (classification) {
|
|
506
|
+
for (var name in backends) {
|
|
507
|
+
if (_servesClassification(backends[name], classification)) return backends[name];
|
|
508
|
+
}
|
|
509
|
+
throw _err("NO_BACKEND_FOR_CLASSIFICATION",
|
|
510
|
+
"no backend serves classification '" + classification + "'", true);
|
|
511
|
+
}
|
|
512
|
+
var role = dbRoleContext.getRole();
|
|
513
|
+
if (role && Object.prototype.hasOwnProperty.call(dbRoleBackends, role)) {
|
|
514
|
+
return backends[dbRoleBackends[role]];
|
|
515
|
+
}
|
|
516
|
+
return backends[defaultBackend] || null;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function _servesClassification(b, cls) {
|
|
520
|
+
return b.classifications.indexOf("*") !== -1 || b.classifications.indexOf(cls) !== -1;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ---- Public API ----
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* @primitive b.externalDb.query
|
|
527
|
+
* @signature b.externalDb.query(sql, params, opts)
|
|
528
|
+
* @since 0.4.0
|
|
529
|
+
* @related b.externalDb.transaction, b.externalDb.read.query, b.externalDb.write.query
|
|
530
|
+
*
|
|
531
|
+
* Execute a single statement against the picked backend. Returns the
|
|
532
|
+
* driver-shaped `{ rows, rowCount }` from the backend's `query` hook.
|
|
533
|
+
* Wraps the call in `b.retry.withRetry` for transient driver errors
|
|
534
|
+
* and the per-backend circuit breaker; emits `system.externaldb.query`
|
|
535
|
+
* audit events plus duration / slow-query metrics; surfaces Postgres
|
|
536
|
+
* SQLSTATE 28000 / 28P01 / 42501 as `db.auth.failed` audit rows for
|
|
537
|
+
* SOC2 forensic walks.
|
|
538
|
+
*
|
|
539
|
+
* Backend selection precedence: `opts.backend` (explicit) →
|
|
540
|
+
* `opts.classification` (first backend serving the class) → ALS-bound
|
|
541
|
+
* dbRole + `dbRoleBackends` map (set by `b.middleware.dbRoleFor` or
|
|
542
|
+
* `b.externalDb.runAs`) → the configured `defaultBackend`.
|
|
543
|
+
*
|
|
544
|
+
* @opts
|
|
545
|
+
* backend?: string, // explicit backend name; bypasses classification + role pick
|
|
546
|
+
* classification?: string, // route to first backend whose classifications include this value
|
|
547
|
+
* includeSqlInAudit?: boolean, // emit SQL text in audit metadata (off by default — may carry literal PII)
|
|
548
|
+
*
|
|
549
|
+
* @example
|
|
550
|
+
* var res = await b.externalDb.query(
|
|
551
|
+
* "SELECT id, email FROM users WHERE tenant_id = $1",
|
|
552
|
+
* ["acme"],
|
|
553
|
+
* { classification: "personal" }
|
|
554
|
+
* );
|
|
555
|
+
* res.rowCount; // → 42
|
|
556
|
+
* res.rows[0]; // → { id: 1, email: "ada@example.com" }
|
|
557
|
+
*/
|
|
558
|
+
async function query(sql, params, opts) {
|
|
559
|
+
_requireInit();
|
|
560
|
+
opts = opts || {};
|
|
561
|
+
var b = _pickBackend(opts);
|
|
562
|
+
var role = dbRoleContext.getRole();
|
|
563
|
+
|
|
564
|
+
var t0 = Date.now();
|
|
565
|
+
try {
|
|
566
|
+
var result = await retryHelper.withRetry(function () {
|
|
567
|
+
return b.breaker.wrap(async function () {
|
|
568
|
+
var client = await b.pool.acquire();
|
|
569
|
+
try {
|
|
570
|
+
var res = await b.query(client, sql, params || []);
|
|
571
|
+
b.pool.release(client);
|
|
572
|
+
return res;
|
|
573
|
+
} catch (e) {
|
|
574
|
+
// Connection-level errors → destroy the client; query errors →
|
|
575
|
+
// release back to the pool. Heuristic: any error with a code
|
|
576
|
+
// looking like a network/connection issue → destroy.
|
|
577
|
+
if (e && (e.code === "ECONNRESET" || e.code === "ECONNREFUSED" ||
|
|
578
|
+
e.code === "ETIMEDOUT" || e.code === "ENOTFOUND" ||
|
|
579
|
+
e.code === "EPIPE")) {
|
|
580
|
+
await b.pool.destroy(client);
|
|
581
|
+
} else {
|
|
582
|
+
b.pool.release(client);
|
|
583
|
+
}
|
|
584
|
+
throw e;
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}, b.retryConfig);
|
|
588
|
+
|
|
589
|
+
var durationMs = Date.now() - t0;
|
|
590
|
+
_emit("system.externaldb.query", "success", {
|
|
591
|
+
backend: b.name,
|
|
592
|
+
role: role,
|
|
593
|
+
durationMs: durationMs,
|
|
594
|
+
classification: opts.classification || null,
|
|
595
|
+
rowCount: result && result.rowCount,
|
|
596
|
+
// SQL is NOT logged by default — may contain sensitive literal values
|
|
597
|
+
// even in parameterized queries. Operators who want SQL in audit
|
|
598
|
+
// metadata pass opts.includeSqlInAudit: true (then sealed via
|
|
599
|
+
// field-crypto on the audit row).
|
|
600
|
+
sql: opts.includeSqlInAudit ? sql : null,
|
|
601
|
+
});
|
|
602
|
+
_emitMetric("externaldb.query.success", 1,
|
|
603
|
+
{ backend: b.name, role: role || "(none)" });
|
|
604
|
+
_emitMetric("externaldb.query.duration_ms", durationMs,
|
|
605
|
+
{ backend: b.name, role: role || "(none)" });
|
|
606
|
+
_emitSlowQuery(b.name, role, durationMs, _classifyStatement(sql));
|
|
607
|
+
return result;
|
|
608
|
+
} catch (e) {
|
|
609
|
+
var failureMs = Date.now() - t0;
|
|
610
|
+
_emit("system.externaldb.query", "failure", {
|
|
611
|
+
backend: b.name,
|
|
612
|
+
role: role,
|
|
613
|
+
durationMs: failureMs,
|
|
614
|
+
classification: opts.classification || null,
|
|
615
|
+
errorCode: e.code || null,
|
|
616
|
+
}, (e && e.message) || String(e));
|
|
617
|
+
_emitMetric("externaldb.query.failure", 1,
|
|
618
|
+
{ backend: b.name, role: role || "(none)", errorCode: e.code || "(none)" });
|
|
619
|
+
_emitSlowQuery(b.name, role, failureMs, _classifyStatement(sql));
|
|
620
|
+
// Postgres signals authorization-denied as SQLSTATE 42501
|
|
621
|
+
// (insufficient_privilege). RLS-shaped writes that violate a
|
|
622
|
+
// policy and GRANT-denied SELECTs both surface this code. The
|
|
623
|
+
// operator's role-views recipe relies on this signal: a row of
|
|
624
|
+
// db.role.denied means a request-time role attempted something its
|
|
625
|
+
// grant or RLS policy forbids — the highest-signal compliance event
|
|
626
|
+
// the externalDb layer can emit.
|
|
627
|
+
if (e && e.code === "42501") {
|
|
628
|
+
_emitMetric("db.role.denied", 1,
|
|
629
|
+
{ backend: b.name, role: role || "(none)" });
|
|
630
|
+
}
|
|
631
|
+
// D-M2 — DB-auth audit visibility. Every 28000 / 28P01 / 42501
|
|
632
|
+
// surfaces an auditable db.auth.failed row tagged with the SQL
|
|
633
|
+
// identity and the statement class so SOC2 reviewers can
|
|
634
|
+
// reconstruct the denial timeline.
|
|
635
|
+
_emitAuthFailureAudit(b, role, sql, e);
|
|
636
|
+
throw e;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* @primitive b.externalDb.transaction
|
|
642
|
+
* @signature b.externalDb.transaction(fn, opts)
|
|
643
|
+
* @since 0.4.0
|
|
644
|
+
* @related b.externalDb.query, b.externalDb.write.query
|
|
645
|
+
*
|
|
646
|
+
* Run `fn(tx)` inside a transaction on the picked backend. Wraps the
|
|
647
|
+
* body in `BEGIN` / `COMMIT` / `ROLLBACK` via the backend's hooks;
|
|
648
|
+
* commits on resolve, rolls back on throw. Transient deadlock /
|
|
649
|
+
* serialization failures (Postgres SQLSTATE `40P01` / `40001`) retry
|
|
650
|
+
* automatically with a small jittered backoff (default 3 attempts;
|
|
651
|
+
* tune via `opts.deadlockRetries`).
|
|
652
|
+
*
|
|
653
|
+
* `tx.query(sql, params)` runs against the same client used by
|
|
654
|
+
* `BEGIN`, so RLS state set by `sessionGucs` (`SET LOCAL`) applies for
|
|
655
|
+
* the duration of the transaction and resets at COMMIT/ROLLBACK.
|
|
656
|
+
*
|
|
657
|
+
* @opts
|
|
658
|
+
* backend?: string, // explicit backend name
|
|
659
|
+
* classification?: string, // route by data class
|
|
660
|
+
* sessionGucs?: { [name]: string|number|boolean }, // SET LOCAL bindings (e.g. { "app.tenant_id": "acme" })
|
|
661
|
+
* statementTimeoutMs?: number, // SET LOCAL statement_timeout
|
|
662
|
+
* idleInTransactionTimeoutMs?: number, // SET LOCAL idle_in_transaction_session_timeout
|
|
663
|
+
* deadlockRetries?: number, // retries for 40P01 / 40001 (default 3)
|
|
664
|
+
*
|
|
665
|
+
* @example
|
|
666
|
+
* var summary = await b.externalDb.transaction(async function (tx) {
|
|
667
|
+
* await tx.query("INSERT INTO orders(id, total) VALUES ($1, $2)", ["o-1", 4200]);
|
|
668
|
+
* await tx.query("UPDATE inventory SET qty = qty - 1 WHERE sku = $1", ["sku-7"]);
|
|
669
|
+
* var res = await tx.query("SELECT count(*) AS n FROM orders WHERE id = $1", ["o-1"]);
|
|
670
|
+
* return res.rows[0];
|
|
671
|
+
* }, {
|
|
672
|
+
* classification: "operational",
|
|
673
|
+
* sessionGucs: { "app.tenant_id": "acme" },
|
|
674
|
+
* statementTimeoutMs: 5000,
|
|
675
|
+
* });
|
|
676
|
+
* summary.n; // → 1
|
|
677
|
+
*/
|
|
678
|
+
async function transaction(fn, opts) {
|
|
679
|
+
_requireInit();
|
|
680
|
+
if (typeof fn !== "function") throw _err("INVALID_FN", "transaction requires a function", true);
|
|
681
|
+
opts = opts || {};
|
|
682
|
+
var b = _pickBackend(opts);
|
|
683
|
+
var role = dbRoleContext.getRole();
|
|
684
|
+
|
|
685
|
+
// sessionGucs — per-transaction `SET LOCAL "name" = value` plumbing.
|
|
686
|
+
// Each name validates as a SQL identifier (Postgres GUC names follow
|
|
687
|
+
// the same NAMEDATALEN-shaped rules; dotted GUCs like 'app.tenant_id'
|
|
688
|
+
// validate per-segment via quoteQualified). Values are emitted as SQL
|
|
689
|
+
// string literals (single-quote escaped) for strings, raw for finite
|
|
690
|
+
// numbers. SET LOCAL ties the binding to the surrounding transaction
|
|
691
|
+
// so the tenant_id used by RLS policies resets cleanly at
|
|
692
|
+
// COMMIT/ROLLBACK without caller cleanup.
|
|
693
|
+
var prebuiltGucs = _buildSessionGucsStatements(opts.sessionGucs);
|
|
694
|
+
|
|
695
|
+
var t0 = Date.now();
|
|
696
|
+
// D-H4 — per-statement timeout. SET LOCAL statement_timeout binds
|
|
697
|
+
// the query-cancel ceiling to this transaction; D-M7 wires
|
|
698
|
+
// idle_in_transaction_session_timeout from the same opt. Both
|
|
699
|
+
// emit at SET LOCAL scope so the next pool checkout starts clean.
|
|
700
|
+
var stmtTimeoutMs = opts.statementTimeoutMs;
|
|
701
|
+
var idleTimeoutMs = opts.idleInTransactionTimeoutMs;
|
|
702
|
+
// D-M8 — deadlock-retry policy. 40P01 (deadlock_detected) and 40001
|
|
703
|
+
// (serialization_failure) are transient — retry with capped attempts
|
|
704
|
+
// and a small jittered backoff. Operators tune retries via opts.deadlockRetries (default 3).
|
|
705
|
+
// numeric-bounds doesn't have a non-negative-int helper; use a
|
|
706
|
+
// direct check with allow marker (zero is permitted to disable
|
|
707
|
+
// retries entirely).
|
|
708
|
+
if (opts.deadlockRetries !== undefined) {
|
|
709
|
+
if (typeof opts.deadlockRetries !== "number" || !isFinite(opts.deadlockRetries) ||
|
|
710
|
+
opts.deadlockRetries < 0 || (opts.deadlockRetries | 0) !== opts.deadlockRetries) {
|
|
711
|
+
throw _err("INVALID_OPT",
|
|
712
|
+
"transaction: opts.deadlockRetries must be a non-negative integer");
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
var maxRetries = (typeof opts.deadlockRetries === "number")
|
|
716
|
+
? Math.floor(opts.deadlockRetries) : 3; // allow:numeric-opt-Infinity
|
|
717
|
+
return await b.breaker.wrap(async function () {
|
|
718
|
+
var client = await b.pool.acquire();
|
|
719
|
+
var txClient = {
|
|
720
|
+
query: function (sql, params) { return b.query(client, sql, params || []); },
|
|
721
|
+
};
|
|
722
|
+
var committed = false;
|
|
723
|
+
var attempt = 0;
|
|
724
|
+
try {
|
|
725
|
+
for (;;) {
|
|
726
|
+
attempt += 1;
|
|
727
|
+
committed = false;
|
|
728
|
+
try {
|
|
729
|
+
await b.beginTx(client);
|
|
730
|
+
if (typeof stmtTimeoutMs === "number" && isFinite(stmtTimeoutMs) && stmtTimeoutMs > 0) {
|
|
731
|
+
await b.query(client, "SET LOCAL statement_timeout = " + Math.floor(stmtTimeoutMs), []);
|
|
732
|
+
}
|
|
733
|
+
if (typeof idleTimeoutMs === "number" && isFinite(idleTimeoutMs) && idleTimeoutMs > 0) {
|
|
734
|
+
await b.query(client, "SET LOCAL idle_in_transaction_session_timeout = " + Math.floor(idleTimeoutMs), []);
|
|
735
|
+
}
|
|
736
|
+
for (var gi = 0; gi < prebuiltGucs.length; gi++) {
|
|
737
|
+
await b.query(client, prebuiltGucs[gi], []);
|
|
738
|
+
}
|
|
739
|
+
var result = await fn(txClient);
|
|
740
|
+
await b.commit(client);
|
|
741
|
+
committed = true;
|
|
742
|
+
var durationMs = Date.now() - t0;
|
|
743
|
+
_emit("system.externaldb.transaction", "success", {
|
|
744
|
+
backend: b.name, role: role, durationMs: durationMs,
|
|
745
|
+
classification: opts.classification || null,
|
|
746
|
+
});
|
|
747
|
+
_emitMetric("externaldb.transaction.success", 1,
|
|
748
|
+
{ backend: b.name, role: role || "(none)" });
|
|
749
|
+
_emitMetric("externaldb.transaction.duration_ms", durationMs,
|
|
750
|
+
{ backend: b.name, role: role || "(none)" });
|
|
751
|
+
return result;
|
|
752
|
+
} catch (txErr) {
|
|
753
|
+
try { if (!committed) await b.rollback(client); } catch (_e) { /* best-effort */ }
|
|
754
|
+
var isTransient = txErr && (txErr.code === "40P01" || txErr.code === "40001");
|
|
755
|
+
if (isTransient && attempt <= maxRetries) {
|
|
756
|
+
_emitMetric("externaldb.transaction.retry", 1,
|
|
757
|
+
{ backend: b.name, code: txErr.code, attempt: String(attempt) });
|
|
758
|
+
var jitter = bCrypto.randomInt(0, 6); // allow:raw-byte-literal — 0-5ms jitter
|
|
759
|
+
await safeAsync.sleep(attempt * 5 + jitter); // allow:raw-time-literal — sub-second backoff
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
var failureMs = Date.now() - t0;
|
|
763
|
+
_emit("system.externaldb.transaction", "failure", {
|
|
764
|
+
backend: b.name, role: role, durationMs: failureMs,
|
|
765
|
+
classification: opts.classification || null,
|
|
766
|
+
errorCode: txErr.code || null,
|
|
767
|
+
}, (txErr && txErr.message) || String(txErr));
|
|
768
|
+
_emitMetric("externaldb.transaction.failure", 1,
|
|
769
|
+
{ backend: b.name, role: role || "(none)", errorCode: txErr.code || "(none)" });
|
|
770
|
+
if (txErr && txErr.code === "42501") {
|
|
771
|
+
_emitMetric("db.role.denied", 1,
|
|
772
|
+
{ backend: b.name, role: role || "(none)" });
|
|
773
|
+
}
|
|
774
|
+
// D-M2 — DB-auth audit visibility on transaction-shaped denials.
|
|
775
|
+
// Statement class always reads as "TX" since the failure
|
|
776
|
+
// surface inside a transaction body could be any statement;
|
|
777
|
+
// operators correlate via the transaction's audit row.
|
|
778
|
+
_emitAuthFailureAudit(b, role, "BEGIN", txErr);
|
|
779
|
+
throw txErr;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
} finally {
|
|
783
|
+
b.pool.release(client);
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* @primitive b.externalDb.healthCheck
|
|
790
|
+
* @signature b.externalDb.healthCheck(backendName)
|
|
791
|
+
* @since 0.4.0
|
|
792
|
+
* @related b.externalDb.listBackends, b.externalDb.shutdown
|
|
793
|
+
*
|
|
794
|
+
* Ping a backend by acquiring a client and running its `ping` hook (or
|
|
795
|
+
* `SELECT 1` when none is supplied). Returns `{ ok, breakerState, pool }`
|
|
796
|
+
* for a single backend, or a `{ [name]: result }` map when called with
|
|
797
|
+
* no argument. Connection-shape errors destroy the client; the breaker
|
|
798
|
+
* state is reflected in the returned record so health endpoints can
|
|
799
|
+
* surface circuit-open conditions.
|
|
800
|
+
*
|
|
801
|
+
* @example
|
|
802
|
+
* var all = await b.externalDb.healthCheck();
|
|
803
|
+
* all.main.ok; // → true
|
|
804
|
+
* all.main.breakerState; // → "closed"
|
|
805
|
+
* all.main.pool; // → { idle: 1, active: 0, waiters: 0 }
|
|
806
|
+
*
|
|
807
|
+
* var one = await b.externalDb.healthCheck("main");
|
|
808
|
+
* one.ok; // → true
|
|
809
|
+
*/
|
|
810
|
+
async function healthCheck(backendName) {
|
|
811
|
+
_requireInit();
|
|
812
|
+
if (backendName) {
|
|
813
|
+
return _pingBackend(backends[backendName]);
|
|
814
|
+
}
|
|
815
|
+
var out = {};
|
|
816
|
+
for (var name in backends) {
|
|
817
|
+
out[name] = await _pingBackend(backends[name]);
|
|
818
|
+
}
|
|
819
|
+
return out;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
async function _pingBackend(b) {
|
|
823
|
+
if (!b) return { ok: false, error: "unknown backend" };
|
|
824
|
+
try {
|
|
825
|
+
var client = await b.pool.acquire();
|
|
826
|
+
try {
|
|
827
|
+
if (b.ping) await b.ping(client);
|
|
828
|
+
else await b.query(client, "SELECT 1", []);
|
|
829
|
+
b.pool.release(client);
|
|
830
|
+
return { ok: true, breakerState: b.breaker.getState(), pool: b.pool.stats() };
|
|
831
|
+
} catch (e) {
|
|
832
|
+
await b.pool.destroy(client);
|
|
833
|
+
return { ok: false, error: e.message, breakerState: b.breaker.getState() };
|
|
834
|
+
}
|
|
835
|
+
} catch (e) {
|
|
836
|
+
return { ok: false, error: e.message, breakerState: b.breaker.getState() };
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* @primitive b.externalDb.listBackends
|
|
842
|
+
* @signature b.externalDb.listBackends()
|
|
843
|
+
* @since 0.4.0
|
|
844
|
+
* @related b.externalDb.healthCheck, b.externalDb.init
|
|
845
|
+
*
|
|
846
|
+
* Snapshot every registered backend's name, dialect, classifications,
|
|
847
|
+
* residency tag, breaker state, and live pool stats. Returns `[]` when
|
|
848
|
+
* `init()` has not run. Cheap — does not open any new connections.
|
|
849
|
+
*
|
|
850
|
+
* @example
|
|
851
|
+
* var rows = b.externalDb.listBackends();
|
|
852
|
+
* rows[0].name; // → "main"
|
|
853
|
+
* rows[0].dialect; // → "postgres"
|
|
854
|
+
* rows[0].classifications; // → ["personal", "operational"]
|
|
855
|
+
* rows[0].residencyTag; // → "EU"
|
|
856
|
+
* rows[0].breakerState; // → "closed"
|
|
857
|
+
* rows[0].pool; // → { idle: 2, active: 0, waiters: 0 }
|
|
858
|
+
*/
|
|
859
|
+
function listBackends() {
|
|
860
|
+
if (!initialized) return [];
|
|
861
|
+
return Object.keys(backends).map(function (name) {
|
|
862
|
+
var b = backends[name];
|
|
863
|
+
return {
|
|
864
|
+
name: name,
|
|
865
|
+
dialect: b.dialect,
|
|
866
|
+
classifications: b.classifications.slice(),
|
|
867
|
+
residencyTag: b.residencyTag,
|
|
868
|
+
breakerState: b.breaker.getState(),
|
|
869
|
+
pool: b.pool.stats(),
|
|
870
|
+
};
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* @primitive b.externalDb.shutdown
|
|
876
|
+
* @signature b.externalDb.shutdown()
|
|
877
|
+
* @since 0.4.0
|
|
878
|
+
* @related b.externalDb.init, b.externalDb.healthCheck
|
|
879
|
+
*
|
|
880
|
+
* Drain every backend pool (and replica pool), close idle clients,
|
|
881
|
+
* then clear all registry state so a subsequent `init()` starts from
|
|
882
|
+
* scratch. Idempotent — calling before `init()` is a no-op. Wire to
|
|
883
|
+
* `b.appShutdown` so process exit waits for in-flight queries to
|
|
884
|
+
* release their clients.
|
|
885
|
+
*
|
|
886
|
+
* @example
|
|
887
|
+
* process.on("SIGTERM", async function () {
|
|
888
|
+
* await b.externalDb.shutdown();
|
|
889
|
+
* process.exit(0);
|
|
890
|
+
* });
|
|
891
|
+
*/
|
|
892
|
+
async function shutdown() {
|
|
893
|
+
if (!initialized) return;
|
|
894
|
+
for (var name in backends) {
|
|
895
|
+
try { await backends[name].pool.drain(); } catch (_e) { /* best effort */ }
|
|
896
|
+
var bk = backends[name];
|
|
897
|
+
if (bk && bk.replicas) {
|
|
898
|
+
for (var i = 0; i < bk.replicas.length; i++) {
|
|
899
|
+
try { await bk.replicas[i].pool.drain(); } catch (_e) { /* best effort */ }
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
backends = {};
|
|
904
|
+
defaultBackend = null;
|
|
905
|
+
initialized = false;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// Build the SET LOCAL statements for a transaction's sessionGucs map.
|
|
909
|
+
// Identifier-validates each GUC name (per dot-segment so dotted names
|
|
910
|
+
// like 'app.tenant_id' work), quotes them with the Postgres dialect,
|
|
911
|
+
// and renders the value as either a SQL string literal (single-quoted,
|
|
912
|
+
// embedded quotes doubled) or a numeric literal for finite numbers.
|
|
913
|
+
// Bad shapes throw at the call site rather than as a confused Postgres
|
|
914
|
+
// error mid-transaction.
|
|
915
|
+
function _buildSessionGucsStatements(sessionGucs) {
|
|
916
|
+
if (sessionGucs === undefined || sessionGucs === null) return [];
|
|
917
|
+
if (typeof sessionGucs !== "object" || Array.isArray(sessionGucs)) {
|
|
918
|
+
throw _err("INVALID_SESSION_GUCS",
|
|
919
|
+
"sessionGucs must be an object map of name → value", true);
|
|
920
|
+
}
|
|
921
|
+
var out = [];
|
|
922
|
+
for (var name in sessionGucs) {
|
|
923
|
+
if (!Object.prototype.hasOwnProperty.call(sessionGucs, name)) continue;
|
|
924
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
925
|
+
throw _err("INVALID_SESSION_GUCS",
|
|
926
|
+
"sessionGucs: GUC name must be a non-empty string", true);
|
|
927
|
+
}
|
|
928
|
+
// Validate per-segment so dotted GUCs (Postgres custom GUC class.
|
|
929
|
+
// setting form) pass. quoteQualified handles both the validation
|
|
930
|
+
// and the dot-quoted rendering.
|
|
931
|
+
var qName;
|
|
932
|
+
try {
|
|
933
|
+
qName = safeSql.quoteQualified(name, "postgres");
|
|
934
|
+
} catch (e) {
|
|
935
|
+
throw _err("INVALID_SESSION_GUCS",
|
|
936
|
+
"sessionGucs: name '" + name + "' is not a valid identifier: " +
|
|
937
|
+
((e && e.message) || String(e)), true);
|
|
938
|
+
}
|
|
939
|
+
var value = sessionGucs[name];
|
|
940
|
+
var literal;
|
|
941
|
+
if (typeof value === "number" && isFinite(value)) {
|
|
942
|
+
literal = String(value);
|
|
943
|
+
} else if (typeof value === "boolean") {
|
|
944
|
+
// Postgres SET accepts on/off/true/false — render true/false.
|
|
945
|
+
literal = value ? "true" : "false";
|
|
946
|
+
} else if (typeof value === "string") {
|
|
947
|
+
// Cap the value length so an operator-controlled tenant_id of
|
|
948
|
+
// 100 KB doesn't hit Postgres' SET LOCAL parser with payload
|
|
949
|
+
// that bloats query logs and consumes max_stack_depth. The cap
|
|
950
|
+
// is generous for legitimate tenant identifiers but rejects
|
|
951
|
+
// amplification.
|
|
952
|
+
if (value.length > C.BYTES.kib(4)) {
|
|
953
|
+
throw _err("INVALID_SESSION_GUCS",
|
|
954
|
+
"sessionGucs['" + name + "']: value exceeds 4 KiB cap (got " +
|
|
955
|
+
value.length + " chars)", true);
|
|
956
|
+
}
|
|
957
|
+
literal = "'" + value.replace(/'/g, "''") + "'";
|
|
958
|
+
} else if (value === null || value === undefined) {
|
|
959
|
+
throw _err("INVALID_SESSION_GUCS",
|
|
960
|
+
"sessionGucs['" + name + "']: value must be a string, finite number, or boolean (got " +
|
|
961
|
+
(value === null ? "null" : "undefined") + ")", true);
|
|
962
|
+
} else {
|
|
963
|
+
throw _err("INVALID_SESSION_GUCS",
|
|
964
|
+
"sessionGucs['" + name + "']: value must be a string, finite number, or boolean (got " +
|
|
965
|
+
typeof value + ")", true);
|
|
966
|
+
}
|
|
967
|
+
out.push("SET LOCAL " + qName + " = " + literal);
|
|
968
|
+
}
|
|
969
|
+
return out;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Fire-and-forget audit emission. We CANNOT await this in cluster mode:
|
|
973
|
+
// audit storage routes back through external-db when cluster mode is
|
|
974
|
+
// active, so awaiting would create a recursive dependency (every audit
|
|
975
|
+
// row insert triggers an external-db query which would await another
|
|
976
|
+
// audit row insert). Tests that need audit-row durability before reading
|
|
977
|
+
// audit_log should flush microtasks explicitly.
|
|
978
|
+
function _emit(action, outcome, metadata, reason) {
|
|
979
|
+
audit().safeEmit({ action: action, outcome: outcome, reason: reason, metadata: metadata });
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function _requireInit() {
|
|
983
|
+
if (!initialized) throw _err("NOT_INITIALIZED", "externalDb.init() must be called first", true);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// ---- Read-replica routing ----
|
|
987
|
+
//
|
|
988
|
+
// Operators with a primary + replicas declare replicas alongside the
|
|
989
|
+
// primary backend config:
|
|
990
|
+
//
|
|
991
|
+
// externalDb.init({
|
|
992
|
+
// backends: {
|
|
993
|
+
// main: {
|
|
994
|
+
// connect, query, // primary
|
|
995
|
+
// replicas: [
|
|
996
|
+
// { connect: replica1, query, weight: 1 },
|
|
997
|
+
// { connect: replica2, query, weight: 2 },
|
|
998
|
+
// ],
|
|
999
|
+
// replicaFallbackToPrimary: true, // default; on all-replicas-unhealthy,
|
|
1000
|
+
// // read.query falls back to primary
|
|
1001
|
+
// },
|
|
1002
|
+
// },
|
|
1003
|
+
// });
|
|
1004
|
+
//
|
|
1005
|
+
// await externalDb.read.query("SELECT * FROM users"); // → replica
|
|
1006
|
+
// await externalDb.write.query("INSERT INTO users ..."); // → primary
|
|
1007
|
+
// await externalDb.query("..."); // → primary (legacy, unchanged)
|
|
1008
|
+
//
|
|
1009
|
+
// Load balancing: weighted round-robin (default weight 1). Weights
|
|
1010
|
+
// expand into a static plan at init — a [w1, w2, w3] vector becomes a
|
|
1011
|
+
// pre-built index sequence, then read.query() advances replicaIdx.
|
|
1012
|
+
//
|
|
1013
|
+
// Health: each replica tracks `lastFailureAt`. After UNHEALTHY_COOLDOWN_MS
|
|
1014
|
+
// since the last failure, the replica re-enters the rotation. Operators
|
|
1015
|
+
// observing all-replicas-down see read.query() fall back to primary
|
|
1016
|
+
// (overridable via replicaFallbackToPrimary: false).
|
|
1017
|
+
|
|
1018
|
+
var REPLICA_UNHEALTHY_COOLDOWN_MS = C.TIME.seconds(30);
|
|
1019
|
+
|
|
1020
|
+
// F-CBT-2 — replica residency-tag compatibility.
|
|
1021
|
+
//
|
|
1022
|
+
// A primary tagged "EU" replicating to a "US" replica is a GDPR
|
|
1023
|
+
// Article 46 cross-border transfer; without an explicit operator
|
|
1024
|
+
// opt-in the framework refuses init under gdpr / dpdp / pipl-cn /
|
|
1025
|
+
// uk-gdpr postures. Operator suppresses the gate per replica via
|
|
1026
|
+
// allowCrossBorder: true (which the framework records in the audit
|
|
1027
|
+
// chain so a compliance reviewer sees the conscious decision).
|
|
1028
|
+
//
|
|
1029
|
+
// Compatible-residency rules:
|
|
1030
|
+
// - Identical tags (EU↔EU, US↔US): always compatible.
|
|
1031
|
+
// - "unrestricted" tag on either side: compatible (operator
|
|
1032
|
+
// declared no constraint).
|
|
1033
|
+
// - Different tags: compatible only when allowCrossBorder is true.
|
|
1034
|
+
var CROSS_BORDER_REGULATED_POSTURES = Object.freeze([
|
|
1035
|
+
"gdpr", "uk-gdpr", "dpdp", "pipl-cn", "lgpd-br", "appi-jp", "pdpa-sg",
|
|
1036
|
+
]);
|
|
1037
|
+
|
|
1038
|
+
function _residencyCompatible(primaryTag, replicaTag) {
|
|
1039
|
+
if (!primaryTag || !replicaTag) return true;
|
|
1040
|
+
if (primaryTag === replicaTag) return true; // allow:raw-hash-compare — residency tag string, not a secret hash
|
|
1041
|
+
if (primaryTag === "unrestricted" || replicaTag === "unrestricted") return true;
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function _activePosture() {
|
|
1046
|
+
try {
|
|
1047
|
+
var compliance = require("./compliance"); // allow:inline-require — defensive against optional load
|
|
1048
|
+
return compliance.current();
|
|
1049
|
+
} catch (_e) { return null; }
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function _buildReplicas(backendName, cfg) {
|
|
1053
|
+
if (!cfg.replicas) return null;
|
|
1054
|
+
if (!Array.isArray(cfg.replicas) || cfg.replicas.length === 0) {
|
|
1055
|
+
throw _err("INVALID_CONFIG",
|
|
1056
|
+
"backend '" + backendName + "': replicas must be a non-empty array", true);
|
|
1057
|
+
}
|
|
1058
|
+
var primaryTag = cfg.residencyTag || "unrestricted";
|
|
1059
|
+
var posture = _activePosture();
|
|
1060
|
+
var out = [];
|
|
1061
|
+
for (var i = 0; i < cfg.replicas.length; i++) {
|
|
1062
|
+
var r = cfg.replicas[i];
|
|
1063
|
+
if (!r || typeof r.connect !== "function") {
|
|
1064
|
+
throw _err("INVALID_CONFIG",
|
|
1065
|
+
"backend '" + backendName + "': replicas[" + i + "].connect must be a function", true);
|
|
1066
|
+
}
|
|
1067
|
+
if (typeof r.query !== "function") {
|
|
1068
|
+
throw _err("INVALID_CONFIG",
|
|
1069
|
+
"backend '" + backendName + "': replicas[" + i + "].query must be a function", true);
|
|
1070
|
+
}
|
|
1071
|
+
var weight = r.weight !== undefined ? r.weight : 1;
|
|
1072
|
+
if (typeof weight !== "number" || !isFinite(weight) || weight <= 0 ||
|
|
1073
|
+
Math.floor(weight) !== weight) {
|
|
1074
|
+
throw _err("INVALID_CONFIG",
|
|
1075
|
+
"backend '" + backendName + "': replicas[" + i + "].weight must be a positive integer", true);
|
|
1076
|
+
}
|
|
1077
|
+
var replicaTag = r.residencyTag || "unrestricted";
|
|
1078
|
+
var allowCrossBorder = r.allowCrossBorder === true;
|
|
1079
|
+
if (!_residencyCompatible(primaryTag, replicaTag) && !allowCrossBorder) {
|
|
1080
|
+
var underPosture = posture && CROSS_BORDER_REGULATED_POSTURES.indexOf(posture) !== -1;
|
|
1081
|
+
throw _err("RESIDENCY_MISMATCH",
|
|
1082
|
+
"backend '" + backendName + "': replica[" + i +
|
|
1083
|
+
"] residencyTag '" + replicaTag +
|
|
1084
|
+
"' is not compatible with primary residencyTag '" + primaryTag +
|
|
1085
|
+
"'" + (underPosture ? " under '" + posture + "' posture" : "") +
|
|
1086
|
+
". This is a cross-border data transfer (GDPR Art 46 / DPDP / PIPL " +
|
|
1087
|
+
"category). Pass allowCrossBorder: true on the replica config with a " +
|
|
1088
|
+
"documented legal basis (SCCs / BCRs / adequacy decision) to suppress.", true);
|
|
1089
|
+
}
|
|
1090
|
+
if (!_residencyCompatible(primaryTag, replicaTag) && allowCrossBorder) {
|
|
1091
|
+
_emit("externalDb.replica.cross_border_allowed", "warning",
|
|
1092
|
+
{ backend: backendName, replicaIndex: i,
|
|
1093
|
+
primaryTag: primaryTag, replicaTag: replicaTag,
|
|
1094
|
+
legalBasis: r.legalBasis || null,
|
|
1095
|
+
posture: posture || null });
|
|
1096
|
+
}
|
|
1097
|
+
out.push({
|
|
1098
|
+
index: i,
|
|
1099
|
+
pool: new Pool(backendName + ":replica:" + i, r),
|
|
1100
|
+
query: r.query,
|
|
1101
|
+
weight: weight,
|
|
1102
|
+
residencyTag: replicaTag,
|
|
1103
|
+
allowCrossBorder: allowCrossBorder,
|
|
1104
|
+
lastFailureAt: 0,
|
|
1105
|
+
consecutiveFailures: 0,
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
return out;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function _pickReplica(b) {
|
|
1112
|
+
if (!b.replicas || b.replicas.length === 0) return null;
|
|
1113
|
+
var now = Date.now();
|
|
1114
|
+
// Build a healthy candidate set.
|
|
1115
|
+
var healthy = [];
|
|
1116
|
+
for (var i = 0; i < b.replicas.length; i++) {
|
|
1117
|
+
var r = b.replicas[i];
|
|
1118
|
+
if (now - r.lastFailureAt >= REPLICA_UNHEALTHY_COOLDOWN_MS) healthy.push(r);
|
|
1119
|
+
}
|
|
1120
|
+
if (healthy.length === 0) return null;
|
|
1121
|
+
// Weighted round-robin: walk by weight, advancing replicaIdx by 1 each
|
|
1122
|
+
// call and modding by total weight. Each replica's "slot" in the
|
|
1123
|
+
// sequence repeats `weight` times.
|
|
1124
|
+
var totalWeight = 0;
|
|
1125
|
+
for (var w = 0; w < healthy.length; w++) totalWeight += healthy[w].weight;
|
|
1126
|
+
var cursor = (b.replicaIdx++) % totalWeight;
|
|
1127
|
+
var acc = 0;
|
|
1128
|
+
for (var c = 0; c < healthy.length; c++) {
|
|
1129
|
+
acc += healthy[c].weight;
|
|
1130
|
+
if (cursor < acc) return healthy[c];
|
|
1131
|
+
}
|
|
1132
|
+
return healthy[0]; // unreachable; defensive
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
async function _readQuery(sql, params, opts) {
|
|
1136
|
+
_requireInit();
|
|
1137
|
+
opts = opts || {};
|
|
1138
|
+
var b = _pickBackend(opts);
|
|
1139
|
+
if (!b.replicas || b.replicas.length === 0) {
|
|
1140
|
+
// No replicas configured — read.query() returns primary.
|
|
1141
|
+
return query(sql, params, opts);
|
|
1142
|
+
}
|
|
1143
|
+
var replica = _pickReplica(b);
|
|
1144
|
+
if (!replica) {
|
|
1145
|
+
if (b.replicaFallbackToPrimary) return query(sql, params, opts);
|
|
1146
|
+
throw _err("ALL_REPLICAS_UNHEALTHY",
|
|
1147
|
+
"backend '" + b.name + "': all replicas unhealthy and fallback disabled", true);
|
|
1148
|
+
}
|
|
1149
|
+
var role = dbRoleContext.getRole();
|
|
1150
|
+
var t0 = Date.now();
|
|
1151
|
+
try {
|
|
1152
|
+
var client = await replica.pool.acquire();
|
|
1153
|
+
try {
|
|
1154
|
+
var res = await replica.query(client, sql, params || []);
|
|
1155
|
+
replica.pool.release(client);
|
|
1156
|
+
replica.consecutiveFailures = 0;
|
|
1157
|
+
var durationMs = Date.now() - t0;
|
|
1158
|
+
_emit("system.externaldb.read", "success", {
|
|
1159
|
+
backend: b.name,
|
|
1160
|
+
role: role,
|
|
1161
|
+
replicaIdx: replica.index,
|
|
1162
|
+
durationMs: durationMs,
|
|
1163
|
+
rowCount: res && res.rowCount,
|
|
1164
|
+
});
|
|
1165
|
+
_emitMetric("externaldb.read.success", 1,
|
|
1166
|
+
{ backend: b.name, role: role || "(none)", replicaIdx: replica.index });
|
|
1167
|
+
_emitMetric("externaldb.read.duration_ms", durationMs,
|
|
1168
|
+
{ backend: b.name, role: role || "(none)", replicaIdx: replica.index });
|
|
1169
|
+
return res;
|
|
1170
|
+
} catch (e) {
|
|
1171
|
+
// Connection-shape errors mark unhealthy + destroy.
|
|
1172
|
+
if (e && (e.code === "ECONNRESET" || e.code === "ECONNREFUSED" ||
|
|
1173
|
+
e.code === "ETIMEDOUT" || e.code === "ENOTFOUND" ||
|
|
1174
|
+
e.code === "EPIPE")) {
|
|
1175
|
+
await replica.pool.destroy(client);
|
|
1176
|
+
replica.lastFailureAt = Date.now();
|
|
1177
|
+
replica.consecutiveFailures += 1;
|
|
1178
|
+
} else {
|
|
1179
|
+
replica.pool.release(client);
|
|
1180
|
+
}
|
|
1181
|
+
throw e;
|
|
1182
|
+
}
|
|
1183
|
+
} catch (e) {
|
|
1184
|
+
_emit("system.externaldb.read", "failure", {
|
|
1185
|
+
backend: b.name,
|
|
1186
|
+
role: role,
|
|
1187
|
+
replicaIdx: replica.index,
|
|
1188
|
+
durationMs: Date.now() - t0,
|
|
1189
|
+
errorCode: e.code || null,
|
|
1190
|
+
}, (e && e.message) || String(e));
|
|
1191
|
+
_emitMetric("externaldb.read.failure", 1,
|
|
1192
|
+
{ backend: b.name, role: role || "(none)", errorCode: e.code || "(none)" });
|
|
1193
|
+
if (e && e.code === "42501") {
|
|
1194
|
+
_emitMetric("db.role.denied", 1,
|
|
1195
|
+
{ backend: b.name, role: role || "(none)" });
|
|
1196
|
+
}
|
|
1197
|
+
// D-M2 — DB-auth audit visibility for read-replica denials too.
|
|
1198
|
+
_emitAuthFailureAudit(b, role, sql, e);
|
|
1199
|
+
// Fallback to primary on a failed replica read when allowed.
|
|
1200
|
+
if (b.replicaFallbackToPrimary) {
|
|
1201
|
+
return query(sql, params, opts);
|
|
1202
|
+
}
|
|
1203
|
+
throw e;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* @primitive b.externalDb.read.query
|
|
1209
|
+
* @signature b.externalDb.read.query(sql, params, opts)
|
|
1210
|
+
* @since 0.4.0
|
|
1211
|
+
* @related b.externalDb.write.query, b.externalDb.query, b.externalDb.init
|
|
1212
|
+
*
|
|
1213
|
+
* Route a read against the backend's declared replicas using weighted
|
|
1214
|
+
* round-robin. A failed replica is sidelined for 30 seconds and the
|
|
1215
|
+
* call falls back to primary when `replicaFallbackToPrimary` is true
|
|
1216
|
+
* (the default). Backends without replicas transparently route to
|
|
1217
|
+
* primary. Same `opts` selection rules as `b.externalDb.query`
|
|
1218
|
+
* (`backend` / `classification` / ALS-bound role).
|
|
1219
|
+
*
|
|
1220
|
+
* @opts
|
|
1221
|
+
* backend?: string, // explicit backend name
|
|
1222
|
+
* classification?: string, // route by data class
|
|
1223
|
+
*
|
|
1224
|
+
* @example
|
|
1225
|
+
* var res = await b.externalDb.read.query(
|
|
1226
|
+
* "SELECT id, total FROM orders WHERE tenant_id = $1",
|
|
1227
|
+
* ["acme"],
|
|
1228
|
+
* { classification: "operational" }
|
|
1229
|
+
* );
|
|
1230
|
+
* res.rowCount; // → 7
|
|
1231
|
+
* res.rows[0]; // → { id: "o-1", total: 4200 }
|
|
1232
|
+
*/
|
|
1233
|
+
var read = {
|
|
1234
|
+
query: _readQuery,
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* @primitive b.externalDb.write.query
|
|
1239
|
+
* @signature b.externalDb.write.query(sql, params, opts)
|
|
1240
|
+
* @since 0.4.0
|
|
1241
|
+
* @related b.externalDb.read.query, b.externalDb.query, b.externalDb.write.transaction
|
|
1242
|
+
*
|
|
1243
|
+
* Symmetric alias for `b.externalDb.query` — always routes to primary.
|
|
1244
|
+
* Pair with `b.externalDb.read.query` when an operator wants the call
|
|
1245
|
+
* site to express read/write intent without a magic-comment hint.
|
|
1246
|
+
* Same `opts` selection rules as `b.externalDb.query`.
|
|
1247
|
+
*
|
|
1248
|
+
* @opts
|
|
1249
|
+
* backend?: string, // explicit backend name
|
|
1250
|
+
* classification?: string, // route by data class
|
|
1251
|
+
* includeSqlInAudit?: boolean, // emit SQL text in audit metadata
|
|
1252
|
+
*
|
|
1253
|
+
* @example
|
|
1254
|
+
* var res = await b.externalDb.write.query(
|
|
1255
|
+
* "INSERT INTO orders(id, tenant_id, total) VALUES ($1, $2, $3)",
|
|
1256
|
+
* ["o-2", "acme", 1500],
|
|
1257
|
+
* { classification: "operational" }
|
|
1258
|
+
* );
|
|
1259
|
+
* res.rowCount; // → 1
|
|
1260
|
+
*/
|
|
1261
|
+
/**
|
|
1262
|
+
* @primitive b.externalDb.write.transaction
|
|
1263
|
+
* @signature b.externalDb.write.transaction(fn, opts)
|
|
1264
|
+
* @since 0.4.0
|
|
1265
|
+
* @related b.externalDb.transaction, b.externalDb.write.query
|
|
1266
|
+
*
|
|
1267
|
+
* Symmetric alias for `b.externalDb.transaction` — always runs against
|
|
1268
|
+
* primary. Same `opts` shape (sessionGucs / statementTimeoutMs /
|
|
1269
|
+
* idleInTransactionTimeoutMs / deadlockRetries) as the canonical form.
|
|
1270
|
+
*
|
|
1271
|
+
* @opts
|
|
1272
|
+
* backend?: string,
|
|
1273
|
+
* classification?: string,
|
|
1274
|
+
* sessionGucs?: { [name]: string|number|boolean },
|
|
1275
|
+
* statementTimeoutMs?: number,
|
|
1276
|
+
* idleInTransactionTimeoutMs?: number,
|
|
1277
|
+
* deadlockRetries?: number,
|
|
1278
|
+
*
|
|
1279
|
+
* @example
|
|
1280
|
+
* var n = await b.externalDb.write.transaction(async function (tx) {
|
|
1281
|
+
* await tx.query("UPDATE counters SET n = n + 1 WHERE k = $1", ["hits"]);
|
|
1282
|
+
* var res = await tx.query("SELECT n FROM counters WHERE k = $1", ["hits"]);
|
|
1283
|
+
* return res.rows[0].n;
|
|
1284
|
+
* }, { sessionGucs: { "app.tenant_id": "acme" } });
|
|
1285
|
+
* typeof n; // → "number"
|
|
1286
|
+
*/
|
|
1287
|
+
// write namespace — alias for the primary path. Lets operators express
|
|
1288
|
+
// intent symmetrically with read.query without a magic-comment hint.
|
|
1289
|
+
var write = {
|
|
1290
|
+
query: function (sql, params, opts) { return query(sql, params, opts); },
|
|
1291
|
+
transaction: function (fn, opts) { return transaction(fn, opts); },
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
function _resetForTest() {
|
|
1295
|
+
Object.keys(backends).forEach(function (n) {
|
|
1296
|
+
try { backends[n].pool.drain(); }
|
|
1297
|
+
catch (e) { log.debug("test-reset pool drain failed", { backend: n, error: e.message }); }
|
|
1298
|
+
var bk = backends[n];
|
|
1299
|
+
if (bk && bk.replicas) {
|
|
1300
|
+
bk.replicas.forEach(function (r) {
|
|
1301
|
+
try { r.pool.drain(); }
|
|
1302
|
+
catch (e2) { log.debug("test-reset replica drain failed", { backend: n, error: e2.message }); }
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
backends = {};
|
|
1307
|
+
defaultBackend = null;
|
|
1308
|
+
dbRoleBackends = {};
|
|
1309
|
+
initialized = false;
|
|
1310
|
+
audit.reset();
|
|
1311
|
+
db.reset();
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// ---- configurePool — runtime resize of an existing backend's pool ----
|
|
1315
|
+
//
|
|
1316
|
+
// Operators tune pool sizing without restarting the app. Existing idle
|
|
1317
|
+
// clients are kept; new acquisitions respect the new max. min is honored
|
|
1318
|
+
// the next time the pool refills. idleTimeoutMs takes effect on the next
|
|
1319
|
+
// reaper tick.
|
|
1320
|
+
/**
|
|
1321
|
+
* @primitive b.externalDb.configurePool
|
|
1322
|
+
* @signature b.externalDb.configurePool(backendName, opts)
|
|
1323
|
+
* @since 0.4.0
|
|
1324
|
+
* @related b.externalDb.init, b.externalDb.listBackends
|
|
1325
|
+
*
|
|
1326
|
+
* Resize a registered backend's pool at runtime. New `max` takes effect
|
|
1327
|
+
* on the next acquire; existing idle clients are kept; `min` is honored
|
|
1328
|
+
* when the pool next refills; `idleTimeoutMs` applies on the next
|
|
1329
|
+
* reaper tick. Throws on unknown options or non-positive integers so a
|
|
1330
|
+
* config typo surfaces at the call site.
|
|
1331
|
+
*
|
|
1332
|
+
* @opts
|
|
1333
|
+
* min?: number, // positive integer; floor on idle clients
|
|
1334
|
+
* max?: number, // positive integer; ceiling on total clients (must be >= min)
|
|
1335
|
+
* idleTimeoutMs?: number, // positive integer; reap idle clients after this many ms
|
|
1336
|
+
*
|
|
1337
|
+
* @example
|
|
1338
|
+
* b.externalDb.configurePool("main", {
|
|
1339
|
+
* min: 4,
|
|
1340
|
+
* max: 50,
|
|
1341
|
+
* idleTimeoutMs: 120000,
|
|
1342
|
+
* });
|
|
1343
|
+
*/
|
|
1344
|
+
function configurePool(backendName, opts) {
|
|
1345
|
+
_requireInit();
|
|
1346
|
+
if (typeof backendName !== "string" || backendName.length === 0) {
|
|
1347
|
+
throw _err("INVALID_CONFIG", "configurePool: backendName must be a non-empty string", true);
|
|
1348
|
+
}
|
|
1349
|
+
var bk = backends[backendName];
|
|
1350
|
+
if (!bk) throw _err("UNKNOWN_BACKEND", "configurePool: no backend named '" + backendName + "'", true);
|
|
1351
|
+
if (!opts || typeof opts !== "object") {
|
|
1352
|
+
throw _err("INVALID_CONFIG", "configurePool: opts must be an object", true);
|
|
1353
|
+
}
|
|
1354
|
+
var allowed = ["min", "max", "idleTimeoutMs"];
|
|
1355
|
+
for (var k in opts) {
|
|
1356
|
+
if (!Object.prototype.hasOwnProperty.call(opts, k)) continue;
|
|
1357
|
+
if (allowed.indexOf(k) === -1) {
|
|
1358
|
+
throw _err("INVALID_CONFIG",
|
|
1359
|
+
"configurePool: unknown option '" + k + "'. Allowed: " + allowed.join(", "), true);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
function _requirePosInt(name, value) {
|
|
1363
|
+
if (typeof value !== "number" || !isFinite(value) || value <= 0 || Math.floor(value) !== value) {
|
|
1364
|
+
throw _err("INVALID_CONFIG",
|
|
1365
|
+
"configurePool: " + name + " must be a positive integer, got " + JSON.stringify(value), true);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
if (opts.min !== undefined) _requirePosInt("min", opts.min);
|
|
1369
|
+
if (opts.max !== undefined) _requirePosInt("max", opts.max);
|
|
1370
|
+
if (opts.idleTimeoutMs !== undefined) _requirePosInt("idleTimeoutMs", opts.idleTimeoutMs);
|
|
1371
|
+
if (opts.min !== undefined && opts.max !== undefined && opts.min > opts.max) {
|
|
1372
|
+
throw _err("INVALID_CONFIG", "configurePool: min must be <= max", true);
|
|
1373
|
+
}
|
|
1374
|
+
Object.assign(bk.pool.config, opts);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// ---- adapters.connectAs — Postgres role-aware connect wrapper ----
|
|
1378
|
+
//
|
|
1379
|
+
// Wraps an operator's connect() so that every fresh client runs
|
|
1380
|
+
// `SET ROLE`, `SET search_path`, `SET application_name`, and any other
|
|
1381
|
+
// operator-supplied GUCs at acquire time. The pattern enables the
|
|
1382
|
+
// search_path-views shape: the same SQL `SELECT * FROM sessions`
|
|
1383
|
+
// resolves to `public.sessions` for app_user and to
|
|
1384
|
+
// `analytics.sessions` (a view with PHI redacted) for analytics_user.
|
|
1385
|
+
// See the "Compliance Patterns" wiki page.
|
|
1386
|
+
//
|
|
1387
|
+
// Identifier inputs (role, schemas in searchPath) are validated via
|
|
1388
|
+
// safeSql.validateIdentifier — bad shapes throw at the call site. String
|
|
1389
|
+
// values (applicationName, statement_timeout) are quoted as SQL string
|
|
1390
|
+
// literals with single-quote escaping per the SQL standard.
|
|
1391
|
+
//
|
|
1392
|
+
// connect: b.externalDb.adapters.connectAs(rawConnect, {
|
|
1393
|
+
// role: "analytics_user",
|
|
1394
|
+
// searchPath: ["analytics", "public"],
|
|
1395
|
+
// applicationName: "wiki:analytics",
|
|
1396
|
+
// statementTimeoutMs: C.TIME.seconds(30),
|
|
1397
|
+
// gucs: {
|
|
1398
|
+
// idle_in_transaction_session_timeout: "60s",
|
|
1399
|
+
// },
|
|
1400
|
+
// })
|
|
1401
|
+
//
|
|
1402
|
+
// `query` is the same query function the backend declares; the wrapper
|
|
1403
|
+
// uses it to issue the SET statements.
|
|
1404
|
+
function _connectAs(rawConnect, query, opts) {
|
|
1405
|
+
if (typeof rawConnect !== "function") {
|
|
1406
|
+
throw _err("INVALID_CONFIG", "connectAs: connect must be a function", true);
|
|
1407
|
+
}
|
|
1408
|
+
if (typeof query !== "function") {
|
|
1409
|
+
throw _err("INVALID_CONFIG", "connectAs: query must be a function", true);
|
|
1410
|
+
}
|
|
1411
|
+
opts = opts || {};
|
|
1412
|
+
var allowed = ["role", "searchPath", "applicationName", "statementTimeoutMs", "gucs"];
|
|
1413
|
+
for (var k in opts) {
|
|
1414
|
+
if (!Object.prototype.hasOwnProperty.call(opts, k)) continue;
|
|
1415
|
+
if (allowed.indexOf(k) === -1) {
|
|
1416
|
+
throw _err("INVALID_CONFIG",
|
|
1417
|
+
"connectAs: unknown option '" + k + "'. Allowed: " + allowed.join(", "), true);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// Validate inputs at config time so a malformed name surfaces at
|
|
1422
|
+
// boot rather than on the first connection.
|
|
1423
|
+
if (opts.role !== undefined) {
|
|
1424
|
+
safeSql.validateIdentifier(String(opts.role), { allowReserved: false });
|
|
1425
|
+
}
|
|
1426
|
+
var pathSegments = null;
|
|
1427
|
+
if (opts.searchPath !== undefined) {
|
|
1428
|
+
var raw = Array.isArray(opts.searchPath) ? opts.searchPath : [opts.searchPath];
|
|
1429
|
+
if (raw.length === 0) {
|
|
1430
|
+
throw _err("INVALID_CONFIG", "connectAs: searchPath must have at least one schema", true);
|
|
1431
|
+
}
|
|
1432
|
+
pathSegments = [];
|
|
1433
|
+
for (var pi = 0; pi < raw.length; pi++) {
|
|
1434
|
+
safeSql.validateIdentifier(String(raw[pi]), { allowReserved: false });
|
|
1435
|
+
pathSegments.push(String(raw[pi]));
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
if (opts.applicationName !== undefined && typeof opts.applicationName !== "string") {
|
|
1439
|
+
throw _err("INVALID_CONFIG", "connectAs: applicationName must be a string", true);
|
|
1440
|
+
}
|
|
1441
|
+
if (opts.statementTimeoutMs !== undefined) {
|
|
1442
|
+
if (typeof opts.statementTimeoutMs !== "number" || !isFinite(opts.statementTimeoutMs) ||
|
|
1443
|
+
opts.statementTimeoutMs <= 0 || Math.floor(opts.statementTimeoutMs) !== opts.statementTimeoutMs) {
|
|
1444
|
+
throw _err("INVALID_CONFIG",
|
|
1445
|
+
"connectAs: statementTimeoutMs must be a positive integer", true);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
if (opts.gucs !== undefined && (typeof opts.gucs !== "object" || opts.gucs === null)) {
|
|
1449
|
+
throw _err("INVALID_CONFIG", "connectAs: gucs must be an object", true);
|
|
1450
|
+
}
|
|
1451
|
+
if (opts.gucs) {
|
|
1452
|
+
for (var gname in opts.gucs) {
|
|
1453
|
+
// GUC names: Postgres NAMEDATALEN-shaped identifiers.
|
|
1454
|
+
safeSql.validateIdentifier(gname, { allowReserved: true });
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// Pre-compute the SET statements once — every fresh client runs the
|
|
1459
|
+
// same list, so building it per-connect would burn microbenchmarks.
|
|
1460
|
+
var stmts = [];
|
|
1461
|
+
if (opts.role) {
|
|
1462
|
+
stmts.push('SET ROLE "' + opts.role + '"');
|
|
1463
|
+
}
|
|
1464
|
+
if (pathSegments) {
|
|
1465
|
+
var pathSql = pathSegments.map(function (s) { return '"' + s + '"'; }).join(", ");
|
|
1466
|
+
stmts.push("SET search_path TO " + pathSql);
|
|
1467
|
+
}
|
|
1468
|
+
if (opts.applicationName !== undefined) {
|
|
1469
|
+
// Single-quoted string literal — SQL-standard escape doubles embedded
|
|
1470
|
+
// single quotes.
|
|
1471
|
+
var an = String(opts.applicationName).replace(/'/g, "''");
|
|
1472
|
+
stmts.push("SET application_name TO '" + an + "'");
|
|
1473
|
+
}
|
|
1474
|
+
if (opts.statementTimeoutMs !== undefined) {
|
|
1475
|
+
stmts.push("SET statement_timeout TO " + opts.statementTimeoutMs);
|
|
1476
|
+
}
|
|
1477
|
+
if (opts.gucs) {
|
|
1478
|
+
for (var gn in opts.gucs) {
|
|
1479
|
+
var gv = opts.gucs[gn];
|
|
1480
|
+
if (typeof gv === "number") {
|
|
1481
|
+
// Numeric GUCs must be finite — Infinity / NaN serialize as
|
|
1482
|
+
// tokens that Postgres would reject at parse time, but only
|
|
1483
|
+
// AFTER the connection started using a half-set state. Refuse
|
|
1484
|
+
// at config-time instead.
|
|
1485
|
+
if (!isFinite(gv)) {
|
|
1486
|
+
throw _err("INVALID_CONFIG",
|
|
1487
|
+
"connectAs: gucs[" + gn + "] number must be finite (got " + gv + ")",
|
|
1488
|
+
true);
|
|
1489
|
+
}
|
|
1490
|
+
stmts.push('SET "' + gn + '" TO ' + gv);
|
|
1491
|
+
} else {
|
|
1492
|
+
var gvs = String(gv).replace(/'/g, "''");
|
|
1493
|
+
// Refuse embedded NUL / line breaks in GUC string values —
|
|
1494
|
+
// they have no legitimate use and would terminate the SET
|
|
1495
|
+
// statement early in some drivers.
|
|
1496
|
+
// eslint-disable-next-line no-control-regex
|
|
1497
|
+
if (/[\r\n\u0000]/.test(gvs)) {
|
|
1498
|
+
throw _err("INVALID_CONFIG",
|
|
1499
|
+
"connectAs: gucs[" + gn + "] string value must not contain NUL or newline characters",
|
|
1500
|
+
true);
|
|
1501
|
+
}
|
|
1502
|
+
stmts.push('SET "' + gn + '" TO \'' + gvs + "'");
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
return async function wrappedConnect() {
|
|
1508
|
+
var client = await rawConnect();
|
|
1509
|
+
try {
|
|
1510
|
+
for (var i = 0; i < stmts.length; i++) {
|
|
1511
|
+
await query(client, stmts[i], []);
|
|
1512
|
+
}
|
|
1513
|
+
} catch (e) {
|
|
1514
|
+
// Initialization failed — the operator's close hook isn't visible
|
|
1515
|
+
// here, so we throw and let the pool's catch destroy the partial
|
|
1516
|
+
// client.
|
|
1517
|
+
throw e;
|
|
1518
|
+
}
|
|
1519
|
+
return client;
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/**
|
|
1524
|
+
* @primitive b.externalDb.adapters.connectAs
|
|
1525
|
+
* @signature b.externalDb.adapters.connectAs(connect, opts)
|
|
1526
|
+
* @since 0.4.0
|
|
1527
|
+
* @related b.externalDb.init, b.externalDb.runAs
|
|
1528
|
+
*
|
|
1529
|
+
* Wrap a Postgres `connect` so every fresh client runs `SET ROLE`,
|
|
1530
|
+
* `SET search_path`, `SET application_name`, `SET statement_timeout`,
|
|
1531
|
+
* and any operator-supplied `gucs` before being handed to the pool.
|
|
1532
|
+
* Identifier inputs (role, schemas, GUC names) are validated via
|
|
1533
|
+
* `safeSql.validateIdentifier` at call time so a bad name throws once
|
|
1534
|
+
* at boot rather than per acquired client. Returns the wrapped
|
|
1535
|
+
* `connect` function suitable for a backend's `connect` hook.
|
|
1536
|
+
*
|
|
1537
|
+
* @opts
|
|
1538
|
+
* query: function, // required — the backend's query function (used to issue SET statements)
|
|
1539
|
+
* role?: string, // SQL identifier; runs SET ROLE "<role>"
|
|
1540
|
+
* searchPath?: string[], // SQL identifiers; runs SET search_path TO "<a>", "<b>", ...
|
|
1541
|
+
* applicationName?: string, // appears in pg_stat_activity
|
|
1542
|
+
* statementTimeoutMs?: number, // positive integer; SET statement_timeout TO <ms>
|
|
1543
|
+
* gucs?: { [name]: string|number }, // raw GUC bindings; finite numbers required for numeric values
|
|
1544
|
+
*
|
|
1545
|
+
* @example
|
|
1546
|
+
* var pg = require("pg");
|
|
1547
|
+
* var pool = new pg.Pool({ connectionString: "postgres://app:pw@db.example.com/app" });
|
|
1548
|
+
* var rawConnect = function () { return pool.connect(); };
|
|
1549
|
+
* var rawQuery = function (client, sql, params) { return client.query(sql, params); };
|
|
1550
|
+
*
|
|
1551
|
+
* b.externalDb.init({
|
|
1552
|
+
* backends: {
|
|
1553
|
+
* analytics: {
|
|
1554
|
+
* dialect: "postgres",
|
|
1555
|
+
* connect: b.externalDb.adapters.connectAs(rawConnect, {
|
|
1556
|
+
* query: rawQuery,
|
|
1557
|
+
* role: "analytics_user",
|
|
1558
|
+
* searchPath: ["analytics", "public"],
|
|
1559
|
+
* applicationName: "blamejs:analytics",
|
|
1560
|
+
* statementTimeoutMs: 30000,
|
|
1561
|
+
* gucs: { idle_in_transaction_session_timeout: "60s" },
|
|
1562
|
+
* }),
|
|
1563
|
+
* query: rawQuery,
|
|
1564
|
+
* },
|
|
1565
|
+
* },
|
|
1566
|
+
* });
|
|
1567
|
+
*/
|
|
1568
|
+
// Operators import the helper as `b.externalDb.adapters.connectAs(connect, opts)`
|
|
1569
|
+
// — declarative wrapping with shared input validation.
|
|
1570
|
+
function _adaptersConnectAs(connect, opts) {
|
|
1571
|
+
// The backend's query function is needed to issue SET statements on a
|
|
1572
|
+
// freshly-acquired client. Operators pass it via opts.query — same
|
|
1573
|
+
// function they declare on the backend itself.
|
|
1574
|
+
if (!opts || typeof opts !== "object") {
|
|
1575
|
+
throw _err("INVALID_CONFIG",
|
|
1576
|
+
"adapters.connectAs: opts must be an object", true);
|
|
1577
|
+
}
|
|
1578
|
+
if (typeof opts.query !== "function") {
|
|
1579
|
+
throw _err("INVALID_CONFIG",
|
|
1580
|
+
"adapters.connectAs: opts.query is required (the backend's query function)", true);
|
|
1581
|
+
}
|
|
1582
|
+
// Pull query off and pass the remaining role-aware opts.
|
|
1583
|
+
var query = opts.query;
|
|
1584
|
+
var roleOpts = {};
|
|
1585
|
+
for (var k in opts) {
|
|
1586
|
+
if (Object.prototype.hasOwnProperty.call(opts, k) && k !== "query") {
|
|
1587
|
+
roleOpts[k] = opts[k];
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
return _connectAs(connect, query, roleOpts);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// ---- runAs / currentRole — out-of-request role binding ----
|
|
1594
|
+
//
|
|
1595
|
+
// Inside an HTTP request the dbRoleFor middleware already pushes the
|
|
1596
|
+
// role into the shared db-role-context ALS. Background workers (jobs,
|
|
1597
|
+
// schedulers, CLI commands) don't run under that middleware — they wrap
|
|
1598
|
+
// their work in runAs(role, fn) so the same backend-pick logic applies.
|
|
1599
|
+
//
|
|
1600
|
+
// await b.externalDb.runAs("analytics_user", async function () {
|
|
1601
|
+
// return await b.externalDb.read.query("SELECT ..."); // → analytics backend
|
|
1602
|
+
// });
|
|
1603
|
+
//
|
|
1604
|
+
// currentRole() returns the active role (or null) — useful for diagnostic
|
|
1605
|
+
// logs and observability labels.
|
|
1606
|
+
/**
|
|
1607
|
+
* @primitive b.externalDb.runAs
|
|
1608
|
+
* @signature b.externalDb.runAs(role, fn)
|
|
1609
|
+
* @since 0.4.0
|
|
1610
|
+
* @related b.externalDb.currentRole, b.externalDb.adapters.connectAs
|
|
1611
|
+
*
|
|
1612
|
+
* Bind a SQL role on the deep async-local context for the duration of
|
|
1613
|
+
* `fn()`. Every `b.externalDb.query` / `read.query` / `write.query` /
|
|
1614
|
+
* `transaction` call inside the bound region picks the backend mapped
|
|
1615
|
+
* to `role` via the `dbRoleBackends` map declared at `init()`, so
|
|
1616
|
+
* background workers (cron, queue consumers, CLI commands) get the
|
|
1617
|
+
* same role-aware routing as HTTP requests under
|
|
1618
|
+
* `b.middleware.dbRoleFor`. Pass `null` to clear. Audits role
|
|
1619
|
+
* transitions as `db.role.switched`. Identifier-validates the role at
|
|
1620
|
+
* the call site so a typo throws synchronously.
|
|
1621
|
+
*
|
|
1622
|
+
* @example
|
|
1623
|
+
* await b.externalDb.runAs("analytics_user", async function () {
|
|
1624
|
+
* var res = await b.externalDb.read.query(
|
|
1625
|
+
* "SELECT count(*) AS n FROM events WHERE day = $1",
|
|
1626
|
+
* ["2026-05-09"]
|
|
1627
|
+
* );
|
|
1628
|
+
* return res.rows[0].n;
|
|
1629
|
+
* });
|
|
1630
|
+
*/
|
|
1631
|
+
function runAs(role, fn) {
|
|
1632
|
+
if (typeof fn !== "function") {
|
|
1633
|
+
throw _err("INVALID_FN", "externalDb.runAs: fn must be a function", true);
|
|
1634
|
+
}
|
|
1635
|
+
if (role !== null && role !== undefined) {
|
|
1636
|
+
if (typeof role !== "string" || role.length === 0) {
|
|
1637
|
+
throw _err("INVALID_ROLE",
|
|
1638
|
+
"externalDb.runAs: role must be a non-empty string or null", true);
|
|
1639
|
+
}
|
|
1640
|
+
safeSql.validateIdentifier(role, { allowReserved: false });
|
|
1641
|
+
}
|
|
1642
|
+
// Audit the role transition. runAs has no req, so the actor 5 W's
|
|
1643
|
+
// come from whatever the caller has bound on the audit-context ALS
|
|
1644
|
+
// (log.js requestId, plus any request-bound actor that was set in
|
|
1645
|
+
// an outer scope). Same audit shape as the dbRoleFor middleware
|
|
1646
|
+
// path — forensic walkers can reconstruct the role timeline whether
|
|
1647
|
+
// the binding came from request middleware or a job runner.
|
|
1648
|
+
var previousRole = dbRoleContext.getRole();
|
|
1649
|
+
var newRole = role || null;
|
|
1650
|
+
if (previousRole !== newRole) {
|
|
1651
|
+
audit().safeEmit({
|
|
1652
|
+
action: "db.role.switched",
|
|
1653
|
+
actor: {},
|
|
1654
|
+
resource: { kind: "db.role", id: newRole || "(none)" },
|
|
1655
|
+
outcome: "success",
|
|
1656
|
+
metadata: {
|
|
1657
|
+
previousRole: previousRole,
|
|
1658
|
+
newRole: newRole,
|
|
1659
|
+
source: "runAs",
|
|
1660
|
+
},
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
return dbRoleContext.runWithRole(role || null, fn);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
/**
|
|
1667
|
+
* @primitive b.externalDb.currentRole
|
|
1668
|
+
* @signature b.externalDb.currentRole()
|
|
1669
|
+
* @since 0.4.0
|
|
1670
|
+
* @related b.externalDb.runAs
|
|
1671
|
+
*
|
|
1672
|
+
* Read the SQL role bound on the deep async-local context. Returns
|
|
1673
|
+
* `null` when no role is bound. Useful for diagnostic logs, audit
|
|
1674
|
+
* metadata, and observability labels — the value flows through the
|
|
1675
|
+
* same context that `b.externalDb.query` consults for backend pick.
|
|
1676
|
+
*
|
|
1677
|
+
* @example
|
|
1678
|
+
* await b.externalDb.runAs("analytics_user", async function () {
|
|
1679
|
+
* b.externalDb.currentRole(); // → "analytics_user"
|
|
1680
|
+
* });
|
|
1681
|
+
* b.externalDb.currentRole(); // → null
|
|
1682
|
+
*/
|
|
1683
|
+
function currentRole() {
|
|
1684
|
+
return dbRoleContext.getRole();
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// OWASP-2 — pg_roles enumeration / unrecognized-role guard.
|
|
1688
|
+
//
|
|
1689
|
+
// Boot-time check that compares pg_roles membership to the operator-
|
|
1690
|
+
// declared role list. Operators declare every role they expect to
|
|
1691
|
+
// exist on the cluster (via opts.declaredRoles); the gate refuses or
|
|
1692
|
+
// audits when pg_roles surfaces names not in that list — typical
|
|
1693
|
+
// signal of a forgotten ALTER ROLE / a leftover migration role / a
|
|
1694
|
+
// privileged role added outside change-management.
|
|
1695
|
+
//
|
|
1696
|
+
// await b.externalDb.assertRoleHardening({
|
|
1697
|
+
// backend: "main",
|
|
1698
|
+
// declaredRoles: ["app_user", "analytics_user", "admin"],
|
|
1699
|
+
// mode: "audit", // "audit" | "throw"
|
|
1700
|
+
// ignoreSystem: true, // skip rds_*, pg_*, postgres
|
|
1701
|
+
// });
|
|
1702
|
+
//
|
|
1703
|
+
// Returns { declared, observed, unrecognized, missing }. mode="throw"
|
|
1704
|
+
// raises ROLE_HARDENING_FAIL when unrecognized rows surface; default
|
|
1705
|
+
// "audit" emits db.role.hardening.unrecognized so dashboards see the
|
|
1706
|
+
// drift without breaking boot.
|
|
1707
|
+
/**
|
|
1708
|
+
* @primitive b.externalDb.assertRoleHardening
|
|
1709
|
+
* @signature b.externalDb.assertRoleHardening(opts)
|
|
1710
|
+
* @since 0.7.0
|
|
1711
|
+
* @related b.externalDb.runAs, b.externalDb.adapters.connectAs
|
|
1712
|
+
*
|
|
1713
|
+
* Compare `pg_roles` membership against an operator-declared role
|
|
1714
|
+
* allowlist on a Postgres backend. Surfaces unrecognized roles
|
|
1715
|
+
* (forgotten ALTER ROLE leftovers, migration roles, privileged grants
|
|
1716
|
+
* added outside change-management) and missing roles (declared but not
|
|
1717
|
+
* present). Default `mode: "audit"` emits
|
|
1718
|
+
* `db.role.hardening.unrecognized` / `.ok` so dashboards see drift
|
|
1719
|
+
* without breaking boot; `mode: "throw"` fails boot when unrecognized
|
|
1720
|
+
* roles surface. Non-Postgres dialects emit `db.role.hardening.skipped`
|
|
1721
|
+
* and return empty observed lists.
|
|
1722
|
+
*
|
|
1723
|
+
* @opts
|
|
1724
|
+
* declaredRoles: string[], // required; allowlist of expected role names
|
|
1725
|
+
* backend?: string, // explicit backend name (defaults to defaultBackend)
|
|
1726
|
+
* mode?: "audit" | "throw", // default "audit"
|
|
1727
|
+
* ignoreSystem?: boolean, // skip postgres / pg_* / rds_* / azure_* / cloudsqlsuperuser (default true)
|
|
1728
|
+
*
|
|
1729
|
+
* @example
|
|
1730
|
+
* var report = await b.externalDb.assertRoleHardening({
|
|
1731
|
+
* backend: "main",
|
|
1732
|
+
* declaredRoles: ["app_user", "analytics_user", "admin"],
|
|
1733
|
+
* mode: "audit",
|
|
1734
|
+
* ignoreSystem: true,
|
|
1735
|
+
* });
|
|
1736
|
+
* report.unrecognized; // → []
|
|
1737
|
+
* report.missing; // → []
|
|
1738
|
+
* report.observed; // → ["admin", "analytics_user", "app_user"]
|
|
1739
|
+
*/
|
|
1740
|
+
async function assertRoleHardening(opts) {
|
|
1741
|
+
_requireInit();
|
|
1742
|
+
if (!opts || typeof opts !== "object") {
|
|
1743
|
+
throw _err("INVALID_CONFIG",
|
|
1744
|
+
"assertRoleHardening: opts is required ({ declaredRoles, backend?, mode? })", true);
|
|
1745
|
+
}
|
|
1746
|
+
if (!Array.isArray(opts.declaredRoles)) {
|
|
1747
|
+
throw _err("INVALID_CONFIG",
|
|
1748
|
+
"assertRoleHardening: opts.declaredRoles must be an array of role names", true);
|
|
1749
|
+
}
|
|
1750
|
+
for (var i = 0; i < opts.declaredRoles.length; i++) {
|
|
1751
|
+
var r = opts.declaredRoles[i];
|
|
1752
|
+
if (typeof r !== "string" || r.length === 0) {
|
|
1753
|
+
throw _err("INVALID_CONFIG",
|
|
1754
|
+
"assertRoleHardening: declaredRoles[" + i + "] must be a non-empty string", true);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
var mode = opts.mode || "audit";
|
|
1758
|
+
if (mode !== "audit" && mode !== "throw") {
|
|
1759
|
+
throw _err("INVALID_CONFIG",
|
|
1760
|
+
"assertRoleHardening: mode must be 'audit' or 'throw' (got '" + mode + "')", true);
|
|
1761
|
+
}
|
|
1762
|
+
var backendName = opts.backend || defaultBackend;
|
|
1763
|
+
var b = backends[backendName];
|
|
1764
|
+
if (!b) {
|
|
1765
|
+
throw _err("UNKNOWN_BACKEND",
|
|
1766
|
+
"assertRoleHardening: no backend named '" + backendName + "'", true);
|
|
1767
|
+
}
|
|
1768
|
+
if (b.dialect !== "postgres") {
|
|
1769
|
+
// Non-Postgres dialects don't have pg_roles. The check is a no-op
|
|
1770
|
+
// with a clear audit row so operators see the skip rather than
|
|
1771
|
+
// assume hardening ran.
|
|
1772
|
+
audit().safeEmit({
|
|
1773
|
+
action: "db.role.hardening.skipped",
|
|
1774
|
+
actor: {},
|
|
1775
|
+
resource: { kind: "db.backend", id: backendName },
|
|
1776
|
+
outcome: "success",
|
|
1777
|
+
metadata: { dialect: b.dialect, reason: "non-postgres" },
|
|
1778
|
+
});
|
|
1779
|
+
return { declared: opts.declaredRoles.slice(), observed: [], unrecognized: [], missing: [] };
|
|
1780
|
+
}
|
|
1781
|
+
var ignoreSystem = opts.ignoreSystem !== false; // default true
|
|
1782
|
+
var rows;
|
|
1783
|
+
try {
|
|
1784
|
+
var res = await query(
|
|
1785
|
+
"SELECT rolname FROM pg_roles ORDER BY rolname",
|
|
1786
|
+
[],
|
|
1787
|
+
{ backend: backendName }
|
|
1788
|
+
);
|
|
1789
|
+
rows = (res && res.rows) || [];
|
|
1790
|
+
} catch (e) {
|
|
1791
|
+
audit().safeEmit({
|
|
1792
|
+
action: "db.role.hardening.unreadable",
|
|
1793
|
+
actor: {},
|
|
1794
|
+
resource: { kind: "db.backend", id: backendName },
|
|
1795
|
+
outcome: "failure",
|
|
1796
|
+
reason: (e && e.message) || String(e),
|
|
1797
|
+
metadata: { backend: backendName },
|
|
1798
|
+
});
|
|
1799
|
+
throw _err("ROLE_HARDENING_UNREADABLE",
|
|
1800
|
+
"assertRoleHardening: could not read pg_roles on backend '" + backendName + "': " +
|
|
1801
|
+
((e && e.message) || String(e)), true);
|
|
1802
|
+
}
|
|
1803
|
+
var observed = rows.map(function (r) { return r.rolname; });
|
|
1804
|
+
if (ignoreSystem) {
|
|
1805
|
+
observed = observed.filter(function (n) {
|
|
1806
|
+
return !(n === "postgres" || n.indexOf("pg_") === 0 || n.indexOf("rds_") === 0 ||
|
|
1807
|
+
n.indexOf("rdsadmin") === 0 || n.indexOf("azure_") === 0 ||
|
|
1808
|
+
n.indexOf("cloudsqlsuperuser") === 0);
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
var declaredSet = {};
|
|
1812
|
+
opts.declaredRoles.forEach(function (n) { declaredSet[n] = true; });
|
|
1813
|
+
var observedSet = {};
|
|
1814
|
+
observed.forEach(function (n) { observedSet[n] = true; });
|
|
1815
|
+
var unrecognized = observed.filter(function (n) { return !declaredSet[n]; });
|
|
1816
|
+
var missing = opts.declaredRoles.filter(function (n) { return !observedSet[n]; });
|
|
1817
|
+
if (unrecognized.length > 0 || missing.length > 0) {
|
|
1818
|
+
audit().safeEmit({
|
|
1819
|
+
action: "db.role.hardening.unrecognized",
|
|
1820
|
+
actor: {},
|
|
1821
|
+
resource: { kind: "db.backend", id: backendName },
|
|
1822
|
+
outcome: unrecognized.length > 0 ? "denied" : "failure",
|
|
1823
|
+
metadata: {
|
|
1824
|
+
backend: backendName,
|
|
1825
|
+
unrecognized: unrecognized,
|
|
1826
|
+
missing: missing,
|
|
1827
|
+
observedCount: observed.length,
|
|
1828
|
+
},
|
|
1829
|
+
});
|
|
1830
|
+
if (mode === "throw" && unrecognized.length > 0) {
|
|
1831
|
+
throw _err("ROLE_HARDENING_FAIL",
|
|
1832
|
+
"assertRoleHardening: pg_roles surfaces " + unrecognized.length +
|
|
1833
|
+
" unrecognized role(s) on backend '" + backendName + "': " +
|
|
1834
|
+
unrecognized.join(", ") + ". Either add them to declaredRoles after " +
|
|
1835
|
+
"review, REVOKE them, or set mode: 'audit' to downgrade to audit-only.",
|
|
1836
|
+
true);
|
|
1837
|
+
}
|
|
1838
|
+
} else {
|
|
1839
|
+
audit().safeEmit({
|
|
1840
|
+
action: "db.role.hardening.ok",
|
|
1841
|
+
actor: {},
|
|
1842
|
+
resource: { kind: "db.backend", id: backendName },
|
|
1843
|
+
outcome: "success",
|
|
1844
|
+
metadata: { backend: backendName, observedCount: observed.length },
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
return {
|
|
1848
|
+
declared: opts.declaredRoles.slice(),
|
|
1849
|
+
observed: observed,
|
|
1850
|
+
unrecognized: unrecognized,
|
|
1851
|
+
missing: missing,
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
module.exports = {
|
|
1856
|
+
init: init,
|
|
1857
|
+
query: query,
|
|
1858
|
+
transaction: transaction,
|
|
1859
|
+
healthCheck: healthCheck,
|
|
1860
|
+
listBackends: listBackends,
|
|
1861
|
+
shutdown: shutdown,
|
|
1862
|
+
configurePool: configurePool,
|
|
1863
|
+
read: read,
|
|
1864
|
+
write: write,
|
|
1865
|
+
runAs: runAs,
|
|
1866
|
+
currentRole: currentRole,
|
|
1867
|
+
assertRoleHardening: assertRoleHardening,
|
|
1868
|
+
adapters: {
|
|
1869
|
+
connectAs: _adaptersConnectAs,
|
|
1870
|
+
},
|
|
1871
|
+
// Migration runner targeting an externalDb backend. Mirrors b.migrations
|
|
1872
|
+
// (which targets local SQLite) but runs against externalDb. Tracking +
|
|
1873
|
+
// lock tables live on the externalDb side. See lib/external-db-migrate.js.
|
|
1874
|
+
migrate: externalDbMigrate,
|
|
1875
|
+
Pool: Pool,
|
|
1876
|
+
_resetForTest: _resetForTest,
|
|
1877
|
+
};
|