UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

353 lines (337 loc) 11.3 kB
import "../server-only.js"; import type { StandardSchemaV1 } from "@standard-schema/spec"; import { BroadcastValidationError, broadcastLimits, type ChannelDefinition, channelKey, type InferChannelEvent, type InferChannelParams, parseChannelEvent, parseChannelParams, } from "./index.js"; /** Server-authenticated scope captured at the originating HTTP boundary. JSON-safe for jobs. */ export interface BroadcastOrigin { readonly version: 1; readonly clientId: string; readonly scope: string; } const clientIdPattern = /^[a-zA-Z0-9_-]{22,128}$/; function parseOrigin(value: unknown): BroadcastOrigin | undefined { if (!value || typeof value !== "object") return undefined; const candidate = value as Record<string, unknown>; 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: StandardSchemaV1<unknown, BroadcastOrigin> = { "~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: { headers: Headers; principalId?: string | null; tenantId?: string | null; namespace: string; }): BroadcastOrigin | undefined { 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, ]), }); } export type BroadcastPublication<C extends ChannelDefinition> = InferChannelEvent<C> & { params: InferChannelParams<C>; excludeOrigin?: BroadcastOrigin; }; /** Readiness proves initial subscription only. Continuity loss requires reconnect and refetch. */ export interface BroadcastSubscription { readonly ready: Promise<void>; unsubscribe(): Promise<void>; } export interface BroadcastPort { /** Resolves when accepted by the provider, including when nobody is subscribed. */ publish<C extends ChannelDefinition>( channel: C, publication: BroadcastPublication<C>, ): Promise<void>; subscribe<C extends ChannelDefinition>( channel: C, options: { params: InferChannelParams<C>; onEvent: ( event: InferChannelEvent<C>, metadata: { excludeOrigin?: BroadcastOrigin }, ) => void; onDisconnect: () => void; }, ): BroadcastSubscription; } /** Internal transport envelope carried by broadcast providers, never a browser payload. */ export interface BroadcastEnvelope { version: 1; channel: string; params: Record<string, string>; event: string; data: unknown; excludeOrigin?: BroadcastOrigin; } /** Low-level adapter contract. Subscription callbacks must be isolated from publishers. */ export interface BroadcastTransport { publish(key: string, envelope: BroadcastEnvelope): Promise<void>; subscribe( key: string, options: { onMessage: (envelope: unknown) => void; onDisconnect: () => void; }, ): BroadcastSubscription; } /** Shared validation and cleanup for provider implementations. */ export function createBroadcastPort( transport: BroadcastTransport, ): BroadcastPort { 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: BroadcastSubscription | undefined; let queued = 0; let queuedBytes = 0; let pending = Promise.resolve(); let cancel: (() => void) | undefined; let cleanup: Promise<void> | undefined; 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: number; 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 as BroadcastEnvelope; 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: ReturnType<typeof setTimeout> | undefined; const ready = Promise.race([ initialize, new Promise<never>((_, 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(); }, }; }, }; } export interface ChannelBinding<Ctx> { readonly kind: "channel-binding"; readonly channel: ChannelDefinition; authorize: (args: { ctx: Ctx; params: Record<string, string>; }) => void | Promise<void>; } export interface ChannelRegistry<Ctx> { readonly kind: "channel-registry"; readonly bindings: readonly ChannelBinding<Ctx>[]; get(name: string): ChannelBinding<Ctx> | undefined; } /** Bind every channel explicitly, including public channels. */ export function createBroadcasting<Ctx>() { function defineChannelBinding<C extends ChannelDefinition>( channel: C, options: { authorize: (args: { ctx: Ctx; params: InferChannelParams<C>; }) => void | Promise<void>; }, ): ChannelBinding<Ctx> { return Object.freeze({ kind: "channel-binding" as const, channel, authorize: ({ ctx, params, }: { ctx: Ctx; params: Record<string, string>; }) => options.authorize({ ctx, params: params as InferChannelParams<C> }), }); } function defineChannelRegistry( bindings: readonly ChannelBinding<Ctx>[], ): ChannelRegistry<Ctx> { const byName = new Map<string, ChannelBinding<Ctx>>(); 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: string) => byName.get(name), }); } return { defineChannelBinding, defineChannelRegistry }; } /** Validate a configurable deadline without allowing an unbounded stream. */ export function broadcastLifetime( value: number = broadcastLimits.defaultLifetimeMs, ): number { 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; }