Sub-exports and Helpers

The extensions package exposes several focused sub-exports for code that wants a specific helper without pulling in a full extension. Every helper here is also used internally by the higher-level extensions, so importing from a sub-export is identical to importing from the implementation module.

svelte-adapter-uws-extensions/sensitive

Three helpers that cover the recurring “this value might be a credential” problem so callers do not have to write a redactor per call site.

stripInternal(obj) - safe to spread, safe to log

Recursively strips sensitive and adapter-internal keys from a user-supplied object, returning a clone you can hand to Object.assign, JSON.stringify, or console.log without leaking credentials or polluting the prototype chain.

import { stripInternal } from 'svelte-adapter-uws-extensions/sensitive';

const userData = JSON.parse(req.body);
const safe = stripInternal(userData);

console.log('upload from', safe);   // safe to log
Object.assign(target, safe);        // safe to spread
JSON.stringify(safe);               // safe to serialize

Safe to spread (no prototype pollution at the target).

  • Keys starting with __ are dropped at every depth. Catches __proto__ on a JSON.parse('{"__proto__":{...}}') payload before it can reach an Object.assign target.
  • The literal own-property names constructor and prototype are dropped.
  • The result is still a plain {} (not Object.create(null)) so callers depend on .toString(), .hasOwnProperty(), and instanceof Object. The spread-safety guarantee comes from filtering the dangerous keys at iteration, not from a null-prototype result.

Safe to log (no credentials in logs).

  • Keys matching /token|secret|password|auth|session|cookie|jwt|credential/i are dropped at every depth. Conservative substring match: a session_id field is dropped; a payment_method field is not. If your domain field name happens to contain one of those substrings, rename the public-surface field.
  • Binary views (Buffer, Uint8Array, any TypedArray, DataView, raw ArrayBuffer) are replaced with the string '[bytes: <byteLength>]'. Naively walking a Buffer via Object.keys would yield {"0":byte0,"1":byte1,...} which JSON.stringify happily serializes - if those bytes were a JWT, the credential lands in stderr.

Cycle-safe. A per-call WeakSet tracks ancestors; the second visit to a node returns undefined rather than recursing forever.

Not safe for keys whose name does not match the conservative pattern. account_balance, internal_billing_ref, medical_record_id - the helper does not know those are sensitive in your domain. Layer a second redactor on top of stripInternal for domain-specific PII.

redactConnectionUrl(url) - safe to embed in logs and errors

Redacts the password segment of a connection URL so the URL is safe to include in error messages, structured log lines, or assertion context. Substitutes :password@ userinfo and password= / pass= / pwd= query params with ***. Other URL bytes pass through unchanged.

import { redactConnectionUrl } from 'svelte-adapter-uws-extensions/sensitive';

redactConnectionUrl('postgres://user:hunter2@db.internal:5432/app?sslmode=require');
// 'postgres://user:***@db.internal:5432/app?sslmode=require'

