@blamejs/blamejs-shop 0.4.31 → 0.4.32
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 +2 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/vendor/MANIFEST.json +392 -278
- package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
- package/lib/vendor/blamejs/.gitignore +6 -0
- package/lib/vendor/blamejs/CHANGELOG.md +26 -0
- package/lib/vendor/blamejs/MIGRATING.md +43 -0
- package/lib/vendor/blamejs/README.md +8 -6
- package/lib/vendor/blamejs/SECURITY.md +19 -3
- package/lib/vendor/blamejs/api-snapshot.json +2190 -664
- package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
- package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
- package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
- package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
- package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
- package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
- package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
- package/lib/vendor/blamejs/index.js +4 -0
- package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
- package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
- package/lib/vendor/blamejs/lib/api-key.js +158 -77
- package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
- package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
- package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
- package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
- package/lib/vendor/blamejs/lib/audit.js +259 -123
- package/lib/vendor/blamejs/lib/auth/oauth.js +53 -9
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
- package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +31 -5
- package/lib/vendor/blamejs/lib/backup/index.js +45 -10
- package/lib/vendor/blamejs/lib/break-glass.js +355 -147
- package/lib/vendor/blamejs/lib/cache.js +174 -105
- package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
- package/lib/vendor/blamejs/lib/cli.js +19 -14
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
- package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
- package/lib/vendor/blamejs/lib/cluster.js +119 -71
- package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
- package/lib/vendor/blamejs/lib/compliance.js +206 -4
- package/lib/vendor/blamejs/lib/consent.js +82 -29
- package/lib/vendor/blamejs/lib/constants.js +27 -11
- package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
- package/lib/vendor/blamejs/lib/db-query.js +882 -260
- package/lib/vendor/blamejs/lib/db-schema.js +228 -44
- package/lib/vendor/blamejs/lib/db.js +249 -99
- package/lib/vendor/blamejs/lib/dsr.js +385 -55
- package/lib/vendor/blamejs/lib/error-page.js +14 -1
- package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
- package/lib/vendor/blamejs/lib/external-db.js +549 -34
- package/lib/vendor/blamejs/lib/file-upload.js +52 -7
- package/lib/vendor/blamejs/lib/framework-error.js +20 -1
- package/lib/vendor/blamejs/lib/framework-files.js +73 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
- package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
- package/lib/vendor/blamejs/lib/guard-all.js +1 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
- package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
- package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
- package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
- package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
- package/lib/vendor/blamejs/lib/guard-email.js +47 -69
- package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
- package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
- package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
- package/lib/vendor/blamejs/lib/guard-html.js +53 -108
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
- package/lib/vendor/blamejs/lib/guard-image.js +46 -103
- package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
- package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
- package/lib/vendor/blamejs/lib/guard-json.js +38 -108
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
- package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
- package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
- package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
- package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
- package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
- package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
- package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
- package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
- package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
- package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
- package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
- package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
- package/lib/vendor/blamejs/lib/guard-template.js +35 -172
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-time.js +32 -154
- package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
- package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
- package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
- package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
- package/lib/vendor/blamejs/lib/http-client.js +37 -9
- package/lib/vendor/blamejs/lib/inbox.js +120 -107
- package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
- package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
- package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
- package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
- package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
- package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
- package/lib/vendor/blamejs/lib/mail-store.js +293 -154
- package/lib/vendor/blamejs/lib/mail.js +8 -4
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
- package/lib/vendor/blamejs/lib/migrations.js +108 -66
- package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
- package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
- package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
- package/lib/vendor/blamejs/lib/observability.js +124 -0
- package/lib/vendor/blamejs/lib/otel-export.js +12 -3
- package/lib/vendor/blamejs/lib/outbox.js +184 -83
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
- package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
- package/lib/vendor/blamejs/lib/queue-local.js +225 -140
- package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
- package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
- package/lib/vendor/blamejs/lib/queue.js +7 -0
- package/lib/vendor/blamejs/lib/redact.js +68 -11
- package/lib/vendor/blamejs/lib/redis-client.js +160 -31
- package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
- package/lib/vendor/blamejs/lib/retention.js +101 -40
- package/lib/vendor/blamejs/lib/router.js +212 -5
- package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
- package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
- package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
- package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
- package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
- package/lib/vendor/blamejs/lib/safe-url.js +170 -3
- package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
- package/lib/vendor/blamejs/lib/scheduler.js +35 -12
- package/lib/vendor/blamejs/lib/seeders.js +122 -74
- package/lib/vendor/blamejs/lib/session-stores.js +42 -14
- package/lib/vendor/blamejs/lib/session.js +175 -77
- package/lib/vendor/blamejs/lib/sql.js +3842 -0
- package/lib/vendor/blamejs/lib/sse.js +26 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +151 -4
- package/lib/vendor/blamejs/lib/static.js +177 -34
- package/lib/vendor/blamejs/lib/subject.js +96 -49
- package/lib/vendor/blamejs/lib/vault/index.js +3 -2
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
- package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
- package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
- package/lib/vendor/blamejs/lib/websocket.js +35 -5
- package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
- package/lib/vendor/blamejs/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
- package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
- package/lib/vendor/blamejs/scripts/check-services.js +21 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +51 -0
- package/lib/vendor/blamejs/scripts/release.js +398 -38
- package/lib/vendor/blamejs/test/00-primitives.js +117 -0
- package/lib/vendor/blamejs/test/10-state.js +140 -14
- package/lib/vendor/blamejs/test/20-db.js +65 -2
- package/lib/vendor/blamejs/test/helpers/db.js +9 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
- package/lib/vendor/blamejs/test/helpers/services.js +21 -0
- package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
- package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
- package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
- package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
- package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
- package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
- package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
- package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
- package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
- package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
- package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
- package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
- package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
- package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
- package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
- package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1120 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +309 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
- package/lib/vendor/blamejs/test/smoke.js +79 -21
- package/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
- package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
- package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
- package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
- package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
- package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
- package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
- package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
- package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
- package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
- package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
- package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
- package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
- package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Live data-layer test against the docker MySQL container. Proves the
|
|
4
|
+
* dialect-threading foundation end-to-end on a real MySQL server: the
|
|
5
|
+
* canonical resolver `b.clusterStorage.dialect()` returns `"mysql"` in
|
|
6
|
+
* cluster mode, and every cluster-routed data-layer backend that threads
|
|
7
|
+
* that dialect into its `b.sql` calls runs against real MySQL with
|
|
8
|
+
* backtick-quoted identifiers and the MySQL `ON DUPLICATE KEY UPDATE`
|
|
9
|
+
* upsert form (not Postgres `ON CONFLICT` / double-quotes):
|
|
10
|
+
*
|
|
11
|
+
* - b.cache (backend: "cluster") — _blamejs_cache + _blamejs_cache_tags
|
|
12
|
+
* - b.session — _blamejs_sessions, sealed at rest
|
|
13
|
+
* - b.nonceStore (backend: "cluster")— _blamejs_api_encrypt_nonces
|
|
14
|
+
* - b.middleware.rateLimit (cluster) — _blamejs_rate_limit_counters
|
|
15
|
+
*
|
|
16
|
+
* The "driver" is a docker-exec mysql shim (batch mode, one statement per
|
|
17
|
+
* exec) modelled on cluster-provider-mysql.test.js. Each primitive routes
|
|
18
|
+
* its SQL through b.clusterStorage -> b.externalDb -> this driver -> real
|
|
19
|
+
* MySQL, so a dialect bug in the emitted SQL surfaces as a real MySQL ERROR,
|
|
20
|
+
* not a test artifact. size() / count() are asserted to return a JS number
|
|
21
|
+
* (the COUNT(*)/BIGINT-as-string coercion) on the real backend.
|
|
22
|
+
*
|
|
23
|
+
* The rate-limit take() is the headline: its per-column window-rollover
|
|
24
|
+
* conflict action is a CASE that reads the proposed row (VALUES(`col`)) and
|
|
25
|
+
* the existing row (`table`.`col`) — both spelled with the MySQL tokens, so
|
|
26
|
+
* the Postgres EXCLUDED form would be a hard ERROR here. b.sql also folds the
|
|
27
|
+
* RETURNING into a readback SELECT (MySQL upsert has no RETURNING); the
|
|
28
|
+
* backend runs that readback and coerces the BIGINT count back to a number.
|
|
29
|
+
*
|
|
30
|
+
* Cluster mode is wired via a no-op custom provider plus externalDbBackend,
|
|
31
|
+
* so isClusterMode() is true and each backend's clusterStorage.execute()
|
|
32
|
+
* calls dispatch to MySQL. The framework tables are created here by hand
|
|
33
|
+
* with the exact column shape frameworkSchema's MySQL DDL builder emits
|
|
34
|
+
* (camelCase, backtick-quoted, VARCHAR key columns, BIGINT ms-epoch);
|
|
35
|
+
* frameworkSchema.ensureSchema's own MySQL DDL is proven directly in
|
|
36
|
+
* framework-schema-mysql.test.js. b.session seals userId/data via the
|
|
37
|
+
* cryptoField registry that helpers.setupTestDb populates.
|
|
38
|
+
*
|
|
39
|
+
* RUN: node scripts/test-integration.js --skip-service-check data-layer-mysql
|
|
40
|
+
*/
|
|
41
|
+
var execFileSync = require("node:child_process").execFileSync;
|
|
42
|
+
var fs = require("node:fs");
|
|
43
|
+
var os = require("node:os");
|
|
44
|
+
var path = require("node:path");
|
|
45
|
+
var helpers = require("../helpers");
|
|
46
|
+
var check = helpers.check;
|
|
47
|
+
var services = require("../helpers/services");
|
|
48
|
+
var b = require("../../");
|
|
49
|
+
|
|
50
|
+
var CONTAINER = "blamejs-test-mysql";
|
|
51
|
+
var DB_NAME = "blamejs_test";
|
|
52
|
+
|
|
53
|
+
var rateLimitModule = require("../../lib/middleware/rate-limit");
|
|
54
|
+
|
|
55
|
+
// Soft assertion: records a pass/fail without throwing, so every section's
|
|
56
|
+
// finding is collected even when an earlier one failed. The recorded
|
|
57
|
+
// results are replayed through the hard `check` at the end of run() so the
|
|
58
|
+
// file still FAILS (and the runner reports it) when any assertion is false,
|
|
59
|
+
// while ALL findings are printed first.
|
|
60
|
+
var _findings = [];
|
|
61
|
+
function softCheck(label, condition) {
|
|
62
|
+
_findings.push({ label: label, ok: !!condition });
|
|
63
|
+
console.log((condition ? " ok " : " FAIL ") + label);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---- docker-exec mysql driver ----
|
|
67
|
+
//
|
|
68
|
+
// Batch mode (--batch --raw) emits TAB-separated rows with a header line;
|
|
69
|
+
// NULL renders as the literal "NULL". One statement per exec (the driver
|
|
70
|
+
// is stateless between calls, fine for the cluster-storage dispatch
|
|
71
|
+
// shape). Affected-row count for a DML statement is recovered with a
|
|
72
|
+
// trailing `SELECT ROW_COUNT()` in the SAME exec so the same connection's
|
|
73
|
+
// session state is read — matching what a real mysql2 driver surfaces as
|
|
74
|
+
// rowCount.
|
|
75
|
+
function _execMysql(sqlText) {
|
|
76
|
+
var out;
|
|
77
|
+
try {
|
|
78
|
+
out = execFileSync("docker",
|
|
79
|
+
["exec", "-i", CONTAINER,
|
|
80
|
+
"mysql", "-uroot", "-pblamejs_test_root", "--batch", "--raw", DB_NAME, "-e", sqlText],
|
|
81
|
+
{ stdio: ["pipe", "pipe", "pipe"], maxBuffer: 16 * 1024 * 1024 }
|
|
82
|
+
).toString("utf8");
|
|
83
|
+
} catch (e) {
|
|
84
|
+
var err = new Error(e.stderr ? e.stderr.toString("utf8") : (e.message || String(e)));
|
|
85
|
+
err.cause = e;
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function _parseBatch(out) {
|
|
92
|
+
var lines = out.split(/\r?\n/).filter(function (l) { return l.length > 0; });
|
|
93
|
+
if (lines.length < 1) return { rows: [], headers: [] };
|
|
94
|
+
var headers = lines[0].split("\t");
|
|
95
|
+
var rows = [];
|
|
96
|
+
for (var i = 1; i < lines.length; i++) {
|
|
97
|
+
var cells = lines[i].split("\t");
|
|
98
|
+
var row = {};
|
|
99
|
+
for (var j = 0; j < headers.length; j++) {
|
|
100
|
+
var v = cells[j];
|
|
101
|
+
row[headers[j]] = (v === "NULL" || v === undefined) ? null : v;
|
|
102
|
+
}
|
|
103
|
+
rows.push(row);
|
|
104
|
+
}
|
|
105
|
+
return { rows: rows, headers: headers };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Substitute `?` placeholders with quoted literals. Every test value is
|
|
109
|
+
// operator-controlled (keys / numbers / JSON strings / null). MySQL is fed
|
|
110
|
+
// `?` (clusterStorage.placeholderize is passthrough for mysql), so this is
|
|
111
|
+
// the shim's bind protocol.
|
|
112
|
+
function _bindParams(sqlText, params) {
|
|
113
|
+
var i = 0;
|
|
114
|
+
return sqlText.replace(/\?/g, function () {
|
|
115
|
+
if (i >= params.length) throw new Error("placeholder/param count mismatch");
|
|
116
|
+
var p = params[i++];
|
|
117
|
+
if (p === null || p === undefined) return "NULL";
|
|
118
|
+
if (typeof p === "number") return String(p);
|
|
119
|
+
if (typeof p === "boolean") return p ? "1" : "0";
|
|
120
|
+
if (Buffer.isBuffer(p)) return "x'" + p.toString("hex") + "'";
|
|
121
|
+
return "'" + String(p).replace(/\\/g, "\\\\").replace(/'/g, "''") + "'";
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function _makeDockerMysqlDriver() {
|
|
126
|
+
return {
|
|
127
|
+
connect: async function () { return { id: 1 }; },
|
|
128
|
+
query: async function (_client, sqlText, params) {
|
|
129
|
+
params = params || [];
|
|
130
|
+
var bound = _bindParams(sqlText, params);
|
|
131
|
+
var t = bound.trim();
|
|
132
|
+
if (/^(SELECT|SHOW|WITH)/i.test(t)) {
|
|
133
|
+
var sel = _parseBatch(_execMysql(bound));
|
|
134
|
+
return { rows: sel.rows, rowCount: sel.rows.length };
|
|
135
|
+
}
|
|
136
|
+
// DML / DDL: run it, then read ROW_COUNT() on the SAME connection
|
|
137
|
+
// (one exec) so the affected-row count is faithful.
|
|
138
|
+
var combined = _execMysql(bound + ";\nSELECT ROW_COUNT() AS `__rc`;");
|
|
139
|
+
var rc = _parseBatch(combined);
|
|
140
|
+
var n = (rc.rows[0] && rc.rows[0].__rc != null) ? Number(rc.rows[0].__rc) : 0;
|
|
141
|
+
// MySQL ROW_COUNT() returns -1 for statements that don't affect rows
|
|
142
|
+
// (e.g. CREATE). Clamp to 0 for those.
|
|
143
|
+
if (!Number.isFinite(n) || n < 0) n = 0;
|
|
144
|
+
return { rows: [], rowCount: n };
|
|
145
|
+
},
|
|
146
|
+
close: async function () { /* no-op */ },
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// No-op cluster provider — we only need isClusterMode() true + a dialect.
|
|
151
|
+
// The framework's leader gate is not under test here (cache/nonce/
|
|
152
|
+
// rate-limit cluster backends don't gate on isLeader()).
|
|
153
|
+
function _noopProvider() {
|
|
154
|
+
return {
|
|
155
|
+
ensureSchema: async function () { /* tables created by hand below */ },
|
|
156
|
+
acquireLease: async function () { return { nodeId: "n", leaseId: "l", fencingToken: 1, expiresAt: Date.now() + 60000 }; },
|
|
157
|
+
renewLease: async function (lease) { return lease; },
|
|
158
|
+
releaseLease: async function () {},
|
|
159
|
+
currentLeader: async function () { return null; },
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---- framework table DDL for MySQL (matches framework-schema._*DDL
|
|
164
|
+
// column shapes; ensureSchema is Postgres/SQLite-only so MySQL is by
|
|
165
|
+
// hand). Identifiers backtick-quoted, camelCase preserved. ----
|
|
166
|
+
var MYSQL_DDL = [
|
|
167
|
+
// _blamejs_cache. VARCHAR lengths kept index-safe under utf8mb4
|
|
168
|
+
// (4 bytes/char) so the PRIMARY KEY / composite key stay under MySQL's
|
|
169
|
+
// 3072-byte index limit.
|
|
170
|
+
"DROP TABLE IF EXISTS `_blamejs_cache`",
|
|
171
|
+
"CREATE TABLE `_blamejs_cache` (" +
|
|
172
|
+
"`cacheKey` VARCHAR(191) PRIMARY KEY, " +
|
|
173
|
+
"`valueJson` LONGTEXT NOT NULL, " +
|
|
174
|
+
"`expiresAt` BIGINT NOT NULL, " +
|
|
175
|
+
"`updatedAt` BIGINT NOT NULL)",
|
|
176
|
+
// _blamejs_cache_tags
|
|
177
|
+
"DROP TABLE IF EXISTS `_blamejs_cache_tags`",
|
|
178
|
+
"CREATE TABLE `_blamejs_cache_tags` (" +
|
|
179
|
+
"`cacheKey` VARCHAR(191) NOT NULL, " +
|
|
180
|
+
"`tag` VARCHAR(191) NOT NULL, " +
|
|
181
|
+
"PRIMARY KEY (`cacheKey`, `tag`))",
|
|
182
|
+
// Minimal audit/consent tip tables so cluster.init's boot-time rollback
|
|
183
|
+
// check finds "no tip row" and skips cleanly (it reads these).
|
|
184
|
+
"DROP TABLE IF EXISTS `_blamejs_audit_tip`",
|
|
185
|
+
"CREATE TABLE `_blamejs_audit_tip` (" +
|
|
186
|
+
"`scope` VARCHAR(64) PRIMARY KEY, " +
|
|
187
|
+
"`atMonotonicCounter` BIGINT NOT NULL DEFAULT 0, " +
|
|
188
|
+
"`rowHash` TEXT)",
|
|
189
|
+
"DROP TABLE IF EXISTS `_blamejs_consent_tip`",
|
|
190
|
+
"CREATE TABLE `_blamejs_consent_tip` (" +
|
|
191
|
+
"`scope` VARCHAR(64) PRIMARY KEY, " +
|
|
192
|
+
"`atMonotonicCounter` BIGINT NOT NULL DEFAULT 0, " +
|
|
193
|
+
"`rowHash` TEXT)",
|
|
194
|
+
"DROP TABLE IF EXISTS `_blamejs_audit_log`",
|
|
195
|
+
"CREATE TABLE `_blamejs_audit_log` (" +
|
|
196
|
+
"`monotonicCounter` BIGINT, `rowHash` TEXT)",
|
|
197
|
+
"DROP TABLE IF EXISTS `_blamejs_consent_log`",
|
|
198
|
+
"CREATE TABLE `_blamejs_consent_log` (" +
|
|
199
|
+
"`monotonicCounter` BIGINT, `rowHash` TEXT)",
|
|
200
|
+
// _blamejs_sessions — mirrors framework-schema._sessionsDDL("mysql"):
|
|
201
|
+
// VARCHAR key columns (index-safe under utf8mb4), BIGINT ms-epoch
|
|
202
|
+
// timestamps. userId/data hold the vault-sealed ciphertext at rest.
|
|
203
|
+
"DROP TABLE IF EXISTS `_blamejs_sessions`",
|
|
204
|
+
"CREATE TABLE `_blamejs_sessions` (" +
|
|
205
|
+
"`sidHash` VARCHAR(191) PRIMARY KEY, " +
|
|
206
|
+
"`userId` TEXT NOT NULL, " +
|
|
207
|
+
"`userIdHash` VARCHAR(191) NOT NULL, " +
|
|
208
|
+
"`data` TEXT, " +
|
|
209
|
+
"`createdAt` BIGINT NOT NULL, " +
|
|
210
|
+
"`expiresAt` BIGINT NOT NULL, " +
|
|
211
|
+
"`lastActivity` BIGINT NOT NULL)",
|
|
212
|
+
// _blamejs_api_encrypt_nonces — replay-protection store. nonceHash is the
|
|
213
|
+
// PRIMARY KEY, so the ON DUPLICATE KEY UPDATE no-op fold makes the first
|
|
214
|
+
// insert win (affectedRows=1) and a replay no-op (affectedRows=0).
|
|
215
|
+
"DROP TABLE IF EXISTS `_blamejs_api_encrypt_nonces`",
|
|
216
|
+
"CREATE TABLE `_blamejs_api_encrypt_nonces` (" +
|
|
217
|
+
"`nonceHash` VARCHAR(191) PRIMARY KEY, " +
|
|
218
|
+
"`expireAt` BIGINT NOT NULL)",
|
|
219
|
+
// _blamejs_rate_limit_counters — fixed-window counter. The PRIMARY KEY on
|
|
220
|
+
// `key` is what take()'s ON DUPLICATE KEY UPDATE CASE-rollover keys on.
|
|
221
|
+
"DROP TABLE IF EXISTS `_blamejs_rate_limit_counters`",
|
|
222
|
+
"CREATE TABLE `_blamejs_rate_limit_counters` (" +
|
|
223
|
+
"`key` VARCHAR(191) PRIMARY KEY, " +
|
|
224
|
+
"`windowStart` BIGINT NOT NULL, " +
|
|
225
|
+
"`count` BIGINT NOT NULL DEFAULT 0)",
|
|
226
|
+
// _blamejs_cluster_state — cluster.init's _checkVaultKeyConsistency records
|
|
227
|
+
// + reads this node's vault-key fingerprint here (the MySQL `ON DUPLICATE
|
|
228
|
+
// KEY UPDATE scope = scope` DO-NOTHING fold). The no-op provider doesn't
|
|
229
|
+
// create it, so it is created by hand; rotationEpoch is materialized up
|
|
230
|
+
// front so the runtime's idempotent ALTER is a no-op. Mirrors
|
|
231
|
+
// cluster-provider-db's _blamejs_cluster_state column shape.
|
|
232
|
+
"DROP TABLE IF EXISTS `_blamejs_cluster_state`",
|
|
233
|
+
"CREATE TABLE `_blamejs_cluster_state` (" +
|
|
234
|
+
"`scope` VARCHAR(64) PRIMARY KEY, " +
|
|
235
|
+
"`vaultKeyFp` TEXT NOT NULL, " +
|
|
236
|
+
"`recordedAt` BIGINT NOT NULL, " +
|
|
237
|
+
"`recordedByNode` TEXT NOT NULL, " +
|
|
238
|
+
"`rotationEpoch` BIGINT NOT NULL DEFAULT 0)",
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
var DROP_ALL = [
|
|
242
|
+
"_blamejs_cache", "_blamejs_cache_tags", "_blamejs_audit_tip",
|
|
243
|
+
"_blamejs_consent_tip", "_blamejs_audit_log", "_blamejs_consent_log",
|
|
244
|
+
"_blamejs_sessions", "_blamejs_api_encrypt_nonces",
|
|
245
|
+
"_blamejs_rate_limit_counters", "_blamejs_cluster_state",
|
|
246
|
+
].map(function (t) { return "DROP TABLE IF EXISTS `" + t + "`"; });
|
|
247
|
+
|
|
248
|
+
function _ddl(stmts) {
|
|
249
|
+
for (var i = 0; i < stmts.length; i++) _execMysql(stmts[i]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Out-of-band assertion helper: run a SELECT directly and return parsed rows.
|
|
253
|
+
function _selectDirect(sqlText) {
|
|
254
|
+
return _parseBatch(_execMysql(sqlText)).rows;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function run() {
|
|
258
|
+
var mysqlSvc = await services.requireService("mysql");
|
|
259
|
+
if (!mysqlSvc.ok) throw new Error("mysql unreachable: " + mysqlSvc.reason);
|
|
260
|
+
|
|
261
|
+
_ddl(MYSQL_DDL);
|
|
262
|
+
|
|
263
|
+
// Full-framework bring-up (vault + db + cryptoField). db.init registers the
|
|
264
|
+
// _blamejs_sessions sealedFields (userId, data) + derived userIdHash, which
|
|
265
|
+
// b.session uses to seal/derive regardless of which backend the SQL routes
|
|
266
|
+
// to. The local SQLite db it opens is unused once cluster mode is active
|
|
267
|
+
// (session SQL dispatches to MySQL) but the cryptoField registry it
|
|
268
|
+
// populates is exactly what b.session's sealing needs.
|
|
269
|
+
var tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-datalayer-mysql-"));
|
|
270
|
+
await helpers.setupTestDb(tmpDir);
|
|
271
|
+
|
|
272
|
+
var driver = _makeDockerMysqlDriver();
|
|
273
|
+
b.cluster._resetForTest();
|
|
274
|
+
b.externalDb._resetForTest();
|
|
275
|
+
b.externalDb.init({
|
|
276
|
+
backends: {
|
|
277
|
+
ops: {
|
|
278
|
+
connect: driver.connect, query: driver.query, close: driver.close,
|
|
279
|
+
dialect: "mysql",
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
await b.cluster.init({
|
|
285
|
+
nodeId: "mysql-data-node",
|
|
286
|
+
role: "leader",
|
|
287
|
+
leaseTtl: b.constants.TIME.seconds(30),
|
|
288
|
+
heartbeatInterval: b.constants.TIME.seconds(10),
|
|
289
|
+
provider: _noopProvider(),
|
|
290
|
+
externalDbBackend: "ops",
|
|
291
|
+
dialect: "mysql",
|
|
292
|
+
});
|
|
293
|
+
softCheck("cluster is in cluster mode (state routes to MySQL)",
|
|
294
|
+
b.cluster.isClusterMode() === true);
|
|
295
|
+
softCheck("cluster dialect is mysql", b.cluster.dialect() === "mysql");
|
|
296
|
+
|
|
297
|
+
// Each backend threads clusterStorage.dialect() into every b.sql call (via
|
|
298
|
+
// its per-file _*SqlOpts()), so its SQL emits MySQL-shaped identifiers + the
|
|
299
|
+
// ON DUPLICATE KEY UPDATE upsert against the real server. A dialect bug
|
|
300
|
+
// surfaces as a real MySQL ERROR rather than a test artifact.
|
|
301
|
+
try {
|
|
302
|
+
await _section("cache", _testCacheCluster);
|
|
303
|
+
await _section("session", _testSession);
|
|
304
|
+
await _section("nonce", _testNonceCluster);
|
|
305
|
+
await _section("rate-limit", _testRateLimitCluster);
|
|
306
|
+
} finally {
|
|
307
|
+
try { await b.cluster.shutdown(); } catch (_e) {}
|
|
308
|
+
b.cluster._resetForTest();
|
|
309
|
+
try { await b.externalDb.shutdown(); } catch (_e) {}
|
|
310
|
+
try { await helpers.teardownTestDb(tmpDir); } catch (_e) {}
|
|
311
|
+
_ddl(DROP_ALL);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Replay every recorded finding through the hard `check` so the file
|
|
315
|
+
// FAILS (and the runner reports it) when any assertion is false. All
|
|
316
|
+
// findings have already been printed above, so the failure message
|
|
317
|
+
// names the first unmet contract while the full picture is visible.
|
|
318
|
+
var failures = _findings.filter(function (f) { return !f.ok; });
|
|
319
|
+
console.log("");
|
|
320
|
+
console.log("[data-layer-mysql] " + (_findings.length - failures.length) + "/" +
|
|
321
|
+
_findings.length + " checks ok; " + failures.length + " failing");
|
|
322
|
+
for (var i = 0; i < _findings.length; i++) {
|
|
323
|
+
check(_findings[i].label, _findings[i].ok);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Run one primitive's section; a thrown driver error becomes a single
|
|
328
|
+
// FAILED check carrying the first line of the MySQL error so each
|
|
329
|
+
// primitive's dialect bug is reported independently.
|
|
330
|
+
async function _section(label, fn) {
|
|
331
|
+
try {
|
|
332
|
+
await fn();
|
|
333
|
+
} catch (e) {
|
|
334
|
+
var first = ((e && e.message) || String(e)).split(/\r?\n/)
|
|
335
|
+
.filter(function (l) { return /ERROR \d+|error in your SQL|EXCLUDED|"/.test(l); })[0] ||
|
|
336
|
+
((e && e.message) || String(e)).split(/\r?\n/)[0];
|
|
337
|
+
softCheck(label + "(mysql): cluster SQL executes against real MySQL " +
|
|
338
|
+
"(emitted with the MySQL dialect — backtick identifiers, no Postgres EXCLUDED) " +
|
|
339
|
+
"— DETAIL: " + first, false);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ======================================================================
|
|
344
|
+
// b.cache cluster backend on real MySQL.
|
|
345
|
+
// get / set (tx upsert + tag rewrite) / del / has / size / clear /
|
|
346
|
+
// invalidateTag (whereLike prefix) / getTags / CAS update.
|
|
347
|
+
// ======================================================================
|
|
348
|
+
async function _testCacheCluster() {
|
|
349
|
+
var cache = b.cache.create({ backend: "cluster", namespace: "mysqlns", ttlMs: b.constants.TIME.minutes(5) });
|
|
350
|
+
|
|
351
|
+
// set + get round-trip through the real _blamejs_cache UPSERT.
|
|
352
|
+
await cache.set("k1", { hello: "world", n: 42 });
|
|
353
|
+
var got = await cache.get("k1");
|
|
354
|
+
softCheck("cache(mysql): set + get JSON round-trips on real MySQL",
|
|
355
|
+
got && got.hello === "world" && got.n === 42);
|
|
356
|
+
|
|
357
|
+
// The row physically lands keyed on "<namespace>:<key>".
|
|
358
|
+
var rowDirect = _selectDirect(
|
|
359
|
+
"SELECT `valueJson` FROM `_blamejs_cache` WHERE `cacheKey` = 'mysqlns:k1';");
|
|
360
|
+
softCheck("cache(mysql): row physically present under composite key",
|
|
361
|
+
rowDirect.length === 1 && /"hello"/.test(rowDirect[0].valueJson));
|
|
362
|
+
|
|
363
|
+
// has / del.
|
|
364
|
+
softCheck("cache(mysql): has returns true for a live key", (await cache.has("k1")) === true);
|
|
365
|
+
softCheck("cache(mysql): del removes the key", (await cache.del("k1")) === true);
|
|
366
|
+
softCheck("cache(mysql): get after del is undefined", (await cache.get("k1")) === undefined);
|
|
367
|
+
|
|
368
|
+
// tags: set with tags -> tag rows written in the same transaction.
|
|
369
|
+
await cache.set("a", "1", { tags: ["grp-x"] });
|
|
370
|
+
await cache.set("bk", "2", { tags: ["grp-x", "grp-y"] });
|
|
371
|
+
await cache.set("ck", "3", { tags: ["grp-y"] });
|
|
372
|
+
var tagsA = await cache.getTags("a");
|
|
373
|
+
softCheck("cache(mysql): getTags returns the tags written for a key",
|
|
374
|
+
Array.isArray(tagsA) && tagsA.indexOf("grp-x") !== -1);
|
|
375
|
+
|
|
376
|
+
// invalidateTag uses whereLike(cacheKey, prefix) + tag match. Drops every
|
|
377
|
+
// key carrying grp-x, scoped to this namespace; leaves grp-y-only keys.
|
|
378
|
+
var purged = await cache.invalidateTag("grp-x");
|
|
379
|
+
softCheck("cache(mysql): invalidateTag purged the grp-x keys (count >= 2)", purged >= 2);
|
|
380
|
+
softCheck("cache(mysql): invalidateTag dropped a + bk",
|
|
381
|
+
(await cache.get("a")) === undefined && (await cache.get("bk")) === undefined);
|
|
382
|
+
softCheck("cache(mysql): invalidateTag preserved grp-y-only key ck",
|
|
383
|
+
(await cache.get("ck")) === "3");
|
|
384
|
+
|
|
385
|
+
// Tag junction rows for the purged keys are gone (no orphans).
|
|
386
|
+
var orphanTags = _selectDirect(
|
|
387
|
+
"SELECT COUNT(*) AS `n` FROM `_blamejs_cache_tags` WHERE `cacheKey` = 'mysqlns:a';");
|
|
388
|
+
softCheck("cache(mysql): purged key's tag junction rows removed",
|
|
389
|
+
orphanTags.length === 1 && Number(orphanTags[0].n) === 0);
|
|
390
|
+
|
|
391
|
+
// size() counts only live, namespace-scoped rows (whereLike prefix).
|
|
392
|
+
var sz = await cache.size();
|
|
393
|
+
softCheck("cache(mysql): size() counts live namespaced rows (ck remains)", sz >= 1);
|
|
394
|
+
|
|
395
|
+
// CAS update (atomic RMW) — counter increment via the transaction +
|
|
396
|
+
// compare-and-set path on real MySQL.
|
|
397
|
+
await cache.update("counter", function (n) { return { value: (n || 0) + 1 }; });
|
|
398
|
+
await cache.update("counter", function (n) { return { value: (n || 0) + 1 }; });
|
|
399
|
+
var counterVal = await cache.get("counter");
|
|
400
|
+
softCheck("cache(mysql): atomic update() increments through CAS on MySQL",
|
|
401
|
+
counterVal === 2);
|
|
402
|
+
|
|
403
|
+
// clear() — namespace-scoped wipe via whereLike prefix.
|
|
404
|
+
var cleared = await cache.clear();
|
|
405
|
+
softCheck("cache(mysql): clear() wiped the namespace", cleared >= 1);
|
|
406
|
+
softCheck("cache(mysql): get after clear is undefined", (await cache.get("ck")) === undefined);
|
|
407
|
+
|
|
408
|
+
// Cross-namespace isolation: a second instance's key is untouched by the
|
|
409
|
+
// first's clear()/invalidateTag (prefix LIKE scoping is real on MySQL).
|
|
410
|
+
var other = b.cache.create({ backend: "cluster", namespace: "othermysql", ttlMs: b.constants.TIME.minutes(5) });
|
|
411
|
+
await other.set("shared", "other-value", { tags: ["grp-x"] });
|
|
412
|
+
await cache.set("shared", "first-value", { tags: ["grp-x"] });
|
|
413
|
+
await cache.invalidateTag("grp-x");
|
|
414
|
+
softCheck("cache(mysql): invalidateTag is namespace-scoped (other ns survives)",
|
|
415
|
+
(await other.get("shared")) === "other-value");
|
|
416
|
+
await other.clear();
|
|
417
|
+
await other.close();
|
|
418
|
+
await cache.close();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ======================================================================
|
|
422
|
+
// b.session full lifecycle on real MySQL (sealed at rest).
|
|
423
|
+
// create / verify / count / touch / rotate / destroyAllForUser /
|
|
424
|
+
// destroy / purgeExpired — every statement dialect-threaded to MySQL
|
|
425
|
+
// (backtick identifiers), userId/data sealed via cryptoField.
|
|
426
|
+
// ======================================================================
|
|
427
|
+
async function _testSession() {
|
|
428
|
+
var created = await b.session.create({
|
|
429
|
+
userId: "user-42",
|
|
430
|
+
data: { roles: ["admin"], theme: "dark" },
|
|
431
|
+
ttlMs: b.constants.TIME.hours(8),
|
|
432
|
+
});
|
|
433
|
+
softCheck("session(mysql): create returns a sealed token + expiry",
|
|
434
|
+
created && typeof created.token === "string" &&
|
|
435
|
+
created.token.indexOf("vault:") === 0 && typeof created.expiresAt === "number");
|
|
436
|
+
|
|
437
|
+
// The row physically landed on MySQL; userId is NOT plaintext.
|
|
438
|
+
var rawRows = _selectDirect("SELECT `userId`, `userIdHash` FROM `_blamejs_sessions`;");
|
|
439
|
+
softCheck("session(mysql): a session row physically landed on MySQL",
|
|
440
|
+
rawRows.length === 1);
|
|
441
|
+
softCheck("session(mysql): userId is sealed at rest (NOT the plaintext 'user-42')",
|
|
442
|
+
rawRows.length === 1 && String(rawRows[0].userId).indexOf("user-42") === -1);
|
|
443
|
+
|
|
444
|
+
// verify -> unseal round-trips userId + data through MySQL.
|
|
445
|
+
var info = await b.session.verify(created.token);
|
|
446
|
+
softCheck("session(mysql): verify unseals userId from the MySQL row",
|
|
447
|
+
info && info.userId === "user-42");
|
|
448
|
+
softCheck("session(mysql): verify unseals the data payload",
|
|
449
|
+
info && info.data && info.data.roles && info.data.roles[0] === "admin" &&
|
|
450
|
+
info.data.theme === "dark");
|
|
451
|
+
softCheck("session(mysql): verify coerces createdAt/expiresAt to JS numbers " +
|
|
452
|
+
"(BIGINT-as-string would break the timeout math)",
|
|
453
|
+
info && typeof info.createdAt === "number" && typeof info.expiresAt === "number" &&
|
|
454
|
+
info.expiresAt > info.createdAt);
|
|
455
|
+
|
|
456
|
+
// count -> the live session is counted (COUNT(*) BIGINT coerced to number).
|
|
457
|
+
var liveCount = await b.session.count();
|
|
458
|
+
softCheck("session(mysql): count() returns the one live session as a JS number",
|
|
459
|
+
typeof liveCount === "number" && liveCount === 1);
|
|
460
|
+
|
|
461
|
+
// touch with extendBy -> bumps lastActivity + expiresAt; affectedRows>0.
|
|
462
|
+
var touched = await b.session.touch(created.token, { extendBy: b.constants.TIME.hours(12) });
|
|
463
|
+
softCheck("session(mysql): touch() updated the live row (returned true)", touched === true);
|
|
464
|
+
var afterTouch = await b.session.verify(created.token);
|
|
465
|
+
softCheck("session(mysql): touch extended expiresAt past the original",
|
|
466
|
+
afterTouch && afterTouch.expiresAt >= created.expiresAt);
|
|
467
|
+
|
|
468
|
+
// rotate -> new sid swapped atomically; old token no longer verifies.
|
|
469
|
+
var rotated = await b.session.rotate(created.token, { reason: "mfa" });
|
|
470
|
+
softCheck("session(mysql): rotate returns a fresh sealed token",
|
|
471
|
+
rotated && typeof rotated.token === "string" && rotated.token !== created.token);
|
|
472
|
+
softCheck("session(mysql): the OLD token no longer verifies after rotate",
|
|
473
|
+
(await b.session.verify(created.token)) === null);
|
|
474
|
+
var rotatedInfo = await b.session.verify(rotated.token);
|
|
475
|
+
softCheck("session(mysql): the NEW token verifies with the same userId",
|
|
476
|
+
rotatedInfo && rotatedInfo.userId === "user-42");
|
|
477
|
+
|
|
478
|
+
// updateData -> writes the sealed data column without rotating the sid.
|
|
479
|
+
var updated = await b.session.updateData(rotated.token, { roles: ["admin"], step: "mfa-done" });
|
|
480
|
+
softCheck("session(mysql): updateData wrote the sealed data column (returned true)",
|
|
481
|
+
updated === true);
|
|
482
|
+
var afterUpdate = await b.session.verify(rotated.token);
|
|
483
|
+
softCheck("session(mysql): updateData payload round-trips through the sealed MySQL column",
|
|
484
|
+
afterUpdate && afterUpdate.data && afterUpdate.data.step === "mfa-done");
|
|
485
|
+
|
|
486
|
+
// destroyAllForUser -> deletes via the derived userIdHash; count drops.
|
|
487
|
+
var revoked = await b.session.destroyAllForUser("user-42");
|
|
488
|
+
softCheck("session(mysql): destroyAllForUser deleted the session via userIdHash",
|
|
489
|
+
revoked === 1);
|
|
490
|
+
softCheck("session(mysql): the session no longer verifies after revoke-all",
|
|
491
|
+
(await b.session.verify(rotated.token)) === null);
|
|
492
|
+
softCheck("session(mysql): count() is 0 after revoke-all", (await b.session.count()) === 0);
|
|
493
|
+
|
|
494
|
+
// destroy single + idempotency.
|
|
495
|
+
var s2 = await b.session.create({ userId: "user-99", ttlMs: b.constants.TIME.hours(1) });
|
|
496
|
+
softCheck("session(mysql): destroy(token) returns true for a live session",
|
|
497
|
+
(await b.session.destroy(s2.token)) === true);
|
|
498
|
+
softCheck("session(mysql): destroy is idempotent (second destroy returns false)",
|
|
499
|
+
(await b.session.destroy(s2.token)) === false);
|
|
500
|
+
|
|
501
|
+
// purgeExpired side-effect: age a row directly, then sweep.
|
|
502
|
+
await b.session.create({ userId: "user-exp", ttlMs: b.constants.TIME.hours(1) });
|
|
503
|
+
_execMysql("UPDATE `_blamejs_sessions` SET `expiresAt` = 1 WHERE `expiresAt` > 1;");
|
|
504
|
+
var purged = await b.session.purgeExpired();
|
|
505
|
+
softCheck("session(mysql): purgeExpired removed the expired row(s) (>=1)", purged >= 1);
|
|
506
|
+
softCheck("session(mysql): count() is 0 after purge", (await b.session.count()) === 0);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// ======================================================================
|
|
510
|
+
// b.nonceStore cluster backend on real MySQL.
|
|
511
|
+
// checkAndInsert (ON DUPLICATE KEY UPDATE no-op fold = atomic
|
|
512
|
+
// first-seen) + replay rejection + purgeExpired. The MySQL no-op fold
|
|
513
|
+
// returns affectedRows=1 for a fresh insert (won) and 0 for a duplicate
|
|
514
|
+
// (replay), which is the wire signal checkAndInsert reads.
|
|
515
|
+
// ======================================================================
|
|
516
|
+
async function _testNonceCluster() {
|
|
517
|
+
var store = b.nonceStore.create({ backend: "cluster" });
|
|
518
|
+
var future = Date.now() + b.constants.TIME.minutes(10);
|
|
519
|
+
|
|
520
|
+
softCheck("nonce(mysql): first checkAndInsert returns true (unseen)",
|
|
521
|
+
(await store.checkAndInsert("nonce-aaa", future)) === true);
|
|
522
|
+
var n1 = _selectDirect(
|
|
523
|
+
"SELECT COUNT(*) AS `n` FROM `_blamejs_api_encrypt_nonces` WHERE `nonceHash` = 'nonce-aaa';");
|
|
524
|
+
softCheck("nonce(mysql): the nonce row physically landed",
|
|
525
|
+
n1.length === 1 && Number(n1[0].n) === 1);
|
|
526
|
+
|
|
527
|
+
softCheck("nonce(mysql): replay of the same nonce returns false (DUPLICATE-KEY no-op fold)",
|
|
528
|
+
(await store.checkAndInsert("nonce-aaa", future)) === false);
|
|
529
|
+
softCheck("nonce(mysql): a distinct nonce is accepted",
|
|
530
|
+
(await store.checkAndInsert("nonce-bbb", future)) === true);
|
|
531
|
+
|
|
532
|
+
await store.checkAndInsert("nonce-expired", Date.now() - 1000);
|
|
533
|
+
var purged = await store.purgeExpired();
|
|
534
|
+
softCheck("nonce(mysql): purgeExpired removed the expired nonce (>=1)", purged >= 1);
|
|
535
|
+
softCheck("nonce(mysql): a live nonce still rejects replay after purge",
|
|
536
|
+
(await store.checkAndInsert("nonce-aaa", future)) === false);
|
|
537
|
+
|
|
538
|
+
store.close();
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ======================================================================
|
|
542
|
+
// b.middleware.rateLimit cluster backend on real MySQL.
|
|
543
|
+
// take() = ON DUPLICATE KEY UPDATE with a per-column CASE conflict
|
|
544
|
+
// action (proposed row = VALUES(`col`), existing row = `table`.`col`)
|
|
545
|
+
// + a readback SELECT (MySQL has no RETURNING). The returned BIGINT
|
|
546
|
+
// count must coerce to a JS number so count<=limit is numeric.
|
|
547
|
+
// ======================================================================
|
|
548
|
+
async function _testRateLimitCluster() {
|
|
549
|
+
var backend = rateLimitModule._clusterBackend({
|
|
550
|
+
backend: "cluster", limit: 3, windowMs: b.constants.TIME.minutes(1),
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
var v1 = await backend.take("ratekey-1", 1);
|
|
554
|
+
softCheck("rate-limit(mysql): first take() is allowed against real MySQL",
|
|
555
|
+
v1 && v1.allowed === true);
|
|
556
|
+
softCheck("rate-limit(mysql): the take() verdict count math is numeric " +
|
|
557
|
+
"(remaining is a finite number, not NaN from a string compare)",
|
|
558
|
+
typeof v1.remaining === "number" && isFinite(v1.remaining) && v1.remaining === 2);
|
|
559
|
+
|
|
560
|
+
var rowAfter1 = _selectDirect(
|
|
561
|
+
"SELECT `count` FROM `_blamejs_rate_limit_counters` WHERE `key` = 'ratekey-1';");
|
|
562
|
+
softCheck("rate-limit(mysql): counter row landed with count=1",
|
|
563
|
+
rowAfter1.length === 1 && Number(rowAfter1[0].count) === 1);
|
|
564
|
+
|
|
565
|
+
var v2 = await backend.take("ratekey-1", 1);
|
|
566
|
+
var v3 = await backend.take("ratekey-1", 1);
|
|
567
|
+
var v4 = await backend.take("ratekey-1", 1);
|
|
568
|
+
softCheck("rate-limit(mysql): 2nd + 3rd allowed, 4th over the limit refused",
|
|
569
|
+
v2.allowed === true && v3.allowed === true && v4.allowed === false);
|
|
570
|
+
softCheck("rate-limit(mysql): the over-limit verdict carries a positive retryAfter",
|
|
571
|
+
typeof v4.retryAfter === "number" && v4.retryAfter > 0);
|
|
572
|
+
var rowAfter4 = _selectDirect(
|
|
573
|
+
"SELECT `count` FROM `_blamejs_rate_limit_counters` WHERE `key` = 'ratekey-1';");
|
|
574
|
+
softCheck("rate-limit(mysql): counter incremented monotonically to 4 (same-window CASE branch)",
|
|
575
|
+
rowAfter4.length === 1 && Number(rowAfter4[0].count) === 4);
|
|
576
|
+
|
|
577
|
+
// A window advance resets the count (the CASE conflict action's
|
|
578
|
+
// window-rollover branch). Force a stale window then take() again.
|
|
579
|
+
_execMysql("UPDATE `_blamejs_rate_limit_counters` SET `windowStart` = 0 WHERE `key` = 'ratekey-1';");
|
|
580
|
+
var vReset = await backend.take("ratekey-1", 1);
|
|
581
|
+
softCheck("rate-limit(mysql): a fresh window resets the count (CASE rollover) — allowed again",
|
|
582
|
+
vReset.allowed === true);
|
|
583
|
+
var rowAfterReset = _selectDirect(
|
|
584
|
+
"SELECT `count` FROM `_blamejs_rate_limit_counters` WHERE `key` = 'ratekey-1';");
|
|
585
|
+
softCheck("rate-limit(mysql): count reset to 1 on window advance (proposed-window CASE branch)",
|
|
586
|
+
rowAfterReset.length === 1 && Number(rowAfterReset[0].count) === 1);
|
|
587
|
+
|
|
588
|
+
// A distinct key is tracked independently.
|
|
589
|
+
var other = await backend.take("ratekey-2", 1);
|
|
590
|
+
softCheck("rate-limit(mysql): a distinct key is counted independently",
|
|
591
|
+
other.allowed === true && other.remaining === 2);
|
|
592
|
+
|
|
593
|
+
// reset(key) deletes the counter row.
|
|
594
|
+
await backend.reset("ratekey-1");
|
|
595
|
+
var afterReset = _selectDirect(
|
|
596
|
+
"SELECT COUNT(*) AS `n` FROM `_blamejs_rate_limit_counters` WHERE `key` = 'ratekey-1';");
|
|
597
|
+
softCheck("rate-limit(mysql): reset(key) deleted the counter row",
|
|
598
|
+
afterReset.length === 1 && Number(afterReset[0].n) === 0);
|
|
599
|
+
|
|
600
|
+
if (typeof backend.close === "function") backend.close();
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
module.exports = { run: run };
|
|
604
|
+
|
|
605
|
+
if (require.main === module) {
|
|
606
|
+
run().then(
|
|
607
|
+
function () { console.log("OK — " + helpers.getChecks() + " checks passed"); process.exit(0); },
|
|
608
|
+
function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
|
|
609
|
+
);
|
|
610
|
+
}
|