@blamejs/core 0.8.42 → 0.8.49
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 +93 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/i18n.js
CHANGED
|
@@ -1,62 +1,60 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.i18n
|
|
3
|
+
* @module b.i18n
|
|
4
|
+
* @nav Tools
|
|
5
|
+
* @title i18n
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* ICU MessageFormat + CLDR Plural Rules + locale-aware Intl
|
|
9
|
+
* formatters with translation lookup. Built on Node 24's bundled
|
|
10
|
+
* `Intl.*` (`PluralRules`, `NumberFormat`, `DateTimeFormat`,
|
|
11
|
+
* `RelativeTimeFormat`, `ListFormat`, `DisplayNames`) — zero
|
|
12
|
+
* vendoring, zero CLDR data shipped, the runtime owns it.
|
|
8
13
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* })
|
|
14
|
+
* Lookup chain: `t("nav.home", vars, { locale })` walks the
|
|
15
|
+
* subtag-stripped chain (`pt-BR` → `pt`), then falls through to the
|
|
16
|
+
* configured `fallbackLocale` and finally `defaultLocale` unless
|
|
17
|
+
* `fallbackLocale: null` (strict "this locale or miss"). Plural-
|
|
18
|
+
* shaped values use CLDR cardinal keys (`zero` / `one` / `two` /
|
|
19
|
+
* `few` / `many` / `other`); `other` is mandatory and validated at
|
|
20
|
+
* load. Ordinal plurals route through a separate `Intl.PluralRules({
|
|
21
|
+
* type: "ordinal" })` cache via `to(key, count)`.
|
|
17
22
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* i.formatNumber(1234.5, { style: "currency", currency: "USD" });
|
|
21
|
-
* i.formatRelative(-5, "minute"); // → "5 minutes ago"
|
|
23
|
+
* Translation file format (JSON loaded eagerly from `opts.dir` or
|
|
24
|
+
* inline via `opts.translations`):
|
|
22
25
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
+
* {
|
|
27
|
+
* "greeting": "Hello, {name}!",
|
|
28
|
+
* "items": { "one": "{count} item", "other": "{count} items" },
|
|
29
|
+
* "nav": { "home": "Home", "about": "About" }
|
|
30
|
+
* }
|
|
26
31
|
*
|
|
27
|
-
*
|
|
32
|
+
* ICU MessageFormat (`{name, plural, ...}` / `{name, select, ...}` /
|
|
33
|
+
* `{name, selectordinal, ...}`) is auto-detected by `t()`; operators
|
|
34
|
+
* force the path with `t(key, vars, { messageFormat: true })`. The
|
|
35
|
+
* companion `b.i18n.messageFormat` namespace exposes the parser /
|
|
36
|
+
* formatter for use outside an instance.
|
|
28
37
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
38
|
+
* Validation policy:
|
|
39
|
+
* - `create()` throws on bad opts (boot).
|
|
40
|
+
* - Bad BCP 47 locale at any boundary → throw at call site.
|
|
41
|
+
* - `t(missingKey)` → return the key + emit `i18n.missing`
|
|
42
|
+
* observability event (never throws unless `missingKey: "throw"`).
|
|
43
|
+
* - Plural shape missing `other` → throw at load time.
|
|
44
|
+
* - Missing interpolation var renders as literal `{var}` unless
|
|
45
|
+
* `interpolation.strict: true`.
|
|
46
|
+
* - `formatNumber` / `formatDate` / `formatRelative` / `formatList`
|
|
47
|
+
* throw at call site on a non-finite value or unparseable date.
|
|
48
|
+
* - Middleware `Accept-Language` parse error falls back to the
|
|
49
|
+
* default locale; the request never crashes on a bad header.
|
|
34
50
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* `interpolation.strict: true`.
|
|
51
|
+
* Security stance: translation values come from operator-controlled
|
|
52
|
+
* files, not user input. `{var}` interpolation does NOT html-escape;
|
|
53
|
+
* `b.template` already escapes at render time. For non-template
|
|
54
|
+
* contexts, pass `interpolation.escape: fn`.
|
|
40
55
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* - create() opts → throw at boot
|
|
44
|
-
* - bad locale tag at any boundary → throw at call site
|
|
45
|
-
* - t(missingKey) → return key + observability event
|
|
46
|
-
* - t() with bad locale override → throw at call site (programming bug)
|
|
47
|
-
* - plural shape missing 'other' → throw at load time
|
|
48
|
-
* - interpolation missing var → render literal {var}
|
|
49
|
-
* - format* bad input → throw at call site
|
|
50
|
-
* - middleware Accept-Language parse error → fall back to defaultLocale
|
|
51
|
-
*
|
|
52
|
-
* Security stance: translation values come from operator-controlled
|
|
53
|
-
* files, not user input. {var} interpolation does NOT html-escape;
|
|
54
|
-
* `b.template` already escapes when rendered. Operators using t() in
|
|
55
|
-
* non-template contexts pass `interpolation.escape`.
|
|
56
|
-
*
|
|
57
|
-
* No audit-chain integration: i18n is not operator-action shaped (no
|
|
58
|
-
* state mutation). Routing observability events (missing key, locale
|
|
59
|
-
* fallback, formatter cache misses) is enough.
|
|
56
|
+
* @card
|
|
57
|
+
* ICU MessageFormat + CLDR Plural Rules + locale-aware Intl formatters with translation lookup.
|
|
60
58
|
*/
|
|
61
59
|
|
|
62
60
|
var fs = require("node:fs");
|
|
@@ -371,6 +369,61 @@ function _makeFormatterCache(make, kind, emitObs) {
|
|
|
371
369
|
|
|
372
370
|
// ---- Public create ----
|
|
373
371
|
|
|
372
|
+
/**
|
|
373
|
+
* @primitive b.i18n.create
|
|
374
|
+
* @signature b.i18n.create(opts)
|
|
375
|
+
* @since 0.6.0
|
|
376
|
+
* @status stable
|
|
377
|
+
* @related b.template.render
|
|
378
|
+
*
|
|
379
|
+
* Build an i18n instance bound to a fixed `locales` set. The returned
|
|
380
|
+
* object exposes translation (`t` / `tn` / `to` / `has`), Intl
|
|
381
|
+
* formatters (`formatNumber` / `formatDate` / `formatRelative` /
|
|
382
|
+
* `formatList` / `displayName`), locale state (`setLocale` /
|
|
383
|
+
* `locale` / `locales()` / `dir()`), translation introspection
|
|
384
|
+
* (`translations(locale)`), and an Express-shaped `middleware()` that
|
|
385
|
+
* negotiates the request locale (resolver → query → cookie →
|
|
386
|
+
* `Accept-Language`) and binds `req.t` / `req.tn` / `req.to` /
|
|
387
|
+
* `req.dir` / `res.locals.t` etc. for handlers.
|
|
388
|
+
*
|
|
389
|
+
* Throws `I18nError` at boot on a malformed locale tag, a
|
|
390
|
+
* `defaultLocale` not present in `locales`, a plural-shaped entry
|
|
391
|
+
* missing `other`, an unknown CLDR plural key, or a missing
|
|
392
|
+
* translation file when `dir` is supplied without `lazyLoad`.
|
|
393
|
+
*
|
|
394
|
+
* @opts
|
|
395
|
+
* defaultLocale: string, // BCP 47 tag; required, must appear in locales
|
|
396
|
+
* locales: [string], // BCP 47 tags; required, non-empty
|
|
397
|
+
* fallbackLocale: string | null, // null = strict; default = defaultLocale
|
|
398
|
+
* translations: { [locale: string]: object }, // inline trees (mutually exclusive with dir)
|
|
399
|
+
* dir: string, // load <dir>/<locale>.json (mutually exclusive with translations)
|
|
400
|
+
* eagerLocales: [string], // with lazyLoad: which locales to load at create
|
|
401
|
+
* lazyLoad: boolean, // with dir: load other locales on first lookup; default false
|
|
402
|
+
* interpolation: { start?: string, end?: string, escape?: fn, strict?: boolean },
|
|
403
|
+
* missingKey: "return-key" | "throw" | (key, locale) => string,
|
|
404
|
+
* onMissingKey: (key, locale) => void, // observability hook (best-effort)
|
|
405
|
+
* rtlLanguages: [string], // override the framework default RTL list
|
|
406
|
+
* observability: { event: (name, value, labels) => void },
|
|
407
|
+
* clock: () => number, // ms-since-epoch override (testing)
|
|
408
|
+
*
|
|
409
|
+
* @example
|
|
410
|
+
* var i = b.i18n.create({
|
|
411
|
+
* defaultLocale: "en",
|
|
412
|
+
* locales: ["en", "es", "fr", "ja", "ar"],
|
|
413
|
+
* translations: {
|
|
414
|
+
* en: { greeting: "Hello, {name}!", items: { one: "{count} item", other: "{count} items" } },
|
|
415
|
+
* es: { greeting: "Hola, {name}!" },
|
|
416
|
+
* },
|
|
417
|
+
* });
|
|
418
|
+
*
|
|
419
|
+
* i.t("greeting", { name: "Alice" }); // → "Hello, Alice!"
|
|
420
|
+
* i.tn("items", 5); // → "5 items"
|
|
421
|
+
* i.t("greeting", { name: "Ana" }, { locale: "es" }); // → "Hola, Ana!"
|
|
422
|
+
* i.formatNumber(1234.5, { style: "currency", currency: "USD" }); // → "$1,234.50"
|
|
423
|
+
* i.formatRelative(-5, "minute"); // → "5 minutes ago"
|
|
424
|
+
* i.dir({ locale: "ar" }); // → "rtl"
|
|
425
|
+
* i.has("nav.missing"); // → false
|
|
426
|
+
*/
|
|
374
427
|
function create(opts) {
|
|
375
428
|
opts = opts || {};
|
|
376
429
|
validateOpts(opts, [
|
package/lib/iab-mspa.js
CHANGED
|
@@ -1,41 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.iabMspa
|
|
4
|
-
*
|
|
3
|
+
* @module b.iabMspa
|
|
4
|
+
* @nav Compliance
|
|
5
|
+
* @title IAB MSPA
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* sections separated by `~`, each tagged with a section ID. The
|
|
10
|
-
* MSPA-relevant sections are the US national + state sections
|
|
11
|
-
* (USNAT, USCA, USVA, USCO, USCT, USUT) carrying:
|
|
7
|
+
* @intro
|
|
8
|
+
* IAB Multi-State Privacy Agreement signal — encode/decode opt-out
|
|
9
|
+
* preferences for state privacy laws (CCPA, CPA, etc.).
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* The IAB Global Privacy Platform (GPP) is the successor to the
|
|
12
|
+
* patchwork of per-state US privacy strings. A GPP string carries
|
|
13
|
+
* multiple sections separated by `~`, each tagged with a section
|
|
14
|
+
* ID. The MSPA-relevant sections cover the US national + state
|
|
15
|
+
* regimes (USNAT, USCA, USVA, USCO, USCT, USUT, plus 2025-26
|
|
16
|
+
* additions) and carry sale / sharing / targeted-ads /
|
|
17
|
+
* sensitive-data / child-data opt-out flags alongside the W3C
|
|
18
|
+
* `Sec-GPC` browser-signal mirror.
|
|
17
19
|
*
|
|
18
|
-
*
|
|
20
|
+
* The framework ships a partial-correct decoder (the binary tag
|
|
21
|
+
* layout is operator-side via the IAB's gpp-cmp libraries), an
|
|
22
|
+
* opt-out evaluator that returns `mustHonor` across in-scope
|
|
23
|
+
* sections, a throw-on-must-honor refusal helper, and a header
|
|
24
|
+
* reader for the `Sec-GPC: 1` universal opt-out signal.
|
|
19
25
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* sections: [{ id, optOuts: { sale, sharing, targetedAds, ... } }]
|
|
23
|
-
*
|
|
24
|
-
* b.iabMspa.checkOptOut(parsed, opts) -> { mustHonor, signals }
|
|
25
|
-
* opts: { dataUse: "sale" | "sharing" | "targeted-ads" |
|
|
26
|
-
* "sensitive" | "child-data", state? }
|
|
27
|
-
* Returns mustHonor=true when ANY in-scope section signals an
|
|
28
|
-
* opt-out for the requested use; signals lists which section IDs
|
|
29
|
-
* produced the verdict.
|
|
30
|
-
*
|
|
31
|
-
* b.iabMspa.refuseProcessing(parsed, opts)
|
|
32
|
-
* Throws IabMspaError when mustHonor → operator's data-flow code
|
|
33
|
-
* halts at the same point a CCPA "do-not-sell" header would.
|
|
34
|
-
*
|
|
35
|
-
* b.iabMspa.gpcFromHeaders(req) -> bool
|
|
36
|
-
* Reads the W3C `Sec-GPC: 1` browser header (RFC draft-davidson-
|
|
37
|
-
* httpbis-gpc-00). Universal opt-out per California CCPA / CPRA
|
|
38
|
-
* §1798.135(b)(1) and Colorado, Connecticut, etc.
|
|
26
|
+
* @card
|
|
27
|
+
* IAB Multi-State Privacy Agreement signal — encode/decode opt-out preferences for state privacy laws (CCPA, CPA, etc.).
|
|
39
28
|
*/
|
|
40
29
|
|
|
41
30
|
var audit = require("./audit");
|
|
@@ -63,6 +52,26 @@ var SECTION_IDS = {
|
|
|
63
52
|
var ALL_SECTIONS = Object.keys(SECTION_IDS).map(Number);
|
|
64
53
|
var DATA_USES = ["sale", "sharing", "targeted-ads", "sensitive", "child-data"];
|
|
65
54
|
|
|
55
|
+
/**
|
|
56
|
+
* @primitive b.iabMspa.parseGpp
|
|
57
|
+
* @signature b.iabMspa.parseGpp(gppString)
|
|
58
|
+
* @since 0.8.44
|
|
59
|
+
* @related b.iabMspa.checkOptOut, b.iabMspa.refuseProcessing
|
|
60
|
+
*
|
|
61
|
+
* Parse the framing of a GPP string into `{ header, sections }`. The
|
|
62
|
+
* decoder splits on `~`, identifies each section by its positional
|
|
63
|
+
* claim in the header's section-ID list, and exposes the per-section
|
|
64
|
+
* raw payloads. The framework deliberately does not decode the
|
|
65
|
+
* binary section layout — operator-side libraries
|
|
66
|
+
* (`@iabtechlab/gpp-cmp`) own that surface and populate
|
|
67
|
+
* `section.optOuts`. Throws on missing input or strings exceeding
|
|
68
|
+
* the 8192-char defensive cap.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* var parsed = b.iabMspa.parseGpp("DBABBg.7.8");
|
|
72
|
+
* parsed.header.sectionIds; // → [7, 8]
|
|
73
|
+
* parsed.sections.length; // → 0 (no payload segments yet)
|
|
74
|
+
*/
|
|
66
75
|
function parseGpp(gppString) {
|
|
67
76
|
if (typeof gppString !== "string" || gppString.length === 0) {
|
|
68
77
|
throw IabMspaError.factory("BAD_INPUT",
|
|
@@ -115,6 +124,37 @@ function parseGpp(gppString) {
|
|
|
115
124
|
return { header: header, sections: sections };
|
|
116
125
|
}
|
|
117
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @primitive b.iabMspa.checkOptOut
|
|
129
|
+
* @signature b.iabMspa.checkOptOut(parsed, opts)
|
|
130
|
+
* @since 0.8.44
|
|
131
|
+
* @related b.iabMspa.parseGpp, b.iabMspa.refuseProcessing
|
|
132
|
+
*
|
|
133
|
+
* Walk the parsed GPP sections and return `{ mustHonor, signals }`
|
|
134
|
+
* for the requested data-use category. `mustHonor` is `true` when
|
|
135
|
+
* ANY in-scope section signals an opt-out for that use; `signals`
|
|
136
|
+
* lists the section labels that produced the verdict. Operators
|
|
137
|
+
* narrow the search to a specific state by passing `opts.state`.
|
|
138
|
+
* Sections whose `optOuts` field hasn't been populated by an
|
|
139
|
+
* operator-side decoder are skipped (no false positives from
|
|
140
|
+
* missing data).
|
|
141
|
+
*
|
|
142
|
+
* @opts
|
|
143
|
+
* dataUse: "sale" | "sharing" | "targeted-ads" | "sensitive" | "child-data",
|
|
144
|
+
* state: string, // optional GPP section label
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* var parsed = {
|
|
148
|
+
* header: { sectionIds: [8] },
|
|
149
|
+
* sections: [
|
|
150
|
+
* { id: 8, idLabel: "usca", raw: "",
|
|
151
|
+
* optOuts: { sale: true, sharing: false, targetedAds: true } },
|
|
152
|
+
* ],
|
|
153
|
+
* };
|
|
154
|
+
* var verdict = b.iabMspa.checkOptOut(parsed, { dataUse: "sale" });
|
|
155
|
+
* verdict.mustHonor; // → true
|
|
156
|
+
* verdict.signals; // → ["usca"]
|
|
157
|
+
*/
|
|
118
158
|
function checkOptOut(parsed, opts) {
|
|
119
159
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.sections)) {
|
|
120
160
|
throw IabMspaError.factory("BAD_PARSED",
|
|
@@ -140,6 +180,28 @@ function checkOptOut(parsed, opts) {
|
|
|
140
180
|
return { mustHonor: signals.length > 0, signals: signals };
|
|
141
181
|
}
|
|
142
182
|
|
|
183
|
+
/**
|
|
184
|
+
* @primitive b.iabMspa.refuseProcessing
|
|
185
|
+
* @signature b.iabMspa.refuseProcessing(parsed, opts)
|
|
186
|
+
* @since 0.8.44
|
|
187
|
+
* @related b.iabMspa.checkOptOut, b.iabMspa.parseGpp
|
|
188
|
+
*
|
|
189
|
+
* Throw `IabMspaError` when `checkOptOut` returns `mustHonor:true`
|
|
190
|
+
* — wires the framework's opt-out signal into the operator's
|
|
191
|
+
* data-flow code at the same point a CCPA do-not-sell header would
|
|
192
|
+
* halt processing. Audits the refusal under
|
|
193
|
+
* `iabmspa.processing_refused` before throwing. Returns the verdict
|
|
194
|
+
* object on the no-opt-out path so the caller can inspect signals.
|
|
195
|
+
*
|
|
196
|
+
* @opts
|
|
197
|
+
* dataUse: "sale" | "sharing" | "targeted-ads" | "sensitive" | "child-data",
|
|
198
|
+
* state: string, // optional GPP section label
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* var parsed = { header: { sectionIds: [] }, sections: [] };
|
|
202
|
+
* var verdict = b.iabMspa.refuseProcessing(parsed, { dataUse: "sale" });
|
|
203
|
+
* verdict.mustHonor; // → false (no signals → no throw)
|
|
204
|
+
*/
|
|
143
205
|
function refuseProcessing(parsed, opts) {
|
|
144
206
|
var rv = checkOptOut(parsed, opts);
|
|
145
207
|
if (rv.mustHonor) {
|
|
@@ -159,6 +221,24 @@ function refuseProcessing(parsed, opts) {
|
|
|
159
221
|
return rv;
|
|
160
222
|
}
|
|
161
223
|
|
|
224
|
+
/**
|
|
225
|
+
* @primitive b.iabMspa.gpcFromHeaders
|
|
226
|
+
* @signature b.iabMspa.gpcFromHeaders(req)
|
|
227
|
+
* @since 0.8.44
|
|
228
|
+
* @related b.iabMspa.checkOptOut, b.iabMspa.refuseProcessing
|
|
229
|
+
*
|
|
230
|
+
* Read the W3C `Sec-GPC: 1` browser header from an inbound request.
|
|
231
|
+
* Returns `true` when the user's browser is asserting the universal
|
|
232
|
+
* opt-out signal (mandatory under California CCPA / CPRA
|
|
233
|
+
* §1798.135(b)(1) and Colorado, Connecticut, etc.). Defensive
|
|
234
|
+
* against missing `req`/`headers` shapes — never throws.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* var req = { headers: { "sec-gpc": "1" } };
|
|
238
|
+
* b.iabMspa.gpcFromHeaders(req); // → true
|
|
239
|
+
* b.iabMspa.gpcFromHeaders({ headers: {} }); // → false
|
|
240
|
+
* b.iabMspa.gpcFromHeaders(null); // → false
|
|
241
|
+
*/
|
|
162
242
|
function gpcFromHeaders(req) {
|
|
163
243
|
if (!req || !req.headers) return false;
|
|
164
244
|
var h = req.headers["sec-gpc"];
|
package/lib/iab-tcf.js
CHANGED
|
@@ -1,7 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.iabTcf
|
|
4
|
-
*
|
|
3
|
+
* @module b.iabTcf
|
|
4
|
+
* @nav Compliance
|
|
5
|
+
* @title IAB TCF
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* IAB Transparency & Consent Framework v2.3 — TCF string
|
|
9
|
+
* parse/encode, vendor list lookup, purpose & special-feature
|
|
10
|
+
* checks.
|
|
11
|
+
*
|
|
12
|
+
* Required by TCF Policy v2.3 §III.B.5 (CMP MUST signal which
|
|
13
|
+
* vendors received disclosure regardless of consent state).
|
|
14
|
+
* Deadline 2026-02-28 is past — Google Ads + every major DSP
|
|
15
|
+
* rejects v2.2-shaped strings since that date. EU/UK adtech
|
|
16
|
+
* operators that didn't migrate are losing inventory.
|
|
17
|
+
*
|
|
18
|
+
* Consent-string format (TCF v2.3 spec, §A): base64url-no-pad of
|
|
19
|
+
* segments separated by `.`:
|
|
20
|
+
* `Core | DisclosedVendors | (AllowedVendors) | PublisherTC`.
|
|
21
|
+
* Core carries cmpVersion=2, version=4 (TCF v2.3),
|
|
22
|
+
* created/lastUpdated, cmpId, vendorListVersion,
|
|
23
|
+
* policyVersion=4, special-feature-opts-in, purpose-consents,
|
|
24
|
+
* purpose-LIs, vendor-consents bitmap, vendor-LIs bitmap,
|
|
25
|
+
* publisher restrictions. DisclosedVendors is REQUIRED in v2.3.
|
|
26
|
+
*
|
|
27
|
+
* The framework does NOT bundle the IAB Global Vendor List —
|
|
28
|
+
* operators fetch the versioned JSON from
|
|
29
|
+
* https://vendor-list.consensu.org/v3/vendor-list.json and use
|
|
30
|
+
* `parsed.core.vendorListVersion` to load the matching cache
|
|
31
|
+
* entry.
|
|
32
|
+
*
|
|
33
|
+
* @card
|
|
34
|
+
* IAB Transparency & Consent Framework v2.3 — TCF string parse/encode, vendor list lookup, purpose & special-feature checks.
|
|
35
|
+
*/
|
|
36
|
+
/*
|
|
37
|
+
* Original prose retained:
|
|
5
38
|
*
|
|
6
39
|
* Required by TCF Policy v2.3 §III.B.5 (CMP MUST signal which vendors
|
|
7
40
|
* received disclosure regardless of consent state). Deadline 2026-02-28
|
|
@@ -205,6 +238,28 @@ function _parseSecondaryVendorSegment(buf, expectedType) {
|
|
|
205
238
|
return _parseVendorSection(r);
|
|
206
239
|
}
|
|
207
240
|
|
|
241
|
+
/**
|
|
242
|
+
* @primitive b.iabTcf.parseString
|
|
243
|
+
* @signature b.iabTcf.parseString(tcString)
|
|
244
|
+
* @since 0.8.0
|
|
245
|
+
* @status stable
|
|
246
|
+
* @compliance iab-tcf
|
|
247
|
+
* @related b.iabTcf.requireV23Disclosed, b.iabTcf.checkVendor
|
|
248
|
+
*
|
|
249
|
+
* Defensively parse a TCF v2.3 consent string (Core + optional
|
|
250
|
+
* DisclosedVendors / AllowedVendors / PublisherTC segments).
|
|
251
|
+
* Refuses non-string input, refuses payloads above 64 KiB, and
|
|
252
|
+
* caps every bit-field to spec-declared widths. Returns a
|
|
253
|
+
* structured object; per-segment decode failures land in
|
|
254
|
+
* `errors[]` instead of throwing so a partial parse still serves.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* var parsed = b.iabTcf.parseString("CPXxRfAPXxRfAAfKABENB-CgAP_AAH_AAA");
|
|
258
|
+
* parsed.core.version;
|
|
259
|
+
* // → 4
|
|
260
|
+
* parsed.errors;
|
|
261
|
+
* // → []
|
|
262
|
+
*/
|
|
208
263
|
function parseString(tcString) {
|
|
209
264
|
if (typeof tcString !== "string" || tcString.length === 0) {
|
|
210
265
|
throw IabTcfError.factory("BAD_INPUT",
|
|
@@ -261,6 +316,34 @@ function parseString(tcString) {
|
|
|
261
316
|
};
|
|
262
317
|
}
|
|
263
318
|
|
|
319
|
+
/**
|
|
320
|
+
* @primitive b.iabTcf.requireV23Disclosed
|
|
321
|
+
* @signature b.iabTcf.requireV23Disclosed(tcString, opts)
|
|
322
|
+
* @since 0.8.0
|
|
323
|
+
* @status stable
|
|
324
|
+
* @compliance iab-tcf
|
|
325
|
+
* @related b.iabTcf.parseString, b.iabTcf.checkVendor
|
|
326
|
+
*
|
|
327
|
+
* Hard gate the operator wires upstream of every ad-bidder
|
|
328
|
+
* forward. Throws `IabTcfError` when the core/policy version is
|
|
329
|
+
* not 4 (i.e. a v2.2 string), when the DisclosedVendors segment
|
|
330
|
+
* is absent (mandatory since 2026-02-28 per TCF Policy v2.3
|
|
331
|
+
* §III.B.5), or when base64url decoding fails. Emits
|
|
332
|
+
* `iabtcf.refused` / `iabtcf.accepted` to the audit chain so the
|
|
333
|
+
* regulator-facing record exists per request.
|
|
334
|
+
*
|
|
335
|
+
* @opts
|
|
336
|
+
* audit: boolean, // default true — emit accept/refuse audit events
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* try {
|
|
340
|
+
* var parsed = b.iabTcf.requireV23Disclosed("CPXxRfAPXxRfAAfKABENB-CgAP_AAH_AAA");
|
|
341
|
+
* parsed.disclosedVendors.present;
|
|
342
|
+
* // → true
|
|
343
|
+
* } catch (e) {
|
|
344
|
+
* // refuse the ad request
|
|
345
|
+
* }
|
|
346
|
+
*/
|
|
264
347
|
function requireV23Disclosed(tcString, opts) {
|
|
265
348
|
opts = opts || {};
|
|
266
349
|
var auditOn = opts.audit !== false;
|
|
@@ -329,6 +412,28 @@ function requireV23Disclosed(tcString, opts) {
|
|
|
329
412
|
return parsed;
|
|
330
413
|
}
|
|
331
414
|
|
|
415
|
+
/**
|
|
416
|
+
* @primitive b.iabTcf.checkVendor
|
|
417
|
+
* @signature b.iabTcf.checkVendor(parsed, vendorId)
|
|
418
|
+
* @since 0.8.0
|
|
419
|
+
* @status stable
|
|
420
|
+
* @compliance iab-tcf
|
|
421
|
+
* @related b.iabTcf.parseString, b.iabTcf.requireV23Disclosed
|
|
422
|
+
*
|
|
423
|
+
* Lookup a vendor id in a parsed TCF object. Returns three flags:
|
|
424
|
+
* `consented` (vendor in `vendorConsents`), `legitimate` (vendor
|
|
425
|
+
* in `vendorLIs`), `disclosed` (vendor in DisclosedVendors).
|
|
426
|
+
* Throws `IabTcfError` for malformed `parsed` or non-positive
|
|
427
|
+
* vendorId.
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* var parsed = b.iabTcf.parseString("CPXxRfAPXxRfAAfKABENB-CgAP_AAH_AAA");
|
|
431
|
+
* var verdict = b.iabTcf.checkVendor(parsed, 755);
|
|
432
|
+
* verdict.consented;
|
|
433
|
+
* // → false
|
|
434
|
+
* verdict.disclosed;
|
|
435
|
+
* // → false
|
|
436
|
+
*/
|
|
332
437
|
function checkVendor(parsed, vendorId) {
|
|
333
438
|
if (!parsed || !parsed.core) {
|
|
334
439
|
throw IabTcfError.factory("BAD_PARSED",
|
package/lib/inbox.js
CHANGED
|
@@ -1,62 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* b.inbox
|
|
3
|
+
* @module b.inbox
|
|
4
|
+
* @nav Production
|
|
5
|
+
* @title Inbox
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the
|
|
11
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Transactional dedupe-on-receive for inbound message handlers.
|
|
9
|
+
* Companion to `b.outbox`: where outbox guarantees at-least-once
|
|
10
|
+
* delivery, inbox lets the receiver guarantee exactly-once handling
|
|
11
|
+
* by recording every `(source, messageId)` pair in the same database
|
|
12
|
+
* transaction as the business state change. A duplicate redelivery
|
|
13
|
+
* (network retry, replay, broker re-dispatch on consumer failure)
|
|
14
|
+
* collides with the primary-key constraint and the second handler
|
|
15
|
+
* short-circuits cleanly.
|
|
12
16
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* await inbox.handle({
|
|
22
|
-
* messageId: kafkaEvent.headers["x-event-id"],
|
|
23
|
-
* source: "kafka:orders.created.v1",
|
|
24
|
-
* payload: kafkaEvent.payload, // optional, audit only
|
|
25
|
-
* }, async function (xdb) {
|
|
26
|
-
* // Business state change runs exactly once per (source, messageId).
|
|
27
|
-
* await xdb.query("INSERT INTO orders ...", [...]);
|
|
28
|
-
* });
|
|
17
|
+
* Schema (declared via `declareSchema(externalDb)`): `message_id
|
|
18
|
+
* TEXT`, `source TEXT`, `received_at TIMESTAMP`, `processed_at
|
|
19
|
+
* TIMESTAMP NULL`, `metadata_json JSONB|TEXT`, with `PRIMARY KEY
|
|
20
|
+
* (source, message_id)`. Postgres uses `ON CONFLICT … DO NOTHING
|
|
21
|
+
* RETURNING` to decide fresh-vs-duplicate in one round-trip; SQLite
|
|
22
|
+
* 3.35+ uses `INSERT OR IGNORE … RETURNING 1` to avoid the
|
|
23
|
+
* `changes()` race when callers issue intervening statements on the
|
|
24
|
+
* same transaction handle.
|
|
29
25
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* });
|
|
38
|
-
*
|
|
39
|
-
* // Schema:
|
|
40
|
-
* await inbox.declareSchema(b.externalDb);
|
|
41
|
-
*
|
|
42
|
-
* // Periodic retention sweep (operator wires their scheduler):
|
|
43
|
-
* await inbox.sweep();
|
|
44
|
-
*
|
|
45
|
-
* Schema columns:
|
|
26
|
+
* Defenses on the input side: `messageId` and `source` are bounded
|
|
27
|
+
* in length (default 256 chars each) and rejected for NUL / C0 /
|
|
28
|
+
* DEL control characters before they reach the primary key — Postgres
|
|
29
|
+
* TEXT may truncate at NUL, opening a dedupe-collision attack where
|
|
30
|
+
* `"abc\\0attacker"` and `"abc"` collide. `metadata` is JSON-
|
|
31
|
+
* serialized through `safeJson` and capped at `maxPayloadBytes`
|
|
32
|
+
* (default 64 KiB).
|
|
46
33
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
34
|
+
* Two APIs: high-level `handle(opts, handler)` opens a transaction,
|
|
35
|
+
* records receive, runs the handler exactly once when fresh, marks
|
|
36
|
+
* processed, commits — recommended for most callers. Low-level
|
|
37
|
+
* `recordReceive(opts, txn)` lets operators manage the transaction
|
|
38
|
+
* directly when they need fine-grained control over what runs in
|
|
39
|
+
* the dedupe envelope.
|
|
52
40
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* Picking semantics:
|
|
56
|
-
* - Postgres backends: ON CONFLICT (source, message_id) DO NOTHING
|
|
57
|
-
* RETURNING * lets `recordReceive` decide fresh vs duplicate in
|
|
58
|
-
* a single round-trip.
|
|
59
|
-
* - SQLite: INSERT OR IGNORE + SELECT changes() to test fresh-ness.
|
|
41
|
+
* @card
|
|
42
|
+
* Transactional dedupe-on-receive for inbound message handlers.
|
|
60
43
|
*/
|
|
61
44
|
|
|
62
45
|
var C = require("./constants");
|
|
@@ -90,6 +73,61 @@ function _utcNowExpr(externalDb) {
|
|
|
90
73
|
return "CURRENT_TIMESTAMP";
|
|
91
74
|
}
|
|
92
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @primitive b.inbox.create
|
|
78
|
+
* @signature b.inbox.create(opts)
|
|
79
|
+
* @since 0.8.48
|
|
80
|
+
* @status stable
|
|
81
|
+
* @related b.outbox, b.externalDb, b.audit
|
|
82
|
+
*
|
|
83
|
+
* Build an inbox dedupe-store. Returns
|
|
84
|
+
* `{ declareSchema, recordReceive, markProcessed, handle, sweep,
|
|
85
|
+
* isFresh, getStats, table, retentionDays }`. Operators call
|
|
86
|
+
* `declareSchema` once at boot, `handle` per inbound message, and
|
|
87
|
+
* `sweep` periodically (under their own scheduler) to age out
|
|
88
|
+
* processed rows past retention.
|
|
89
|
+
*
|
|
90
|
+
* @opts
|
|
91
|
+
* externalDb: Object, // b.externalDb instance (transaction()-shaped)
|
|
92
|
+
* table: string, // SQL identifier; required
|
|
93
|
+
* retentionDays: number, // sweep horizon (default 30); unprocessed rows kept 2x as long
|
|
94
|
+
* audit: boolean, // emit inbox.* audit events (default true)
|
|
95
|
+
* maxPayloadBytes: number, // metadata serialized cap (default 64 KiB)
|
|
96
|
+
* messageIdMaxLen: number, // chars (default 256)
|
|
97
|
+
* sourceMaxLen: number, // chars (default 256)
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* var inbox = b.inbox.create({
|
|
101
|
+
* externalDb: externalDbInstance,
|
|
102
|
+
* table: "inbox_events",
|
|
103
|
+
* retentionDays: 30,
|
|
104
|
+
* });
|
|
105
|
+
*
|
|
106
|
+
* await inbox.declareSchema(externalDbInstance);
|
|
107
|
+
*
|
|
108
|
+
* var outcome = await inbox.handle({
|
|
109
|
+
* messageId: "evt-9f3c4d",
|
|
110
|
+
* source: "kafka:orders.created.v1",
|
|
111
|
+
* }, async function (xdb) {
|
|
112
|
+
* await xdb.query("INSERT INTO orders (id) VALUES ($1)", ["o-42"]);
|
|
113
|
+
* return { orderId: "o-42" };
|
|
114
|
+
* });
|
|
115
|
+
* outcome.fresh; // → true on first delivery, false on replay
|
|
116
|
+
* outcome.result.orderId; // → "o-42"
|
|
117
|
+
*
|
|
118
|
+
* // Replay short-circuits:
|
|
119
|
+
* var replay = await inbox.handle({
|
|
120
|
+
* messageId: "evt-9f3c4d", source: "kafka:orders.created.v1",
|
|
121
|
+
* }, async function () { return { orderId: "should-not-run" }; });
|
|
122
|
+
* replay.fresh; // → false
|
|
123
|
+
* replay.result; // → null
|
|
124
|
+
*
|
|
125
|
+
* var stats = await inbox.getStats({ source: "kafka:orders.created.v1" });
|
|
126
|
+
* stats.total; // → 1
|
|
127
|
+
* stats.processed; // → 1
|
|
128
|
+
*
|
|
129
|
+
* var deleted = await inbox.sweep(); // age out beyond retention
|
|
130
|
+
*/
|
|
93
131
|
function create(opts) {
|
|
94
132
|
opts = opts || {};
|
|
95
133
|
validateOpts(opts, [
|