Three classes the byte-level scan handles correctly that a naive regex misses:

  • Passwords containing @ (redis://user:p@ssword@host) - a first-@ regex stops too early and leaks the password tail; the scan walks to the LAST @ in the authority region.
  • IPv6 hosts (redis://:secret@[::1]:6379) - bracket-aware scanning suspends authority-terminator detection inside [...] so the : and @ of an IPv6 host are not confused with userinfo.
  • Query-string passwords (postgres://host/db?password=hunter2) - pg accepts password as a connection parameter; redaction is case-insensitive and also matches pass and pwd.

Non-string input passes through String(url) without scanning. Used internally by every connection failure log so a leaked DSN cannot escape via an unwrapped err.message.

createSensitiveWarner(prefix) - one-shot dev warning

Returns a function that scans a userData object (up to depth 3) and calls console.warn once if it finds a key matching /token|secret|password|key|auth|session|cookie|jwt|credential/i. After the first warning the function latches and is a no-op. Designed for plugin select callbacks: the plugin wires a warner per topic and surfaces a one-time hint when a developer forgets to strip credentials from broadcast data.

import { createSensitiveWarner } from 'svelte-adapter-uws-extensions/sensitive';
const warn = createSensitiveWarner('redis/cursor');
warn(userData); // logs once if userData has anything credential-shaped
warn(userData); // no-op after the latch

The warner uses the broader pattern (the bare substring "key" is included) because at the warning stage the cost of a false positive is just a developer-facing console.warn line, not silent data loss. The redaction pattern used by stripInternal deliberately omits "key" to avoid dropping legitimate id-like fields.

What these helpers do NOT do

  • Inbound payload validation. They redact what flows OUT; they do not validate what comes IN. Use a schema validator (zod, valibot, custom) at the message-handler boundary.
  • Wire-format payloads to clients. A handler that returns user data to the client should redact at the boundary explicitly. Piping through stripInternal works but is a heavy hammer - a typed select projection is usually clearer.
  • Domain-specific PII filtering. Conservative substring pattern catches generic credentials, not application-specific PII.
  • Replace not logging sensitive data in the first place. Default to redaction at the source.

svelte-adapter-uws-extensions/bus-validate

createBusValidator(options) and isValidBusTopic(topic) validate inbound bus envelopes before republishing them. Used internally by createPubSubBus, createShardedBus, createNotifyBridge, and createCursor.

import { createBusValidator, isValidBusTopic } from 'svelte-adapter-uws-extensions/bus-validate';

const validator = createBusValidator({
  maxEnvelopeBytes: 1_048_576,
  allowSystemTopics: false,
  systemChannel: '__realtime'
});

if (!validator.acceptRaw(rawMessage)) return;
const envelope = JSON.parse(rawMessage);
if (!validator.acceptEnvelope(envelope.topic, envelope.event)) return;

Two-stage validation - size DoS (very large payloads) is rejected before JSON.parse. Topics 1-256 chars, no control bytes, no " / \\.

isValidBusTopic(topic) is the same accept-set check exposed standalone for callers building their own validation pipelines.

acceptRaw(message) vs acceptSize(bytes)

acceptRaw(message) takes the raw message (string, Buffer, Uint8Array, or ArrayBufferView) and computes the byte length itself via Buffer.byteLength for strings and .byteLength for binary inputs. Non-string non-buffer inputs return false (fail-closed).

validator.acceptRaw('some message');    // string -> Buffer.byteLength
validator.acceptRaw(buf);               // Buffer -> .byteLength
validator.acceptRaw(new Uint8Array());  // view -> .byteLength
validator.acceptRaw(null);              // false

The legacy acceptSize(bytes) API is retained for backward compatibility but deprecated. The footgun it enabled: every subscriber pasted boilerplate to compute byte length before calling acceptSize, and message.length (character count) on a UTF-8 string with multi-byte characters under-counted the byte length. The cap would silently fail open for inputs that contained multi-byte characters. acceptRaw removes the per-caller computation and uses Buffer.byteLength internally so byte counting is correct for any input shape.

Migration is mechanical: replace validator.acceptSize(typeof m === 'string' ? Buffer.byteLength(m) : m.length) with validator.acceptRaw(m).

svelte-adapter-uws-extensions/shared/time

monotonicNow() returns a Date.now()-shaped number backed by performance.now() + processStartEpoch (snapshot taken at module load). It advances strictly forward regardless of system-clock adjustments, so it is the right primitive for elapsed-time math: lock acquire budgets, lease renewal deltas, timeout windows, histogram observations.

import { monotonicNow } from 'svelte-adapter-uws-extensions/shared/time';

const start = monotonicNow();
await doSomething();
const elapsedMs = monotonicNow() - start;

The failure mode it fixes: a backward NTP step or DST transition between two Date.now() reads makes Date.now() - start appear negative; a if (elapsed >= timeout) budget check silently extends the timeout (sometimes indefinitely if the step is large), and histogram observations can underflow their bucket boundaries.

now() (also exported from this module) is the existing wall-clock-shaped cached number with ~1s precision - use it for timestamps that go on the wire. Rule of thumb: “how long did this take?” -> monotonicNow() deltas; “what time is it?” -> now(). Both helpers are also used internally by redis/lock.js and the leader-election helper.

svelte-adapter-uws-extensions/assert

Production assertions used inside the extensions package. Categories cover bus envelope shape, replay batch shape, presence-ref bookkeeping, etc.

import { assert } from 'svelte-adapter-uws-extensions/assert';

assert(typeof topic === 'string', 'bus.topic-shape', { topic });

Production logs [extensions/assert] records and increments the extensions_assertion_violations_total{category} Prometheus counter via wireAssertionMetrics(metrics). Test mode throws.

svelte-adapter-uws-extensions/redis/fence

Redis fence provider for the task runner - hard-isolation guarantees on top of the default Postgres FOR UPDATE SKIP LOCKED row lock.

import { createFence } from 'svelte-adapter-uws-extensions/redis/fence';
import { createTaskRunner } from 'svelte-adapter-uws-extensions/postgres/tasks';

const fence = createFence(redis);
const tasks = await createTaskRunner(pg, { fence });

svelte-adapter-uws-extensions/testing

Mock platform / Redis / Postgres / WebSocket primitives plus PLATFORM_KEYS. See Resilience and Testing.

Was this page helpful?