@blamejs/core 0.15.12 → 0.15.14

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 (365) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/index.js +2 -0
  3. package/lib/a2a-tasks.js +45 -29
  4. package/lib/acme.js +6 -5
  5. package/lib/agent-event-bus.js +18 -4
  6. package/lib/agent-idempotency.js +7 -6
  7. package/lib/agent-orchestrator.js +2 -5
  8. package/lib/agent-saga.js +3 -5
  9. package/lib/agent-snapshot.js +32 -2
  10. package/lib/agent-tenant.js +2 -5
  11. package/lib/ai-adverse-decision.js +2 -15
  12. package/lib/ai-aedt-bias-audit.js +2 -1
  13. package/lib/ai-capability.js +1 -6
  14. package/lib/ai-content-detect.js +1 -3
  15. package/lib/ai-dp.js +1 -5
  16. package/lib/ai-frontier-protocol.js +1 -1
  17. package/lib/ai-input.js +2 -2
  18. package/lib/ai-model-manifest.js +1 -1
  19. package/lib/ai-output.js +16 -7
  20. package/lib/ai-pref.js +3 -8
  21. package/lib/ai-quota.js +3 -14
  22. package/lib/api-key.js +37 -28
  23. package/lib/api-snapshot.js +4 -1
  24. package/lib/app-shutdown.js +7 -1
  25. package/lib/archive-adapters.js +2 -4
  26. package/lib/archive-entry-policy.js +32 -0
  27. package/lib/archive-gz.js +9 -0
  28. package/lib/archive-read.js +14 -24
  29. package/lib/archive-tar-read.js +56 -24
  30. package/lib/archive.js +6 -12
  31. package/lib/arg-parser.js +7 -6
  32. package/lib/asn1-der.js +70 -22
  33. package/lib/asyncapi-traits.js +2 -6
  34. package/lib/atomic-file.js +312 -33
  35. package/lib/audit-chain.js +183 -54
  36. package/lib/audit-daily-review.js +36 -16
  37. package/lib/audit-emit.js +82 -0
  38. package/lib/audit-sign.js +258 -0
  39. package/lib/audit-tools.js +108 -23
  40. package/lib/audit.js +91 -2
  41. package/lib/auth/access-lock.js +7 -29
  42. package/lib/auth/bot-challenge.js +1 -1
  43. package/lib/auth/ciba.js +43 -4
  44. package/lib/auth/dpop.js +29 -36
  45. package/lib/auth/fido-mds3.js +14 -16
  46. package/lib/auth/jwt-external.js +26 -8
  47. package/lib/auth/jwt.js +15 -17
  48. package/lib/auth/lockout.js +24 -27
  49. package/lib/auth/oauth.js +67 -45
  50. package/lib/auth/oid4vci.js +55 -32
  51. package/lib/auth/oid4vp.js +3 -2
  52. package/lib/auth/openid-federation.js +6 -6
  53. package/lib/auth/passkey.js +4 -4
  54. package/lib/auth/password.js +8 -2
  55. package/lib/auth/saml.js +70 -40
  56. package/lib/auth/sd-jwt-vc-holder.js +2 -14
  57. package/lib/auth/sd-jwt-vc-issuer.js +2 -14
  58. package/lib/auth/sd-jwt-vc.js +25 -5
  59. package/lib/auth/status-list.js +21 -9
  60. package/lib/auth/step-up.js +15 -13
  61. package/lib/auth-bot-challenge.js +27 -39
  62. package/lib/backup/bundle.js +18 -20
  63. package/lib/backup/crypto.js +23 -8
  64. package/lib/backup/index.js +55 -67
  65. package/lib/backup/manifest.js +7 -1
  66. package/lib/bounded-map.js +112 -1
  67. package/lib/breach-deadline.js +1 -11
  68. package/lib/break-glass.js +41 -22
  69. package/lib/cache.js +7 -18
  70. package/lib/cbor.js +36 -12
  71. package/lib/cdn-cache-control.js +15 -12
  72. package/lib/cert.js +8 -13
  73. package/lib/chain-writer.js +162 -47
  74. package/lib/cli.js +10 -2
  75. package/lib/client-hints.js +7 -9
  76. package/lib/cloud-events.js +43 -33
  77. package/lib/cluster-storage.js +9 -41
  78. package/lib/cms-codec.js +2 -1
  79. package/lib/codepoint-class.js +84 -1
  80. package/lib/compliance-ai-act-logging.js +1 -6
  81. package/lib/compliance-ai-act-transparency.js +2 -4
  82. package/lib/compliance-eaa.js +2 -12
  83. package/lib/compliance-sanctions-fetcher.js +21 -28
  84. package/lib/compliance-sanctions.js +11 -21
  85. package/lib/compliance.js +3 -10
  86. package/lib/config-drift.js +24 -20
  87. package/lib/content-credentials.js +11 -8
  88. package/lib/content-digest.js +21 -12
  89. package/lib/cookies.js +15 -13
  90. package/lib/cose.js +39 -11
  91. package/lib/cra-report.js +1 -11
  92. package/lib/crdt.js +2 -1
  93. package/lib/crypto-field.js +34 -33
  94. package/lib/crypto.js +138 -7
  95. package/lib/csp.js +239 -3
  96. package/lib/daemon.js +46 -42
  97. package/lib/data-act.js +46 -13
  98. package/lib/db-file-lifecycle.js +14 -6
  99. package/lib/db-query.js +75 -49
  100. package/lib/db.js +66 -27
  101. package/lib/dbsc.js +5 -6
  102. package/lib/ddl-change-control.js +18 -14
  103. package/lib/deprecate.js +4 -5
  104. package/lib/did.js +6 -2
  105. package/lib/dora.js +43 -13
  106. package/lib/dr-runbook.js +1 -1
  107. package/lib/dsa.js +2 -1
  108. package/lib/dsr.js +30 -20
  109. package/lib/early-hints.js +19 -0
  110. package/lib/eat.js +5 -1
  111. package/lib/external-db-migrate.js +21 -22
  112. package/lib/external-db.js +74 -30
  113. package/lib/fda-21cfr11.js +42 -13
  114. package/lib/fdx.js +22 -18
  115. package/lib/file-upload.js +80 -66
  116. package/lib/flag-evaluation-context.js +5 -8
  117. package/lib/flag-providers.js +6 -2
  118. package/lib/forms.js +1 -1
  119. package/lib/framework-error.js +12 -0
  120. package/lib/framework-schema.js +19 -0
  121. package/lib/fsm.js +7 -12
  122. package/lib/gate-contract.js +911 -39
  123. package/lib/gdpr-ropa.js +22 -22
  124. package/lib/graphql-federation.js +18 -5
  125. package/lib/guard-agent-registry.js +2 -1
  126. package/lib/guard-all.js +3 -2
  127. package/lib/guard-archive.js +9 -30
  128. package/lib/guard-auth.js +23 -80
  129. package/lib/guard-cidr.js +20 -96
  130. package/lib/guard-csv.js +135 -196
  131. package/lib/guard-domain.js +23 -106
  132. package/lib/guard-dsn.js +17 -14
  133. package/lib/guard-email.js +46 -53
  134. package/lib/guard-envelope.js +2 -2
  135. package/lib/guard-event-bus-topic.js +2 -1
  136. package/lib/guard-filename.js +12 -60
  137. package/lib/guard-graphql.js +28 -75
  138. package/lib/guard-html-wcag.js +15 -2
  139. package/lib/guard-html.js +74 -128
  140. package/lib/guard-idempotency-key.js +2 -1
  141. package/lib/guard-image.js +280 -77
  142. package/lib/guard-imap-command.js +9 -10
  143. package/lib/guard-jmap.js +1 -1
  144. package/lib/guard-json.js +101 -109
  145. package/lib/guard-jsonpath.js +20 -88
  146. package/lib/guard-jwt.js +32 -114
  147. package/lib/guard-list-id.js +2 -7
  148. package/lib/guard-list-unsubscribe.js +2 -7
  149. package/lib/guard-mail-compose.js +5 -6
  150. package/lib/guard-mail-move.js +3 -2
  151. package/lib/guard-mail-query.js +5 -3
  152. package/lib/guard-mail-sieve.js +6 -7
  153. package/lib/guard-managesieve-command.js +6 -5
  154. package/lib/guard-markdown.js +76 -110
  155. package/lib/guard-message-id.js +5 -6
  156. package/lib/guard-mime.js +20 -99
  157. package/lib/guard-oauth.js +19 -73
  158. package/lib/guard-pdf.js +65 -72
  159. package/lib/guard-pop3-command.js +13 -14
  160. package/lib/guard-posture-chain.js +2 -1
  161. package/lib/guard-regex.js +24 -99
  162. package/lib/guard-saga-config.js +2 -1
  163. package/lib/guard-shell.js +22 -87
  164. package/lib/guard-smtp-command.js +9 -12
  165. package/lib/guard-sql.js +15 -13
  166. package/lib/guard-stream-args.js +2 -1
  167. package/lib/guard-svg.js +103 -149
  168. package/lib/guard-template.js +23 -80
  169. package/lib/guard-tenant-id.js +2 -1
  170. package/lib/guard-text.js +592 -0
  171. package/lib/guard-time.js +27 -95
  172. package/lib/guard-uuid.js +21 -93
  173. package/lib/guard-xml.js +76 -106
  174. package/lib/guard-yaml.js +24 -60
  175. package/lib/html-balance.js +7 -3
  176. package/lib/http-client-cache.js +5 -12
  177. package/lib/http-client-cookie-jar.js +35 -16
  178. package/lib/http-client.js +233 -74
  179. package/lib/http-message-signature.js +3 -2
  180. package/lib/i18n-messageformat.js +5 -7
  181. package/lib/i18n.js +83 -26
  182. package/lib/iab-tcf.js +3 -2
  183. package/lib/importmap-integrity.js +41 -1
  184. package/lib/inbox.js +8 -8
  185. package/lib/incident-report.js +12 -27
  186. package/lib/ip-utils.js +49 -6
  187. package/lib/jobs.js +3 -2
  188. package/lib/json-patch.js +1 -1
  189. package/lib/json-path.js +24 -3
  190. package/lib/jtd.js +2 -2
  191. package/lib/keychain.js +6 -18
  192. package/lib/legal-hold.js +30 -23
  193. package/lib/log-stream-cloudwatch.js +44 -112
  194. package/lib/log-stream-otlp-grpc.js +17 -14
  195. package/lib/log-stream-otlp.js +16 -80
  196. package/lib/log-stream-webhook.js +20 -92
  197. package/lib/log.js +2 -2
  198. package/lib/mail-agent.js +2 -2
  199. package/lib/mail-arc-sign.js +8 -3
  200. package/lib/mail-arf.js +4 -3
  201. package/lib/mail-auth.js +43 -69
  202. package/lib/mail-bimi.js +35 -55
  203. package/lib/mail-bounce.js +3 -3
  204. package/lib/mail-crypto-pgp.js +3 -2
  205. package/lib/mail-crypto-smime.js +71 -6
  206. package/lib/mail-dav.js +8 -35
  207. package/lib/mail-deploy.js +15 -14
  208. package/lib/mail-dkim.js +19 -38
  209. package/lib/mail-greylist.js +17 -30
  210. package/lib/mail-helo.js +4 -7
  211. package/lib/mail-journal.js +13 -9
  212. package/lib/mail-mdn.js +12 -7
  213. package/lib/mail-rbl.js +7 -12
  214. package/lib/mail-scan.js +5 -7
  215. package/lib/mail-send-deliver.js +33 -20
  216. package/lib/mail-server-imap.js +32 -87
  217. package/lib/mail-server-jmap.js +39 -52
  218. package/lib/mail-server-managesieve.js +29 -83
  219. package/lib/mail-server-mx.js +33 -74
  220. package/lib/mail-server-net.js +177 -0
  221. package/lib/mail-server-pop3.js +30 -83
  222. package/lib/mail-server-rate-limit.js +30 -6
  223. package/lib/mail-server-registry.js +1 -1
  224. package/lib/mail-server-submission.js +34 -73
  225. package/lib/mail-server-tls.js +98 -2
  226. package/lib/mail-spam-score.js +9 -12
  227. package/lib/mail-store-fts.js +1 -1
  228. package/lib/mail-store.js +3 -2
  229. package/lib/mail.js +28 -13
  230. package/lib/markup-escape.js +31 -0
  231. package/lib/markup-tokenizer.js +78 -0
  232. package/lib/mcp-tool-registry.js +26 -23
  233. package/lib/mcp.js +7 -5
  234. package/lib/mdoc.js +36 -14
  235. package/lib/metrics.js +46 -33
  236. package/lib/middleware/age-gate.js +3 -23
  237. package/lib/middleware/api-encrypt.js +13 -24
  238. package/lib/middleware/assetlinks.js +2 -7
  239. package/lib/middleware/bearer-auth.js +2 -1
  240. package/lib/middleware/body-parser.js +41 -52
  241. package/lib/middleware/bot-disclose.js +7 -10
  242. package/lib/middleware/bot-guard.js +28 -28
  243. package/lib/middleware/clear-site-data.js +5 -1
  244. package/lib/middleware/compression.js +9 -0
  245. package/lib/middleware/cors.js +32 -23
  246. package/lib/middleware/csrf-protect.js +73 -23
  247. package/lib/middleware/daily-byte-quota.js +12 -30
  248. package/lib/middleware/deny-response.js +19 -1
  249. package/lib/middleware/fetch-metadata.js +35 -4
  250. package/lib/middleware/idempotency-key.js +4 -20
  251. package/lib/middleware/network-allowlist.js +61 -30
  252. package/lib/middleware/rate-limit.js +45 -44
  253. package/lib/middleware/scim-server.js +5 -3
  254. package/lib/middleware/security-headers.js +33 -7
  255. package/lib/middleware/security-txt.js +2 -6
  256. package/lib/middleware/speculation-rules.js +6 -3
  257. package/lib/middleware/tus-upload.js +14 -29
  258. package/lib/middleware/web-app-manifest.js +2 -7
  259. package/lib/migrations.js +4 -13
  260. package/lib/mime-parse.js +34 -17
  261. package/lib/module-loader.js +44 -0
  262. package/lib/money.js +105 -0
  263. package/lib/mtls-ca.js +125 -41
  264. package/lib/network-byte-quota.js +4 -18
  265. package/lib/network-dane.js +8 -6
  266. package/lib/network-dns-resolver.js +98 -7
  267. package/lib/network-dns.js +9 -2
  268. package/lib/network-dnssec.js +18 -17
  269. package/lib/network-heartbeat.js +3 -2
  270. package/lib/network-smtp-policy.js +52 -46
  271. package/lib/network-tls.js +67 -47
  272. package/lib/network-tsig.js +2 -2
  273. package/lib/nis2-report.js +2 -12
  274. package/lib/nist-crosswalk.js +1 -1
  275. package/lib/nonce-store.js +81 -3
  276. package/lib/ntp-check.js +28 -0
  277. package/lib/numeric-bounds.js +31 -8
  278. package/lib/object-store/azure-blob-bucket-ops.js +2 -6
  279. package/lib/object-store/azure-blob.js +6 -47
  280. package/lib/object-store/gcs-bucket-ops.js +4 -2
  281. package/lib/object-store/gcs.js +14 -75
  282. package/lib/object-store/http-put.js +2 -7
  283. package/lib/object-store/http-request.js +157 -0
  284. package/lib/object-store/local.js +37 -17
  285. package/lib/object-store/sigv4-bucket-ops.js +2 -1
  286. package/lib/object-store/sigv4.js +10 -79
  287. package/lib/observability-otlp-exporter.js +29 -29
  288. package/lib/observability.js +95 -0
  289. package/lib/openapi-paths-builder.js +7 -3
  290. package/lib/otel-export.js +7 -10
  291. package/lib/outbox.js +54 -41
  292. package/lib/pagination.js +11 -15
  293. package/lib/parsers/safe-env.js +5 -4
  294. package/lib/parsers/safe-ini.js +10 -4
  295. package/lib/parsers/safe-toml.js +11 -9
  296. package/lib/parsers/safe-xml.js +3 -3
  297. package/lib/parsers/safe-yaml.js +28 -9
  298. package/lib/pick.js +63 -5
  299. package/lib/pipl-cn.js +69 -59
  300. package/lib/privacy-pass.js +8 -6
  301. package/lib/problem-details.js +2 -5
  302. package/lib/pubsub.js +4 -8
  303. package/lib/queue-local.js +10 -3
  304. package/lib/redact.js +9 -4
  305. package/lib/render.js +4 -2
  306. package/lib/request-helpers.js +334 -29
  307. package/lib/resource-access-lock.js +3 -3
  308. package/lib/restore-bundle.js +46 -18
  309. package/lib/restore-rollback.js +10 -4
  310. package/lib/restore.js +24 -22
  311. package/lib/retention.js +23 -9
  312. package/lib/router.js +17 -4
  313. package/lib/safe-async.js +457 -0
  314. package/lib/safe-buffer.js +137 -6
  315. package/lib/safe-decompress.js +2 -1
  316. package/lib/safe-dns.js +2 -1
  317. package/lib/safe-ical.js +14 -28
  318. package/lib/safe-icap.js +1 -1
  319. package/lib/safe-json.js +51 -8
  320. package/lib/safe-jsonpath.js +6 -5
  321. package/lib/safe-mime.js +25 -29
  322. package/lib/safe-redirect.js +2 -5
  323. package/lib/safe-schema.js +7 -6
  324. package/lib/safe-sieve.js +1 -1
  325. package/lib/safe-sql.js +126 -0
  326. package/lib/safe-vcard.js +13 -27
  327. package/lib/sandbox-worker.js +15 -2
  328. package/lib/sandbox.js +4 -3
  329. package/lib/scheduler.js +22 -13
  330. package/lib/sec-cyber.js +82 -37
  331. package/lib/seeders.js +4 -11
  332. package/lib/self-update-standalone-verifier.js +16 -0
  333. package/lib/self-update.js +92 -69
  334. package/lib/session-device-binding.js +112 -66
  335. package/lib/session.js +219 -7
  336. package/lib/sql.js +228 -81
  337. package/lib/sse.js +6 -10
  338. package/lib/static.js +201 -111
  339. package/lib/storage.js +4 -2
  340. package/lib/structured-fields.js +275 -16
  341. package/lib/template.js +7 -5
  342. package/lib/tenant-quota.js +53 -38
  343. package/lib/tsa.js +16 -17
  344. package/lib/validate-opts.js +154 -8
  345. package/lib/vault/index.js +5 -0
  346. package/lib/vault/passphrase-ops.js +22 -26
  347. package/lib/vault/passphrase-source.js +8 -3
  348. package/lib/vault/rotate.js +13 -18
  349. package/lib/vault/seal-pem-file.js +34 -49
  350. package/lib/vault-aad.js +3 -2
  351. package/lib/vc.js +3 -8
  352. package/lib/vendor/MANIFEST.json +10 -10
  353. package/lib/vendor/public-suffix-list.dat +23 -10
  354. package/lib/vendor/public-suffix-list.data.js +5498 -5494
  355. package/lib/vex.js +35 -13
  356. package/lib/web-push-vapid.js +23 -20
  357. package/lib/webhook-dispatcher.js +706 -0
  358. package/lib/webhook.js +59 -19
  359. package/lib/websocket-channels.js +9 -7
  360. package/lib/websocket.js +8 -4
  361. package/lib/worm.js +3 -4
  362. package/lib/ws-client.js +65 -56
  363. package/lib/x509-chain.js +44 -0
  364. package/package.json +1 -1
  365. package/sbom.cdx.json +6 -6
