UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

213 lines 9.26 kB
import "../server-only.js"; import { BroadcastValidationError, broadcastLimits, channelKey, parseChannelEvent, parseChannelParams, } from "./index.js"; const clientIdPattern = /^[a-zA-Z0-9_-]{22,128}$/; function parseOrigin(value) { if (!value || typeof value !== "object") return undefined; const candidate = value; if (candidate.version !== 1 || typeof candidate.clientId !== "string" || !clientIdPattern.test(candidate.clientId) || typeof candidate.scope !== "string" || candidate.scope.length === 0 || candidate.scope.length > 2_048) return undefined; return { version: 1, clientId: candidate.clientId, scope: candidate.scope }; } /** Validate an origin already captured by a trusted server for a job payload. */ export const broadcastOriginSchema = { "~standard": { version: 1, vendor: "beignet", validate(value) { const origin = parseOrigin(value); return origin ? { value: origin } : { issues: [{ message: "Invalid broadcast origin" }] }; }, }, }; /** Never derive principal, tenant, or namespace from the optional client header. */ export function resolveBroadcastOrigin(options) { if (!options.principalId || !options.namespace) return undefined; return parseOrigin({ version: 1, clientId: options.headers.get("X-Beignet-Broadcast-Client"), scope: JSON.stringify([ options.namespace, options.tenantId ?? null, options.principalId, ]), }); } /** Shared validation and cleanup for provider implementations. */ export function createBroadcastPort(transport) { return { async publish(channel, publication) { const params = await parseChannelParams(channel, publication.params); const event = await parseChannelEvent(channel, publication); const excludeOrigin = publication.excludeOrigin === undefined ? undefined : parseOrigin(publication.excludeOrigin); if (publication.excludeOrigin !== undefined && !excludeOrigin) throw new BroadcastValidationError("Invalid broadcast origin"); await transport.publish(channelKey(channel.name, params), { version: 1, channel: channel.name, params, ...event, ...(excludeOrigin ? { excludeOrigin } : {}), }); }, subscribe(channel, options) { let stopped = false; let failed = false; let subscription; let queued = 0; let queuedBytes = 0; let pending = Promise.resolve(); let cancel; let cleanup; const release = () => (cleanup ??= subscription?.unsubscribe() ?? Promise.resolve()); const disconnect = () => { if (stopped || failed) return; failed = true; try { options.onDisconnect(); } catch { /* Consumer callbacks cannot break provider lifecycle. */ } }; const initialize = (async () => { const params = await parseChannelParams(channel, options.params); if (stopped) return; const key = channelKey(channel.name, params); subscription = transport.subscribe(key, { onDisconnect: disconnect, onMessage(value) { if (stopped || failed) return; let bytes; try { bytes = new TextEncoder().encode(JSON.stringify(value)).byteLength; } catch { disconnect(); return; } if (bytes > broadcastLimits.payloadBytes + broadcastLimits.requestBytes + 16_384 || queuedBytes + bytes > broadcastLimits.bufferedBytes) { disconnect(); return; } if (++queued > 128) { disconnect(); return; } queuedBytes += bytes; pending = pending .then(async () => { if (stopped || failed) return; if (!value || typeof value !== "object") throw new BroadcastValidationError("Invalid broadcast envelope"); const envelope = value; if (envelope.version !== 1 || envelope.channel !== channel.name) throw new BroadcastValidationError("Invalid broadcast envelope"); const receivedParams = await parseChannelParams(channel, envelope.params); if (channelKey(channel.name, receivedParams) !== key) throw new BroadcastValidationError("Broadcast channel mismatch"); const event = await parseChannelEvent(channel, envelope); const excludeOrigin = envelope.excludeOrigin === undefined ? undefined : parseOrigin(envelope.excludeOrigin); if (envelope.excludeOrigin !== undefined && !excludeOrigin) throw new BroadcastValidationError("Invalid broadcast origin"); if (!stopped && !failed) await options.onEvent(event, { excludeOrigin }); }) .catch(disconnect) .finally(() => { queued--; queuedBytes -= bytes; }); }, }); await subscription.ready; if (stopped) await release(); if (failed) throw new Error("Broadcast subscription lost continuity before readiness"); })(); let timeout; const ready = Promise.race([ initialize, new Promise((_, reject) => { cancel = () => reject(new Error("Broadcast subscription cancelled")); timeout = setTimeout(() => reject(new Error("Broadcast subscription readiness timed out")), broadcastLimits.readyTimeoutMs); }), ]) .catch((error) => { stopped = true; void release().catch(() => undefined); throw error; }) .finally(() => { if (timeout !== undefined) clearTimeout(timeout); cancel = undefined; }); // Callers still observe rejection through ready; early cancellation must not leak a rejection. void ready.catch(() => undefined); return { ready, async unsubscribe() { stopped = true; cancel?.(); await ready.catch(() => undefined); await release(); }, }; }, }; } /** Bind every channel explicitly, including public channels. */ export function createBroadcasting() { function defineChannelBinding(channel, options) { return Object.freeze({ kind: "channel-binding", channel, authorize: ({ ctx, params, }) => options.authorize({ ctx, params: params }), }); } function defineChannelRegistry(bindings) { const byName = new Map(); for (const binding of bindings) { if (byName.has(binding.channel.name)) throw new BroadcastValidationError(`Duplicate channel ${binding.channel.name}`); byName.set(binding.channel.name, binding); } return Object.freeze({ kind: "channel-registry", bindings: Object.freeze([...bindings]), get: (name) => byName.get(name), }); } return { defineChannelBinding, defineChannelRegistry }; } /** Validate a configurable deadline without allowing an unbounded stream. */ export function broadcastLifetime(value = broadcastLimits.defaultLifetimeMs) { if (!Number.isSafeInteger(value) || value < 1 || value > broadcastLimits.maxLifetimeMs) throw new BroadcastValidationError(`Broadcast maxLifetimeMs must be a safe integer between 1 and ${broadcastLimits.maxLifetimeMs} milliseconds`); return value; } //# sourceMappingURL=server.js.map