feat: add native module realtime transport
CI / verify (pull_request) Successful in 37s
CI / infrastructure-smoke (pull_request) Successful in 13s
CI / migration-smoke (pull_request) Successful in 1m7s

This commit is contained in:
ekklesia
2026-08-23 18:47:18 -05:00
parent fcc531a39c
commit fc6c8045c4
53 changed files with 3959 additions and 108 deletions
+147
View File
@@ -0,0 +1,147 @@
import { z } from "zod";
import { type PermissionId, permissionIdSchema } from "./identifiers.js";
/** A module-local channel name. Core supplies the owning module namespace; modules cannot publish
* into another module's channel. The deliberately flat grammar has no hierarchy or wildcards. */
export const MODULE_REALTIME_CHANNEL_MAX_LENGTH = 48;
export const moduleRealtimeChannelSchema = z
.string()
.min(1)
.max(MODULE_REALTIME_CHANNEL_MAX_LENGTH)
.regex(
/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u,
"Must be a lowercase kebab-case module realtime channel.",
)
.brand<"ModuleRealtimeChannel">();
export type ModuleRealtimeChannel = z.infer<typeof moduleRealtimeChannelSchema>;
export function parseModuleRealtimeChannel(input: unknown): ModuleRealtimeChannel {
return moduleRealtimeChannelSchema.parse(input);
}
export function safeParseModuleRealtimeChannel(
input: unknown,
): z.ZodSafeParseResult<ModuleRealtimeChannel> {
return moduleRealtimeChannelSchema.safeParse(input);
}
/** Registration and fan-out ceilings are platform policy, identical for every module. */
export const MODULE_REALTIME_MAX_CHANNELS_PER_MODULE = 16;
export const MODULE_REALTIME_MAX_SUBSCRIBERS_PER_CHANNEL = 100;
export const MODULE_REALTIME_MAX_PAYLOAD_BYTES = 64 * 1024;
export type ModuleRealtimeJsonPrimitive = boolean | number | string | null;
export type ModuleRealtimeJsonValue =
| ModuleRealtimeJsonPrimitive
| readonly ModuleRealtimeJsonValue[]
| { readonly [key: string]: ModuleRealtimeJsonValue };
export interface ModuleRealtimeChannelDefinitionInput {
readonly channel: string;
readonly permission: string;
}
export interface ModuleRealtimeChannelDefinition {
readonly channel: ModuleRealtimeChannel;
readonly permission: PermissionId;
}
export interface ModuleRealtimeChannelRegistrar {
channel(definition: ModuleRealtimeChannelDefinitionInput): void;
}
/** The runtime publisher is already scoped to its module. No module id, origin, transport, token,
* room, or topic can be supplied by module code. */
export interface ModuleRealtimePublisher {
/** `unknown` is deliberate: an ordinary named interface is valid input without having to add a
* meaningless string index signature. TypeScript cannot prove recursive JSON compatibility for
* every structural interface, so the host validates the exact runtime value authoritatively. */
publish(channel: string, payload: unknown): void;
}
export type ModuleRealtimeChannelDefinitionIssueCode =
| "MODULE_REALTIME_CHANNEL_DEFINITION_INVALID"
| "MODULE_REALTIME_CHANNEL_INVALID"
| "MODULE_REALTIME_PERMISSION_INVALID";
export type ModuleRealtimeChannelDefinitionParseResult =
| { readonly success: true; readonly data: ModuleRealtimeChannelDefinition }
| { readonly success: false; readonly code: ModuleRealtimeChannelDefinitionIssueCode };
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
/** Validates only the definition's transport-neutral shape. Core separately proves that the
* permission belongs to the registering module. Rejected input is never reflected in the result. */
export function safeParseModuleRealtimeChannelDefinition(
value: unknown,
): ModuleRealtimeChannelDefinitionParseResult {
if (!isRecord(value)) {
return { success: false, code: "MODULE_REALTIME_CHANNEL_DEFINITION_INVALID" };
}
const keys = Object.keys(value);
if (keys.length !== 2 || !keys.includes("channel") || !keys.includes("permission")) {
return { success: false, code: "MODULE_REALTIME_CHANNEL_DEFINITION_INVALID" };
}
const channel = moduleRealtimeChannelSchema.safeParse(value.channel);
if (!channel.success) {
return { success: false, code: "MODULE_REALTIME_CHANNEL_INVALID" };
}
const permission = permissionIdSchema.safeParse(value.permission);
if (!permission.success) {
return { success: false, code: "MODULE_REALTIME_PERMISSION_INVALID" };
}
return {
success: true,
data: Object.freeze({ channel: channel.data, permission: permission.data }),
};
}
export function parseModuleRealtimeChannelDefinition(
value: unknown,
): ModuleRealtimeChannelDefinition {
const result = safeParseModuleRealtimeChannelDefinition(value);
if (!result.success) {
throw new TypeError("Invalid Ekklesia module realtime channel definition.");
}
return result.data;
}
export const MODULE_REALTIME_ERROR_CODES = [
"REALTIME_CHANNEL_INVALID",
"REALTIME_CHANNEL_UNDECLARED",
"REALTIME_PAYLOAD_INVALID",
"REALTIME_PAYLOAD_TOO_LARGE",
"REALTIME_PUBLISHER_STOPPED",
"REALTIME_SNAPSHOT_UNAVAILABLE",
"REALTIME_SUBSCRIBER_LIMIT",
"REALTIME_UNAVAILABLE",
] as const;
export type ModuleRealtimeErrorCode = (typeof MODULE_REALTIME_ERROR_CODES)[number];
const realtimeErrorMessages: Readonly<Record<ModuleRealtimeErrorCode, string>> = Object.freeze({
REALTIME_CHANNEL_INVALID: "The realtime channel is invalid.",
REALTIME_CHANNEL_UNDECLARED: "This module did not declare the realtime channel.",
REALTIME_PAYLOAD_INVALID: "The realtime payload is not JSON-compatible.",
REALTIME_PAYLOAD_TOO_LARGE: "The realtime payload exceeds the platform limit.",
REALTIME_PUBLISHER_STOPPED: "The realtime publisher is no longer running.",
REALTIME_SNAPSHOT_UNAVAILABLE: "The realtime channel has no current snapshot.",
REALTIME_SUBSCRIBER_LIMIT: "The realtime channel subscriber limit has been reached.",
REALTIME_UNAVAILABLE: "Realtime delivery is not available.",
});
/** A fixed-code, fixed-message failure with no cause, payload, URL, path, or transport detail. */
export class ModuleRealtimeError extends Error {
readonly code: ModuleRealtimeErrorCode;
constructor(code: ModuleRealtimeErrorCode) {
super(realtimeErrorMessages[code]);
this.name = "ModuleRealtimeError";
this.code = code;
this.stack = `${this.name}: ${this.message}`;
Object.freeze(this);
}
}