import type { InstalledOfflineCommandContribution } from "./offline-command.ts"; import type { InstalledServiceWorkerSelection } from "./service-worker.ts"; import type { InstalledWebWorkerContribution } from "./web-worker.ts"; /** * §3.4–§3.6. Optional runtime capability selection and hosting. * * Source contribution decides what is installed. Runtime Config may only carry * `DEFAULT | DISABLED`, so a configuration document can never switch on a * capability whose source is absent. */ export type RuntimeCapabilityOverride = "DEFAULT" | "DISABLED"; export type RuntimeStopReason = | "APPLICATION_SHUTDOWN" | "SCOPE_FENCED" | "FEATURE_DISABLED" | "HIDDEN_POLICY" | "INCIDENT_CONTAINMENT"; export interface RuntimeLifecycle { start(): void | Promise; stop(reason: RuntimeStopReason): void | Promise; dispose(): void | Promise; } /** §20.2. `FAILED -> STARTING` is never automatic. */ export type RuntimeLifecycleState = | "NEW" | "STARTING" | "RUNNING" | "STOPPING" | "STOPPED" | "FAILED" | "DISPOSING" | "DISPOSED"; /** §22.12. Health is per capability; there is no global `healthy` boolean. */ export type RuntimeHealth = | "AVAILABLE" | "DEGRADED" | "UNAVAILABLE" | "INCOMPATIBLE" | "DISABLED"; // §13.2. Realtime product contribution. export type RealtimeEffectKind = | "INVALIDATE_TOPICS" | "APPLY_AUTHORITATIVE_DELTA" | "EPHEMERAL_NOTIFICATION"; export interface InstalledRealtimeEventEffect { readonly eventType: string; readonly mapperId: string; readonly effect: RealtimeEffectKind; readonly invalidationTopics: readonly string[]; } export type InstalledRealtimeTransport = | Readonly<{ kind: "SSE"; endpointId: string }> | Readonly<{ kind: "WEBSOCKET"; endpointId: string }> | Readonly<{ kind: "POLLING"; operationId: string; intervalMs: number }>; export interface InstalledRealtimeContribution { readonly contributionId: string; readonly featureId: string; readonly contractSourcePackageId: string; readonly streamId: string; readonly recoveryMode: "CURSOR" | "SNAPSHOT_ONLY" | "SESSION_REBUILD"; readonly eventEffects: readonly InstalledRealtimeEventEffect[]; readonly transport: InstalledRealtimeTransport; } export const REALTIME_CONTRIBUTION_BOUNDS = Object.freeze({ contributions: 64, streams: 128, eventTypes: 512, effectsPerEvent: 8, invalidationTopicsPerEvent: 32, minimumPollingIntervalMs: 5_000, maximumPollingIntervalMs: 300_000, }); export interface InstalledRuntimeCapabilities { readonly realtime: readonly InstalledRealtimeContribution[]; readonly webWorkers: readonly InstalledWebWorkerContribution[]; readonly serviceWorker: InstalledServiceWorkerSelection | null; readonly offlineCommands: InstalledOfflineCommandContribution | null; } export type CapabilityOverrideMap = Readonly<{ REALTIME: RuntimeCapabilityOverride; WEB_WORKER: RuntimeCapabilityOverride; SERVICE_WORKER: RuntimeCapabilityOverride; OFFLINE_COMMANDS: RuntimeCapabilityOverride; }>; /** * The effective selection after applying runtime overrides. `serviceWorkerMode` * keeps the §3.6 persistent-registration exception explicit: a statically * `ACTIVE` worker that runtime config disables still performs exactly one * owned-registration lookup and at most one unregister, and deletes no cache. */ export type ResolvedRuntimeCapabilities = Readonly<{ realtime: readonly InstalledRealtimeContribution[]; webWorkers: readonly InstalledWebWorkerContribution[]; serviceWorker: InstalledServiceWorkerSelection | null; serviceWorkerDisabledCleanup: boolean; offlineCommands: InstalledOfflineCommandContribution | null; }>; export function resolveRuntimeCapabilities( installed: InstalledRuntimeCapabilities, overrides: CapabilityOverrideMap, ): ResolvedRuntimeCapabilities { const realtimeDisabled = overrides.REALTIME === "DISABLED"; const workersDisabled = overrides.WEB_WORKER === "DISABLED"; const serviceWorkerDisabled = overrides.SERVICE_WORKER === "DISABLED"; const offlineDisabled = overrides.OFFLINE_COMMANDS === "DISABLED"; return Object.freeze({ realtime: realtimeDisabled ? Object.freeze([]) : installed.realtime, webWorkers: workersDisabled ? Object.freeze([]) : installed.webWorkers, serviceWorker: serviceWorkerDisabled ? null : installed.serviceWorker, serviceWorkerDisabledCleanup: serviceWorkerDisabled && installed.serviceWorker?.mode === "ACTIVE", offlineCommands: offlineDisabled ? null : installed.offlineCommands, }); } export function validateRealtimeContributions( contributions: readonly InstalledRealtimeContribution[], ): readonly InstalledRealtimeContribution[] { const bounds = REALTIME_CONTRIBUTION_BOUNDS; if (contributions.length > bounds.contributions) { throw new TypeError("Realtime contributions exceed their bound."); } const contributionIds = new Set(); const streamIds = new Set(); let eventTypeCount = 0; for (const contribution of contributions) { if ( !contribution.contributionId || contributionIds.has(contribution.contributionId) ) { throw new TypeError("Duplicate realtime contribution identity."); } contributionIds.add(contribution.contributionId); streamIds.add(contribution.streamId); if (streamIds.size > bounds.streams) { throw new TypeError("Realtime streams exceed their bound."); } const transport = contribution.transport; if (transport.kind === "POLLING") { if ( !Number.isSafeInteger(transport.intervalMs) || transport.intervalMs < bounds.minimumPollingIntervalMs || transport.intervalMs > bounds.maximumPollingIntervalMs ) { throw new TypeError( `Realtime polling interval is out of range: ${contribution.contributionId}`, ); } } const seenEvents = new Map(); for (const effect of contribution.eventEffects) { eventTypeCount += 1; if (eventTypeCount > bounds.eventTypes) { throw new TypeError("Realtime event types exceed their bound."); } const count = (seenEvents.get(effect.eventType) ?? 0) + 1; if (count > bounds.effectsPerEvent) { throw new TypeError( `Realtime effects per event exceeded: ${effect.eventType}`, ); } seenEvents.set(effect.eventType, count); if ( effect.invalidationTopics.length > bounds.invalidationTopicsPerEvent ) { throw new TypeError( `Realtime invalidation topics exceeded: ${effect.eventType}`, ); } if ( effect.effect === "INVALIDATE_TOPICS" && effect.invalidationTopics.length === 0 ) { throw new TypeError( `INVALIDATE_TOPICS effect declares no topic: ${effect.eventType}`, ); } } } return Object.freeze([...contributions]); } export type RuntimeCapabilityId = | "REALTIME" | "WEB_WORKER" | "SERVICE_WORKER" | "OFFLINE_COMMANDS"; /** * §3.5. A bounded, serialisable view of one capability. `selected` is the * static SSOT count and `active` is what survived the runtime override, so the * difference between the two is exactly the operator's effect. An override can * only subtract, which is why a never-selected capability stays at zero. */ export type RuntimeCapabilityStatus = Readonly<{ capabilityId: RuntimeCapabilityId; selected: number; active: number; override: RuntimeCapabilityOverride; }>; export type RuntimeCapabilitySnapshot = readonly RuntimeCapabilityStatus[]; const CAPABILITY_ORDER = Object.freeze([ "REALTIME", "WEB_WORKER", "SERVICE_WORKER", "OFFLINE_COMMANDS", ] as const); export function describeRuntimeCapabilities( installed: InstalledRuntimeCapabilities, overrides: CapabilityOverrideMap, ): RuntimeCapabilitySnapshot { const resolved = resolveRuntimeCapabilities(installed, overrides); const counts: Readonly< Record> > = Object.freeze({ REALTIME: Object.freeze({ selected: installed.realtime.length, active: resolved.realtime.length, }), WEB_WORKER: Object.freeze({ selected: installed.webWorkers.length, active: resolved.webWorkers.length, }), SERVICE_WORKER: Object.freeze({ selected: installed.serviceWorker === null ? 0 : 1, active: resolved.serviceWorker === null ? 0 : 1, }), OFFLINE_COMMANDS: Object.freeze({ selected: installed.offlineCommands === null ? 0 : 1, active: resolved.offlineCommands === null ? 0 : 1, }), }); return Object.freeze( CAPABILITY_ORDER.map((capabilityId) => Object.freeze({ capabilityId, selected: counts[capabilityId].selected, active: counts[capabilityId].active, override: overrides[capabilityId], }), ), ); }