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 aJSON.parse('{"__proto__":{...}}')payload before it can reach anObject.assigntarget. - The literal own-property names
constructorandprototypeare dropped. - The result is still a plain
{}(notObject.create(null)) so callers depend on.toString(),.hasOwnProperty(), andinstanceof 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/iare dropped at every depth. Conservative substring match: asession_idfield is dropped; apayment_methodfield is not. If your domain field name happens to contain one of those substrings, rename the public-surface field. - Binary views (
Buffer,Uint8Array, anyTypedArray,DataView, rawArrayBuffer) are replaced with the string'[bytes: <byteLength>]'. Naively walking aBufferviaObject.keyswould yield{"0":byte0,"1":byte1,...}whichJSON.stringifyhappily 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 acceptspasswordas a connection parameter; redaction is case-insensitive and also matchespassandpwd.
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
stripInternalworks 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?