@beignet/core
Version:
Core framework primitives for Beignet
353 lines (337 loc) • 11.3 kB
text/typescript
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;
}