@@ -50,6 +50,7 @@
50
50
  * BUCKET_ALREADY_OWNED, BUCKET_NOT_EMPTY, etc.).
51
51
  */
52
52
  var nodeCrypto = require("node:crypto");
53
+ var safeBuffer = require("../safe-buffer");
53
54
  var C = require("../constants");
54
55
  var requestHelpers = require("../request-helpers");
55
56
  var sigv4 = require("./sigv4");
@@ -403,7 +404,7 @@ function _validateObjectKey(key) {
403
404
  if (typeof key !== "string" || key.length === 0) {
404
405
  throw _err("INVALID_KEY", "object key must be a non-empty string", true);
405
406
  }
406
- if (key.length > C.BYTES.kib(1)) {
407
+ if (safeBuffer.byteLengthOf(key) > C.BYTES.kib(1)) {
407
408
  throw _err("INVALID_KEY", "object key exceeds 1024 bytes (S3 limit)", true);
408
409
  }
409
410
  }
@@ -25,11 +25,9 @@
25
25
  */
26
26
  var nodeCrypto = require("node:crypto");
27
27
  var { URL } = require("node:url");
28
- var { Readable } = require("node:stream");
29
28
  var safeXml = require("../parsers/safe-xml");
30
29
  var sharedRequest = require("./http-request");
31
30
  var C = require("../constants");
32
- var numericBounds = require("../numeric-bounds");
33
31
  var requestHelpers = require("../request-helpers");
34
32
  var { ObjectStoreError } = require("../framework-error");
35
33
  var safeUrl = require("../safe-url");
@@ -66,12 +64,6 @@ function _arrayify(value) {
66
64
  var SERVICE = "s3";
67
65
  var ALGORITHM = "AWS4-HMAC-SHA256";
68
66
 
69
- // Presigned-URL expiry bounds. The SigV4 spec caps query-string-signed
70
- // URLs at 7 days; below 1 second is effectively unsignable.
71
- var PRESIGN_DEFAULT_EXPIRES_SECONDS = C.TIME.minutes(15) / C.TIME.seconds(1); // 15 minutes
72
- var PRESIGN_MAX_EXPIRES_SECONDS = C.TIME.days(7) / C.TIME.seconds(1); // 7 days (SigV4 hard cap)
73
- var PRESIGN_MIN_EXPIRES_SECONDS = 1;
74
-
75
67
  var _err = ObjectStoreError.factory;
76
68
 
77
69
  // ---- SigV4 primitives ----
@@ -640,7 +632,7 @@ function create(config) {
640
632
  }
641
633
 
642
634
  function getStream(key, opts) {
643
- return Readable.from(get(key, opts));
635
+ return sharedRequest.promiseToStream(get(key, opts));
644
636
  }
645
637
 
646
638
  // getResponse(key, opts?) — full-fidelity GET. Returns
@@ -658,35 +650,16 @@ function create(config) {
658
650
  // param is in the SigV4 canonical request.
659
651
  if (opts.versionId) url.searchParams.set("versionId", opts.versionId);
660
652
  var headers = _makeSigned("GET", url, sha256Hex(Buffer.alloc(0)));
661
- if (opts.range) {
662
- headers["Range"] = "bytes=" + opts.range.start + "-" + opts.range.end;
663
- }
664
- if (opts.ifNoneMatch) headers["If-None-Match"] = opts.ifNoneMatch;
665
- if (opts.ifMatch) headers["If-Match"] = opts.ifMatch;
666
- if (opts.ifModifiedSince) headers["If-Modified-Since"] = opts.ifModifiedSince;
667
- if (opts.ifUnmodifiedSince) headers["If-Unmodified-Since"] = opts.ifUnmodifiedSince;
653
+ sharedRequest.applyConditionalGetHeaders(headers, opts, "Range");
668
654
  var localReqOpts = Object.assign({}, reqOpts, { _resolveOnRedirect: false });
669
655
  return _request("GET", url, headers, null, localReqOpts).then(function (res) {
670
- return {
671
- statusCode: res.statusCode,
672
- body: res.body,
673
- etag: res.headers && res.headers.etag,
674
- lastModified: res.headers && res.headers["last-modified"]
675
- ? Date.parse(res.headers["last-modified"]) : null,
676
- contentRange: res.headers && res.headers["content-range"] || null,
677
- size: res.headers && res.headers["content-length"]
678
- ? parseInt(res.headers["content-length"], 10) : null,
679
- contentType: res.headers && res.headers["content-type"] || null,
680
- };
656
+ return sharedRequest.mapGetResponse(res);
681
657
  }, function (err) {
682
658
  // 304 surfaces as a "non-2xx error" via httpClient; propagate it
683
659
  // as a structured 304 result instead so operator routes get
684
660
  // the conditional-GET short-circuit they expect.
685
661
  if (err && err.statusCode === requestHelpers.HTTP_STATUS.NOT_MODIFIED) {
686
- return {
687
- statusCode: requestHelpers.HTTP_STATUS.NOT_MODIFIED,
688
- body: null, etag: null, lastModified: null,
689
- };
662
+ return sharedRequest.notModifiedGetResult();
690
663
  }
691
664
  throw err;
692
665
  });
@@ -698,11 +671,7 @@ function create(config) {
698
671
  if (opts.versionId) url.searchParams.set("versionId", opts.versionId);
699
672
  var headers = _makeSigned("HEAD", url, sha256Hex(Buffer.alloc(0)));
700
673
  return _request("HEAD", url, headers, null, reqOpts).then(function (res) {
701
- return {
702
- size: res.headers["content-length"] ? parseInt(res.headers["content-length"], 10) : null,
703
- etag: res.headers.etag,
704
- lastModified: res.headers["last-modified"] ? Date.parse(res.headers["last-modified"]) : null,
705
- };
674
+ return sharedRequest.mapHeadResponse(res);
706
675
  }, function (e) {
707
676
  // A missing key surfaces as the framework NOT_FOUND code — the same
708
677
  // contract local.js head() exposes and that deleteKey already maps 404
@@ -758,21 +727,8 @@ function create(config) {
758
727
 
759
728
  function _presign(method, opts) {
760
729
  opts = opts || {};
761
- if (!opts.key || typeof opts.key !== "string") {
762
- throw _err("INVALID_KEY", "presigned URL: key is required", true);
763
- }
764
- if (opts.key.indexOf("\0") !== -1) {
765
- throw _err("INVALID_KEY", "null byte in key", true);
766
- }
767
- var expiresIn = opts.expiresIn != null ? opts.expiresIn : PRESIGN_DEFAULT_EXPIRES_SECONDS;
768
- if (typeof expiresIn !== "number" ||
769
- expiresIn < PRESIGN_MIN_EXPIRES_SECONDS ||
770
- expiresIn > PRESIGN_MAX_EXPIRES_SECONDS) {
771
- throw _err("INVALID_EXPIRES",
772
- "presigned URL: expiresIn must be a number of seconds between " +
773
- PRESIGN_MIN_EXPIRES_SECONDS + " and " + PRESIGN_MAX_EXPIRES_SECONDS +
774
- " (7 days, SigV4 hard cap)", true);
775
- }
730
+ sharedRequest.requirePresignKey(opts, "presigned URL");
731
+ var expiresIn = sharedRequest.resolvePresignExpires(opts, "presigned URL", "SigV4");
776
732
 
777
733
  // Validate opts.responseHeaders shape — operators pass camelCase
778
734
  // keys; refuse unknown keys at config-time so a typo surfaces at
@@ -876,34 +832,9 @@ function create(config) {
876
832
  // Reference: AWS S3 "Browser-Based Uploads Using POST".
877
833
  function presignedUploadPolicy(opts) {
878
834
  opts = opts || {};
879
- if (!opts.key || typeof opts.key !== "string") {
880
- throw _err("INVALID_KEY", "presignedUploadPolicy: key is required", true);
881
- }
882
- if (opts.key.indexOf("\0") !== -1) {
883
- throw _err("INVALID_KEY", "null byte in key", true);
884
- }
885
- if (typeof opts.maxBytes !== "number" || !Number.isFinite(opts.maxBytes) ||
886
- opts.maxBytes <= 0) {
887
- throw _err("INVALID_MAX_BYTES",
888
- "presignedUploadPolicy: maxBytes (positive number of bytes) is required — " +
889
- "POST-form policy enforces body size via the content-length-range condition; " +
890
- "use presignedUploadUrl if size enforcement is not needed", true);
891
- }
892
- if (opts.minBytes !== undefined && !numericBounds.isNonNegativeFiniteInt(opts.minBytes)) {
893
- throw _err("INVALID_MIN_BYTES",
894
- "presignedUploadPolicy: minBytes must be a non-negative finite integer; got " +
895
- numericBounds.shape(opts.minBytes), true);
896
- }
897
- var minBytes = opts.minBytes !== undefined ? opts.minBytes : 0;
898
- var expiresIn = opts.expiresIn != null ? opts.expiresIn : PRESIGN_DEFAULT_EXPIRES_SECONDS;
899
- if (typeof expiresIn !== "number" ||
900
- expiresIn < PRESIGN_MIN_EXPIRES_SECONDS ||
901
- expiresIn > PRESIGN_MAX_EXPIRES_SECONDS) {
902
- throw _err("INVALID_EXPIRES",
903
- "presignedUploadPolicy: expiresIn must be a number of seconds between " +
904
- PRESIGN_MIN_EXPIRES_SECONDS + " and " + PRESIGN_MAX_EXPIRES_SECONDS +
905
- " (7 days, SigV4 hard cap)", true);
906
- }
835
+ sharedRequest.requirePresignKey(opts, "presignedUploadPolicy");
836
+ var minBytes = sharedRequest.resolvePresignUploadMinBytes(opts);
837
+ var expiresIn = sharedRequest.resolvePresignExpires(opts, "presignedUploadPolicy", "SigV4");
907
838
 
908
839
  var date = opts.date || new Date();
909
840
  var amzDate = _formatAmzDate(date);
@@ -37,6 +37,7 @@ var safeBuffer = require("./safe-buffer");
37
37
  var validateOpts = require("./validate-opts");
38
38
  var safeUrl = require("./safe-url");
39
39
  var pb = require("./protobuf-encoder");
40
+ var boundedMap = require("./bounded-map");
40
41
  var { defineClass } = require("./framework-error");
41
42
 
42
43
  var OtlpExporterError = defineClass("OtlpExporterError", { alwaysPermanent: true });
@@ -132,12 +133,27 @@ function _valueToOtlp(v) {
132
133
  return { stringValue: String(v) };
133
134
  }
134
135
 
136
+ // Run a single wire STRING (span name / status message) through the telemetry
137
+ // redactor. These reach the collector exactly like attribute values, so a
138
+ // connection string / token / PII in an error message (the tracer's canonical
139
+ // `setStatus("error", e.message)`) must be scrubbed too (CWE-532). Routes
140
+ // through redactAttrs (the same chokepoint attributes use); fails toward an
141
+ // empty string on a redactor throw, matching redactAttrs' drop-on-error.
142
+ function _redactWireString(key, value) {
143
+ if (typeof value !== "string" || value.length === 0) return value || "";
144
+ try {
145
+ var holder = {};
146
+ holder[key] = value;
147
+ return observability().redactAttrs(holder)[key];
148
+ } catch (_e) { return ""; }
149
+ }
150
+
135
151
  function _spanToOtlp(span) {
136
152
  return {
137
153
  traceId: span.traceId,
138
154
  spanId: span.spanId,
139
155
  parentSpanId: span.parentSpanId || "",
140
- name: span.name,
156
+ name: _redactWireString("otel.span.name", span.name),
141
157
  kind: KIND_TO_OTLP[span.kind] || KIND_TO_OTLP.internal,
142
158
  startTimeUnixNano: span.startTimeUnixNano,
143
159
  endTimeUnixNano: span.endTimeUnixNano || span.startTimeUnixNano,
@@ -154,7 +170,7 @@ function _spanToOtlp(span) {
154
170
  droppedEventsCount: span.droppedEventsCount || 0,
155
171
  status: {
156
172
  code: STATUS_CODE_TO_OTLP[span.status && span.status.code] || 0,
157
- message: (span.status && span.status.message) || "",
173
+ message: _redactWireString("exception.message", (span.status && span.status.message) || ""),
158
174
  },
159
175
  };
160
176
  }
@@ -167,15 +183,13 @@ function _bundleSpans(spans) {
167
183
  for (var i = 0; i < spans.length; i++) {
168
184
  var s = spans[i];
169
185
  var resKey = JSON.stringify(s.resource || {});
170
- var bucket = byResource.get(resKey);
171
- if (!bucket) {
172
- bucket = {
186
+ var bucket = boundedMap.getOrInsert(byResource, resKey, function () {
187
+ return {
173
188
  resource: s.resource || {},
174
189
  scope: s.scope || { name: "blamejs", version: null },
175
190
  spans: [],
176
191
  };
177
- byResource.set(resKey, bucket);
178
- }
192
+ });
179
193
  bucket.spans.push(s);
180
194
  }
181
195
  var resourceSpans = [];
@@ -337,7 +351,7 @@ function _attrsToProto(attrs) {
337
351
  function _spanToProto(span) {
338
352
  // Status code: 0=Unset, 1=Ok, 2=Error. Status field 1 is reserved.
339
353
  var statusBody = Buffer.concat([
340
- pb.string(2, (span.status && span.status.message) || ""),
354
+ pb.string(2, _redactWireString("exception.message", (span.status && span.status.message) || "")),
341
355
  pb.uint32(3, STATUS_CODE_TO_OTLP[span.status && span.status.code] || 0),
342
356
  ]);
343
357
  var eventsRepeated = pb.repeatedMessage(11, span.events || [], function (e) {
@@ -353,7 +367,7 @@ function _spanToProto(span) {
353
367
  pb.bytes(2, _hexToBytes(span.spanId)),
354
368
  pb.string(3, ""), // trace_state (not yet propagated by the framework)
355
369
  pb.bytes(4, _hexToBytes(span.parentSpanId || "")),
356
- pb.string(5, span.name || ""),
370
+ pb.string(5, _redactWireString("otel.span.name", span.name || "")),
357
371
  pb.uint32(6, KIND_TEXT_TO_ENUM[span.kind] != null ? KIND_TEXT_TO_ENUM[span.kind] : KIND_TEXT_TO_ENUM.internal),
358
372
  pb.fixed64(7, span.startTimeUnixNano || 0),
359
373
  pb.fixed64(8, span.endTimeUnixNano || span.startTimeUnixNano || 0), // proto field number 8, not bytes
@@ -380,15 +394,13 @@ function _bundleSpansToProto(spansArray) {
380
394
  for (var i = 0; i < spansArray.length; i += 1) {
381
395
  var s = spansArray[i];
382
396
  var resKey = JSON.stringify(s.resource || {});
383
- var bucket = byResource.get(resKey);
384
- if (!bucket) {
385
- bucket = {
397
+ var bucket = boundedMap.getOrInsert(byResource, resKey, function () {
398
+ return {
386
399
  resource: s.resource || {},
387
400
  scope: s.scope || { name: "blamejs", version: null },
388
401
  spans: [],
389
402
  };
390
- byResource.set(resKey, bucket);
391
- }
403
+ });
392
404
  bucket.spans.push(s);
393
405
  }
394
406
  var resourceSpansPieces = [];
@@ -493,21 +505,8 @@ function create(opts) {
493
505
  var inFlight = false;
494
506
  var stopping = false;
495
507
 
496
- var auditOn = opts.audit !== false;
497
- function _emitMetric(verb, n, labels) {
498
- try { observability().safeEvent("otlp.exporter." + verb, n || 1, labels || {}); }
499
- catch (_e) { /* drop-silent */ }
500
- }
501
- function _emitAudit(action, outcome, metadata) {
502
- if (!auditOn) return;
503
- try {
504
- audit().safeEmit({
505
- action: "system.observability.otlp_exporter." + action,
506
- outcome: outcome,
507
- metadata: metadata || {},
508
- });
509
- } catch (_e) { /* drop-silent — audit is best-effort, never crashes the exporter */ }
510
- }
508
+ var _emitMetric = observability().namespaced("otlp.exporter");
509
+ var _emitAudit = audit().namespaced("system.observability.otlp_exporter", opts.audit);
511
510
 
512
511
  function queue_(span) {
513
512
  if (stopping) { droppedExportFailed += 1; return; }
@@ -664,6 +663,7 @@ module.exports = {
664
663
  OtlpExporterError: OtlpExporterError,
665
664
  // Exported for tests
666
665
  _spanToOtlp: _spanToOtlp,
666
+ _spanToProto: _spanToProto,
667
667
  _bundleSpans: _bundleSpans,
668
668
  _attrToOtlp: _attrToOtlp,
669
669
  _BASE64URL_RE_REF: safeBuffer.BASE64URL_RE, // not used; reserved for OTLP/protobuf shape upgrade
@@ -343,6 +343,98 @@ function safeEvent(name, value, labels) {
343
343
  catch (_e) { /* hot-path observability sink — drops silent on internal throws */ }
344
344
  }
345
345
 
346
+ /**
347
+ * @primitive b.observability.safeEmit
348
+ * @signature b.observability.safeEmit(sink, name, value, labels)
349
+ * @since 0.15.13
350
+ * @related b.observability.safeEvent, b.observability.event
351
+ *
352
+ * The sink-aware sibling of `safeEvent`: emit a metric event to an
353
+ * explicitly-configured observability `sink` (a per-instance
354
+ * observability object) when one is supplied, otherwise fall back to
355
+ * the global registry — each path wrapped in a try/catch so a
356
+ * misconfigured counter never crashes the request that triggered it.
357
+ * Replaces the `_emitObs` + `_safeGlobalObs` helper pair that the auth
358
+ * brute-force modules (bot-challenge / lockout / session-device-binding)
359
+ * each duplicated to route a configured observability instance with a
360
+ * global fallback.
361
+ *
362
+ * @example
363
+ * b.observability.safeEmit(opts.observability, "auth.lockout.hit", 1,
364
+ * { namespace: ns });
365
+ */
366
+ function safeEmit(sink, name, value, labels) {
367
+ if (sink) {
368
+ try { sink.event(name, value, labels); }
369
+ catch (_e) { /* per-instance observability sink — drops silent */ }
370
+ return;
371
+ }
372
+ safeEvent(name, value, labels);
373
+ }
374
+
375
+ /**
376
+ * @primitive b.observability.makeCounterEmitter
377
+ * @signature b.observability.makeCounterEmitter(sink)
378
+ * @since 0.15.13
379
+ * @status stable
380
+ * @related b.observability.safeEmit, b.observability.namespaced
381
+ *
382
+ * Bind a per-instance counter emitter. Returns `(name, labels)` that
383
+ * increments metric `name` by 1 (with `labels`) on the supplied
384
+ * observability `sink`, drop-silent on a sink throw and falling back to
385
+ * the global tap when `sink` is null. The shorthand every primitive that
386
+ * accepts an `observability` instance wrapped in a private
387
+ * `_emitObs(name, labels)` closure around
388
+ * `safeEmit(obsInst, name, 1, labels)` — build it once with the instance
389
+ * and call the returned emitter.
390
+ *
391
+ * @example
392
+ * var b = require("blamejs");
393
+ * var emit = b.observability.makeCounterEmitter(myObsInstance);
394
+ * emit("auth.lockout.tripped", { actor: "alice" });
395
+ */
396
+ function makeCounterEmitter(sink) {
397
+ return function (name, labels) {
398
+ safeEmit(sink, name, 1, labels);
399
+ };
400
+ }
401
+
402
+ /**
403
+ * @primitive b.observability.namespaced
404
+ * @signature b.observability.namespaced(prefix, gateFlag?)
405
+ * @since 0.15.13
406
+ * @status stable
407
+ * @related b.observability.safeEvent, b.observability.event, b.audit.namespaced
408
+ *
409
+ * Build a drop-silent metric emitter bound to one name prefix — the shape every
410
+ * primitive hand-rolled as a private `_emitMetric(verb, n, labels)` closure
411
+ * (`try { observability().safeEvent("ns." + verb, n || 1, labels || {}); }
412
+ * catch {}`). The returned function prefixes `verb` with `prefix + "."`,
413
+ * defaults the value to `1` and labels to `{}`, and routes through `safeEvent`
414
+ * so a misconfigured counter / label name cannot crash the caller. The metric
415
+ * sibling of `b.audit.namespaced`. Metrics emit unconditionally by default;
416
+ * pass `gateFlag === false` to disable a primitive's own metrics in lockstep
417
+ * with its audit (the few primitives that gate both behind one `opts.audit`).
418
+ *
419
+ * @example
420
+ * var emitMetric = b.observability.namespaced("network.byte_quota");
421
+ * emitMetric("exceeded", 1, { key: k });
422
+ * // → safeEvent("network.byte_quota.exceeded", 1, { key: k })
423
+ * emitMetric("reset");
424
+ * // → safeEvent("network.byte_quota.reset", 1, {})
425
+ */
426
+ function namespaced(prefix, gateFlag) {
427
+ var on = gateFlag !== false;
428
+ return function (verb, n, labels) {
429
+ if (!on) return;
430
+ // module.exports.safeEvent (late-bound) so a test that stubs
431
+ // b.observability.safeEvent still observes the emit — the closures this
432
+ // replaces all called `observability().safeEvent(...)`.
433
+ try { module.exports.safeEvent(prefix + "." + verb, n || 1, labels || {}); }
434
+ catch (_e) { /* drop-silent — observability sink */ }
435
+ };
436
+ }
437
+
346
438
  // timed — convenience wrapper that measures wall-clock duration of a
347
439
  // sync or async operation and emits a counter event with
348
440
  // duration_ms in the labels. Returns the wrapped function's return
@@ -870,6 +962,9 @@ module.exports = {
870
962
  tap: tap,
871
963
  event: event,
872
964
  safeEvent: safeEvent,
965
+ safeEmit: safeEmit,
966
+ makeCounterEmitter: makeCounterEmitter,
967
+ namespaced: namespaced,
873
968
  timed: timed,
874
969
  setTap: setTap,
875
970
  setRedactor: setRedactor,
@@ -25,6 +25,7 @@
25
25
 
26
26
  var validateOpts = require("./validate-opts");
27
27
  var schemaWalk = require("./openapi-schema-walk");
28
+ var pick = require("./pick");
28
29
  var { defineClass } = require("./framework-error");
29
30
  var OpenApiError = defineClass("OpenApiError", { alwaysPermanent: true });
30
31
 
@@ -44,7 +45,10 @@ function _extractPathParams(pathTemplate) {
44
45
  }
45
46
 
46
47
  function PathsBuilder() {
47
- this._paths = {};
48
+ // null-proto so a urlPattern keyed into _paths (operator-supplied API path)
49
+ // can never reach Object.prototype — a "__proto__" / "constructor" path lands
50
+ // as an own property, not a prototype mutation (prototype-polluting-assignment).
51
+ this._paths = Object.create(null);
48
52
  }
49
53
 
50
54
  // _buildOperation — normalise a single Operation Object from operator
@@ -133,7 +137,7 @@ PathsBuilder.prototype.add = function (method, urlPattern, opts) {
133
137
  }
134
138
  }
135
139
 
136
- if (!this._paths[urlPattern]) this._paths[urlPattern] = {};
140
+ if (!this._paths[urlPattern]) this._paths[urlPattern] = Object.create(null);
137
141
  if (this._paths[urlPattern][method.toLowerCase()]) {
138
142
  throw new OpenApiError("openapi/duplicate-operation",
139
143
  "paths.add: duplicate operation " + method.toUpperCase() + " " + urlPattern);
@@ -279,7 +283,7 @@ WebhooksBuilder.prototype.add = function (name, method, opts) {
279
283
  opts = opts || {};
280
284
  validateOpts.requireNonEmptyString(name, "webhook.add: name",
281
285
  OpenApiError, "openapi/bad-webhook");
282
- if (name === "__proto__" || name === "constructor" || name === "prototype") {
286
+ if (pick.isPoisonedKey(name)) {
283
287
  throw new OpenApiError("openapi/bad-webhook",
284
288
  "webhook.add: name must not be a reserved object key (" + JSON.stringify(name) + ")");
285
289
  }
@@ -40,6 +40,7 @@
40
40
  * hand-rolled wire format with no compelling benefit at this scope.
41
41
  */
42
42
  var C = require("./constants");
43
+ var boundedMap = require("./bounded-map");
43
44
  var canonicalJson = require("./canonical-json");
44
45
  var httpClient = require("./http-client");
45
46
  var observability = require("./observability");
@@ -144,11 +145,9 @@ function create(opts) {
144
145
  if (typeof name !== "string" || name.length === 0) return;
145
146
  var v = typeof value === "number" && isFinite(value) ? value : 1;
146
147
  var key = _bucketKey(name, attrs);
147
- var b = counters.get(key);
148
- if (!b) {
149
- b = { name: name, attrs: attrs || {}, value: 0, startUnixNano: startUnixNano };
150
- counters.set(key, b);
151
- }
148
+ var b = boundedMap.getOrInsert(counters, key, function () {
149
+ return { name: name, attrs: attrs || {}, value: 0, startUnixNano: startUnixNano };
150
+ });
152
151
  b.value += v;
153
152
  }
154
153
 
@@ -157,11 +156,9 @@ function create(opts) {
157
156
  if (typeof name !== "string" || name.length === 0) return;
158
157
  if (typeof value !== "number" || !isFinite(value)) return;
159
158
  var key = _bucketKey(name, attrs);
160
- var b = observations.get(key);
161
- if (!b) {
162
- b = { name: name, attrs: attrs || {}, sum: 0, count: 0, min: value, max: value, startUnixNano: startUnixNano };
163
- observations.set(key, b);
164
- }
159
+ var b = boundedMap.getOrInsert(observations, key, function () {
160
+ return { name: name, attrs: attrs || {}, sum: 0, count: 0, min: value, max: value, startUnixNano: startUnixNano };
161
+ });
165
162
  b.sum += value;
166
163
  b.count += 1;
167
164
  if (value < b.min) b.min = value;
package/lib/outbox.js CHANGED
@@ -208,29 +208,47 @@ function create(opts) {
208
208
  "envelope", "connectorName", "connectorVersion", "dbName",
209
209
  ], "outbox.create");
210
210
 
211
- if (!opts.externalDb || typeof opts.externalDb.transaction !== "function") {
212
- throw new OutboxError("outbox/bad-externaldb",
213
- "outbox.create: externalDb must be the b.externalDb namespace (with transaction/query)");
214
- }
215
- validateOpts.requireNonEmptyString(opts.table,
216
- "outbox.create: table", OutboxError, "outbox/bad-table");
217
- // Validate the table identifier at create-time so a bad name throws at
218
- // boot, not at first query. b.sql re-quotes the name by construction on
219
- // every emitted statement (the builder owns identifier quoting now).
220
- _validateTableName(opts.table);
221
-
222
- if (typeof opts.publisher !== "function") {
223
- throw new OutboxError("outbox/bad-publisher",
224
- "outbox.create: publisher must be an async function (event) → void");
225
- }
226
- validateOpts.optionalPositiveFinite(opts.pollIntervalMs,
227
- "outbox.create: pollIntervalMs", OutboxError, "outbox/bad-opts");
228
- validateOpts.optionalPositiveFinite(opts.batchSize,
229
- "outbox.create: batchSize", OutboxError, "outbox/bad-opts");
230
- validateOpts.optionalPositiveFinite(opts.maxAttempts,
231
- "outbox.create: maxAttempts", OutboxError, "outbox/bad-opts");
232
- validateOpts.optionalPositiveFinite(opts.claimReclaimMs,
233
- "outbox.create: claimReclaimMs", OutboxError, "outbox/bad-opts");
211
+ validateOpts.shape(opts, {
212
+ externalDb: function (v) {
213
+ if (!v || typeof v.transaction !== "function") {
214
+ throw new OutboxError("outbox/bad-externaldb",
215
+ "outbox.create: externalDb must be the b.externalDb namespace (with transaction/query)");
216
+ }
217
+ },
218
+ table: function (v) {
219
+ validateOpts.requireNonEmptyString(v,
220
+ "outbox.create: table", OutboxError, "outbox/bad-table");
221
+ // Validate the table identifier at create-time so a bad name throws
222
+ // at boot, not at first query. b.sql re-quotes the name by
223
+ // construction on every emitted statement (the builder owns
224
+ // identifier quoting now).
225
+ _validateTableName(v);
226
+ },
227
+ publisher: function (v) {
228
+ if (typeof v !== "function") {
229
+ throw new OutboxError("outbox/bad-publisher",
230
+ "outbox.create: publisher must be an async function (event) → void");
231
+ }
232
+ },
233
+ pollIntervalMs: "optional-positive-finite",
234
+ batchSize: "optional-positive-finite",
235
+ maxAttempts: "optional-positive-finite",
236
+ claimReclaimMs: "optional-positive-finite",
237
+ retryBackoff: {
238
+ optional: true,
239
+ shape: {
240
+ initialMs: "optional-positive-finite",
241
+ maxMs: "optional-positive-finite",
242
+ factor: "optional-positive-finite",
243
+ },
244
+ },
245
+ audit: "optional-boolean",
246
+ name: "optional-string",
247
+ envelope: "optional-string",
248
+ connectorName: "optional-string",
249
+ connectorVersion: "optional-string",
250
+ dbName: "optional-string",
251
+ }, "outbox.create", OutboxError, "outbox/bad-opts");
234
252
 
235
253
  var pollIntervalMs = opts.pollIntervalMs || DEFAULT_POLL_MS;
236
254
  var batchSize = opts.batchSize || DEFAULT_BATCH_SIZE;
@@ -268,20 +286,8 @@ function create(opts) {
268
286
  return Math.floor(ms);
269
287
  }
270
288
 
271
- function _emitMetric(verb, n) {
272
- try { observability().safeEvent("outbox." + verb, n || 1, {}); }
273
- catch (_e) { /* drop-silent */ }
274
- }
275
- function _emitAudit(action, outcome, metadata) {
276
- if (!auditOn) return;
277
- try {
278
- audit().safeEmit({
279
- action: action,
280
- outcome: outcome,
281
- metadata: metadata || {},
282
- });
283
- } catch (_e) { /* drop-silent */ }
284
- }
289
+ var _emitMetric = observability().namespaced("outbox");
290
+ var _emitAudit = audit().namespaced(null, { audit: auditOn });
285
291
 
286
292
  async function enqueue(event, txn) {
287
293
  if (!txn || typeof txn.query !== "function") {
@@ -369,11 +375,18 @@ function create(opts) {
369
375
  { name: "last_error", type: "TEXT" },
370
376
  { name: "status", type: "VARCHAR(16)", notNull: true, default: "pending" },
371
377
  ], { dialect: dialect }), dialect);
372
- // Partial index on the pending pool (the publisher's claim path scans
373
- // status='pending' ORDER BY next_attempt_at). The 'pending' literal is
374
- // a builder-emitted static predicate, opted in via allowLiterals.
378
+ // Index for the publisher's claim path (scans status='pending' ORDER BY
379
+ // next_attempt_at). sqlite/postgres support a partial index (WHERE on
380
+ // CREATE INDEX) on next_attempt_at; MySQL does NOT — a WHERE there is a
381
+ // syntax error that made declareSchema() throw on MySQL — so fall back to a
382
+ // composite (status, next_attempt_at) index, which serves the same
383
+ // equality+range scan. The 'pending' literal is a builder-emitted static
384
+ // predicate, opted in via allowLiterals.
385
+ var idxCols = dialect === "mysql" ? ["status", "next_attempt_at"] : ["next_attempt_at"];
386
+ var idxOpts = { dialect: dialect };
387
+ if (dialect !== "mysql") idxOpts.where = "status = 'pending'";
375
388
  var idx = sql.toExternalSql(sql.createIndex(opts.table + "_pending_idx", opts.table,
376
- ["next_attempt_at"], { dialect: dialect, where: "status = 'pending'" }), dialect);
389
+ idxCols, idxOpts), dialect);
377
390
  await target.query(ddl.sql, ddl.params);
378
391
  await target.query(idx.sql, idx.params);
379
392
  // Back-compat: an outbox table created before the claimed_at column
package/lib/pagination.js CHANGED
@@ -50,6 +50,7 @@ var nodeCrypto = require("node:crypto");
50
50
  var C = require("./constants");
51
51
  var canonicalJson = require("./canonical-json");
52
52
  var bCrypto = require("./crypto");
53
+ var validateOpts = require("./validate-opts");
53
54
  var numericBounds = require("./numeric-bounds");
54
55
  var safeJson = require("./safe-json");
55
56
  var safeSql = require("./safe-sql");
@@ -85,11 +86,12 @@ function _toBuf(secret) {
85
86
 
86
87
  function _b64urlEncode(buf) { return bCrypto.toBase64Url(buf); }
87
88
 
88
- function _b64urlDecode(s) {
89
- if (typeof s !== "string") throw new PaginationError("pagination/bad-cursor", "cursor must be a string");
90
- try { return bCrypto.fromBase64Url(s); }
91
- catch (_e) { throw new PaginationError("pagination/bad-cursor", "cursor is not valid base64url"); }
92
- }
89
+ var _b64urlDecode = bCrypto.makeBase64UrlDecoder({
90
+ errorClass: PaginationError,
91
+ code: "pagination/bad-cursor",
92
+ typeMessage: "cursor must be a string",
93
+ badMessage: "cursor is not valid base64url",
94
+ });
93
95
 
94
96
  function _tag(secretBuf, stateJson) {
95
97
  var h = nodeCrypto.createHash("sha3-512");
@@ -345,11 +347,8 @@ function _buildKeysetWhere(orderEntries, cursorVals, forward) {
345
347
  * });
346
348
  */
347
349
  async function cursor(query, opts) {
348
- if (!query || typeof query.where !== "function" || typeof query.orderBy !== "function" ||
349
- typeof query.limit !== "function" || typeof query.all !== "function") {
350
- throw new PaginationError("pagination/bad-query",
351
- "cursor: first arg must be a db Query (must support where, orderBy, limit, all)");
352
- }
350
+ validateOpts.requireMethods(query, ["where", "orderBy", "limit", "all"],
351
+ "cursor: first arg (db Query)", PaginationError, "pagination/bad-query");
353
352
  opts = opts || {};
354
353
  if (opts.secret == null) {
355
354
  throw new PaginationError("pagination/no-secret",
@@ -484,11 +483,8 @@ async function cursor(query, opts) {
484
483
  * page.hasMore; // → true when page < totalPages
485
484
  */
486
485
  async function offset(query, opts) {
487
- if (!query || typeof query.limit !== "function" || typeof query.offset !== "function" ||
488
- typeof query.all !== "function" || typeof query.count !== "function") {
489
- throw new PaginationError("pagination/bad-query",
490
- "offset: first arg must be a db Query (must support limit, offset, all, count)");
491
- }
486
+ validateOpts.requireMethods(query, ["limit", "offset", "all", "count"],
487
+ "offset: first arg (db Query)", PaginationError, "pagination/bad-query");
492
488
  opts = opts || {};
493
489
  var perPage = _resolveLimit({ limit: opts.perPage, max: opts.max, default: opts.default });
494
490
  var page = parseInt(opts.page, 10);