@blamejs/blamejs-shop 0.4.31 → 0.4.33

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.
Files changed (343) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -1
  3. package/lib/asset-manifest.json +1 -1
  4. package/lib/vendor/MANIFEST.json +400 -282
  5. package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
  6. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
  7. package/lib/vendor/blamejs/.gitignore +6 -0
  8. package/lib/vendor/blamejs/CHANGELOG.md +28 -0
  9. package/lib/vendor/blamejs/MIGRATING.md +55 -0
  10. package/lib/vendor/blamejs/README.md +8 -6
  11. package/lib/vendor/blamejs/SECURITY.md +19 -3
  12. package/lib/vendor/blamejs/api-snapshot.json +2190 -664
  13. package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
  14. package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
  15. package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
  16. package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
  17. package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
  18. package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
  19. package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
  20. package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
  21. package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
  22. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
  23. package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
  24. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
  25. package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
  26. package/lib/vendor/blamejs/index.js +4 -0
  27. package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
  28. package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
  29. package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
  30. package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
  31. package/lib/vendor/blamejs/lib/api-key.js +158 -77
  32. package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
  33. package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
  34. package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
  35. package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
  36. package/lib/vendor/blamejs/lib/audit.js +259 -123
  37. package/lib/vendor/blamejs/lib/auth/elevation-grant.js +6 -2
  38. package/lib/vendor/blamejs/lib/auth/oauth.js +66 -9
  39. package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
  40. package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
  41. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +36 -7
  42. package/lib/vendor/blamejs/lib/backup/index.js +45 -10
  43. package/lib/vendor/blamejs/lib/break-glass.js +355 -147
  44. package/lib/vendor/blamejs/lib/cache.js +174 -105
  45. package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
  46. package/lib/vendor/blamejs/lib/cli.js +19 -14
  47. package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
  48. package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
  49. package/lib/vendor/blamejs/lib/cluster.js +119 -71
  50. package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
  51. package/lib/vendor/blamejs/lib/compliance.js +210 -4
  52. package/lib/vendor/blamejs/lib/consent.js +82 -29
  53. package/lib/vendor/blamejs/lib/constants.js +27 -11
  54. package/lib/vendor/blamejs/lib/credential-hash.js +9 -0
  55. package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
  56. package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
  57. package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
  58. package/lib/vendor/blamejs/lib/db-query.js +882 -260
  59. package/lib/vendor/blamejs/lib/db-schema.js +228 -44
  60. package/lib/vendor/blamejs/lib/db.js +249 -99
  61. package/lib/vendor/blamejs/lib/dsr.js +385 -55
  62. package/lib/vendor/blamejs/lib/error-page.js +14 -1
  63. package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
  64. package/lib/vendor/blamejs/lib/external-db.js +549 -34
  65. package/lib/vendor/blamejs/lib/file-upload.js +52 -7
  66. package/lib/vendor/blamejs/lib/framework-error.js +20 -1
  67. package/lib/vendor/blamejs/lib/framework-files.js +73 -0
  68. package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
  69. package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
  70. package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
  71. package/lib/vendor/blamejs/lib/guard-all.js +1 -0
  72. package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
  73. package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
  74. package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
  75. package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
  76. package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
  77. package/lib/vendor/blamejs/lib/guard-email.js +47 -69
  78. package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
  79. package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
  80. package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
  81. package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
  82. package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
  83. package/lib/vendor/blamejs/lib/guard-html.js +53 -108
  84. package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
  85. package/lib/vendor/blamejs/lib/guard-image.js +46 -103
  86. package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
  87. package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
  88. package/lib/vendor/blamejs/lib/guard-json.js +38 -108
  89. package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
  90. package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
  91. package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
  92. package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
  93. package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
  94. package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
  95. package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
  96. package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
  97. package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
  98. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
  99. package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
  100. package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
  101. package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
  102. package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
  103. package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
  104. package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
  105. package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
  106. package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
  107. package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
  108. package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
  109. package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
  110. package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
  111. package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
  112. package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
  113. package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
  114. package/lib/vendor/blamejs/lib/guard-template.js +35 -172
  115. package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
  116. package/lib/vendor/blamejs/lib/guard-time.js +32 -154
  117. package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
  118. package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
  119. package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
  120. package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
  121. package/lib/vendor/blamejs/lib/http-client.js +37 -9
  122. package/lib/vendor/blamejs/lib/inbox.js +120 -107
  123. package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
  124. package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
  125. package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
  126. package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
  127. package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
  128. package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
  129. package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
  130. package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
  131. package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
  132. package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
  133. package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
  134. package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
  135. package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
  136. package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
  137. package/lib/vendor/blamejs/lib/mail-store.js +293 -154
  138. package/lib/vendor/blamejs/lib/mail.js +8 -4
  139. package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
  140. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
  141. package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
  142. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
  143. package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
  144. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
  145. package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
  146. package/lib/vendor/blamejs/lib/migrations.js +108 -66
  147. package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
  148. package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
  149. package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
  150. package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
  151. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
  152. package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
  153. package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
  154. package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
  155. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
  156. package/lib/vendor/blamejs/lib/observability.js +124 -0
  157. package/lib/vendor/blamejs/lib/otel-export.js +12 -3
  158. package/lib/vendor/blamejs/lib/outbox.js +184 -83
  159. package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
  160. package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
  161. package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
  162. package/lib/vendor/blamejs/lib/queue-local.js +225 -140
  163. package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
  164. package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
  165. package/lib/vendor/blamejs/lib/queue.js +7 -0
  166. package/lib/vendor/blamejs/lib/redact.js +68 -11
  167. package/lib/vendor/blamejs/lib/redis-client.js +160 -31
  168. package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
  169. package/lib/vendor/blamejs/lib/retention.js +117 -42
  170. package/lib/vendor/blamejs/lib/router.js +212 -5
  171. package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
  172. package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
  173. package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
  174. package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
  175. package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
  176. package/lib/vendor/blamejs/lib/safe-url.js +170 -3
  177. package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
  178. package/lib/vendor/blamejs/lib/scheduler.js +47 -12
  179. package/lib/vendor/blamejs/lib/seeders.js +122 -74
  180. package/lib/vendor/blamejs/lib/session-stores.js +42 -14
  181. package/lib/vendor/blamejs/lib/session.js +175 -77
  182. package/lib/vendor/blamejs/lib/sql.js +3842 -0
  183. package/lib/vendor/blamejs/lib/sse.js +26 -0
  184. package/lib/vendor/blamejs/lib/ssrf-guard.js +169 -4
  185. package/lib/vendor/blamejs/lib/static.js +177 -34
  186. package/lib/vendor/blamejs/lib/subject.js +96 -49
  187. package/lib/vendor/blamejs/lib/vault/index.js +3 -2
  188. package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
  189. package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
  190. package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
  191. package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
  192. package/lib/vendor/blamejs/lib/websocket.js +35 -5
  193. package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
  194. package/lib/vendor/blamejs/package.json +2 -2
  195. package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
  196. package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
  197. package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
  198. package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
  199. package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
  200. package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
  201. package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
  202. package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
  203. package/lib/vendor/blamejs/release-notes/v0.15.7.json +43 -0
  204. package/lib/vendor/blamejs/scripts/check-services.js +21 -0
  205. package/lib/vendor/blamejs/scripts/gen-migrating.js +67 -0
  206. package/lib/vendor/blamejs/scripts/release.js +398 -38
  207. package/lib/vendor/blamejs/test/00-primitives.js +168 -0
  208. package/lib/vendor/blamejs/test/10-state.js +140 -14
  209. package/lib/vendor/blamejs/test/20-db.js +65 -2
  210. package/lib/vendor/blamejs/test/helpers/db.js +9 -0
  211. package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
  212. package/lib/vendor/blamejs/test/helpers/services.js +21 -0
  213. package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
  214. package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
  215. package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
  216. package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
  217. package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
  218. package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
  219. package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
  220. package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
  221. package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
  222. package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
  223. package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
  224. package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
  225. package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
  226. package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
  227. package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
  228. package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
  229. package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
  230. package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
  231. package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
  232. package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
  233. package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
  234. package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
  235. package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
  236. package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
  237. package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
  238. package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
  239. package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
  240. package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
  241. package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
  242. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
  243. package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
  244. package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
  245. package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
  246. package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
  247. package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
  248. package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
  249. package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
  250. package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
  251. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
  252. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1196 -14
  253. package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
  254. package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +18 -0
  255. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
  256. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
  257. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
  258. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
  259. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
  260. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
  261. package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
  262. package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
  263. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
  264. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
  265. package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
  266. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
  267. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
  268. package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
  269. package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
  270. package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
  271. package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
  272. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
  273. package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
  274. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
  275. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
  276. package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
  277. package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
  278. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
  279. package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
  280. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
  281. package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
  282. package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
  283. package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
  284. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
  285. package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
  286. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
  287. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
  288. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
  289. package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
  290. package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
  291. package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
  292. package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
  293. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
  294. package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
  295. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
  296. package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
  297. package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
  298. package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
  299. package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
  300. package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +59 -0
  301. package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
  302. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +362 -0
  303. package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
  304. package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
  305. package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-watchdog-stale-settle.test.js +71 -0
  306. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
  307. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
  308. package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
  309. package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
  310. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
  311. package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
  312. package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
  313. package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
  314. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
  315. package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
  316. package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
  317. package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
  318. package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
  319. package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
  320. package/lib/vendor/blamejs/test/smoke.js +79 -21
  321. package/package.json +2 -2
  322. package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
  323. package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
  324. package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
  325. package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
  326. package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
  327. package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
  328. package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
  329. package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
  330. package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
  331. package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
  332. package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
  333. package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
  334. package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
  335. package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
  336. package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
  337. package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
  338. package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
  339. package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
  340. package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
  341. package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
  342. package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
  343. package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ /**
3
+ * Live SQS queue-backend round-trip — exercises lib/queue-sqs.js against
4
+ * LocalStack's SQS API (AWSJsonProtocol_1.0) over TLS.
5
+ *
6
+ * What this proves (real, end-to-end against a live SQS server):
7
+ * - The adapter's SendMessage wire shape is correct: X-Amz-Target
8
+ * AmazonSQS.SendMessage, Content-Type application/x-amz-json-1.0, a
9
+ * { QueueUrl, MessageBody } body — and the message actually lands in
10
+ * the queue (size() rises, ReceiveMessage returns it).
11
+ * - The sealed-envelope round-trip: enqueue seals the job row through
12
+ * cryptoField.sealRow("_blamejs_jobs", ...) before SendMessage; lease
13
+ * unseals it on receive. A structured payload round-trips
14
+ * field-identical through the SQS wire (the message body in transit is
15
+ * the sealed envelope, not the cleartext payload).
16
+ * - ReceiveMessage surfaces a ReceiptHandle + MessageId, and complete()
17
+ * (DeleteMessage by that handle) removes the message so it does NOT
18
+ * redeliver — the queue drains.
19
+ * - The visibility-timeout / redelivery path: a leased-but-not-deleted
20
+ * message becomes invisible for VisibilityTimeout, and fail()
21
+ * (ChangeMessageVisibility VisibilityTimeout=0) makes it immediately
22
+ * visible again so another consumer re-leases it.
23
+ * - size() (GetQueueAttributes ApproximateNumberOfMessages) and purge()
24
+ * (PurgeQueue) hit the wire and reflect the queue state.
25
+ * - The endpoint override (cfg.endpoint / cfg.queueUrlByName) is
26
+ * honoured — requests reach 127.0.0.1:4566, not
27
+ * sqs.<region>.amazonaws.com.
28
+ *
29
+ * Scope honesty — what this does NOT prove:
30
+ * LocalStack accepts the test credentials and does NOT verify the SigV4
31
+ * signature (it ignores the secret key). So this proves the SQS wire /
32
+ * marshalling + the send/receive/delete/ack + visibility-timeout flow,
33
+ * NOT signature correctness. (Signature correctness against a server
34
+ * that DOES verify SigV4 is covered by object-store-sigv4.test.js
35
+ * against MinIO.)
36
+ *
37
+ * No security bypass: TLS to LocalStack trusts the test CA via
38
+ * NODE_EXTRA_CA_CERTS (exported by scripts/test-integration.js).
39
+ * rejectUnauthorized stays on; allowInternal:true only permits the
40
+ * loopback host, it does not disable verification. The job payload is
41
+ * sealed at rest in the queue (vault key + framework crypto stack) — the
42
+ * test registers the seal table and inits the vault exactly as a queue
43
+ * node would.
44
+ *
45
+ * To run:
46
+ * docker compose -f docker-compose.test.yml up -d --wait
47
+ * node scripts/test-integration.js --skip-service-check queue-sqs
48
+ */
49
+ var fs = require("node:fs");
50
+ var os = require("node:os");
51
+ var path = require("node:path");
52
+ var nodeCrypto = require("node:crypto");
53
+ var helpers = require("../helpers");
54
+ var check = helpers.check;
55
+ var services = require("../helpers/services");
56
+ var b = require("../../");
57
+
58
+ var queueSqs = require("../../lib/queue-sqs");
59
+ var cryptoField = require("../../lib/crypto-field");
60
+ var sigv4 = require("../../lib/object-store/sigv4");
61
+ var httpClient = require("../../lib/http-client");
62
+ var safeUrl = require("../../lib/safe-url");
63
+
64
+ var REGION = "us-east-1";
65
+ var ACCESS = "test";
66
+ var SECRET = "test";
67
+
68
+ // ---- raw SigV4-signed SQS control-plane helper ----
69
+ // The adapter intentionally has no CreateQueue / DeleteQueue (queue
70
+ // lifecycle is operator/IaC territory), so the test stands the queue up
71
+ // and tears it down with its own signed AWSJsonProtocol_1.0 calls — the
72
+ // same signer the adapter uses. Returns the parsed JSON body; throws on a
73
+ // non-2xx so the caller sees the AWS exception text.
74
+ function _sqsCall(endpoint, action, payload) {
75
+ var body = Buffer.from(JSON.stringify(payload || {}), "utf8");
76
+ var payloadHash = nodeCrypto.createHash("sha256").update(body).digest("hex");
77
+ var signed = sigv4.signRequest({
78
+ method: "POST",
79
+ url: endpoint,
80
+ headers: {
81
+ "Content-Type": "application/x-amz-json-1.0",
82
+ "X-Amz-Target": "AmazonSQS." + action,
83
+ },
84
+ payloadHash: payloadHash,
85
+ region: REGION,
86
+ service: "sqs",
87
+ accessKeyId: ACCESS,
88
+ secretAccessKey: SECRET,
89
+ allowedProtocols: safeUrl.ALLOW_HTTP_TLS,
90
+ });
91
+ return httpClient.request({
92
+ method: "POST",
93
+ url: endpoint,
94
+ headers: signed.headers,
95
+ body: body,
96
+ allowInternal: true,
97
+ allowedProtocols: safeUrl.ALLOW_HTTP_TLS,
98
+ }).then(function (res) {
99
+ var text = Buffer.isBuffer(res.body) ? res.body.toString("utf8")
100
+ : (res.body || "").toString();
101
+ return text.length ? JSON.parse(text) : {};
102
+ });
103
+ }
104
+
105
+ async function run() {
106
+ var ls = await services.requireService("localstack");
107
+ if (!ls.ok) throw new Error("localstack unreachable: " + ls.reason);
108
+
109
+ var endpoint = services.URLS.localstack; // https://127.0.0.1:4566
110
+
111
+ // ---- 0) framework-side seal wiring ----
112
+ // The SQS adapter seals the job row through cryptoField.sealRow(
113
+ // "_blamejs_jobs", ...) before SendMessage and unsealRow()s it on
114
+ // receive. Without registering the table + a live vault key, sealRow is
115
+ // a no-op pass-through and the round-trip wouldn't actually exercise the
116
+ // seal/unseal envelope — so init the vault + register the table exactly
117
+ // as a standalone SQS queue node would (queue.init() calls
118
+ // _ensureSealTable() for the same reason).
119
+ var dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-queue-sqs-"));
120
+ if (typeof b.vault._resetForTest === "function") b.vault._resetForTest();
121
+ await b.vault.init({ dataDir: dataDir, mode: "plaintext" });
122
+ cryptoField.registerTable("_blamejs_jobs", { sealedFields: ["payload", "lastError"] });
123
+
124
+ // ---- 1) create a unique queue out-of-band ----
125
+ // LocalStack's default AWS account is 000000000000; CreateQueue echoes
126
+ // an AWS-shaped advertised URL (sqs.us-east-1.localhost.localstack.cloud),
127
+ // but the adapter routes every action to cfg.endpoint and only carries
128
+ // the QueueUrl in the JSON body — LocalStack accepts the path-style
129
+ // endpoint+accountId+name URL the adapter's own resolver synthesizes.
130
+ var ACCOUNT_ID = "000000000000";
131
+ var queueName = "blamejs-sqs-test-" + Date.now() + "-" + Math.floor(Math.random() * 1e6);
132
+ var created = await _sqsCall(endpoint, "CreateQueue", { QueueName: queueName });
133
+ var advertisedUrl = created && created.QueueUrl;
134
+ check("CreateQueue: returned a QueueUrl", typeof advertisedUrl === "string" && advertisedUrl.length > 0);
135
+ check("CreateQueue: QueueUrl carries the queue name", advertisedUrl.indexOf(queueName) !== -1);
136
+
137
+ // ---- 2) build the adapter pointed at LocalStack ----
138
+ // No queueUrlByName override: this exercises the adapter's built-in
139
+ // endpoint + accountId + name URL synthesis (the single-account default
140
+ // path), so the test proves that path too — not just a hand-fed resolver.
141
+ var q = queueSqs.create({
142
+ endpoint: endpoint,
143
+ region: REGION,
144
+ accountId: ACCOUNT_ID,
145
+ accessKeyId: ACCESS,
146
+ secretAccessKey: SECRET,
147
+ allowInternal: true,
148
+ allowedProtocols: safeUrl.ALLOW_HTTP_TLS,
149
+ });
150
+
151
+ // endpoint override honoured — the synthesized queue URL points at the
152
+ // LocalStack host, not the AWS regional host.
153
+ check("endpoint override honoured (queue URL is the LocalStack host, not sqs.<region>.amazonaws.com)",
154
+ q._queueUrl(queueName).indexOf("127.0.0.1:4566") !== -1 &&
155
+ q._queueUrl(queueName).indexOf("amazonaws.com") === -1);
156
+
157
+ try {
158
+ // ---- 3) enqueue → receive round-trip + sealed-payload fidelity ----
159
+ var Q = queueName;
160
+ var payload = {
161
+ kind: "report.generate",
162
+ orderId: "ord-" + Math.floor(Math.random() * 1e9),
163
+ nested: { tags: ["alpha", "beta"], count: 3, flag: true },
164
+ unicode: "café — naïve — 日本語",
165
+ amount: 1234.56,
166
+ };
167
+ var sentId = await q.enqueue(Q, payload, { traceId: "t-sqs-1" });
168
+ check("enqueue: SendMessage returned a message id", typeof sentId === "string" && sentId.length > 0);
169
+
170
+ // size() reflects the just-enqueued message (ApproximateNumberOfMessages
171
+ // is eventually-consistent on real SQS; LocalStack is prompt — poll).
172
+ var sz1 = await helpers.waitUntil(async function () {
173
+ var n = await q.size(Q);
174
+ return n >= 1 ? n : false;
175
+ }, { timeoutMs: 8000, label: "queue-sqs: size() reflects the enqueued message" });
176
+ check("size: >= 1 after enqueue", sz1 >= 1);
177
+
178
+ // Receive it. The body on the wire is the sealed envelope; lease()
179
+ // unseals it back to the structured payload.
180
+ var leased = await helpers.waitUntil(async function () {
181
+ var rv = await q.lease(Q, { maxRows: 5, waitTimeSec: 1, visibilityTimeoutSec: 30 });
182
+ return rv.length >= 1 ? rv : false;
183
+ }, { timeoutMs: 10000, label: "queue-sqs: ReceiveMessage returns the enqueued job" });
184
+ check("lease: ReceiveMessage returned exactly one job", leased.length === 1);
185
+
186
+ var job = leased[0];
187
+ check("lease: surfaces a ReceiptHandle (the only key for delete/visibility)",
188
+ typeof job.receiptHandle === "string" && job.receiptHandle.length > 0);
189
+ check("lease: surfaces the SQS MessageId",
190
+ typeof job.sqsMessageId === "string" && job.sqsMessageId.length > 0);
191
+ check("lease: jobId present (the framework job id, not the SQS id)",
192
+ typeof job.jobId === "string" && job.jobId.length > 0);
193
+ check("lease: queueName round-tripped through the sealed row",
194
+ job.queueName === Q);
195
+
196
+ // The headline proof: structured payload survives the seal → SQS wire
197
+ // → unseal round-trip byte/field-identical (deep equality).
198
+ check("lease: payload round-trips field-identical through seal + SQS wire",
199
+ JSON.stringify(job.payload) === JSON.stringify(payload));
200
+ check("lease: nested object fields intact",
201
+ job.payload && job.payload.nested &&
202
+ job.payload.nested.count === 3 && job.payload.nested.flag === true &&
203
+ job.payload.nested.tags.length === 2 &&
204
+ job.payload.nested.tags[0] === "alpha" && job.payload.nested.tags[1] === "beta");
205
+ check("lease: unicode payload byte-identical (no mojibake on the wire)",
206
+ job.payload.unicode === "café — naïve — 日本語");
207
+ check("lease: numeric field preserved (not stringified)",
208
+ job.payload.amount === 1234.56 && job.payload.orderId === payload.orderId);
209
+
210
+ // ---- 4) ack (DeleteMessage) → does NOT redeliver, queue drains ----
211
+ var compRv = await q.complete(Q, job.jobId, { receiptHandle: job.receiptHandle });
212
+ check("complete: DeleteMessage returned true", compRv === true);
213
+
214
+ // After delete, the message must not come back. Long-poll a couple of
215
+ // times with a fresh (short) visibility window; nothing should appear.
216
+ var redelivered = await q.lease(Q, { maxRows: 5, waitTimeSec: 2, visibilityTimeoutSec: 5 });
217
+ check("complete: deleted message does NOT redeliver (queue drained)",
218
+ redelivered.length === 0);
219
+
220
+ // NOTE on size() semantics: ApproximateNumberOfMessages counts only
221
+ // VISIBLE messages — a leased-but-not-deleted message is in-flight
222
+ // (NotVisible) and reports 0 here. So 0 after complete confirms the
223
+ // message is neither visible nor in-flight. (waitUntil treats a falsy
224
+ // return as "not ready", so the predicate returns true on success,
225
+ // not the numeric 0.)
226
+ var szDrained = await helpers.waitUntil(async function () {
227
+ var n = await q.size(Q);
228
+ return n === 0 ? true : false;
229
+ }, { timeoutMs: 8000, label: "queue-sqs: size() back to 0 after complete" });
230
+ check("size: 0 after complete (no in-flight, no visible)", szDrained === true);
231
+
232
+ // ---- 5) visibility-timeout / redelivery path via fail() ----
233
+ // Enqueue, lease with a long visibility timeout (so it would NOT come
234
+ // back on its own within the test window), then fail() — which sets
235
+ // VisibilityTimeout=0 and makes it immediately visible to the next
236
+ // consumer. Proves ChangeMessageVisibility re-delivery, the SQS-native
237
+ // retry the adapter relies on (server-side RedrivePolicy decides DLQ).
238
+ var redeliverPayload = { step: "retry-me", n: 7 };
239
+ await q.enqueue(Q, redeliverPayload, { traceId: "t-sqs-redeliver" });
240
+
241
+ var firstLease = await helpers.waitUntil(async function () {
242
+ var rv = await q.lease(Q, { maxRows: 1, waitTimeSec: 1, visibilityTimeoutSec: 300 });
243
+ return rv.length >= 1 ? rv : false;
244
+ }, { timeoutMs: 10000, label: "queue-sqs: redelivery job first lease" });
245
+ check("redelivery: first lease grabbed the job under a 300s visibility window",
246
+ firstLease.length === 1 && firstLease[0].payload &&
247
+ firstLease[0].payload.step === "retry-me");
248
+
249
+ // While the 300s visibility window is in effect, a second lease sees
250
+ // nothing — the message is in-flight (invisible).
251
+ var whileInflight = await q.lease(Q, { maxRows: 1, waitTimeSec: 1, visibilityTimeoutSec: 5 });
252
+ check("redelivery: message invisible to a second consumer during the visibility window",
253
+ whileInflight.length === 0);
254
+
255
+ // fail() → VisibilityTimeout=0 → immediately visible again.
256
+ var failRv = await q.fail(Q, firstLease[0].jobId, { receiptHandle: firstLease[0].receiptHandle });
257
+ check("fail: ChangeMessageVisibility(0) returned true", failRv === true);
258
+
259
+ var reLease = await helpers.waitUntil(async function () {
260
+ var rv = await q.lease(Q, { maxRows: 1, waitTimeSec: 2, visibilityTimeoutSec: 30 });
261
+ return rv.length >= 1 ? rv : false;
262
+ }, { timeoutMs: 12000, label: "queue-sqs: fail() re-delivered the job to a new consumer" });
263
+ check("redelivery: fail() made the message visible again (re-leased)",
264
+ reLease.length === 1 && reLease[0].payload &&
265
+ reLease[0].payload.step === "retry-me" && reLease[0].payload.n === 7);
266
+ check("redelivery: re-leased message carries a fresh receipt handle",
267
+ typeof reLease[0].receiptHandle === "string" &&
268
+ reLease[0].receiptHandle.length > 0);
269
+
270
+ // Clean it up so the purge leg starts from a known state.
271
+ await q.complete(Q, reLease[0].jobId, { receiptHandle: reLease[0].receiptHandle });
272
+
273
+ // ---- 6) extendLease (ChangeMessageVisibility to a longer window) ----
274
+ await q.enqueue(Q, { step: "extend-me" }, {});
275
+ var extLease = await helpers.waitUntil(async function () {
276
+ var rv = await q.lease(Q, { maxRows: 1, waitTimeSec: 1, visibilityTimeoutSec: 30 });
277
+ return rv.length >= 1 ? rv : false;
278
+ }, { timeoutMs: 10000, label: "queue-sqs: extendLease job leased" });
279
+ check("extendLease: job leased", extLease.length === 1);
280
+ var extRv = await q.extendLease(Q, extLease[0].jobId, {
281
+ receiptHandle: extLease[0].receiptHandle, visibilityTimeoutSec: 120,
282
+ });
283
+ check("extendLease: ChangeMessageVisibility(120) returned true", extRv === true);
284
+ await q.complete(Q, extLease[0].jobId, { receiptHandle: extLease[0].receiptHandle });
285
+
286
+ // ---- 7) purge clears the queue ----
287
+ // Re-fill, confirm size, purge, confirm drained. (PurgeQueue is
288
+ // 60s-rate-limited server-side; this test calls it once.)
289
+ await q.enqueue(Q, { p: 1 }, {});
290
+ await q.enqueue(Q, { p: 2 }, {});
291
+ await q.enqueue(Q, { p: 3 }, {});
292
+ var preSize = await helpers.waitUntil(async function () {
293
+ var n = await q.size(Q);
294
+ return n >= 3 ? n : false;
295
+ }, { timeoutMs: 8000, label: "queue-sqs: size() reflects 3 enqueued before purge" });
296
+ check("purge: pre-purge size >= 3", preSize >= 3);
297
+
298
+ var purgeRv = await q.purge(Q);
299
+ check("purge: PurgeQueue returned (count 0 — SQS doesn't report a count)", purgeRv === 0);
300
+
301
+ var postPurge = await helpers.waitUntil(async function () {
302
+ var n = await q.size(Q);
303
+ return n === 0 ? true : false;
304
+ }, { timeoutMs: 12000, label: "queue-sqs: size() back to 0 after PurgeQueue" });
305
+ check("purge: post-purge size 0", postPurge === true);
306
+
307
+ } finally {
308
+ // Tear the queue down (control-plane, out-of-band like creation).
309
+ try { await _sqsCall(endpoint, "DeleteQueue", { QueueUrl: advertisedUrl }); } catch (_e) {}
310
+ try { fs.rmSync(dataDir, { recursive: true, force: true }); } catch (_e) {}
311
+ if (typeof b.vault._resetForTest === "function") b.vault._resetForTest();
312
+ }
313
+ }
314
+
315
+ module.exports = { run: run };
316
+
317
+ if (require.main === module) {
318
+ run().then(
319
+ function () { console.log("OK — " + helpers.getChecks() + " checks passed"); process.exit(0); },
320
+ function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
321
+ );
322
+ }
@@ -0,0 +1,300 @@
1
+ "use strict";
2
+ /**
3
+ * Live reconnect / failover proof for lib/redis-client.js against a REAL
4
+ * redis dropped mid-connection via toxiproxy.
5
+ *
6
+ * The framework redis client points at toxiproxy's proxied redis
7
+ * (127.0.0.1:16379, upstream redis:6379). A working connection is
8
+ * established (SET/GET round-trips), then the proxy is DISABLED through
9
+ * the toxiproxy HTTP API (127.0.0.1:8474) so redis appears DOWN — every
10
+ * established connection is closed and new dials are refused. The test
11
+ * asserts:
12
+ *
13
+ * 1. An in-flight command issued during the outage REJECTS (with a
14
+ * transport / timeout error code) rather than hanging the caller
15
+ * forever — no caller wedge.
16
+ * 2. The client schedules a reconnect (the _state() reconnect machinery
17
+ * advances) — the lost socket drives the reconnect loop.
18
+ * 3. No reconnect storm: one outage schedules at most ONE pending timer
19
+ * at a time (the single-flight guard) — a socket failure surfacing
20
+ * as BOTH `error` and `close` must not stack two reconnects and burn
21
+ * the budget at 2x.
22
+ * 4. Re-enabling the proxy RECOVERS the client: it reconnects on its own
23
+ * and a subsequent SET/GET round-trips, with the backoff counter +
24
+ * give-up latch reset.
25
+ *
26
+ * Recovery is polled with helpers.waitUntil — no fixed sleep. Fault is
27
+ * injected over the toxiproxy HTTP control plane, not by killing the real
28
+ * redis (which other workflows may share); the upstream redis is never
29
+ * touched.
30
+ *
31
+ * Run:
32
+ * node scripts/test-integration.js --skip-service-check redis-reconnect-toxiproxy
33
+ */
34
+ var http = require("node:http");
35
+ var helpers = require("../helpers");
36
+ var check = helpers.check;
37
+ var services = require("../helpers/services");
38
+ var redisClient = require("../../lib/redis-client");
39
+
40
+ // toxiproxy control plane. The proxy named "redis" listens on :16379 and
41
+ // forwards to upstream redis:6379; flipping its `enabled` flag is the
42
+ // outage switch (disabled = existing conns closed + new dials refused).
43
+ var TOXIPROXY_API = services.URLS.toxiproxy; // http://127.0.0.1:8474
44
+ var PROXY_NAME = "redis";
45
+ var PROXIED_REDIS = services.URLS.toxiproxyRedis; // redis://127.0.0.1:16379
46
+
47
+ // Minimal toxiproxy HTTP client over node:http — POST a JSON body to the
48
+ // proxy endpoint to flip `enabled`. Returns the parsed proxy object.
49
+ function _toxiproxyRequest(method, urlPath, bodyObj) {
50
+ return new Promise(function (resolve, reject) {
51
+ var u = new URL(TOXIPROXY_API + urlPath);
52
+ var payload = bodyObj === undefined ? null : Buffer.from(JSON.stringify(bodyObj), "utf8");
53
+ var req = http.request({
54
+ host: u.hostname,
55
+ port: u.port,
56
+ path: u.pathname + (u.search || ""),
57
+ method: method,
58
+ headers: payload
59
+ ? { "content-type": "application/json", "content-length": payload.length }
60
+ : {},
61
+ }, function (res) {
62
+ var chunks = [];
63
+ res.on("data", function (c) { chunks.push(c); });
64
+ res.on("end", function () {
65
+ var raw = Buffer.concat(chunks).toString("utf8");
66
+ if (res.statusCode < 200 || res.statusCode >= 300) {
67
+ return reject(new Error("toxiproxy " + method + " " + urlPath +
68
+ " -> HTTP " + res.statusCode + ": " + raw));
69
+ }
70
+ var parsed = null;
71
+ try { parsed = raw ? JSON.parse(raw) : null; } catch (_e) { parsed = raw; }
72
+ resolve(parsed);
73
+ });
74
+ });
75
+ req.on("error", reject);
76
+ if (payload) req.write(payload);
77
+ req.end();
78
+ });
79
+ }
80
+
81
+ function _setProxyEnabled(enabled) {
82
+ return _toxiproxyRequest("POST", "/proxies/" + PROXY_NAME, { enabled: !!enabled });
83
+ }
84
+
85
+ async function run() {
86
+ // Probe both the proxied-redis port AND the toxiproxy control plane —
87
+ // either being down means we can't run the fault-injection proof.
88
+ var svcProxy = await services.requireService("toxiproxyRedis");
89
+ var svcApi = await services.requireService("toxiproxy");
90
+ if (!svcProxy.ok || !svcApi.ok) {
91
+ console.log(" [redis-reconnect] toxiproxy unreachable — skipping " +
92
+ "(" + (svcProxy.ok ? "" : svcProxy.reason + " ") + (svcApi.ok ? "" : svcApi.reason) + ")");
93
+ console.log(" [redis-reconnect] bring up: docker compose -f docker-compose.test.yml up -d --wait");
94
+ return;
95
+ }
96
+
97
+ // Always leave the proxy enabled when we exit, even on a mid-test
98
+ // failure — a left-disabled proxy would poison every later redis test.
99
+ var restored = false;
100
+ async function _restoreProxy() {
101
+ if (restored) return;
102
+ restored = true;
103
+ try { await _setProxyEnabled(true); } catch (_e) { /* best-effort */ }
104
+ }
105
+
106
+ var c = null;
107
+ try {
108
+ // Start from a known-good proxy state.
109
+ await _setProxyEnabled(true);
110
+
111
+ // Client through the proxy. A generous reconnect budget (the outage
112
+ // window spans several backoff steps) + short connect/command timeouts
113
+ // so an in-flight op against a dead proxy settles fast instead of
114
+ // sitting on the full default. db 15 isolates test data.
115
+ c = redisClient.create({
116
+ url: PROXIED_REDIS + "/15",
117
+ connectTimeoutMs: 1000,
118
+ commandTimeoutMs: 1500,
119
+ maxReconnectAttempts: 50,
120
+ });
121
+
122
+ // ---- 1. working connection ----
123
+ await c.connect();
124
+ check("connect: established through toxiproxy", c.isOpen());
125
+
126
+ var key = "blamejs:test:reconnect:" + Date.now();
127
+ var setRv = await c.command("SET", key, "before-outage");
128
+ check("pre-outage SET: returns OK",
129
+ setRv === "OK" || (Buffer.isBuffer(setRv) && setRv.toString() === "OK"));
130
+ var getRv = await c.command("GET", key);
131
+ check("pre-outage GET: round-trips",
132
+ Buffer.isBuffer(getRv) && getRv.toString() === "before-outage");
133
+
134
+ var stateBefore = c._state();
135
+ check("pre-outage _state: connected, no reconnect pending, attempt 0",
136
+ stateBefore.connected === true &&
137
+ stateBefore.reconnectPending === false &&
138
+ stateBefore.reconnect === 0);
139
+
140
+ // ---- 2. outage: disable the proxy. Existing conns are closed; new
141
+ // dials are refused. This drives _teardownSocket → drain
142
+ // pending + schedule reconnect. ----
143
+ await _setProxyEnabled(false);
144
+
145
+ // An in-flight op issued DURING the outage must reject, not wedge.
146
+ // Whether the socket-drop reaches the client before or after this
147
+ // write, the command settles with an error (drained pending, write
148
+ // failure, queued-timeout, or command-timeout) — never an unsettled
149
+ // await. Bound the whole thing well under any hang with waitUntil so
150
+ // a regression that DOES wedge fails the gate as a timeout instead of
151
+ // hanging the suite.
152
+ var inflightErr = null;
153
+ var inflightResolved = false;
154
+ var inflightSettled = false;
155
+ c.command("GET", key).then(
156
+ function () { inflightResolved = true; inflightSettled = true; },
157
+ function (e) { inflightErr = e; inflightSettled = true; }
158
+ );
159
+ await helpers.waitUntil(function () { return inflightSettled; }, {
160
+ timeoutMs: 8000,
161
+ label: "redis-reconnect: in-flight op settles during outage (no caller wedge)",
162
+ });
163
+ check("in-flight op during outage SETTLES (no wedge)", inflightSettled === true);
164
+ check("in-flight op during outage REJECTS with a transport/timeout error",
165
+ inflightResolved === false &&
166
+ inflightErr !== null && typeof inflightErr.code === "string");
167
+
168
+ // ---- 3. the lost socket scheduled a reconnect. The client is now
169
+ // disconnected and the reconnect machinery is engaged
170
+ // (a timer pending and/or attempts advancing). ----
171
+ var reconnecting = await helpers.waitUntil(function () {
172
+ var s = c._state();
173
+ // Engaged = no longer connected AND (a backoff timer is pending OR a
174
+ // dial is mid-flight OR at least one attempt has been counted).
175
+ if (s.connected) return false;
176
+ return (s.reconnectPending || s.connecting || s.reconnect > 0) ? s : false;
177
+ }, {
178
+ timeoutMs: 8000,
179
+ label: "redis-reconnect: client schedules a reconnect after the socket drop",
180
+ });
181
+ check("outage: client is disconnected", reconnecting.connected === false);
182
+ check("outage: a reconnect is engaged (pending timer / dialing / attempt counted)",
183
+ reconnecting.reconnectPending === true ||
184
+ reconnecting.connecting === true ||
185
+ reconnecting.reconnect > 0);
186
+ check("outage: did NOT give up (budget not exhausted mid-outage)",
187
+ reconnecting.gaveUp === false);
188
+
189
+ // ---- 4. no reconnect storm. Single-flight means: at any instant at
190
+ // most ONE backoff timer is pending, and the attempt counter
191
+ // advances monotonically by the dial cadence — it must not
192
+ // leap by 2 from a single socket failure surfacing as both
193
+ // `error` and `close`. The first retry backoff is 100ms, so
194
+ // sampling at 20ms (provably below the minimum inter-dial gap)
195
+ // means at most ONE dial can complete per sample window; a
196
+ // per-sample attempt delta of 2 would prove two reconnects
197
+ // fired for one window — a storm / double-schedule. We sample
198
+ // across ~2.4s, which spans the early small-backoff steps
199
+ // (100/200/400/800ms) where stacking would show first. ----
200
+ var maxDelta = 0;
201
+ var prevAttempt = c._state().reconnect;
202
+ var sawNonBoolPending = false; // (structurally a boolean — timer
203
+ // !== null — but assert it: a count
204
+ // here would mean stacked timers)
205
+ for (var i = 0; i < 120; i++) {
206
+ var s = c._state();
207
+ var delta = s.reconnect - prevAttempt;
208
+ if (delta > maxDelta) maxDelta = delta;
209
+ prevAttempt = s.reconnect;
210
+ if (s.reconnectPending !== true && s.reconnectPending !== false) sawNonBoolPending = true;
211
+ await helpers.passiveObserve(20, "redis-reconnect storm-watch sample " + i);
212
+ }
213
+ check("no reconnect storm: attempts advance by <= 1 per 20ms sample (single-flight, no double-schedule)",
214
+ maxDelta <= 1);
215
+ check("no reconnect storm: reconnectPending is always a single boolean (one timer max)",
216
+ sawNonBoolPending === false);
217
+
218
+ // ---- 5. recovery: re-enable the proxy. The next scheduled dial
219
+ // succeeds and the client reconnects on its own. ----
220
+ await _setProxyEnabled(true);
221
+
222
+ // Poll a SYNCHRONOUS predicate (_state().connected) for the reconnect.
223
+ // Awaiting c.command() inside the predicate would backlog the command
224
+ // behind the client's reconnect timer — and that timer is unref'd by
225
+ // design (a backoff window must not by itself keep the loop alive), so
226
+ // with no other ref'd handle the loop could drain mid-await and the
227
+ // process would exit before the reconnect fires. waitUntil's own 25ms
228
+ // poll timer IS ref'd, so a sync predicate keeps the loop alive on each
229
+ // tick and lets the unref'd reconnect timer fire — the same role a
230
+ // real app's listening server socket plays in production.
231
+ var recovered = await helpers.waitUntil(function () {
232
+ return c._state().connected ? c._state() : false;
233
+ }, {
234
+ timeoutMs: 20000,
235
+ label: "redis-reconnect: client reconnects on its own after proxy re-enabled",
236
+ });
237
+ check("recovery: client reconnected after outage", recovered.connected === true);
238
+ check("recovery: client reports open again", c.isOpen());
239
+
240
+ // Now that the socket is back, a fresh SET/GET must round-trip.
241
+ var setAfter = await c.command("SET", key, "after-recovery");
242
+ check("recovery: post-reconnect SET returns OK",
243
+ setAfter === "OK" || (Buffer.isBuffer(setAfter) && setAfter.toString() === "OK"));
244
+
245
+ var getAfter = await c.command("GET", key);
246
+ check("recovery: GET round-trips the post-recovery value",
247
+ Buffer.isBuffer(getAfter) && getAfter.toString() === "after-recovery");
248
+
249
+ var stateAfter = c._state();
250
+ check("recovery: reconnect counter reset to 0 on successful reconnect",
251
+ stateAfter.reconnect === 0);
252
+ check("recovery: give-up latch cleared", stateAfter.gaveUp === false);
253
+ check("recovery: no reconnect timer left pending", stateAfter.reconnectPending === false);
254
+
255
+ // ---- 6. the recovered connection survives a SECOND independent
256
+ // outage — proves the reconnect path is reusable, not a
257
+ // one-shot, and the budget genuinely reset. ----
258
+ await _setProxyEnabled(false);
259
+ await helpers.waitUntil(function () {
260
+ var s = c._state();
261
+ return (!s.connected && (s.reconnectPending || s.connecting || s.reconnect > 0)) ? s : false;
262
+ }, {
263
+ timeoutMs: 8000,
264
+ label: "redis-reconnect: second outage re-engages the reconnect loop",
265
+ });
266
+ check("second outage: reconnect loop re-engaged (path is reusable)", !c._state().connected);
267
+
268
+ await _setProxyEnabled(true);
269
+ // Sync predicate again (see the phase-5 note on unref'd reconnect timers).
270
+ await helpers.waitUntil(function () {
271
+ return c._state().connected ? c._state() : false;
272
+ }, {
273
+ timeoutMs: 20000,
274
+ label: "redis-reconnect: second recovery — client reconnects again",
275
+ });
276
+ check("second recovery: client reconnected", c.isOpen());
277
+ var getAfter2 = await c.command("GET", key);
278
+ check("second recovery: GET round-trips the prior value (path is reusable)",
279
+ Buffer.isBuffer(getAfter2) && getAfter2.toString() === "after-recovery");
280
+
281
+ // ---- cleanup ----
282
+ await c.command("DEL", key);
283
+ } finally {
284
+ await _restoreProxy();
285
+ if (c) { try { await c.close(); } catch (_e) {} }
286
+ }
287
+ }
288
+
289
+ module.exports = { run: run };
290
+
291
+ if (require.main === module) {
292
+ run().then(
293
+ // No process.exit(0) on success: an immediate exit after console.log
294
+ // can drop the buffered banner when stdout is a pipe (the integration
295
+ // runner pipes it). close() + unref'd timers let the loop drain on its
296
+ // own, so the line flushes. The failure path still hard-exits 1.
297
+ function () { console.log("OK — " + helpers.getChecks() + " checks passed"); },
298
+ function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
299
+ );
300
+ }