@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.
- package/CHANGELOG.md +4 -0
- package/index.js +2 -0
- package/lib/a2a-tasks.js +45 -29
- package/lib/acme.js +6 -5
- package/lib/agent-event-bus.js +18 -4
- package/lib/agent-idempotency.js +7 -6
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-snapshot.js +32 -2
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-capability.js +1 -6
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-dp.js +1 -5
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-input.js +2 -2
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +14 -24
- package/lib/archive-tar-read.js +56 -24
- package/lib/archive.js +6 -12
- package/lib/arg-parser.js +7 -6
- package/lib/asn1-der.js +70 -22
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +312 -33
- package/lib/audit-chain.js +183 -54
- package/lib/audit-daily-review.js +36 -16
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +258 -0
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +91 -2
- package/lib/auth/access-lock.js +7 -29
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +43 -4
- package/lib/auth/dpop.js +29 -36
- package/lib/auth/fido-mds3.js +14 -16
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +15 -17
- package/lib/auth/lockout.js +24 -27
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +4 -4
- package/lib/auth/password.js +8 -2
- package/lib/auth/saml.js +70 -40
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +25 -5
- package/lib/auth/status-list.js +21 -9
- package/lib/auth/step-up.js +15 -13
- package/lib/auth-bot-challenge.js +27 -39
- package/lib/backup/bundle.js +18 -20
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +55 -67
- package/lib/backup/manifest.js +7 -1
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/break-glass.js +41 -22
- package/lib/cache.js +7 -18
- package/lib/cbor.js +36 -12
- package/lib/cdn-cache-control.js +15 -12
- package/lib/cert.js +8 -13
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +10 -2
- package/lib/client-hints.js +7 -9
- package/lib/cloud-events.js +43 -33
- package/lib/cluster-storage.js +9 -41
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +84 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +2 -12
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +11 -21
- package/lib/compliance.js +3 -10
- package/lib/config-drift.js +24 -20
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +21 -12
- package/lib/cookies.js +15 -13
- package/lib/cose.js +39 -11
- package/lib/cra-report.js +1 -11
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +34 -33
- package/lib/crypto.js +138 -7
- package/lib/csp.js +239 -3
- package/lib/daemon.js +46 -42
- package/lib/data-act.js +46 -13
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +75 -49
- package/lib/db.js +66 -27
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +30 -20
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +74 -30
- package/lib/fda-21cfr11.js +42 -13
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +911 -39
- package/lib/gdpr-ropa.js +22 -22
- package/lib/graphql-federation.js +18 -5
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +3 -2
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +17 -14
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +2 -2
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +12 -60
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +74 -128
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +9 -10
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +101 -109
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +3 -2
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +6 -5
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +13 -14
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +9 -12
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +103 -149
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/html-balance.js +7 -3
- package/lib/http-client-cache.js +5 -12
- package/lib/http-client-cookie-jar.js +35 -16
- package/lib/http-client.js +233 -74
- package/lib/http-message-signature.js +3 -2
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +12 -27
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +30 -23
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +17 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +4 -3
- package/lib/mail-auth.js +43 -69
- package/lib/mail-bimi.js +35 -55
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +15 -14
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +17 -30
- package/lib/mail-helo.js +4 -7
- package/lib/mail-journal.js +13 -9
- package/lib/mail-mdn.js +12 -7
- package/lib/mail-rbl.js +7 -12
- package/lib/mail-scan.js +5 -7
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +39 -52
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-registry.js +1 -1
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +98 -2
- package/lib/mail-spam-score.js +9 -12
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +28 -13
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +78 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +7 -5
- package/lib/mdoc.js +36 -14
- package/lib/metrics.js +46 -33
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +13 -24
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +41 -52
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +28 -28
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +73 -23
- package/lib/middleware/daily-byte-quota.js +12 -30
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +35 -4
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +45 -44
- package/lib/middleware/scim-server.js +5 -3
- package/lib/middleware/security-headers.js +33 -7
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +14 -29
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +125 -41
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +98 -7
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +18 -17
- package/lib/network-heartbeat.js +3 -2
- package/lib/network-smtp-policy.js +52 -46
- package/lib/network-tls.js +67 -47
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +2 -12
- package/lib/nist-crosswalk.js +1 -1
- package/lib/nonce-store.js +81 -3
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +31 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +6 -47
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +14 -75
- package/lib/object-store/http-put.js +2 -7
- package/lib/object-store/http-request.js +157 -0
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +10 -79
- package/lib/observability-otlp-exporter.js +29 -29
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +54 -41
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/parsers/safe-yaml.js +28 -9
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/queue-local.js +10 -3
- package/lib/redact.js +9 -4
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +334 -29
- package/lib/resource-access-lock.js +3 -3
- package/lib/restore-bundle.js +46 -18
- package/lib/restore-rollback.js +10 -4
- package/lib/restore.js +24 -22
- package/lib/retention.js +23 -9
- package/lib/router.js +17 -4
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +14 -28
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +51 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sieve.js +1 -1
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +13 -27
- package/lib/sandbox-worker.js +15 -2
- package/lib/sandbox.js +4 -3
- package/lib/scheduler.js +22 -13
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +4 -11
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +219 -7
- package/lib/sql.js +228 -81
- package/lib/sse.js +6 -10
- package/lib/static.js +201 -111
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +275 -16
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +53 -38
- package/lib/tsa.js +16 -17
- package/lib/validate-opts.js +154 -8
- package/lib/vault/index.js +5 -0
- package/lib/vault/passphrase-ops.js +22 -26
- package/lib/vault/passphrase-source.js +8 -3
- package/lib/vault/rotate.js +13 -18
- package/lib/vault/seal-pem-file.js +34 -49
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +3 -8
- package/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/public-suffix-list.data.js +5498 -5494
- package/lib/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +59 -19
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +8 -4
- package/lib/worm.js +3 -4
- package/lib/ws-client.js +65 -56
- package/lib/x509-chain.js +44 -0
- package/package.json +1 -1
- 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
762
|
-
|
|
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
|
-
|
|
880
|
-
|
|
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 =
|
|
171
|
-
|
|
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
|
-
|
|
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 =
|
|
384
|
-
|
|
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
|
-
|
|
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
|
|
497
|
-
|
|
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
|
package/lib/observability.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
}
|
package/lib/otel-export.js
CHANGED
|
@@ -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 =
|
|
148
|
-
|
|
149
|
-
|
|
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 =
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
272
|
-
|
|
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
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
488
|
-
|
|
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);
|