CultLib/packages/cultmesh-ts/src/index.ts
2026-07-14 23:47:30 +01:00

6356 lines
198 KiB
TypeScript

import { createSocket, type Socket } from "node:dgram";
import { decode, encode } from "@msgpack/msgpack";
import {
CultCache,
SingleFileMessagePackBackingStore,
type AnyCultCacheDocumentDefinition,
type CacheBackingStore,
type CultCacheEnvelope,
type CultCacheDocumentValue,
} from "cultcache-ts";
import {
CultNetDocumentRegistry,
CultNetRudpSession,
CultNetPeer,
CultNetRudpSocketTransportConnection,
CultNetSchemaCatalog,
CultNetShardCatalog,
cultNetBuiltinSchemaRegistry,
decodeRudpPacket,
defineCultNetDocumentBinding,
encodeCultNetMessageForWire,
encodeRudpPacket,
parseCultNetMessage,
type CultNetErrorMessage,
type CultNetDocumentBinding,
type CultNetMessage,
type CultNetOperationRequestMessage,
type CultNetOperationResponseMessage,
type CultNetRawDocumentRecord,
type CultNetReconnectPolicy,
type CultNetSchemaCatalogOptions,
type CultNetSnapshotResponseRawMessage,
type CultNetWireContract,
} from "cultnet-ts";
export * from "./provider-session";
export * from "./provider-session-wire";
export * from "./provider-rudp-transport";
export * from "./provider-session-broker";
export interface CultMeshVec2 {
readonly x: number;
readonly y: number;
}
export interface CultMeshRect {
readonly min: CultMeshVec2;
readonly max: CultMeshVec2;
}
export interface CultMeshViewportRequest {
readonly minX: number;
readonly minY: number;
readonly maxX: number;
readonly maxY: number;
readonly controlledEntityIndices?: readonly number[];
}
export type CultMeshLocalityKind =
| "automatic"
| "in-process"
| "shared-memory"
| "ipc"
| "network"
| "wasm";
export interface CultMeshRouteHint {
readonly kind: CultMeshLocalityKind;
readonly description?: string;
}
export interface CultMeshRouteRecord {
readonly kind: string;
readonly description: string;
}
export type CultMeshDocumentPublicationSource =
| {
readonly kind: "single-file";
readonly path: string;
}
| {
readonly kind: "store";
readonly store: CacheBackingStore;
}
| {
readonly kind: "peer-snapshot";
readonly peer: CultNetPeer | (() => CultNetPeer | Promise<CultNetPeer>);
readonly endpoint?: string;
};
export interface CultMeshPublicationDocumentBinding {
readonly schema: string | AnyCultCacheDocumentDefinition;
readonly recordKey: string;
readonly documentId?: string;
readonly sourceId?: string;
readonly source?: CultMeshDocumentPublicationSource;
}
export type CultMeshDocumentPublicationSourceResolver =
| CultMeshDocumentPublicationSource
| ((binding: CultMeshPublicationDocumentBinding) => CultMeshDocumentPublicationSource);
export interface CultMeshAuthorityClaim {
readonly role: string;
readonly shardId?: string;
readonly leaseId?: string;
}
export interface CultMeshVerseContext {
readonly verseId: string;
readonly runtimeId: string;
readonly routeHint: CultMeshRouteHint;
readonly claims: readonly CultMeshAuthorityClaim[];
}
export interface CultMeshOperationContext {
readonly runtimeId: string;
readonly claims: readonly CultMeshAuthorityClaim[];
readonly routeHint: CultMeshRouteHint;
readonly idempotencyKey?: string;
}
export interface CultMeshOperationReceipt {
readonly operationId: string;
readonly accepted: boolean;
readonly route: CultMeshRouteHint;
readonly diagnostic?: string;
}
export interface CultMeshQueryContext {
readonly runtimeId: string;
readonly routeHint: CultMeshRouteHint;
}
export type CultMeshUnsubscribe = () => void;
export type CultMeshDocumentWatcher<TDocument> = (
context: CultMeshQueryContext,
callback: (value: TDocument) => void,
) => CultMeshUnsubscribe;
export type CultMeshDocumentReplacer<TDocument> = (
context: CultMeshQueryContext,
value: TDocument,
) => Promise<void>;
export type CultMeshDocumentPredictionSubmitter<TDocument> = (
context: CultMeshQueryContext,
value: TDocument,
) => Promise<void>;
export type CultMeshDocumentUpdater<TDocument> = (
current: TDocument,
) => TDocument | Promise<TDocument>;
export type CultMeshCollectionSnapshot<TDocument> = readonly TDocument[];
export type CultMeshCollectionChangeKind = "added" | "updated" | "removed" | "reset";
export interface CultMeshCollectionChange<TDocument> {
readonly kind: CultMeshCollectionChangeKind;
readonly key?: string;
readonly value?: TDocument;
}
export type CultMeshCollectionWatcher<TDocument> = (
context: CultMeshQueryContext,
callback: (change: CultMeshCollectionChange<TDocument>) => void,
) => CultMeshUnsubscribe;
export interface CultMeshDocumentSchemaDescriptor {
readonly type?: string;
readonly schemaId?: string;
readonly schemaName?: string;
readonly schemaVersion?: string;
}
export type CultMeshStatePointerResolver<T> = (
context: CultMeshQueryContext,
) => Promise<T | undefined>;
export type CultMeshStatePointerWatcher<T> = (
context: CultMeshQueryContext,
callback: (value: T) => void,
) => CultMeshUnsubscribe;
export type CultMeshMutableStatePointerReplacer<T> = (
context: CultMeshQueryContext,
value: T,
) => Promise<void>;
export type CultMeshQueryWatcher<TParameters, TResult> = (
parameters: TParameters,
context: CultMeshQueryContext,
callback: (value: TResult) => void,
) => CultMeshUnsubscribe;
export type CultMeshLiveFeedWatcher<TParameters, TResult> = (
parameters: TParameters,
context: CultMeshQueryContext,
callback: (value: TResult) => void,
) => CultMeshUnsubscribe;
export interface CultMeshPollingWatchOptions<TResult> {
readonly intervalMs?: number;
readonly emitInitial?: boolean;
readonly equals?: (left: TResult, right: TResult) => boolean;
}
export interface CultMeshReactiveDocumentOptions {
readonly context?: CultMeshQueryContext | string;
readonly watch?: boolean;
readonly debounceMs?: number;
readonly onError?: (error: unknown) => void;
}
export type CultMeshReactiveDocumentDelta<TDocument extends object> = Partial<{
readonly [K in keyof TDocument]: TDocument[K] extends number ? number : TDocument[K];
}>;
export interface CultMeshReactiveDocumentReconciliation<TDocument extends object> {
readonly version: number;
readonly canonical: TDocument;
readonly predicted: TDocument;
readonly delta: CultMeshReactiveDocumentDelta<TDocument>;
readonly receivedAtMs: number;
}
export interface CultMeshReactiveDocument<TDocument extends object> {
readonly current: TDocument;
readonly reconciliation: CultMeshReactiveDocumentReconciliation<TDocument> | undefined;
readonly ready: Promise<TDocument>;
readonly disposed: boolean;
clearReconciliation(): void;
refresh(): Promise<TDocument>;
dispose(): void;
}
export interface CultMeshNativeSliceColumn {
readonly name: string;
readonly valueType: string;
readonly elementSizeBytes: number;
}
export interface CultMeshNativeSliceViewDescriptor {
readonly viewId: string;
readonly schemaId: string;
readonly rowCount: number;
readonly columns: readonly CultMeshNativeSliceColumn[];
readonly route: CultMeshRouteHint;
readonly nativeHandle?: string;
}
export interface CultMeshProjectionSource {
readonly sourceId: string;
readonly schemaId?: string;
readonly description?: string;
}
export interface CultMeshStateBindingDescriptor {
readonly targetProp: string;
readonly pointerId: string;
readonly sourceId?: string;
readonly schemaId?: string;
readonly routeHint: CultMeshRouteHint;
}
export interface CultMeshStateBindingRecord {
readonly targetProp: string;
readonly pointerId: string;
readonly sourceId: string;
readonly schemaId: string;
readonly routeKind: string;
readonly routeDescription: string;
}
export interface CultMeshOperationBindingDescriptor {
readonly operationId: string;
readonly label: string;
readonly schemaId: string;
readonly routeHint: CultMeshRouteHint;
}
export interface CultMeshOperationBindingRecord {
readonly operationId: string;
readonly label: string;
readonly schemaId: string;
readonly routeKind: string;
readonly routeDescription: string;
}
export interface CultMeshOperationInvocationDescriptor {
readonly operationId: string;
readonly schemaId: string;
readonly routeHint: CultMeshRouteHint;
readonly idempotencyKey?: string;
}
export interface CultMeshOperationInvocationRecord {
readonly operationId: string;
readonly schemaId: string;
readonly routeKind: string;
readonly routeDescription: string;
readonly idempotencyKey: string;
}
export interface CultMeshOperationPayload {
readonly fields: Readonly<Record<string, string>>;
getString(key: string, defaultValue?: string): string;
getInt(key: string, defaultValue?: number): number;
getDouble(key: string, defaultValue?: number): number;
getBoolean(key: string, defaultValue?: boolean): boolean;
with(key: string, value: string | number | boolean): CultMeshOperationPayload;
toRecord(): Readonly<Record<string, string>>;
}
export interface CultMeshQuerySurfaceDiagnostic {
readonly queryId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
export interface CultMeshOperationHandleDiagnostic {
readonly operationId: string;
}
export interface CultMeshStatePointerDiagnostic {
readonly pointerId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
export interface CultMeshStateRefResolverDiagnostic {
readonly resolverId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
export interface CultMeshProjectionRecipeDiagnostic {
readonly projectionId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
export interface CultMeshLiveFeedDiagnostic {
readonly feedId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
export type CultMeshSurfaceKind =
| "query"
| "projection-recipe"
| "live-feed"
| "operation"
| "document"
| "collection"
| "state-pointer"
| "native-slice-view";
export interface CultMeshSurfaceDiagnostic {
readonly kind: CultMeshSurfaceKind;
readonly surfaceId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
export interface CultMeshSurfaceCatalogDiagnostic {
readonly catalogId: string;
readonly surfaces: readonly CultMeshSurfaceDiagnostic[];
}
export interface CultMeshSurfaceCatalogIndexDiagnostic {
readonly catalogId: string;
readonly queries: readonly CultMeshSurfaceDiagnostic[];
readonly projectionRecipes: readonly CultMeshSurfaceDiagnostic[];
readonly liveFeeds: readonly CultMeshSurfaceDiagnostic[];
readonly operations: readonly CultMeshSurfaceDiagnostic[];
readonly documents: readonly CultMeshSurfaceDiagnostic[];
readonly collections: readonly CultMeshSurfaceDiagnostic[];
readonly statePointers: readonly CultMeshSurfaceDiagnostic[];
readonly nativeSliceViews: readonly CultMeshSurfaceDiagnostic[];
}
export interface CultMeshNativeSliceViewDiagnostic {
readonly viewId: string;
readonly schemaId: string;
readonly rowCount: number;
readonly columns: readonly CultMeshNativeSliceColumn[];
readonly route: CultMeshRouteHint;
readonly nativeHandle?: string;
readonly denseRowStrideBytes: number;
}
export class CultMeshOperationContextBuilder {
readonly #runtimeId: string;
#claims: CultMeshAuthorityClaim[] = [];
#routeHint: CultMeshRouteHint = cultMeshRouteHint();
#idempotencyKey: string | undefined;
public constructor(runtimeId: string) {
requireNonEmpty(runtimeId, "runtimeId");
this.#runtimeId = runtimeId;
}
public claim(
role: string,
options: { shardId?: string; leaseId?: string } = {},
): this {
this.#claims.push(cultMeshAuthorityClaim(role, options));
return this;
}
public claims(claims: readonly CultMeshAuthorityClaim[]): this {
this.#claims.push(...claims);
return this;
}
public route(kind: CultMeshLocalityKind, description?: string): this {
this.#routeHint = cultMeshRouteHint(kind, description);
return this;
}
public idempotency(key: string): this {
requireNonEmpty(key, "idempotencyKey");
this.#idempotencyKey = key;
return this;
}
public build(): CultMeshOperationContext {
return cultMeshOperationContext(this.#runtimeId, {
claims: this.#claims,
routeHint: this.#routeHint,
idempotencyKey: this.#idempotencyKey,
});
}
}
export class CultMeshQueryContextBuilder {
readonly #runtimeId: string;
#routeHint: CultMeshRouteHint = cultMeshRouteHint();
public constructor(runtimeId: string) {
requireNonEmpty(runtimeId, "runtimeId");
this.#runtimeId = runtimeId;
}
public route(kind: CultMeshLocalityKind, description?: string): this {
this.#routeHint = cultMeshRouteHint(kind, description);
return this;
}
public build(): CultMeshQueryContext {
return cultMeshQueryContext(this.#runtimeId, {
routeHint: this.#routeHint,
});
}
}
export class CultMeshVerse {
public constructor(public readonly context: CultMeshVerseContext) {
requireNonEmpty(context.verseId, "verseId");
requireNonEmpty(context.runtimeId, "runtimeId");
}
public get verseId(): string {
return this.context.verseId;
}
public get runtimeId(): string {
return this.context.runtimeId;
}
public use<TSchema>(schemaFactory: (context: CultMeshVerseContext) => TSchema): TSchema {
return schemaFactory(this.context);
}
public withRoute(kind: CultMeshLocalityKind, description?: string): CultMeshVerse {
return new CultMeshVerse({
...this.context,
routeHint: cultMeshRouteHint(kind, description),
});
}
public withClaim(role: string, options: { shardId?: string; leaseId?: string } = {}): CultMeshVerse {
return new CultMeshVerse({
...this.context,
claims: [...this.context.claims, cultMeshAuthorityClaim(role, options)],
});
}
public operationContext(options: { idempotencyKey?: string } = {}): CultMeshOperationContext {
return cultMeshOperationContextFromVerse(this.context, options);
}
public queryContext(): CultMeshQueryContext {
return cultMeshQueryContextFromVerse(this.context);
}
public bindOperation<TRequest, TResponse>(
operation: CultMeshOperationHandle<TRequest, TResponse>,
): CultMeshBoundOperationHandle<TRequest, TResponse> {
return cultMeshBindOperation(this.context, operation);
}
public bindQuery<TParameters, TResult>(
query: CultMeshQuerySurface<TParameters, TResult>,
): CultMeshBoundQuerySurface<TParameters, TResult> {
return cultMeshBindQuery(this.context, query);
}
public bindLiveFeed<TParameters, TResult>(
feed: CultMeshLiveFeed<TParameters, TResult>,
): CultMeshBoundLiveFeed<TParameters, TResult> {
return cultMeshBindLiveFeed(this.context, feed);
}
public bindDocument<TDocument>(
document: CultMeshDocumentHandle<TDocument>,
): CultMeshBoundDocumentHandle<TDocument> {
return cultMeshBindDocument(this.context, document);
}
public bindCollection<TDocument>(
collection: CultMeshCollectionHandle<TDocument>,
): CultMeshBoundCollectionHandle<TDocument> {
return cultMeshBindCollection(this.context, collection);
}
public bindStatePointer<T>(
pointer: CultMeshStatePointer<T>,
): CultMeshBoundStatePointer<T> {
return cultMeshBindStatePointer(this.context, pointer);
}
public bindMutableStatePointer<T>(
pointer: CultMeshMutableStatePointer<T>,
): CultMeshBoundMutableStatePointer<T> {
return cultMeshBindMutableStatePointer(this.context, pointer);
}
}
export class CultMeshOperationHandle<TRequest, TResponse> {
public constructor(
public readonly operationId: string,
private readonly invokeOperation: (
request: TRequest,
context: CultMeshOperationContext,
) => Promise<TResponse>,
) {
requireNonEmpty(operationId, "operationId");
}
public invoke(
request: TRequest,
context: CultMeshOperationContext | string,
): Promise<TResponse> {
return this.invokeOperation(
request,
typeof context === "string" ? cultMeshOperationContext(context) : context,
);
}
public bind(
verse: CultMeshVerseContext | CultMeshVerse,
): CultMeshBoundOperationHandle<TRequest, TResponse> {
return cultMeshBindOperation(verse, this);
}
}
export class CultMeshBoundOperationHandle<TRequest, TResponse> {
public constructor(
public readonly verse: CultMeshVerseContext,
public readonly operation: CultMeshOperationHandle<TRequest, TResponse>,
) {}
public get operationId(): string {
return this.operation.operationId;
}
public invoke(
request: TRequest,
options: { idempotencyKey?: string } = {},
): Promise<TResponse> {
return this.operation.invoke(
request,
cultMeshOperationContextFromVerse(this.verse, options),
);
}
}
export class CultMeshQuerySurface<TParameters, TResult> {
public readonly sources: readonly CultMeshProjectionSource[];
public readonly routeHint: CultMeshRouteHint;
public constructor(
public readonly queryId: string,
private readonly executeQuery: (
parameters: TParameters,
context: CultMeshQueryContext,
) => Promise<TResult>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchQuery?: CultMeshQueryWatcher<TParameters, TResult>;
} = {},
) {
requireNonEmpty(queryId, "queryId");
this.sources = [...(options.sources ?? [])];
this.routeHint = options.routeHint ?? cultMeshRouteHint();
this.watchQuery = options.watchQuery;
}
private readonly watchQuery: CultMeshQueryWatcher<TParameters, TResult> | undefined;
public execute(
parameters: TParameters,
context: CultMeshQueryContext | string,
): Promise<TResult> {
return this.executeQuery(
parameters,
typeof context === "string" ? cultMeshQueryContext(context) : context,
);
}
public query(parameters: TParameters, context: CultMeshQueryContext | string): Promise<TResult> {
return this.execute(parameters, context);
}
public watch(
parameters: TParameters,
context: CultMeshQueryContext | string,
callback: (value: TResult) => void,
): CultMeshUnsubscribe {
if (!this.watchQuery) {
throw new Error(`Query surface '${this.queryId}' does not support watches.`);
}
return this.watchQuery(
parameters,
typeof context === "string" ? cultMeshQueryContext(context) : context,
callback,
);
}
public bind(
verse: CultMeshVerseContext | CultMeshVerse,
): CultMeshBoundQuerySurface<TParameters, TResult> {
return cultMeshBindQuery(verse, this);
}
}
export class CultMeshBoundQuerySurface<TParameters, TResult> {
public constructor(
public readonly verse: CultMeshVerseContext,
public readonly query: CultMeshQuerySurface<TParameters, TResult>,
) {}
public get queryId(): string {
return this.query.queryId;
}
public get sources(): readonly CultMeshProjectionSource[] {
return this.query.sources;
}
public get routeHint(): CultMeshRouteHint {
return this.query.routeHint;
}
public execute(parameters: TParameters): Promise<TResult> {
return this.query.execute(parameters, cultMeshQueryContextFromVerse(this.verse));
}
public queryOnce(parameters: TParameters): Promise<TResult> {
return this.execute(parameters);
}
public watch(
parameters: TParameters,
callback: (value: TResult) => void,
): CultMeshUnsubscribe {
return this.query.watch(parameters, cultMeshQueryContextFromVerse(this.verse), callback);
}
}
export class CultMeshLiveFeed<TParameters, TResult> {
public readonly sources: readonly CultMeshProjectionSource[];
public readonly routeHint: CultMeshRouteHint;
public constructor(
public readonly feedId: string,
private readonly snapshotFeed: (
parameters: TParameters,
context: CultMeshQueryContext,
) => Promise<TResult>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchFeed?: CultMeshLiveFeedWatcher<TParameters, TResult>;
} = {},
) {
requireNonEmpty(feedId, "feedId");
this.sources = [...(options.sources ?? [])];
this.routeHint = options.routeHint ?? cultMeshRouteHint();
this.watchFeed = options.watchFeed;
}
private readonly watchFeed: CultMeshLiveFeedWatcher<TParameters, TResult> | undefined;
public snapshot(
parameters: TParameters,
context: CultMeshQueryContext | string,
): Promise<TResult> {
return this.snapshotFeed(
parameters,
this.resolveContext(typeof context === "string" ? cultMeshQueryContext(context) : context),
);
}
public watch(
parameters: TParameters,
context: CultMeshQueryContext | string,
callback: (value: TResult) => void,
): CultMeshUnsubscribe {
if (!this.watchFeed) {
throw new Error(`Live feed '${this.feedId}' does not support watches.`);
}
return this.watchFeed(
parameters,
this.resolveContext(typeof context === "string" ? cultMeshQueryContext(context) : context),
callback,
);
}
public bind(
verse: CultMeshVerseContext | CultMeshVerse,
): CultMeshBoundLiveFeed<TParameters, TResult> {
return cultMeshBindLiveFeed(verse, this);
}
private resolveContext(context: CultMeshQueryContext): CultMeshQueryContext {
if (context.routeHint.kind !== "automatic" || this.routeHint.kind === "automatic") {
return context;
}
return cultMeshQueryContext(context.runtimeId, {
routeHint: this.routeHint,
});
}
}
export class CultMeshBoundLiveFeed<TParameters, TResult> {
public constructor(
public readonly verse: CultMeshVerseContext,
public readonly feed: CultMeshLiveFeed<TParameters, TResult>,
) {}
public get feedId(): string {
return this.feed.feedId;
}
public get sources(): readonly CultMeshProjectionSource[] {
return this.feed.sources;
}
public get routeHint(): CultMeshRouteHint {
return this.feed.routeHint;
}
public snapshot(parameters: TParameters): Promise<TResult> {
return this.feed.snapshot(parameters, cultMeshQueryContextFromVerse(this.verse));
}
public watch(
parameters: TParameters,
callback: (value: TResult) => void,
): CultMeshUnsubscribe {
return this.feed.watch(parameters, cultMeshQueryContextFromVerse(this.verse), callback);
}
}
export class CultMeshDocumentHandle<TDocument> {
public readonly schema: CultMeshDocumentSchemaDescriptor;
public readonly sources: readonly CultMeshProjectionSource[];
public readonly routeHint: CultMeshRouteHint;
public constructor(
public readonly documentId: string,
schema: CultMeshDocumentSchemaDescriptor,
private readonly snapshotDocument: (context: CultMeshQueryContext) => Promise<TDocument>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchDocument?: CultMeshDocumentWatcher<TDocument>;
replaceDocument?: CultMeshDocumentReplacer<TDocument>;
submitPrediction?: CultMeshDocumentPredictionSubmitter<TDocument>;
} = {},
) {
requireNonEmpty(documentId, "documentId");
this.schema = normalizeCultMeshDocumentSchema(schema);
this.sources = [...(options.sources ?? [])];
this.routeHint = options.routeHint ?? cultMeshRouteHint();
this.watchDocument = options.watchDocument;
this.replaceDocument = options.replaceDocument;
this.submitPredictionDocument = options.submitPrediction;
}
private readonly watchDocument: CultMeshDocumentWatcher<TDocument> | undefined;
private readonly replaceDocument: CultMeshDocumentReplacer<TDocument> | undefined;
private readonly submitPredictionDocument:
| CultMeshDocumentPredictionSubmitter<TDocument>
| undefined;
public get canReplace(): boolean {
return this.replaceDocument !== undefined;
}
public get canSubmitPrediction(): boolean {
return this.submitPredictionDocument !== undefined;
}
public get canSet(): boolean {
return this.canSubmitPrediction || this.canReplace;
}
public latest(context: CultMeshQueryContext | string = "local"): Promise<TDocument> {
return this.snapshotDocument(
this.resolveContext(typeof context === "string" ? cultMeshQueryContext(context) : context),
);
}
public read(context: CultMeshQueryContext | string = "local"): Promise<TDocument> {
return this.latest(context);
}
public watch(callback: (value: TDocument) => void): CultMeshUnsubscribe;
public watch(context: CultMeshQueryContext | string, callback: (value: TDocument) => void): CultMeshUnsubscribe;
public watch(
contextOrCallback: CultMeshQueryContext | string | ((value: TDocument) => void),
maybeCallback?: (value: TDocument) => void,
): CultMeshUnsubscribe {
if (!this.watchDocument) {
throw new Error(`Document '${this.documentId}' does not support watches.`);
}
const callback =
typeof contextOrCallback === "function" ? contextOrCallback : maybeCallback;
if (!callback) {
throw new Error(`Document '${this.documentId}' requires a watch callback.`);
}
const context =
typeof contextOrCallback === "function"
? cultMeshQueryContext("local")
: typeof contextOrCallback === "string"
? cultMeshQueryContext(contextOrCallback)
: contextOrCallback;
return this.watchDocument(this.resolveContext(context), callback);
}
public replace(value: TDocument): Promise<void>;
public replace(context: CultMeshQueryContext | string, value: TDocument): Promise<void>;
public replace(
contextOrValue: CultMeshQueryContext | string | TDocument,
maybeValue?: TDocument,
): Promise<void> {
if (!this.replaceDocument) {
throw new Error(`Document '${this.documentId}' does not support replacement.`);
}
const hasContext =
typeof contextOrValue === "string" || isCultMeshQueryContext(contextOrValue);
const context = hasContext
? typeof contextOrValue === "string"
? cultMeshQueryContext(contextOrValue)
: contextOrValue as CultMeshQueryContext
: cultMeshQueryContext("local");
const value = hasContext ? maybeValue : contextOrValue as TDocument;
if (value === undefined) {
throw new Error(`Document '${this.documentId}' requires a replacement value.`);
}
return this.replaceDocument(this.resolveContext(context), value);
}
public submitPrediction(value: TDocument): Promise<void>;
public submitPrediction(context: CultMeshQueryContext | string, value: TDocument): Promise<void>;
public submitPrediction(
contextOrValue: CultMeshQueryContext | string | TDocument,
maybeValue?: TDocument,
): Promise<void> {
if (!this.submitPredictionDocument) {
throw new Error(`Document '${this.documentId}' does not support prediction submission.`);
}
const hasContext =
typeof contextOrValue === "string" || isCultMeshQueryContext(contextOrValue);
const context = hasContext
? typeof contextOrValue === "string"
? cultMeshQueryContext(contextOrValue)
: contextOrValue as CultMeshQueryContext
: cultMeshQueryContext("local");
const value = hasContext ? maybeValue : contextOrValue as TDocument;
if (value === undefined) {
throw new Error(`Document '${this.documentId}' requires a prediction value.`);
}
return this.submitPredictionDocument(this.resolveContext(context), value);
}
public set(value: TDocument): Promise<void>;
public set(context: CultMeshQueryContext | string, value: TDocument): Promise<void>;
public set(
contextOrValue: CultMeshQueryContext | string | TDocument,
maybeValue?: TDocument,
): Promise<void> {
const hasContext =
typeof contextOrValue === "string" || isCultMeshQueryContext(contextOrValue);
const context = hasContext
? typeof contextOrValue === "string"
? cultMeshQueryContext(contextOrValue)
: contextOrValue as CultMeshQueryContext
: cultMeshQueryContext("local");
const value = hasContext ? maybeValue : contextOrValue as TDocument;
if (value === undefined) {
throw new Error(`Document '${this.documentId}' requires a value.`);
}
const resolved = this.resolveContext(context);
if (this.submitPredictionDocument) {
return this.submitPredictionDocument(resolved, value);
}
if (this.replaceDocument) {
return this.replaceDocument(resolved, value);
}
throw new Error(`Document '${this.documentId}' does not support mutation.`);
}
public update(update: CultMeshDocumentUpdater<TDocument>): Promise<TDocument>;
public update(
context: CultMeshQueryContext | string,
update: CultMeshDocumentUpdater<TDocument>,
): Promise<TDocument>;
public async update(
contextOrUpdate: CultMeshQueryContext | string | CultMeshDocumentUpdater<TDocument>,
maybeUpdate?: CultMeshDocumentUpdater<TDocument>,
): Promise<TDocument> {
const hasContext =
typeof contextOrUpdate === "string" || isCultMeshQueryContext(contextOrUpdate);
const context = hasContext
? contextOrUpdate as CultMeshQueryContext | string
: "local";
const update = hasContext ? maybeUpdate : contextOrUpdate as CultMeshDocumentUpdater<TDocument>;
if (!update) {
throw new Error(`Document '${this.documentId}' requires an update function.`);
}
const next = await update(await this.latest(context));
await this.set(context, next);
return next;
}
public reactive<TObject extends TDocument & object>(
options: CultMeshReactiveDocumentOptions = {},
): CultMeshReactiveDocument<TObject> {
return new CultMeshReactiveDocumentHandle<TObject>(
this as unknown as CultMeshDocumentHandle<TObject>,
options,
);
}
public asSchemaAlias<TAlias>(
schema: CultMeshDocumentSchemaDescriptor,
options: { parse?: (value: unknown) => TAlias } = {},
): CultMeshDocumentHandle<TAlias> {
const aliasSchema = normalizeCultMeshDocumentSchema(schema);
if (!cultMeshSchemasAreCompatible(this.schema, aliasSchema)) {
throw new Error(
`Document '${this.documentId}' schema ${cultMeshSchemaLabel(this.schema)} is not compatible with alias ${cultMeshSchemaLabel(aliasSchema)}.`,
);
}
const parse = options.parse ?? ((value: unknown) => value as TAlias);
return new CultMeshDocumentHandle<TAlias>(
this.documentId,
aliasSchema,
async (context) => parse(await this.latest(context)),
{
sources: this.sources,
routeHint: this.routeHint,
watchDocument: this.watchDocument
? (context, callback) => this.watch(context, value => callback(parse(value)))
: undefined,
replaceDocument: this.replaceDocument
? (context, value) => this.replace(context, value as unknown as TDocument)
: undefined,
submitPrediction: this.submitPredictionDocument
? (context, value) => this.submitPrediction(context, value as unknown as TDocument)
: undefined,
},
);
}
public bind(verse: CultMeshVerseContext | CultMeshVerse): CultMeshBoundDocumentHandle<TDocument> {
return cultMeshBindDocument(verse, this);
}
private resolveContext(context: CultMeshQueryContext): CultMeshQueryContext {
if (context.routeHint.kind !== "automatic" || this.routeHint.kind === "automatic") {
return context;
}
return cultMeshQueryContext(context.runtimeId, {
routeHint: this.routeHint,
});
}
}
class CultMeshReactiveDocumentHandle<TDocument extends object>
implements CultMeshReactiveDocument<TDocument> {
readonly #document: CultMeshDocumentHandle<TDocument>;
readonly #context: CultMeshQueryContext | string;
readonly #onError: (error: unknown) => void;
readonly #debounceMs: number;
#current: TDocument;
#disposed = false;
#applyingRemote = false;
#pendingSet: Promise<void> = Promise.resolve();
#flushScheduled = false;
#flushTimer: ReturnType<typeof setTimeout> | undefined;
#unsubscribe: CultMeshUnsubscribe | undefined;
#lastPredicted: TDocument | undefined;
#reconciliation: CultMeshReactiveDocumentReconciliation<TDocument> | undefined;
#reconciliationVersion = 0;
public readonly ready: Promise<TDocument>;
public constructor(
document: CultMeshDocumentHandle<TDocument>,
options: CultMeshReactiveDocumentOptions = {},
) {
this.#document = document;
this.#context = options.context ?? "local";
this.#debounceMs = options.debounceMs ?? 0;
this.#onError = options.onError ?? ((error) => {
queueMicrotask(() => {
throw error;
});
});
this.#current = this.createProxy({} as TDocument);
this.ready = this.refresh();
if (options.watch !== false) {
try {
this.#unsubscribe = document.watch(this.#context, value => {
this.applySnapshot(value);
});
} catch (error) {
this.#onError(error);
}
}
}
public get current(): TDocument {
return this.#current;
}
public get reconciliation(): CultMeshReactiveDocumentReconciliation<TDocument> | undefined {
return this.#reconciliation;
}
public get disposed(): boolean {
return this.#disposed;
}
public async refresh(): Promise<TDocument> {
const latest = await this.#document.latest(this.#context);
this.applySnapshot(latest);
return this.#current;
}
public clearReconciliation(): void {
this.#reconciliation = undefined;
}
public dispose(): void {
this.#disposed = true;
if (this.#flushTimer !== undefined) {
clearTimeout(this.#flushTimer);
this.#flushTimer = undefined;
}
this.#flushScheduled = false;
this.#unsubscribe?.();
this.#unsubscribe = undefined;
}
private applySnapshot(value: TDocument): void {
const canonical = cloneCultMeshDocument(value);
this.captureReconciliation(canonical);
this.#applyingRemote = true;
try {
for (const key of Reflect.ownKeys(this.#current)) {
if (!Reflect.has(canonical, key)) {
Reflect.deleteProperty(this.#current, key);
}
}
Object.assign(this.#current, canonical);
} finally {
this.#applyingRemote = false;
}
}
private captureReconciliation(canonical: TDocument): void {
if (!this.#lastPredicted) {
return;
}
const predicted = this.#lastPredicted;
const delta = createCultMeshReconciliationDelta(predicted, canonical) as
CultMeshReactiveDocumentDelta<TDocument>;
if (Reflect.ownKeys(delta).length === 0) {
this.#lastPredicted = undefined;
return;
}
this.#reconciliation = {
version: ++this.#reconciliationVersion,
canonical: cloneCultMeshDocument(canonical),
predicted: cloneCultMeshDocument(predicted),
delta,
receivedAtMs: Date.now(),
};
this.#lastPredicted = undefined;
}
private createProxy(seed: TDocument): TDocument {
return new Proxy(seed, {
set: (target, property, value) => {
const changed = !Object.is(Reflect.get(target, property), value);
const ok = Reflect.set(target, property, value);
if (ok && changed && !this.#applyingRemote) {
this.queueSet();
}
return ok;
},
deleteProperty: (target, property) => {
const hadProperty = Reflect.has(target, property);
const ok = Reflect.deleteProperty(target, property);
if (ok && hadProperty && !this.#applyingRemote) {
this.queueSet();
}
return ok;
},
});
}
private queueSet(): void {
if (this.#disposed) {
return;
}
if (this.#flushScheduled) {
return;
}
this.#flushScheduled = true;
const flush = () => {
this.#flushScheduled = false;
this.#flushTimer = undefined;
if (this.#disposed) {
return;
}
const snapshot = cloneCultMeshDocument(this.#current);
this.#lastPredicted = snapshot;
this.#pendingSet = this.#pendingSet
.catch(() => undefined)
.then(() => this.#document.set(this.#context, snapshot))
.catch(error => this.#onError(error));
};
const debounceMs = this.#debounceMs;
if (debounceMs > 0) {
this.#flushTimer = setTimeout(flush, debounceMs);
return;
}
const requestFrame = (globalThis as {
requestAnimationFrame?: (callback: FrameRequestCallback) => number;
}).requestAnimationFrame;
if (typeof requestFrame === "function") {
requestFrame(() => flush());
return;
}
this.#flushTimer = setTimeout(flush, 0);
}
}
function cloneCultMeshDocument<TDocument>(value: TDocument): TDocument {
if (typeof structuredClone === "function") {
try {
return structuredClone(value);
} catch (error) {
if ((error as { name?: string }).name !== "DataCloneError") {
throw error;
}
}
}
return JSON.parse(JSON.stringify(value)) as TDocument;
}
function createCultMeshReconciliationDelta<TDocument extends object>(
predicted: TDocument,
canonical: TDocument,
): Partial<Record<keyof TDocument, unknown>> {
const delta: Partial<Record<keyof TDocument, unknown>> = {};
const keys = new Set([
...Reflect.ownKeys(predicted),
...Reflect.ownKeys(canonical),
]);
for (const key of keys) {
const predictedValue = Reflect.get(predicted, key);
const canonicalValue = Reflect.get(canonical, key);
if (cultMeshValuesEqual(predictedValue, canonicalValue)) {
continue;
}
const deltaKey = key as keyof TDocument;
delta[deltaKey] =
typeof predictedValue === "number" && typeof canonicalValue === "number"
? predictedValue - canonicalValue
: predictedValue;
}
return delta;
}
function cultMeshValuesEqual(left: unknown, right: unknown): boolean {
if (Object.is(left, right)) {
return true;
}
if (typeof left !== "object" || left === null ||
typeof right !== "object" || right === null) {
return false;
}
return JSON.stringify(left) === JSON.stringify(right);
}
export class CultMeshBoundDocumentHandle<TDocument> {
public constructor(
public readonly verse: CultMeshVerseContext,
public readonly document: CultMeshDocumentHandle<TDocument>,
) {}
public get documentId(): string {
return this.document.documentId;
}
public get schema(): CultMeshDocumentSchemaDescriptor {
return this.document.schema;
}
public get canReplace(): boolean {
return this.document.canReplace;
}
public get canSubmitPrediction(): boolean {
return this.document.canSubmitPrediction;
}
public get canSet(): boolean {
return this.document.canSet;
}
public latest(): Promise<TDocument> {
return this.document.latest(cultMeshQueryContextFromVerse(this.verse));
}
public read(): Promise<TDocument> {
return this.latest();
}
public watch(callback: (value: TDocument) => void): CultMeshUnsubscribe {
return this.document.watch(cultMeshQueryContextFromVerse(this.verse), callback);
}
public replace(value: TDocument): Promise<void> {
return this.document.replace(cultMeshQueryContextFromVerse(this.verse), value);
}
public submitPrediction(value: TDocument): Promise<void> {
return this.document.submitPrediction(cultMeshQueryContextFromVerse(this.verse), value);
}
public set(value: TDocument): Promise<void> {
return this.document.set(cultMeshQueryContextFromVerse(this.verse), value);
}
public update(update: CultMeshDocumentUpdater<TDocument>): Promise<TDocument> {
return this.document.update(cultMeshQueryContextFromVerse(this.verse), update);
}
public reactive<TObject extends TDocument & object>(
options: Omit<CultMeshReactiveDocumentOptions, "context"> = {},
): CultMeshReactiveDocument<TObject> {
return this.document.reactive<TObject>({
...options,
context: cultMeshQueryContextFromVerse(this.verse),
});
}
public asSchemaAlias<TAlias>(
schema: CultMeshDocumentSchemaDescriptor,
options: { parse?: (value: unknown) => TAlias } = {},
): CultMeshBoundDocumentHandle<TAlias> {
return cultMeshBindDocument(this.verse, this.document.asSchemaAlias(schema, options));
}
}
export class CultMeshDocumentCatalog {
readonly #byDocumentId = new Map<string, CultMeshDocumentHandle<any>>();
readonly #byType = new Map<string, CultMeshDocumentHandle<any>>();
readonly #bySchemaId = new Map<string, CultMeshDocumentHandle<any>>();
readonly #bySchemaNameVersion = new Map<string, CultMeshDocumentHandle<any>>();
public constructor(documents: Iterable<CultMeshDocumentHandle<any>>) {
for (const document of documents) {
this.add(document);
}
}
public get documents(): readonly CultMeshDocumentHandle<any>[] {
return [...this.#byDocumentId.values()];
}
public add<TDocument>(document: CultMeshDocumentHandle<TDocument>): this {
this.#byDocumentId.set(document.documentId, document as CultMeshDocumentHandle<any>);
if (document.schema.type) {
this.#byType.set(document.schema.type, document as CultMeshDocumentHandle<any>);
}
if (document.schema.schemaId) {
this.#bySchemaId.set(document.schema.schemaId, document as CultMeshDocumentHandle<any>);
}
const key = cultMeshSchemaNameVersionKey(document.schema);
if (key) {
this.#bySchemaNameVersion.set(key, document as CultMeshDocumentHandle<any>);
}
return this;
}
public tryDocument<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
options: { parse?: (value: unknown) => TDocument } = {},
): CultMeshDocumentHandle<TDocument> | undefined {
const descriptor = normalizeCultMeshDocumentSchema(schema);
const exact =
(descriptor.type ? this.#byType.get(descriptor.type) : undefined) ??
(descriptor.schemaId ? this.#bySchemaId.get(descriptor.schemaId) : undefined) ??
(() => {
const key = cultMeshSchemaNameVersionKey(descriptor);
return key ? this.#bySchemaNameVersion.get(key) : undefined;
})();
if (!exact) {
return undefined;
}
return cultMeshSchemasAreCompatible(exact.schema, descriptor)
? exact.asSchemaAlias(descriptor, options)
: undefined;
}
public document<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
options: { parse?: (value: unknown) => TDocument } = {},
): CultMeshDocumentHandle<TDocument> {
const document = this.tryDocument(schema, options);
if (!document) {
throw new Error(`Document catalog has no document for ${cultMeshSchemaLabel(schema)}.`);
}
return document;
}
public latest<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
context: CultMeshQueryContext | string = "local",
options: { parse?: (value: unknown) => TDocument } = {},
): Promise<TDocument> {
return this.document(schema, options).latest(context);
}
public canReplace(schema: CultMeshDocumentSchemaDescriptor): boolean {
return this.tryDocument(schema)?.canReplace ?? false;
}
public replace<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
value: TDocument,
options?: { parse?: (value: unknown) => TDocument; context?: CultMeshQueryContext | string },
): Promise<void>;
public replace<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
context: CultMeshQueryContext | string,
value: TDocument,
options?: { parse?: (value: unknown) => TDocument },
): Promise<void>;
public replace<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
contextOrValue: CultMeshQueryContext | string | TDocument,
valueOrOptions?: TDocument | { parse?: (value: unknown) => TDocument; context?: CultMeshQueryContext | string },
maybeOptions: { parse?: (value: unknown) => TDocument } = {},
): Promise<void> {
const hasContext =
typeof contextOrValue === "string" || isCultMeshQueryContext(contextOrValue);
const context = hasContext
? contextOrValue as CultMeshQueryContext | string
: (valueOrOptions as { context?: CultMeshQueryContext | string } | undefined)?.context ?? "local";
const value = hasContext ? valueOrOptions as TDocument : contextOrValue as TDocument;
const options = hasContext ? maybeOptions : valueOrOptions as { parse?: (value: unknown) => TDocument } | undefined;
return this.document(schema, options).replace(context, value);
}
public canSubmitPrediction(schema: CultMeshDocumentSchemaDescriptor): boolean {
return this.tryDocument(schema)?.canSubmitPrediction ?? false;
}
public canSet(schema: CultMeshDocumentSchemaDescriptor): boolean {
return this.tryDocument(schema)?.canSet ?? false;
}
public submitPrediction<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
value: TDocument,
options?: { parse?: (value: unknown) => TDocument; context?: CultMeshQueryContext | string },
): Promise<void>;
public submitPrediction<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
context: CultMeshQueryContext | string,
value: TDocument,
options?: { parse?: (value: unknown) => TDocument },
): Promise<void>;
public submitPrediction<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
contextOrValue: CultMeshQueryContext | string | TDocument,
valueOrOptions?: TDocument | { parse?: (value: unknown) => TDocument; context?: CultMeshQueryContext | string },
maybeOptions: { parse?: (value: unknown) => TDocument } = {},
): Promise<void> {
const hasContext =
typeof contextOrValue === "string" || isCultMeshQueryContext(contextOrValue);
const context = hasContext
? contextOrValue as CultMeshQueryContext | string
: (valueOrOptions as { context?: CultMeshQueryContext | string } | undefined)?.context ?? "local";
const value = hasContext ? valueOrOptions as TDocument : contextOrValue as TDocument;
const options = hasContext ? maybeOptions : valueOrOptions as { parse?: (value: unknown) => TDocument } | undefined;
return this.document(schema, options).submitPrediction(context, value);
}
public set<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
value: TDocument,
options?: { parse?: (value: unknown) => TDocument; context?: CultMeshQueryContext | string },
): Promise<void>;
public set<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
context: CultMeshQueryContext | string,
value: TDocument,
options?: { parse?: (value: unknown) => TDocument },
): Promise<void>;
public set<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
contextOrValue: CultMeshQueryContext | string | TDocument,
valueOrOptions?: TDocument | { parse?: (value: unknown) => TDocument; context?: CultMeshQueryContext | string },
maybeOptions: { parse?: (value: unknown) => TDocument } = {},
): Promise<void> {
const hasContext =
typeof contextOrValue === "string" || isCultMeshQueryContext(contextOrValue);
const context = hasContext
? contextOrValue as CultMeshQueryContext | string
: (valueOrOptions as { context?: CultMeshQueryContext | string } | undefined)?.context ?? "local";
const value = hasContext ? valueOrOptions as TDocument : contextOrValue as TDocument;
const options = hasContext ? maybeOptions : valueOrOptions as { parse?: (value: unknown) => TDocument } | undefined;
return this.document(schema, options).set(context, value);
}
public update<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
update: CultMeshDocumentUpdater<TDocument>,
options?: { parse?: (value: unknown) => TDocument; context?: CultMeshQueryContext | string },
): Promise<TDocument>;
public update<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
context: CultMeshQueryContext | string,
update: CultMeshDocumentUpdater<TDocument>,
options?: { parse?: (value: unknown) => TDocument },
): Promise<TDocument>;
public update<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
contextOrUpdate: CultMeshQueryContext | string | CultMeshDocumentUpdater<TDocument>,
updateOrOptions?: CultMeshDocumentUpdater<TDocument> | { parse?: (value: unknown) => TDocument; context?: CultMeshQueryContext | string },
maybeOptions: { parse?: (value: unknown) => TDocument } = {},
): Promise<TDocument> {
const hasContext =
typeof contextOrUpdate === "string" || isCultMeshQueryContext(contextOrUpdate);
const context = hasContext
? contextOrUpdate as CultMeshQueryContext | string
: (updateOrOptions as { context?: CultMeshQueryContext | string } | undefined)?.context ?? "local";
const update = hasContext
? updateOrOptions as CultMeshDocumentUpdater<TDocument>
: contextOrUpdate as CultMeshDocumentUpdater<TDocument>;
const options = hasContext ? maybeOptions : updateOrOptions as { parse?: (value: unknown) => TDocument } | undefined;
return this.document(schema, options).update(context, update);
}
public watch<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
callback: (value: TDocument) => void,
options: { parse?: (value: unknown) => TDocument; context?: CultMeshQueryContext | string } = {},
): CultMeshUnsubscribe {
return this.document(schema, options).watch(options.context ?? "local", callback);
}
}
export class CultMeshCollectionHandle<TDocument> {
public readonly schema: CultMeshDocumentSchemaDescriptor;
public readonly sources: readonly CultMeshProjectionSource[];
public readonly routeHint: CultMeshRouteHint;
public constructor(
public readonly collectionId: string,
schema: CultMeshDocumentSchemaDescriptor,
private readonly snapshotCollection: (context: CultMeshQueryContext) => Promise<CultMeshCollectionSnapshot<TDocument>>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchCollection?: CultMeshCollectionWatcher<TDocument>;
} = {},
) {
requireNonEmpty(collectionId, "collectionId");
this.schema = normalizeCultMeshDocumentSchema(schema);
this.sources = [...(options.sources ?? [])];
this.routeHint = options.routeHint ?? cultMeshRouteHint();
this.watchCollection = options.watchCollection;
}
private readonly watchCollection: CultMeshCollectionWatcher<TDocument> | undefined;
public latest(context: CultMeshQueryContext | string = "local"): Promise<CultMeshCollectionSnapshot<TDocument>> {
return this.snapshotCollection(
this.resolveContext(typeof context === "string" ? cultMeshQueryContext(context) : context),
);
}
public watchChanges(callback: (change: CultMeshCollectionChange<TDocument>) => void): CultMeshUnsubscribe;
public watchChanges(
context: CultMeshQueryContext | string,
callback: (change: CultMeshCollectionChange<TDocument>) => void,
): CultMeshUnsubscribe;
public watchChanges(
contextOrCallback:
| CultMeshQueryContext
| string
| ((change: CultMeshCollectionChange<TDocument>) => void),
maybeCallback?: (change: CultMeshCollectionChange<TDocument>) => void,
): CultMeshUnsubscribe {
if (!this.watchCollection) {
throw new Error(`Collection '${this.collectionId}' does not support watches.`);
}
const callback =
typeof contextOrCallback === "function" ? contextOrCallback : maybeCallback;
if (!callback) {
throw new Error(`Collection '${this.collectionId}' requires a watch callback.`);
}
const context =
typeof contextOrCallback === "function"
? cultMeshQueryContext("local")
: typeof contextOrCallback === "string"
? cultMeshQueryContext(contextOrCallback)
: contextOrCallback;
return this.watchCollection(this.resolveContext(context), callback);
}
public asSchemaAlias<TAlias>(
schema: CultMeshDocumentSchemaDescriptor,
options: { parse?: (value: unknown) => TAlias } = {},
): CultMeshCollectionHandle<TAlias> {
const aliasSchema = normalizeCultMeshDocumentSchema(schema);
if (!cultMeshSchemasAreCompatible(this.schema, aliasSchema)) {
throw new Error(
`Collection '${this.collectionId}' schema ${cultMeshSchemaLabel(this.schema)} is not compatible with alias ${cultMeshSchemaLabel(aliasSchema)}.`,
);
}
const parse = options.parse ?? ((value: unknown) => value as TAlias);
return new CultMeshCollectionHandle<TAlias>(
this.collectionId,
aliasSchema,
async (context) => (await this.latest(context)).map(value => parse(value)),
{
sources: this.sources,
routeHint: this.routeHint,
watchCollection: this.watchCollection
? (context, callback) => this.watchChanges(context, change => callback({
...change,
value: change.value === undefined ? undefined : parse(change.value),
}))
: undefined,
},
);
}
public bind(verse: CultMeshVerseContext | CultMeshVerse): CultMeshBoundCollectionHandle<TDocument> {
return cultMeshBindCollection(verse, this);
}
private resolveContext(context: CultMeshQueryContext): CultMeshQueryContext {
if (context.routeHint.kind !== "automatic" || this.routeHint.kind === "automatic") {
return context;
}
return cultMeshQueryContext(context.runtimeId, {
routeHint: this.routeHint,
});
}
}
export class CultMeshCollectionCatalog {
readonly #byCollectionId = new Map<string, CultMeshCollectionHandle<any>>();
readonly #byType = new Map<string, CultMeshCollectionHandle<any>>();
readonly #bySchemaId = new Map<string, CultMeshCollectionHandle<any>>();
readonly #bySchemaNameVersion = new Map<string, CultMeshCollectionHandle<any>>();
public constructor(collections: Iterable<CultMeshCollectionHandle<any>>) {
for (const collection of collections) {
this.add(collection);
}
}
public get collections(): readonly CultMeshCollectionHandle<any>[] {
return [...this.#byCollectionId.values()];
}
public add<TDocument>(collection: CultMeshCollectionHandle<TDocument>): this {
this.#byCollectionId.set(collection.collectionId, collection as CultMeshCollectionHandle<any>);
if (collection.schema.type) {
this.#byType.set(collection.schema.type, collection as CultMeshCollectionHandle<any>);
}
if (collection.schema.schemaId) {
this.#bySchemaId.set(collection.schema.schemaId, collection as CultMeshCollectionHandle<any>);
}
const key = cultMeshSchemaNameVersionKey(collection.schema);
if (key) {
this.#bySchemaNameVersion.set(key, collection as CultMeshCollectionHandle<any>);
}
return this;
}
public tryCollection<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
options: { parse?: (value: unknown) => TDocument } = {},
): CultMeshCollectionHandle<TDocument> | undefined {
const descriptor = normalizeCultMeshDocumentSchema(schema);
const exact =
(descriptor.type ? this.#byType.get(descriptor.type) : undefined) ??
(descriptor.schemaId ? this.#bySchemaId.get(descriptor.schemaId) : undefined) ??
(() => {
const key = cultMeshSchemaNameVersionKey(descriptor);
return key ? this.#bySchemaNameVersion.get(key) : undefined;
})();
if (!exact) {
return undefined;
}
return cultMeshSchemasAreCompatible(exact.schema, descriptor)
? exact.asSchemaAlias(descriptor, options)
: undefined;
}
public collection<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
options: { parse?: (value: unknown) => TDocument } = {},
): CultMeshCollectionHandle<TDocument> {
const collection = this.tryCollection(schema, options);
if (!collection) {
throw new Error(`Collection catalog has no collection for ${cultMeshSchemaLabel(schema)}.`);
}
return collection;
}
public latest<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
context: CultMeshQueryContext | string = "local",
options: { parse?: (value: unknown) => TDocument } = {},
): Promise<CultMeshCollectionSnapshot<TDocument>> {
return this.collection(schema, options).latest(context);
}
public watchChanges<TDocument>(
schema: CultMeshDocumentSchemaDescriptor,
callback: (change: CultMeshCollectionChange<TDocument>) => void,
options: { parse?: (value: unknown) => TDocument; context?: CultMeshQueryContext | string } = {},
): CultMeshUnsubscribe {
return this.collection(schema, options).watchChanges(options.context ?? "local", callback);
}
}
export class CultMeshBoundCollectionHandle<TDocument> {
public constructor(
public readonly verse: CultMeshVerseContext,
public readonly collection: CultMeshCollectionHandle<TDocument>,
) {}
public get collectionId(): string {
return this.collection.collectionId;
}
public get schema(): CultMeshDocumentSchemaDescriptor {
return this.collection.schema;
}
public latest(): Promise<CultMeshCollectionSnapshot<TDocument>> {
return this.collection.latest(cultMeshQueryContextFromVerse(this.verse));
}
public watchChanges(callback: (change: CultMeshCollectionChange<TDocument>) => void): CultMeshUnsubscribe {
return this.collection.watchChanges(cultMeshQueryContextFromVerse(this.verse), callback);
}
public asSchemaAlias<TAlias>(
schema: CultMeshDocumentSchemaDescriptor,
options: { parse?: (value: unknown) => TAlias } = {},
): CultMeshBoundCollectionHandle<TAlias> {
return cultMeshBindCollection(this.verse, this.collection.asSchemaAlias(schema, options));
}
}
export class CultMeshStatePointer<T> {
public readonly sources: readonly CultMeshProjectionSource[];
public readonly routeHint: CultMeshRouteHint;
public constructor(
public readonly pointerId: string,
private readonly resolvePointer: (() => Promise<T | undefined>) | CultMeshStatePointerResolver<T>,
private readonly watchPointer?:
| ((callback: (value: T) => void) => CultMeshUnsubscribe)
| CultMeshStatePointerWatcher<T>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
} = {},
) {
requireNonEmpty(pointerId, "pointerId");
this.sources = [...(options.sources ?? [])];
this.routeHint = options.routeHint ?? cultMeshRouteHint();
}
public resolve(context?: CultMeshQueryContext | string): Promise<T | undefined> {
return (this.resolvePointer as CultMeshStatePointerResolver<T>)(
this.resolveContext(
typeof context === "string" ? cultMeshQueryContext(context) : context,
),
);
}
public watch(callback: (value: T) => void): CultMeshUnsubscribe;
public watch(context: CultMeshQueryContext | string, callback: (value: T) => void): CultMeshUnsubscribe;
public watch(
contextOrCallback: CultMeshQueryContext | string | ((value: T) => void),
maybeCallback?: (value: T) => void,
): CultMeshUnsubscribe {
if (!this.watchPointer) {
throw new Error(`State pointer '${this.pointerId}' does not support watches.`);
}
const callback =
typeof contextOrCallback === "function" ? contextOrCallback : maybeCallback;
if (!callback) {
throw new Error(`State pointer '${this.pointerId}' requires a watch callback.`);
}
if (this.watchPointer.length <= 1) {
return (this.watchPointer as (callback: (value: T) => void) => CultMeshUnsubscribe)(callback);
}
const context =
typeof contextOrCallback === "function"
? undefined
: typeof contextOrCallback === "string"
? cultMeshQueryContext(contextOrCallback)
: contextOrCallback;
return (this.watchPointer as CultMeshStatePointerWatcher<T>)(
this.resolveContext(context),
callback,
);
}
public bind(
verse: CultMeshVerseContext | CultMeshVerse,
): CultMeshBoundStatePointer<T> {
return cultMeshBindStatePointer(verse, this);
}
private resolveContext(context?: CultMeshQueryContext): CultMeshQueryContext {
const resolved = context ?? cultMeshQueryContext("local");
if (resolved.routeHint.kind !== "automatic" || this.routeHint.kind === "automatic") {
return resolved;
}
return cultMeshQueryContext(resolved.runtimeId, {
routeHint: this.routeHint,
});
}
}
export class CultMeshBoundStatePointer<T> {
public constructor(
public readonly verse: CultMeshVerseContext,
public readonly pointer: CultMeshStatePointer<T>,
) {}
public get pointerId(): string {
return this.pointer.pointerId;
}
public get sources(): readonly CultMeshProjectionSource[] {
return this.pointer.sources;
}
public get routeHint(): CultMeshRouteHint {
return this.pointer.routeHint;
}
public resolve(): Promise<T | undefined> {
return this.pointer.resolve(cultMeshQueryContextFromVerse(this.verse));
}
public watch(callback: (value: T) => void): CultMeshUnsubscribe {
return this.pointer.watch(cultMeshQueryContextFromVerse(this.verse), callback);
}
}
export class CultMeshMutableStatePointer<T> {
public readonly sources: readonly CultMeshProjectionSource[];
public readonly routeHint: CultMeshRouteHint;
public constructor(
public readonly pointerId: string,
private readonly resolvePointer: (() => Promise<T | undefined>) | CultMeshStatePointerResolver<T>,
private readonly watchPointer:
| ((callback: (value: T) => void) => CultMeshUnsubscribe)
| CultMeshStatePointerWatcher<T>,
private readonly replacePointer:
| ((value: T) => Promise<void>)
| CultMeshMutableStatePointerReplacer<T>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
} = {},
) {
requireNonEmpty(pointerId, "pointerId");
this.sources = [...(options.sources ?? [])];
this.routeHint = options.routeHint ?? cultMeshRouteHint();
}
public resolve(context?: CultMeshQueryContext | string): Promise<T | undefined> {
return (this.resolvePointer as CultMeshStatePointerResolver<T>)(
this.resolveContext(
typeof context === "string" ? cultMeshQueryContext(context) : context,
),
);
}
public read(context?: CultMeshQueryContext | string): Promise<T | undefined> {
return this.resolve(context);
}
public replace(value: T): Promise<void>;
public replace(context: CultMeshQueryContext | string, value: T): Promise<void>;
public replace(
contextOrValue: CultMeshQueryContext | string | T,
maybeValue?: T,
): Promise<void> {
if (this.replacePointer.length <= 1) {
return (this.replacePointer as (value: T) => Promise<void>)(
contextOrValue as T,
);
}
const context =
typeof contextOrValue === "string"
? cultMeshQueryContext(contextOrValue)
: isCultMeshQueryContext(contextOrValue)
? contextOrValue
: undefined;
const value = context ? maybeValue : (contextOrValue as T);
if (value === undefined) {
throw new Error(`Mutable state pointer '${this.pointerId}' requires a replacement value.`);
}
return (this.replacePointer as CultMeshMutableStatePointerReplacer<T>)(
this.resolveContext(context),
value,
);
}
public watch(callback: (value: T) => void): CultMeshUnsubscribe;
public watch(context: CultMeshQueryContext | string, callback: (value: T) => void): CultMeshUnsubscribe;
public watch(
contextOrCallback: CultMeshQueryContext | string | ((value: T) => void),
maybeCallback?: (value: T) => void,
): CultMeshUnsubscribe {
const callback =
typeof contextOrCallback === "function" ? contextOrCallback : maybeCallback;
if (!callback) {
throw new Error(`Mutable state pointer '${this.pointerId}' requires a watch callback.`);
}
if (this.watchPointer.length <= 1) {
return (this.watchPointer as (callback: (value: T) => void) => CultMeshUnsubscribe)(callback);
}
const context =
typeof contextOrCallback === "function"
? undefined
: typeof contextOrCallback === "string"
? cultMeshQueryContext(contextOrCallback)
: contextOrCallback;
return (this.watchPointer as CultMeshStatePointerWatcher<T>)(
this.resolveContext(context),
callback,
);
}
public asStatePointer(): CultMeshStatePointer<T> {
return cultMeshStatePointer(
this.pointerId,
(context) => this.resolve(context),
(context, callback) => this.watch(context, callback),
{
routeHint: this.routeHint,
sources: this.sources,
},
);
}
public bind(
verse: CultMeshVerseContext | CultMeshVerse,
): CultMeshBoundMutableStatePointer<T> {
return cultMeshBindMutableStatePointer(verse, this);
}
private resolveContext(context?: CultMeshQueryContext): CultMeshQueryContext {
const resolved = context ?? cultMeshQueryContext("local");
if (resolved.routeHint.kind !== "automatic" || this.routeHint.kind === "automatic") {
return resolved;
}
return cultMeshQueryContext(resolved.runtimeId, {
routeHint: this.routeHint,
});
}
}
export class CultMeshBoundMutableStatePointer<T> {
public constructor(
public readonly verse: CultMeshVerseContext,
public readonly pointer: CultMeshMutableStatePointer<T>,
) {}
public get pointerId(): string {
return this.pointer.pointerId;
}
public get sources(): readonly CultMeshProjectionSource[] {
return this.pointer.sources;
}
public get routeHint(): CultMeshRouteHint {
return this.pointer.routeHint;
}
public resolve(): Promise<T | undefined> {
return this.pointer.resolve(cultMeshQueryContextFromVerse(this.verse));
}
public read(): Promise<T | undefined> {
return this.resolve();
}
public replace(value: T): Promise<void> {
return this.pointer.replace(cultMeshQueryContextFromVerse(this.verse), value);
}
public watch(callback: (value: T) => void): CultMeshUnsubscribe {
return this.pointer.watch(cultMeshQueryContextFromVerse(this.verse), callback);
}
public asStatePointer(): CultMeshBoundStatePointer<T> {
return cultMeshBindStatePointer(this.verse, this.pointer.asStatePointer());
}
}
export class CultMeshProjectionRecipe<TParameters, TResult> {
public readonly sources: readonly CultMeshProjectionSource[];
public readonly routeHint: CultMeshRouteHint;
public constructor(
public readonly projectionId: string,
sources: readonly CultMeshProjectionSource[],
private readonly projectProjection: (
parameters: TParameters,
context: CultMeshQueryContext,
) => Promise<TResult>,
options: {
routeHint?: CultMeshRouteHint;
watchProjection?: (
parameters: TParameters,
context: CultMeshQueryContext,
callback: (value: TResult) => void,
) => CultMeshUnsubscribe;
} = {},
) {
requireNonEmpty(projectionId, "projectionId");
this.sources = [...sources];
this.routeHint = options.routeHint ?? cultMeshRouteHint();
this.watchProjection = options.watchProjection;
}
private readonly watchProjection:
| ((
parameters: TParameters,
context: CultMeshQueryContext,
callback: (value: TResult) => void,
) => CultMeshUnsubscribe)
| undefined;
public project(
parameters: TParameters,
context: CultMeshQueryContext | string,
): Promise<TResult> {
return this.projectProjection(
parameters,
this.resolveContext(
typeof context === "string" ? cultMeshQueryContext(context) : context,
),
);
}
public watch(
parameters: TParameters,
context: CultMeshQueryContext | string,
callback: (value: TResult) => void,
): CultMeshUnsubscribe {
if (!this.watchProjection) {
throw new Error(`Projection recipe '${this.projectionId}' does not support watches.`);
}
return this.watchProjection(
parameters,
this.resolveContext(
typeof context === "string" ? cultMeshQueryContext(context) : context,
),
callback,
);
}
public asQuerySurface(): CultMeshQuerySurface<TParameters, TResult> {
return cultMeshQuery(this.projectionId, (parameters, context) =>
this.project(parameters, context),
{
sources: this.sources,
routeHint: this.routeHint,
watchQuery: this.watchProjection
? (parameters, context, callback) => this.watch(parameters, context, callback)
: undefined,
},
);
}
private resolveContext(context: CultMeshQueryContext): CultMeshQueryContext {
if (context.routeHint.kind !== "automatic" || this.routeHint.kind === "automatic") {
return context;
}
return cultMeshQueryContext(context.runtimeId, {
routeHint: this.routeHint,
});
}
}
export function cultMeshOperationContextFor(runtimeId: string): CultMeshOperationContextBuilder {
return new CultMeshOperationContextBuilder(runtimeId);
}
export function cultMeshQueryContextFor(runtimeId: string): CultMeshQueryContextBuilder {
return new CultMeshQueryContextBuilder(runtimeId);
}
export function cultMeshVec2(x: number, y: number): CultMeshVec2 {
return { x, y };
}
export function cultMeshRect(min: CultMeshVec2, max: CultMeshVec2): CultMeshRect {
return {
min: cultMeshVec2(Math.min(min.x, max.x), Math.min(min.y, max.y)),
max: cultMeshVec2(Math.max(min.x, max.x), Math.max(min.y, max.y)),
};
}
export function cultMeshRectFromBounds(
minX: number,
minY: number,
maxX: number,
maxY: number,
): CultMeshRect {
return cultMeshRect(cultMeshVec2(minX, minY), cultMeshVec2(maxX, maxY));
}
export function cultMeshViewportRequest(
viewport: CultMeshRect,
controlledEntityIndices?: readonly number[],
): CultMeshViewportRequest {
return {
minX: viewport.min.x,
minY: viewport.min.y,
maxX: viewport.max.x,
maxY: viewport.max.y,
controlledEntityIndices,
};
}
export function cultMeshRouteHint(
kind: CultMeshLocalityKind = "automatic",
description?: string,
): CultMeshRouteHint {
return description ? { kind, description } : { kind };
}
export function cultMeshRouteRecord(
routeHint?: CultMeshRouteHint,
): CultMeshRouteRecord;
export function cultMeshRouteRecord(
kind?: string,
description?: string,
): CultMeshRouteRecord;
export function cultMeshRouteRecord(
routeOrKind?: CultMeshRouteHint | string,
description = "",
): CultMeshRouteRecord {
if (typeof routeOrKind === "string" || routeOrKind === undefined) {
return {
kind: routeOrKind ?? "",
description,
};
}
return {
kind: routeOrKind.kind,
description: routeOrKind.description ?? "",
};
}
export function cultMeshRouteFromRecord(
record: Partial<CultMeshRouteRecord>,
fallback: CultMeshRouteHint = cultMeshRouteHint(),
): CultMeshRouteHint {
return cultMeshRouteHint(
parseCultMeshLocalityKind(record.kind, fallback.kind),
nonBlankOr(record.description, fallback.description),
);
}
export function cultMeshAuthorityClaim(
role: string,
options: { shardId?: string; leaseId?: string } = {},
): CultMeshAuthorityClaim {
requireNonEmpty(role, "role");
return {
role,
shardId: options.shardId,
leaseId: options.leaseId,
};
}
export function cultMeshVerseContext(
verseId: string,
runtimeId: string,
options: {
routeHint?: CultMeshRouteHint;
claims?: readonly CultMeshAuthorityClaim[];
} = {},
): CultMeshVerseContext {
requireNonEmpty(verseId, "verseId");
requireNonEmpty(runtimeId, "runtimeId");
return {
verseId,
runtimeId,
routeHint: options.routeHint ?? cultMeshRouteHint(),
claims: options.claims ? [...options.claims] : [],
};
}
export function cultMeshVerse(
verseId: string,
runtimeId: string,
options: {
routeHint?: CultMeshRouteHint;
claims?: readonly CultMeshAuthorityClaim[];
} = {},
): CultMeshVerse {
return new CultMeshVerse(cultMeshVerseContext(verseId, runtimeId, options));
}
export function cultMeshOperationContextFromVerse(
context: CultMeshVerseContext,
options: { idempotencyKey?: string } = {},
): CultMeshOperationContext {
return cultMeshOperationContext(context.runtimeId, {
claims: context.claims,
routeHint: context.routeHint,
idempotencyKey: options.idempotencyKey,
});
}
export function cultMeshQueryContextFromVerse(
context: CultMeshVerseContext,
): CultMeshQueryContext {
return cultMeshQueryContext(context.runtimeId, {
routeHint: context.routeHint,
});
}
export function cultMeshBindOperation<TRequest, TResponse>(
verse: CultMeshVerseContext | CultMeshVerse,
operation: CultMeshOperationHandle<TRequest, TResponse>,
): CultMeshBoundOperationHandle<TRequest, TResponse> {
return new CultMeshBoundOperationHandle(resolveCultMeshVerseContext(verse), operation);
}
export function cultMeshBindQuery<TParameters, TResult>(
verse: CultMeshVerseContext | CultMeshVerse,
query: CultMeshQuerySurface<TParameters, TResult>,
): CultMeshBoundQuerySurface<TParameters, TResult> {
return new CultMeshBoundQuerySurface(resolveCultMeshVerseContext(verse), query);
}
export function cultMeshBindLiveFeed<TParameters, TResult>(
verse: CultMeshVerseContext | CultMeshVerse,
feed: CultMeshLiveFeed<TParameters, TResult>,
): CultMeshBoundLiveFeed<TParameters, TResult> {
return new CultMeshBoundLiveFeed(resolveCultMeshVerseContext(verse), feed);
}
export function cultMeshBindStatePointer<T>(
verse: CultMeshVerseContext | CultMeshVerse,
pointer: CultMeshStatePointer<T>,
): CultMeshBoundStatePointer<T> {
return new CultMeshBoundStatePointer(resolveCultMeshVerseContext(verse), pointer);
}
export function cultMeshBindMutableStatePointer<T>(
verse: CultMeshVerseContext | CultMeshVerse,
pointer: CultMeshMutableStatePointer<T>,
): CultMeshBoundMutableStatePointer<T> {
return new CultMeshBoundMutableStatePointer(resolveCultMeshVerseContext(verse), pointer);
}
export function cultMeshBindDocument<TDocument>(
verse: CultMeshVerseContext | CultMeshVerse,
document: CultMeshDocumentHandle<TDocument>,
): CultMeshBoundDocumentHandle<TDocument> {
return new CultMeshBoundDocumentHandle(resolveCultMeshVerseContext(verse), document);
}
export function cultMeshBindCollection<TDocument>(
verse: CultMeshVerseContext | CultMeshVerse,
collection: CultMeshCollectionHandle<TDocument>,
): CultMeshBoundCollectionHandle<TDocument> {
return new CultMeshBoundCollectionHandle(resolveCultMeshVerseContext(verse), collection);
}
function resolveCultMeshVerseContext(verse: CultMeshVerseContext | CultMeshVerse): CultMeshVerseContext {
return verse instanceof CultMeshVerse ? verse.context : verse;
}
function isCultMeshQueryContext(value: unknown): value is CultMeshQueryContext {
if (typeof value !== "object" || value === null) {
return false;
}
const maybeContext = value as Partial<CultMeshQueryContext>;
return typeof maybeContext.runtimeId === "string" &&
typeof maybeContext.routeHint === "object" &&
maybeContext.routeHint !== null;
}
export function cultMeshOperationContext(
runtimeId: string,
options: {
claims?: readonly CultMeshAuthorityClaim[];
routeHint?: CultMeshRouteHint;
idempotencyKey?: string;
} = {},
): CultMeshOperationContext {
requireNonEmpty(runtimeId, "runtimeId");
return {
runtimeId,
claims: options.claims ? [...options.claims] : [],
routeHint: options.routeHint ?? cultMeshRouteHint(),
idempotencyKey: options.idempotencyKey,
};
}
export function cultMeshQueryContext(
runtimeId: string,
options: { routeHint?: CultMeshRouteHint } = {},
): CultMeshQueryContext {
requireNonEmpty(runtimeId, "runtimeId");
return {
runtimeId,
routeHint: options.routeHint ?? cultMeshRouteHint(),
};
}
export function cultMeshOperationReceipt(
operationId: string,
accepted: boolean,
options: { route?: CultMeshRouteHint; diagnostic?: string } = {},
): CultMeshOperationReceipt {
requireNonEmpty(operationId, "operationId");
return {
operationId,
accepted,
route: options.route ?? cultMeshRouteHint(),
diagnostic: options.diagnostic,
};
}
export function cultMeshOperation<TRequest, TResponse>(
operationId: string,
invokeOperation: (
request: TRequest,
context: CultMeshOperationContext,
) => Promise<TResponse>,
): CultMeshOperationHandle<TRequest, TResponse> {
return new CultMeshOperationHandle(operationId, invokeOperation);
}
export function cultMeshQuery<TParameters, TResult>(
queryId: string,
executeQuery: (
parameters: TParameters,
context: CultMeshQueryContext,
) => Promise<TResult>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchQuery?: CultMeshQueryWatcher<TParameters, TResult>;
} = {},
): CultMeshQuerySurface<TParameters, TResult> {
return new CultMeshQuerySurface(queryId, executeQuery, options);
}
export function cultMeshDescribeQuerySurface(
query: {
readonly queryId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
},
): CultMeshQuerySurfaceDiagnostic {
return {
queryId: query.queryId,
routeHint: query.routeHint,
sources: [...query.sources],
};
}
export function cultMeshDescribeOperationHandle(
operation: {
readonly operationId: string;
},
): CultMeshOperationHandleDiagnostic {
return {
operationId: operation.operationId,
};
}
export function cultMeshDescribeStatePointer(
pointer: {
readonly pointerId: string;
readonly routeHint?: CultMeshRouteHint;
readonly sources?: readonly CultMeshProjectionSource[];
},
): CultMeshStatePointerDiagnostic {
return {
pointerId: pointer.pointerId,
routeHint: pointer.routeHint ?? cultMeshRouteHint(),
sources: [...(pointer.sources ?? [])],
};
}
export function cultMeshLiveFeed<TParameters, TResult>(
feedId: string,
snapshotFeed: (
parameters: TParameters,
context: CultMeshQueryContext,
) => Promise<TResult>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchFeed?: CultMeshLiveFeedWatcher<TParameters, TResult>;
} = {},
): CultMeshLiveFeed<TParameters, TResult> {
return new CultMeshLiveFeed(feedId, snapshotFeed, options);
}
export function cultMeshDocument<TDocument>(
documentId: string,
schema: CultMeshDocumentSchemaDescriptor,
snapshotDocument: (context: CultMeshQueryContext) => Promise<TDocument>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchDocument?: CultMeshDocumentWatcher<TDocument>;
replaceDocument?: CultMeshDocumentReplacer<TDocument>;
submitPrediction?: CultMeshDocumentPredictionSubmitter<TDocument>;
} = {},
): CultMeshDocumentHandle<TDocument> {
return new CultMeshDocumentHandle(documentId, schema, snapshotDocument, options);
}
export function cultMeshDocumentFromCache<TDefinition extends AnyCultCacheDocumentDefinition>(
cache: CultCache,
definition: TDefinition,
key: string,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>> {
requireNonEmpty(key, "key");
const documentId = options.documentId ?? `${definition.type}:${key}`;
const schema = cultMeshSchemaFromDefinition(definition);
return cultMeshDocument(
documentId,
schema,
async () => cache.getRequired(definition, key),
{
routeHint: options.routeHint ?? cultMeshRouteHint("in-process", "CultCache"),
sources: [cultMeshProjectionSource(documentId, { schemaId: schema.schemaId })],
watchDocument: cultMeshPollingDocumentWatcher(
async () => cache.getRequired(definition, key),
{ intervalMs: options.pollMs ?? 50 },
),
replaceDocument: async (_context, value) => {
await cache.put(definition, key, value);
},
},
);
}
export function cultMeshDocumentFromStore(
store: CacheBackingStore,
schemaId: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
},
): CultMeshDocumentHandle<unknown>;
export function cultMeshDocumentFromStore<TDefinition extends AnyCultCacheDocumentDefinition>(
store: CacheBackingStore,
definition: TDefinition,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>>;
export function cultMeshDocumentFromStore(
store: CacheBackingStore,
schemaOrDefinition: string | AnyCultCacheDocumentDefinition,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
} = {},
): CultMeshDocumentHandle<unknown> {
const schema = typeof schemaOrDefinition === "string"
? { schemaId: schemaOrDefinition }
: cultMeshSchemaFromDefinition(schemaOrDefinition);
const resolvedSchemaId = schema.schemaId ?? schema.type;
if (!resolvedSchemaId) {
throw new Error("CultMesh store document requires a schema id or document type.");
}
if (typeof schemaOrDefinition === "string") {
requireNonEmpty(schemaOrDefinition, "schemaId");
}
const documentId = options.documentId ?? resolvedSchemaId;
const read = async () => {
const records = await store.pullAll();
const record = resolveCultMeshStoreDocumentRecord(records, schema, schemaOrDefinition);
if (!record) {
throw new Error(`CultMesh store document '${documentId}' did not contain schema ${resolvedSchemaId}.`);
}
return parseCultMeshDocumentValue(schemaOrDefinition, decode(record.payload));
};
return cultMeshDocument(
documentId,
schema,
async () => read(),
{
routeHint: options.routeHint ?? cultMeshRouteHint("in-process", "CultCache store"),
sources: [cultMeshProjectionSource(options.sourceId ?? documentId, { schemaId: resolvedSchemaId })],
watchDocument: cultMeshPollingDocumentWatcher(read, {
intervalMs: options.pollMs ?? 50,
}),
},
);
}
export function cultMeshDocumentFromSingleFile(
path: string,
schemaId: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
},
): CultMeshDocumentHandle<unknown>;
export function cultMeshDocumentFromSingleFile<TDefinition extends AnyCultCacheDocumentDefinition>(
path: string,
definition: TDefinition,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>>;
export function cultMeshDocumentFromSingleFile(
path: string,
schemaOrDefinition: string | AnyCultCacheDocumentDefinition,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
} = {},
): CultMeshDocumentHandle<unknown> {
requireNonEmpty(path, "path");
const store = new SingleFileMessagePackBackingStore(path);
const resolvedOptions = {
...options,
routeHint: options.routeHint ?? cultMeshRouteHint("shared-memory", path),
};
return typeof schemaOrDefinition === "string"
? cultMeshDocumentFromStore(store, schemaOrDefinition, resolvedOptions)
: cultMeshDocumentFromStore(store, schemaOrDefinition, resolvedOptions);
}
export function cultMeshDocumentFromPeerSnapshot(
peer: CultNetPeer | (() => CultNetPeer | Promise<CultNetPeer>),
schemaId: string,
recordKey: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
},
): CultMeshDocumentHandle<unknown>;
export function cultMeshDocumentFromPeerSnapshot<TDefinition extends AnyCultCacheDocumentDefinition>(
peer: CultNetPeer | (() => CultNetPeer | Promise<CultNetPeer>),
definition: TDefinition,
recordKey: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>>;
export function cultMeshDocumentFromPeerSnapshot(
peer: CultNetPeer | (() => CultNetPeer | Promise<CultNetPeer>),
schemaOrDefinition: string | AnyCultCacheDocumentDefinition,
recordKey: string,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
} = {},
): CultMeshDocumentHandle<unknown> {
const schema = typeof schemaOrDefinition === "string"
? { schemaId: schemaOrDefinition }
: cultMeshSchemaFromDefinition(schemaOrDefinition);
const resolvedSchemaId = schema.schemaId ?? schema.type;
if (!resolvedSchemaId) {
throw new Error("CultMesh peer snapshot document requires a schema id or document type.");
}
if (typeof schemaOrDefinition === "string") {
requireNonEmpty(schemaOrDefinition, "schemaId");
}
requireNonEmpty(recordKey, "recordKey");
const documentId = options.documentId ?? recordKey;
const read = async () =>
parseCultMeshDocumentValue(
schemaOrDefinition,
decodeCultNetRawDocumentPayload(
await requestCultNetRawSnapshotDocument(
peer,
resolvedSchemaId,
recordKey,
{
timeoutMs: options.timeoutMs,
messageIdPrefix: options.messageIdPrefix ?? documentId,
accepts: typeof schemaOrDefinition === "string"
? undefined
: record => {
try {
schemaOrDefinition.schema.parse(decodeCultNetRawDocumentPayload(record));
return true;
} catch {
return false;
}
},
},
),
),
);
return cultMeshDocument(
documentId,
schema,
async () => read(),
{
routeHint: options.routeHint ?? cultMeshRouteHint("network", "CultNet snapshot"),
sources: [cultMeshProjectionSource(options.sourceId ?? documentId, { schemaId: resolvedSchemaId })],
watchDocument: cultMeshPollingDocumentWatcher(read, {
intervalMs: options.pollMs ?? 250,
}),
},
);
}
export function cultMeshDocumentFromPublication(
source: CultMeshDocumentPublicationSource,
schemaId: string,
recordKey: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
},
): CultMeshDocumentHandle<unknown>;
export function cultMeshDocumentFromPublication<TDefinition extends AnyCultCacheDocumentDefinition>(
source: CultMeshDocumentPublicationSource,
definition: TDefinition,
recordKey: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>>;
export function cultMeshDocumentFromPublication(
source: CultMeshDocumentPublicationSource,
schemaOrDefinition: string | AnyCultCacheDocumentDefinition,
recordKey: string,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
} = {},
): CultMeshDocumentHandle<unknown> {
if (!source) {
throw new Error("CultMesh publication source is required.");
}
requireNonEmpty(recordKey, "recordKey");
switch (source.kind) {
case "single-file":
return typeof schemaOrDefinition === "string"
? cultMeshDocumentFromSingleFile(source.path, schemaOrDefinition, {
documentId: options.documentId ?? recordKey,
routeHint: options.routeHint,
sourceId: options.sourceId ?? recordKey,
pollMs: options.pollMs,
})
: cultMeshDocumentFromSingleFile(source.path, schemaOrDefinition, {
documentId: options.documentId ?? recordKey,
routeHint: options.routeHint,
sourceId: options.sourceId ?? recordKey,
pollMs: options.pollMs,
});
case "store":
return typeof schemaOrDefinition === "string"
? cultMeshDocumentFromStore(source.store, schemaOrDefinition, {
documentId: options.documentId ?? recordKey,
routeHint: options.routeHint,
sourceId: options.sourceId ?? recordKey,
pollMs: options.pollMs,
})
: cultMeshDocumentFromStore(source.store, schemaOrDefinition, {
documentId: options.documentId ?? recordKey,
routeHint: options.routeHint,
sourceId: options.sourceId ?? recordKey,
pollMs: options.pollMs,
});
case "peer-snapshot":
return typeof schemaOrDefinition === "string"
? cultMeshDocumentFromPeerSnapshot(source.peer, schemaOrDefinition, recordKey, {
documentId: options.documentId ?? recordKey,
routeHint: options.routeHint ?? cultMeshRouteHint("network", source.endpoint ?? "CultNet snapshot"),
sourceId: options.sourceId ?? recordKey,
timeoutMs: options.timeoutMs,
pollMs: options.pollMs,
messageIdPrefix: options.messageIdPrefix,
})
: cultMeshDocumentFromPeerSnapshot(source.peer, schemaOrDefinition, recordKey, {
documentId: options.documentId ?? recordKey,
routeHint: options.routeHint ?? cultMeshRouteHint("network", source.endpoint ?? "CultNet snapshot"),
sourceId: options.sourceId ?? recordKey,
timeoutMs: options.timeoutMs,
pollMs: options.pollMs,
messageIdPrefix: options.messageIdPrefix,
});
default:
throw new Error(`Unsupported CultMesh publication source kind '${(source as { kind?: string }).kind}'.`);
}
}
export async function cultMeshSyncDocumentFromPublication<TDefinition extends AnyCultCacheDocumentDefinition>(
node: CultMeshNode,
source: CultMeshDocumentPublicationSource,
definition: TDefinition,
recordKey: string,
options: {
timeoutMs?: number;
messageIdPrefix?: string;
} = {},
): Promise<CultCacheDocumentValue<TDefinition>> {
if (!source) {
throw new Error("CultMesh publication source is required.");
}
requireNonEmpty(recordKey, "recordKey");
if (source.kind === "peer-snapshot") {
return node.syncDocumentFromPeerSnapshot(source.peer, definition, recordKey, options);
}
const value = await cultMeshDocumentFromPublication(
source,
definition,
recordKey,
{
timeoutMs: options.timeoutMs,
messageIdPrefix: options.messageIdPrefix,
},
).latest();
return node.put(definition, recordKey, value);
}
export async function cultMeshSyncDocumentsFromPublication(
node: CultMeshNode,
source: CultMeshDocumentPublicationSourceResolver,
bindings: readonly CultMeshPublicationDocumentBinding[],
options: {
routeHint?: CultMeshRouteHint;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
} = {},
): Promise<CultMeshDocumentCatalog> {
if (!node) {
throw new Error("CultMesh node is required.");
}
if (!source) {
throw new Error("CultMesh publication source is required.");
}
if (!bindings) {
throw new Error("CultMesh publication bindings are required.");
}
const documents = await Promise.all(bindings.map(async binding => {
if (!binding) {
throw new Error("CultMesh publication binding is required.");
}
if (typeof binding.schema === "string") {
throw new Error(
`CultMesh synced publication binding '${binding.recordKey}' requires a document definition, not only a schema id.`,
);
}
const resolvedSource = binding.source ??
(typeof source === "function" ? source(binding) : source);
await node.syncDocumentFromPublication(
resolvedSource,
binding.schema,
binding.recordKey,
{
timeoutMs: options.timeoutMs,
messageIdPrefix: options.messageIdPrefix,
},
);
return node.document(binding.schema, binding.recordKey, {
documentId: binding.documentId ?? binding.recordKey,
routeHint: options.routeHint,
pollMs: options.pollMs,
});
}));
return cultMeshDocuments(...documents);
}
export function cultMeshGlobalDocumentFromCache<TDefinition extends AnyCultCacheDocumentDefinition>(
cache: CultCache,
definition: TDefinition,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>> {
const documentId = options.documentId ?? `${definition.type}:global`;
const schema = cultMeshSchemaFromDefinition(definition);
return cultMeshDocument(
documentId,
schema,
async () => cache.getRequiredGlobal(definition),
{
routeHint: options.routeHint ?? cultMeshRouteHint("in-process", "CultCache"),
sources: [cultMeshProjectionSource(documentId, { schemaId: schema.schemaId })],
watchDocument: cultMeshPollingDocumentWatcher(
async () => cache.getRequiredGlobal(definition),
{ intervalMs: options.pollMs ?? 50 },
),
replaceDocument: async (_context, value) => {
await cache.putGlobal(definition, value);
},
},
);
}
export function cultMeshDocuments(
...documents: readonly CultMeshDocumentHandle<any>[]
): CultMeshDocumentCatalog {
return new CultMeshDocumentCatalog(documents);
}
export function cultMeshPublicationDocument(
schema: string | AnyCultCacheDocumentDefinition,
recordKey: string,
options: {
documentId?: string;
sourceId?: string;
source?: CultMeshDocumentPublicationSource;
} = {},
): CultMeshPublicationDocumentBinding {
requireNonEmpty(recordKey, "recordKey");
return {
schema,
recordKey,
documentId: options.documentId,
sourceId: options.sourceId,
source: options.source,
};
}
export function cultMeshDocumentsFromPublication(
source: CultMeshDocumentPublicationSourceResolver,
bindings: readonly CultMeshPublicationDocumentBinding[],
options: {
routeHint?: CultMeshRouteHint;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
} = {},
): CultMeshDocumentCatalog {
if (!source) {
throw new Error("CultMesh publication source is required.");
}
if (!bindings) {
throw new Error("CultMesh publication bindings are required.");
}
return cultMeshDocuments(
...bindings.map(binding => {
if (!binding) {
throw new Error("CultMesh publication binding is required.");
}
const resolvedSource = binding.source ??
(typeof source === "function" ? source(binding) : source);
return typeof binding.schema === "string"
? cultMeshDocumentFromPublication(
resolvedSource,
binding.schema,
binding.recordKey,
{
documentId: binding.documentId ?? binding.recordKey,
sourceId: binding.sourceId ?? binding.documentId ?? binding.recordKey,
routeHint: options.routeHint,
timeoutMs: options.timeoutMs,
pollMs: options.pollMs,
messageIdPrefix: options.messageIdPrefix,
},
)
: cultMeshDocumentFromPublication(
resolvedSource,
binding.schema,
binding.recordKey,
{
documentId: binding.documentId ?? binding.recordKey,
sourceId: binding.sourceId ?? binding.documentId ?? binding.recordKey,
routeHint: options.routeHint,
timeoutMs: options.timeoutMs,
pollMs: options.pollMs,
messageIdPrefix: options.messageIdPrefix,
},
);
}),
);
}
export function cultMeshCollection<TDocument>(
collectionId: string,
schema: CultMeshDocumentSchemaDescriptor,
snapshotCollection: (context: CultMeshQueryContext) => Promise<CultMeshCollectionSnapshot<TDocument>>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchCollection?: CultMeshCollectionWatcher<TDocument>;
} = {},
): CultMeshCollectionHandle<TDocument> {
return new CultMeshCollectionHandle(collectionId, schema, snapshotCollection, options);
}
export function cultMeshCollections(
...collections: readonly CultMeshCollectionHandle<any>[]
): CultMeshCollectionCatalog {
return new CultMeshCollectionCatalog(collections);
}
export function cultMeshCollectionFromCache<TDefinition extends AnyCultCacheDocumentDefinition>(
cache: CultCache,
definition: TDefinition,
options: {
collectionId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshCollectionHandle<CultCacheDocumentValue<TDefinition>> {
const collectionId = options.collectionId ?? definition.type;
const schema = cultMeshSchemaFromDefinition(definition);
return cultMeshCollection(
collectionId,
schema,
async () => cache.getAll(definition),
{
routeHint: options.routeHint ?? cultMeshRouteHint("in-process", "CultCache"),
sources: [cultMeshProjectionSource(collectionId, { schemaId: schema.schemaId })],
watchCollection: cultMeshPollingCollectionWatcher(
async () => cache.getAll(definition),
{ intervalMs: options.pollMs ?? 50 },
),
},
);
}
export function cultMeshDescribeLiveFeed(
feed: {
readonly feedId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
},
): CultMeshLiveFeedDiagnostic {
return {
feedId: feed.feedId,
routeHint: feed.routeHint,
sources: [...feed.sources],
};
}
export function cultMeshPollingQueryWatcher<TParameters, TResult>(
executeQuery: (
parameters: TParameters,
context: CultMeshQueryContext,
) => Promise<TResult>,
options: CultMeshPollingWatchOptions<TResult> = {},
): CultMeshQueryWatcher<TParameters, TResult> {
const intervalMs = Math.max(1, options.intervalMs ?? 50);
const emitInitial = options.emitInitial ?? true;
return (parameters, context, callback) => {
let disposed = false;
let running = false;
let last: TResult | undefined;
let hasLast = false;
let timer: ReturnType<typeof setInterval> | undefined;
const poll = async () => {
if (disposed || running) {
return;
}
running = true;
try {
const next = await executeQuery(parameters, context);
const changed = !hasLast || !(options.equals?.(last as TResult, next) ?? Object.is(last, next));
const shouldEmit = hasLast ? changed : emitInitial;
last = next;
hasLast = true;
if (shouldEmit) {
callback(next);
}
} finally {
running = false;
}
};
void poll();
timer = setInterval(() => {
void poll();
}, intervalMs);
return () => {
disposed = true;
if (timer) {
clearInterval(timer);
}
};
};
}
export function cultMeshPollingDocumentWatcher<TDocument>(
readDocument: (context: CultMeshQueryContext) => Promise<TDocument>,
options: CultMeshPollingWatchOptions<TDocument> = {},
): CultMeshDocumentWatcher<TDocument> {
const watch = cultMeshPollingQueryWatcher<void, TDocument>(
async (_parameters, context) => readDocument(context),
options,
);
return (context, callback) => watch(undefined, context, callback);
}
export function cultMeshPollingCollectionWatcher<TDocument>(
readCollection: (context: CultMeshQueryContext) => Promise<CultMeshCollectionSnapshot<TDocument>>,
options: CultMeshPollingWatchOptions<CultMeshCollectionSnapshot<TDocument>> = {},
): CultMeshCollectionWatcher<TDocument> {
const watch = cultMeshPollingQueryWatcher<void, CultMeshCollectionSnapshot<TDocument>>(
async (_parameters, context) => readCollection(context),
{
...options,
equals: options.equals ?? ((left, right) => JSON.stringify(left) === JSON.stringify(right)),
},
);
return (context, callback) => watch(undefined, context, () => callback({
kind: "reset",
}));
}
export function cultMeshStatePointer<T>(
pointerId: string,
resolvePointer: (() => Promise<T | undefined>) | CultMeshStatePointerResolver<T>,
watchPointer?:
| ((callback: (value: T) => void) => CultMeshUnsubscribe)
| CultMeshStatePointerWatcher<T>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
} = {},
): CultMeshStatePointer<T> {
return new CultMeshStatePointer(pointerId, resolvePointer, watchPointer, options);
}
export function cultMeshMutableStatePointer<T>(
pointerId: string,
resolvePointer: (() => Promise<T | undefined>) | CultMeshStatePointerResolver<T>,
watchPointer:
| ((callback: (value: T) => void) => CultMeshUnsubscribe)
| CultMeshStatePointerWatcher<T>,
replacePointer:
| ((value: T) => Promise<void>)
| CultMeshMutableStatePointerReplacer<T>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
} = {},
): CultMeshMutableStatePointer<T> {
return new CultMeshMutableStatePointer(
pointerId,
resolvePointer,
watchPointer,
replacePointer,
options,
);
}
export function cultMeshProjectionSource(
sourceId: string,
options: { schemaId?: string; description?: string } = {},
): CultMeshProjectionSource {
requireNonEmpty(sourceId, "sourceId");
return {
sourceId,
schemaId: options.schemaId,
description: options.description,
};
}
export class CultMeshStateRefResolver {
public readonly sources: readonly CultMeshProjectionSource[];
public readonly routeHint: CultMeshRouteHint;
public constructor(
public readonly resolverId: string,
private readonly resolveRef: (stateRef: string, context: CultMeshQueryContext) => string | undefined,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
} = {},
) {
requireNonEmpty(resolverId, "resolverId");
this.sources = [...(options.sources ?? [])];
this.routeHint = options.routeHint ?? cultMeshRouteHint();
}
public resolve(stateRef: string, context: CultMeshQueryContext | string = "local"): string {
if (!stateRef.trim()) {
return "";
}
return (
this.resolveRef(
stateRef,
this.resolveContext(typeof context === "string" ? cultMeshQueryContext(context) : context),
) ?? ""
);
}
public tryResolve(
stateRef: string,
context: CultMeshQueryContext | string = "local",
): { resolved: boolean; value: string } {
const value = this.resolve(stateRef, context);
return { resolved: value.length > 0, value };
}
public or(fallback: CultMeshStateRefResolver): CultMeshStateRefResolver {
return new CultMeshStateRefResolver(
`${this.resolverId}|${fallback.resolverId}`,
(stateRef, context) => {
const value = this.resolve(stateRef, context);
return value.length > 0 ? value : fallback.resolve(stateRef, context);
},
{
sources: [...this.sources, ...fallback.sources],
routeHint: this.routeHint.kind === "automatic" ? fallback.routeHint : this.routeHint,
},
);
}
public asFunction(): (stateRef: string) => string {
return (stateRef) => this.resolve(stateRef);
}
private resolveContext(context: CultMeshQueryContext): CultMeshQueryContext {
if (context.routeHint.kind !== "automatic" || this.routeHint.kind === "automatic") {
return context;
}
return cultMeshQueryContext(context.runtimeId, { routeHint: this.routeHint });
}
}
export function cultMeshStateRefResolver(
resolverId: string,
resolveRef:
| ((stateRef: string) => string | undefined)
| ((stateRef: string, context: CultMeshQueryContext) => string | undefined),
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
} = {},
): CultMeshStateRefResolver {
return new CultMeshStateRefResolver(
resolverId,
(stateRef, context) =>
resolveRef.length <= 1
? (resolveRef as (stateRef: string) => string | undefined)(stateRef)
: (resolveRef as (stateRef: string, context: CultMeshQueryContext) => string | undefined)(
stateRef,
context,
),
options,
);
}
export function cultMeshDescribeStateRefResolver(
resolver: CultMeshStateRefResolver,
): CultMeshStateRefResolverDiagnostic {
return {
resolverId: resolver.resolverId,
routeHint: resolver.routeHint,
sources: [...resolver.sources],
};
}
export function cultMeshStateBinding(
targetProp: string,
pointer:
| {
readonly pointerId: string;
readonly routeHint?: CultMeshRouteHint;
readonly sources?: readonly CultMeshProjectionSource[];
}
| string,
options: {
sourceId?: string;
schemaId?: string;
routeHint?: CultMeshRouteHint;
} = {},
): CultMeshStateBindingDescriptor {
requireNonEmpty(targetProp, "targetProp");
if (typeof pointer === "string") {
requireNonEmpty(pointer, "pointerId");
return {
targetProp,
pointerId: pointer,
sourceId: options.sourceId,
schemaId: options.schemaId,
routeHint: options.routeHint ?? cultMeshRouteHint(),
};
}
requireNonEmpty(pointer.pointerId, "pointerId");
const source = pointer.sources?.[0];
return {
targetProp,
pointerId: pointer.pointerId,
sourceId: options.sourceId ?? source?.sourceId,
schemaId: options.schemaId ?? source?.schemaId,
routeHint: options.routeHint ?? pointer.routeHint ?? cultMeshRouteHint(),
};
}
export function cultMeshStateBindingRecord(
binding?: Partial<CultMeshStateBindingDescriptor>,
): CultMeshStateBindingRecord;
export function cultMeshStateBindingRecord(
targetProp?: string,
pointerId?: string,
sourceId?: string,
schemaId?: string,
routeKind?: string,
routeDescription?: string,
): CultMeshStateBindingRecord;
export function cultMeshStateBindingRecord(
bindingOrTargetProp?: Partial<CultMeshStateBindingDescriptor> | string,
pointerId?: string,
sourceId?: string,
schemaId?: string,
routeKind?: string,
routeDescription?: string,
): CultMeshStateBindingRecord {
if (typeof bindingOrTargetProp === "object" || bindingOrTargetProp === undefined) {
const binding = bindingOrTargetProp;
const route = cultMeshRouteRecord(binding?.routeHint);
return {
targetProp: binding?.targetProp?.trim() || "value",
pointerId: binding?.pointerId ?? "",
sourceId: binding?.sourceId ?? "",
schemaId: binding?.schemaId ?? "",
routeKind: route.kind,
routeDescription: route.description,
};
}
return {
targetProp: bindingOrTargetProp?.trim() || "value",
pointerId: pointerId ?? "",
sourceId: sourceId ?? "",
schemaId: schemaId ?? "",
routeKind: routeKind ?? "",
routeDescription: routeDescription ?? "",
};
}
export function cultMeshStateBindingFromRecord(
record: Partial<CultMeshStateBindingRecord>,
options: {
fallbackRouteHint?: CultMeshRouteHint;
fallbackTargetProp?: string;
} = {},
): CultMeshStateBindingDescriptor {
const targetProp = record.targetProp?.trim() || options.fallbackTargetProp?.trim() || "value";
return {
targetProp,
pointerId: record.pointerId?.trim() || `${targetProp}.unknown`,
sourceId: record.sourceId ?? "",
schemaId: record.schemaId ?? "",
routeHint: cultMeshRouteFromRecord(
{
kind: record.routeKind,
description: record.routeDescription,
},
options.fallbackRouteHint,
),
};
}
export function cultMeshOperationBinding(
operation:
| {
readonly operationId: string;
}
| string,
options: {
label?: string;
schemaId?: string;
routeHint?: CultMeshRouteHint;
} = {},
): CultMeshOperationBindingDescriptor {
const operationId = typeof operation === "string" ? operation : operation.operationId;
requireNonEmpty(operationId, "operationId");
return {
operationId,
label: options.label ?? "",
schemaId: options.schemaId ?? "",
routeHint: options.routeHint ?? cultMeshRouteHint(),
};
}
export function cultMeshOperationBindingRecord(
binding?: Partial<CultMeshOperationBindingDescriptor>,
): CultMeshOperationBindingRecord;
export function cultMeshOperationBindingRecord(
operationId?: string,
label?: string,
schemaId?: string,
routeKind?: string,
routeDescription?: string,
): CultMeshOperationBindingRecord;
export function cultMeshOperationBindingRecord(
bindingOrOperationId?: Partial<CultMeshOperationBindingDescriptor> | string,
label?: string,
schemaId?: string,
routeKind?: string,
routeDescription?: string,
): CultMeshOperationBindingRecord {
if (typeof bindingOrOperationId === "object" || bindingOrOperationId === undefined) {
const binding = bindingOrOperationId;
const route = cultMeshRouteRecord(binding?.routeHint);
return {
operationId: binding?.operationId ?? "",
label: binding?.label ?? "",
schemaId: binding?.schemaId ?? "",
routeKind: route.kind,
routeDescription: route.description,
};
}
return {
operationId: bindingOrOperationId ?? "",
label: label ?? "",
schemaId: schemaId ?? "",
routeKind: routeKind ?? "",
routeDescription: routeDescription ?? "",
};
}
export function cultMeshOperationBindingFromRecord(
record: Partial<CultMeshOperationBindingRecord>,
options: {
fallbackRouteHint?: CultMeshRouteHint;
fallbackOperationId?: string;
} = {},
): CultMeshOperationBindingDescriptor {
return {
operationId: record.operationId?.trim() || options.fallbackOperationId || "",
label: record.label ?? "",
schemaId: record.schemaId ?? "",
routeHint: cultMeshRouteFromRecord(
{
kind: record.routeKind,
description: record.routeDescription,
},
options.fallbackRouteHint,
),
};
}
export function cultMeshOperationInvocation(
operation:
| {
readonly operationId: string;
readonly schemaId?: string;
readonly routeHint?: CultMeshRouteHint;
}
| string,
options: {
schemaId?: string;
routeHint?: CultMeshRouteHint;
idempotencyKey?: string;
} = {},
): CultMeshOperationInvocationDescriptor {
const operationId = typeof operation === "string" ? operation : operation.operationId;
requireNonEmpty(operationId, "operationId");
return {
operationId,
schemaId: options.schemaId ?? (typeof operation === "string" ? "" : operation.schemaId) ?? "",
routeHint:
options.routeHint ??
(typeof operation === "string" ? undefined : operation.routeHint) ??
cultMeshRouteHint(),
idempotencyKey: options.idempotencyKey,
};
}
export function cultMeshOperationInvocationRecord(
invocation?: CultMeshOperationInvocationDescriptor,
options: {
fallbackOperationId?: string;
fallbackSchemaId?: string;
fallbackRouteHint?: CultMeshRouteHint;
fallbackIdempotencyKey?: string;
} = {},
): CultMeshOperationInvocationRecord {
const route = cultMeshRouteRecord(invocation?.routeHint ?? options.fallbackRouteHint);
return {
operationId: nonBlankOr(invocation?.operationId, options.fallbackOperationId),
schemaId: nonBlankOr(invocation?.schemaId, options.fallbackSchemaId),
routeKind: route.kind,
routeDescription: route.description,
idempotencyKey: nonBlankOr(invocation?.idempotencyKey, options.fallbackIdempotencyKey),
};
}
export function cultMeshOperationInvocationFromRecord(
record: Partial<CultMeshOperationInvocationRecord>,
options: {
fallbackOperationId?: string;
fallbackSchemaId?: string;
fallbackRouteHint?: CultMeshRouteHint;
fallbackIdempotencyKey?: string;
} = {},
): CultMeshOperationInvocationDescriptor {
const operationId = nonBlankOr(record.operationId, options.fallbackOperationId);
requireNonEmpty(operationId, "operationId");
const fallbackRoute = options.fallbackRouteHint ?? cultMeshRouteHint();
const route = cultMeshRouteFromRecord(
{
kind: record.routeKind,
description: record.routeDescription,
},
fallbackRoute,
);
return {
operationId,
schemaId: nonBlankOr(record.schemaId, options.fallbackSchemaId),
routeHint: route,
idempotencyKey: nonBlankOr(record.idempotencyKey, options.fallbackIdempotencyKey) || undefined,
};
}
export function cultMeshOperationPayload(
fields: Readonly<Record<string, string | number | boolean | undefined>> = {},
): CultMeshOperationPayload {
const normalized: Record<string, string> = {};
for (const [key, value] of Object.entries(fields)) {
if (!key.trim() || value === undefined) {
continue;
}
normalized[key] = String(value);
}
return {
fields: Object.freeze({ ...normalized }),
getString(key, defaultValue = "") {
return normalized[key] ?? defaultValue;
},
getInt(key, defaultValue = 0) {
const raw = normalized[key];
if (raw === undefined || raw.trim() === "") {
return defaultValue;
}
const parsed = Number.parseInt(raw, 10);
return Number.isNaN(parsed) ? defaultValue : parsed;
},
getDouble(key, defaultValue = 0) {
const raw = normalized[key];
if (raw === undefined || raw.trim() === "") {
return defaultValue;
}
const parsed = Number.parseFloat(raw);
return Number.isNaN(parsed) ? defaultValue : parsed;
},
getBoolean(key, defaultValue = false) {
const raw = normalized[key]?.toLowerCase();
if (raw === "true" || raw === "1" || raw === "yes" || raw === "on") {
return true;
}
if (raw === "false" || raw === "0" || raw === "no" || raw === "off") {
return false;
}
return defaultValue;
},
with(key, value) {
requireNonEmpty(key, "key");
return cultMeshOperationPayload({
...normalized,
[key]: value,
});
},
toRecord() {
return Object.freeze({ ...normalized });
},
};
}
export function cultMeshProjectionRecipe<TParameters, TResult>(
projectionId: string,
sources: readonly CultMeshProjectionSource[],
projectProjection: (
parameters: TParameters,
context: CultMeshQueryContext,
) => Promise<TResult>,
options: {
routeHint?: CultMeshRouteHint;
watchProjection?: (
parameters: TParameters,
context: CultMeshQueryContext,
callback: (value: TResult) => void,
) => CultMeshUnsubscribe;
} = {},
): CultMeshProjectionRecipe<TParameters, TResult> {
return new CultMeshProjectionRecipe(
projectionId,
sources,
projectProjection,
options,
);
}
export function cultMeshDescribeProjectionRecipe(
recipe: {
readonly projectionId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
},
): CultMeshProjectionRecipeDiagnostic {
return {
projectionId: recipe.projectionId,
routeHint: recipe.routeHint,
sources: [...recipe.sources],
};
}
export function cultMeshDescribeSurface(
surface:
| {
readonly queryId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
| {
readonly operationId: string;
}
| {
readonly projectionId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
| {
readonly feedId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
| {
readonly documentId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
| {
readonly collectionId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
| {
readonly pointerId: string;
readonly routeHint?: CultMeshRouteHint;
readonly sources?: readonly CultMeshProjectionSource[];
}
| CultMeshNativeSliceViewDescriptor,
): CultMeshSurfaceDiagnostic {
if ("queryId" in surface) {
return cultMeshSurfaceDiagnostic("query", surface.queryId, {
routeHint: surface.routeHint,
sources: surface.sources,
});
}
if ("operationId" in surface) {
return cultMeshSurfaceDiagnostic("operation", surface.operationId);
}
if ("pointerId" in surface) {
return cultMeshSurfaceDiagnostic("state-pointer", surface.pointerId, {
routeHint: surface.routeHint,
sources: surface.sources,
});
}
if ("documentId" in surface) {
return cultMeshSurfaceDiagnostic("document", surface.documentId, {
routeHint: surface.routeHint,
sources: surface.sources,
});
}
if ("collectionId" in surface) {
return cultMeshSurfaceDiagnostic("collection", surface.collectionId, {
routeHint: surface.routeHint,
sources: surface.sources,
});
}
if ("viewId" in surface) {
return cultMeshSurfaceDiagnostic("native-slice-view", surface.viewId, {
routeHint: surface.route,
});
}
if ("projectionId" in surface) {
return cultMeshSurfaceDiagnostic("projection-recipe", surface.projectionId, {
routeHint: surface.routeHint,
sources: surface.sources,
});
}
return cultMeshSurfaceDiagnostic("live-feed", surface.feedId, {
routeHint: surface.routeHint,
sources: surface.sources,
});
}
export function cultMeshSurfaceDiagnostic(
kind: CultMeshSurfaceKind,
surfaceId: string,
options: {
routeHint?: CultMeshRouteHint;
sources?: readonly CultMeshProjectionSource[];
} = {},
): CultMeshSurfaceDiagnostic {
requireNonEmpty(surfaceId, "surfaceId");
return {
kind,
surfaceId,
routeHint: options.routeHint ?? cultMeshRouteHint(),
sources: [...(options.sources ?? [])],
};
}
export function cultMeshDescribeSurfaceCatalog(
catalogId: string,
surfaces: readonly CultMeshSurfaceDiagnostic[],
): CultMeshSurfaceCatalogDiagnostic {
requireNonEmpty(catalogId, "catalogId");
return {
catalogId,
surfaces: surfaces.map(surface => cultMeshSurfaceDiagnostic(surface.kind, surface.surfaceId, {
routeHint: surface.routeHint,
sources: surface.sources,
})),
};
}
export function cultMeshFindSurface(
catalog: CultMeshSurfaceCatalogDiagnostic,
surfaceId: string,
): CultMeshSurfaceDiagnostic | undefined {
requireNonEmpty(surfaceId, "surfaceId");
return catalog.surfaces.find(surface => surface.surfaceId === surfaceId);
}
export function cultMeshSurfacesByKind(
catalog: CultMeshSurfaceCatalogDiagnostic,
kind: CultMeshSurfaceKind,
): CultMeshSurfaceDiagnostic[] {
return catalog.surfaces
.filter(surface => surface.kind === kind)
.map(surface => cultMeshSurfaceDiagnostic(surface.kind, surface.surfaceId, {
routeHint: surface.routeHint,
sources: surface.sources,
}));
}
export function cultMeshSurfaceCatalogIndex(
catalog: CultMeshSurfaceCatalogDiagnostic,
): CultMeshSurfaceCatalogIndexDiagnostic {
return {
catalogId: catalog.catalogId,
queries: cultMeshSurfacesByKind(catalog, "query"),
projectionRecipes: cultMeshSurfacesByKind(catalog, "projection-recipe"),
liveFeeds: cultMeshSurfacesByKind(catalog, "live-feed"),
operations: cultMeshSurfacesByKind(catalog, "operation"),
documents: cultMeshSurfacesByKind(catalog, "document"),
collections: cultMeshSurfacesByKind(catalog, "collection"),
statePointers: cultMeshSurfacesByKind(catalog, "state-pointer"),
nativeSliceViews: cultMeshSurfacesByKind(catalog, "native-slice-view"),
};
}
export function cultMeshNativeSliceColumn(
name: string,
valueType: string,
elementSizeBytes: number,
): CultMeshNativeSliceColumn {
requireNonEmpty(name, "name");
requireNonEmpty(valueType, "valueType");
if (!Number.isInteger(elementSizeBytes) || elementSizeBytes <= 0) {
throw new Error("elementSizeBytes must be a positive integer.");
}
return { name, valueType, elementSizeBytes };
}
export function cultMeshNativeSliceView(
viewId: string,
schemaId: string,
rowCount: number,
columns: readonly CultMeshNativeSliceColumn[],
options: { route?: CultMeshRouteHint; nativeHandle?: string } = {},
): CultMeshNativeSliceViewDescriptor {
requireNonEmpty(viewId, "viewId");
requireNonEmpty(schemaId, "schemaId");
if (!Number.isInteger(rowCount) || rowCount < 0) {
throw new Error("rowCount must be a non-negative integer.");
}
return {
viewId,
schemaId,
rowCount,
columns: [...columns],
route: options.route ?? cultMeshRouteHint(),
nativeHandle: options.nativeHandle,
};
}
export function cultMeshDenseRowStrideBytes(
view: CultMeshNativeSliceViewDescriptor,
): number {
return view.columns.reduce((sum, column) => sum + column.elementSizeBytes, 0);
}
export function cultMeshFindNativeSliceColumn(
view: CultMeshNativeSliceViewDescriptor,
name: string,
): CultMeshNativeSliceColumn | undefined {
requireNonEmpty(name, "name");
return view.columns.find((column) => column.name === name);
}
export function cultMeshDescribeNativeSliceView(
view: CultMeshNativeSliceViewDescriptor,
): CultMeshNativeSliceViewDiagnostic {
return {
viewId: view.viewId,
schemaId: view.schemaId,
rowCount: view.rowCount,
columns: [...view.columns],
route: view.route,
nativeHandle: view.nativeHandle,
denseRowStrideBytes: cultMeshDenseRowStrideBytes(view),
};
}
export interface CultMeshNodeOptions {
documents?: Iterable<AnyCultCacheDocumentDefinition>;
bindings?: Iterable<CultNetDocumentBinding>;
store?: CacheBackingStore;
pullOnStart?: boolean;
}
export class CultMeshNode {
public constructor(
public readonly cache: CultCache,
public readonly store: CacheBackingStore,
public readonly documents: CultNetDocumentRegistry,
) {}
public get<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
key: string,
): CultCacheDocumentValue<TDefinition> | undefined {
const registered = this.resolveCacheDefinition(definition);
const value = this.cache.get(registered, key);
return value === undefined
? undefined
: this.parseDocumentValue(definition, value);
}
public getRequired<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
key: string,
): CultCacheDocumentValue<TDefinition> {
const value = this.get(definition, key);
if (value === undefined) {
throw new Error(`CultMesh node has no "${definition.type}" document at key "${key}".`);
}
return value;
}
public put<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
key: string,
value: CultCacheDocumentValue<TDefinition>,
): Promise<CultCacheDocumentValue<TDefinition>> {
const registered = this.resolveCacheDefinition(definition);
return this.cache
.put(registered, key, this.parseDocumentValue(definition, value) as never)
.then(stored => this.parseDocumentValue(definition, stored));
}
public delete<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
key: string,
): Promise<boolean> {
return this.cache.delete(this.resolveCacheDefinition(definition), key);
}
public document<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
key: string,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>> {
const documentId = options.documentId ?? `${definition.type}:${key}`;
return this.asRequestedDocument(
cultMeshDocumentFromCache(
this.cache,
this.resolveCacheDefinition(definition),
key,
{ ...options, documentId },
),
definition,
);
}
public reactiveDocument<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
key: string,
options: {
context?: CultMeshQueryContext | string;
watch?: boolean;
debounceMs?: number;
onError?: (error: unknown) => void;
documentId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshReactiveDocument<CultCacheDocumentValue<TDefinition> & object> {
const {
documentId,
routeHint,
pollMs,
...reactiveOptions
} = options;
return this.document(definition, key, { documentId, routeHint, pollMs })
.reactive<CultCacheDocumentValue<TDefinition> & object>(reactiveOptions);
}
public async syncDocumentFromPeerSnapshot<TDefinition extends AnyCultCacheDocumentDefinition>(
peer: CultNetPeer | (() => CultNetPeer | Promise<CultNetPeer>),
definition: TDefinition,
key: string,
options: {
timeoutMs?: number;
messageIdPrefix?: string;
} = {},
): Promise<CultCacheDocumentValue<TDefinition>> {
const registered = this.resolveCacheDefinition(definition);
const schema = cultMeshSchemaFromDefinition(definition);
const schemaId = schema.schemaId ?? schema.type;
if (!schemaId) {
throw new Error("CultMesh synced document requires a schema id or document type.");
}
const document = await requestCultNetRawSnapshotDocument(
peer,
schemaId,
key,
{
timeoutMs: options.timeoutMs,
messageIdPrefix: options.messageIdPrefix ?? key,
accepts: record => {
try {
definition.schema.parse(decodeCultNetRawDocumentPayload(record));
return true;
} catch {
return false;
}
},
},
);
await this.documents.applyRawDocumentPutMessage(this.cache, {
schemaVersion: "cultnet.document_put_raw.v0",
messageId: options.messageIdPrefix ?? `sync:${key}`,
document,
});
return this.parseDocumentValue(definition, this.cache.getRequired(registered, key));
}
public syncDocumentFromPublication<TDefinition extends AnyCultCacheDocumentDefinition>(
source: CultMeshDocumentPublicationSource,
definition: TDefinition,
key: string,
options: {
timeoutMs?: number;
messageIdPrefix?: string;
} = {},
): Promise<CultCacheDocumentValue<TDefinition>> {
return cultMeshSyncDocumentFromPublication(this, source, definition, key, options);
}
public syncDocumentsFromPublication(
source: CultMeshDocumentPublicationSourceResolver,
bindings: readonly CultMeshPublicationDocumentBinding[],
options: {
routeHint?: CultMeshRouteHint;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
} = {},
): Promise<CultMeshDocumentCatalog> {
return cultMeshSyncDocumentsFromPublication(this, source, bindings, options);
}
public globalDocument<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>> {
const documentId = options.documentId ?? `${definition.type}:global`;
return this.asRequestedDocument(
cultMeshGlobalDocumentFromCache(
this.cache,
this.resolveCacheDefinition(definition),
{ ...options, documentId },
),
definition,
);
}
public collection<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
options: {
collectionId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshCollectionHandle<CultCacheDocumentValue<TDefinition>> {
const collectionId = options.collectionId ?? definition.type;
return cultMeshCollectionFromCache(
this.cache,
this.resolveCacheDefinition(definition),
{ ...options, collectionId },
).asSchemaAlias<CultCacheDocumentValue<TDefinition>>(
cultMeshSchemaFromDefinition(definition),
{ parse: value => this.parseDocumentValue(definition, value) },
);
}
public async flush(soft = false): Promise<void> {
await this.store.pushAll?.(this.cache.snapshot(), { soft });
}
private resolveCacheDefinition<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
): AnyCultCacheDocumentDefinition {
const schema = cultMeshSchemaFromDefinition(definition);
return (
this.documents.get(definition.type) ??
(schema.schemaId ? this.documents.getBySchemaId(schema.schemaId) : undefined)
)?.definition ?? definition;
}
private asRequestedDocument<TDefinition extends AnyCultCacheDocumentDefinition>(
document: CultMeshDocumentHandle<unknown>,
definition: TDefinition,
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>> {
return document.asSchemaAlias<CultCacheDocumentValue<TDefinition>>(
cultMeshSchemaFromDefinition(definition),
{ parse: value => this.parseDocumentValue(definition, value) },
);
}
private parseDocumentValue<TDefinition extends AnyCultCacheDocumentDefinition>(
definition: TDefinition,
value: unknown,
): CultCacheDocumentValue<TDefinition> {
return definition.schema.parse(value) as CultCacheDocumentValue<TDefinition>;
}
}
export class CultMeshVerseCatalog<TVerse = unknown> {
readonly #verses = new Map<string, TVerse>();
readonly #subscribers = new Set<(verse: TVerse) => void>();
public get verses(): readonly TVerse[] {
return [...this.#verses.entries()]
.sort(([left], [right]) => left.localeCompare(right))
.map(([, verse]) => verse);
}
public watch(callback: (verse: TVerse) => void): () => void {
this.#subscribers.add(callback);
return () => {
this.#subscribers.delete(callback);
};
}
public upsert(verseId: string, verse: TVerse): void {
requireNonEmpty(verseId, "verseId");
this.#verses.set(verseId, verse);
for (const subscriber of [...this.#subscribers]) {
subscriber(verse);
}
}
public get(verseId: string): TVerse | undefined {
requireNonEmpty(verseId, "verseId");
return this.#verses.get(verseId);
}
}
export interface CultMeshPeerCard {
peerId: string;
verseId: string;
endpoints: readonly string[];
roles?: readonly string[];
shardIds?: readonly string[];
authorityLeaseId?: string;
}
export interface CultMeshRudpDocumentPut {
schemaId: string;
recordKey: string;
storedAt: string;
payload: unknown;
sourceRuntimeId: string | null;
sourceAgentId: string | null;
sourceRole: string | null;
tags: string[];
remote: {
address: string;
family: string;
port: number;
};
}
export interface CultMeshRudpDocumentServerOptions extends CultMeshRudpSocketOptions {
documents: CultNetDocumentRegistry;
getCache?: () => Promise<CultCache> | CultCache;
onError?: (error: Error) => void;
onDocumentPutRaw?: (document: CultMeshRudpDocumentPut) => void | Promise<void>;
onOperationRequest?: (
request: CultNetOperationRequestMessage,
session: CultMeshRudpServerSession,
) => CultNetOperationResponseMessage | Promise<CultNetOperationResponseMessage>;
onSessionClosed?: (session: CultMeshRudpServerSession) => void | Promise<void>;
wireContract?: CultNetWireContract;
sessionTimeoutMs?: number;
}
export interface CultMeshRudpServerSession {
readonly sessionId: string;
readonly remote: { address: string; family: string; port: number };
send(message: CultNetMessage): void;
}
export interface CultMeshRudpDocumentServer {
readonly bind: { host: string; port: number };
start(): Promise<void>;
close(): void;
}
export interface CultMeshRudpDocumentPublishOptions extends CultMeshRudpPeerOptions {
messageId?: string;
sourceRuntimeId?: string;
sourceAgentId?: string;
sourceRole?: string;
tags?: string[];
flushTimeoutMs?: number;
}
export interface CultMeshRudpEndpoint {
host: string;
port: number;
uri: string;
}
export interface CultMeshRudpSocketOptions {
bindHost?: string;
bindPort?: number;
socket?: Socket;
initialSequence?: number;
resendDelayMs?: number;
resendPollMs?: number;
transportId?: string;
maxPayloadBytes?: number;
maxFragmentBytes?: number;
maxPendingReliablePackets?: number;
reconnectPolicy?: CultNetReconnectPolicy;
}
export interface CultMeshAuthorizedRudpSocketOptions
extends CultMeshRudpSocketOptions {
shardId?: string;
at?: Date;
}
export interface CultMeshRudpPeerOptions extends CultMeshRudpSocketOptions {
connectPayload?: Uint8Array;
connectTimeoutMs?: number;
wireContract?: CultNetWireContract;
}
export interface CultMeshAuthorizedRudpPeerOptions
extends CultMeshRudpPeerOptions {
shardId?: string;
at?: Date;
}
export class CultMeshPeerCatalog {
readonly #peers = new Map<string, CultMeshPeerCard>();
readonly #subscribers = new Set<(peer: CultMeshPeerCard) => void>();
public get peers(): readonly CultMeshPeerCard[] {
return [...this.#peers.values()].sort((left, right) =>
left.peerId.localeCompare(right.peerId),
);
}
public watch(callback: (peer: CultMeshPeerCard) => void): () => void {
this.#subscribers.add(callback);
return () => {
this.#subscribers.delete(callback);
};
}
public upsert(peer: CultMeshPeerCard): void {
requireNonEmpty(peer.peerId, "peer.peerId");
requireNonEmpty(peer.verseId, "peer.verseId");
this.#peers.set(peer.peerId, peer);
for (const subscriber of [...this.#subscribers]) {
subscriber(peer);
}
}
public get(peerId: string): CultMeshPeerCard | undefined {
requireNonEmpty(peerId, "peerId");
return this.#peers.get(peerId);
}
public find(verseId: string, role?: string): readonly CultMeshPeerCard[] {
requireNonEmpty(verseId, "verseId");
return this.peers.filter(
(peer) =>
peer.verseId === verseId &&
(!role || peer.roles?.includes(role) === true),
);
}
public findAuthorized(
verseId: string,
role: string,
leases: CultMeshAuthorityLeaseCatalog,
shardId?: string,
at = new Date(),
): readonly CultMeshPeerCard[] {
requireNonEmpty(verseId, "verseId");
requireNonEmpty(role, "role");
return this.find(verseId, role).filter((peer) =>
leases.isAuthorized(peer, role, shardId, at),
);
}
public firstAuthorized(
verseId: string,
role: string,
leases: CultMeshAuthorityLeaseCatalog,
shardId?: string,
at = new Date(),
): CultMeshPeerCard | undefined {
return this.findAuthorized(verseId, role, leases, shardId, at)[0];
}
}
export interface CultMeshAuthorityLease {
leaseId: string;
verseId: string;
peerId: string;
roles: readonly string[];
shardIds?: readonly string[];
validFrom: Date;
expiresAt: Date;
}
export class CultMeshAuthorityLeaseCatalog {
readonly #leases = new Map<string, CultMeshAuthorityLease>();
readonly #subscribers = new Set<(lease: CultMeshAuthorityLease) => void>();
public get leases(): readonly CultMeshAuthorityLease[] {
return [...this.#leases.values()].sort((left, right) =>
left.leaseId.localeCompare(right.leaseId),
);
}
public watch(callback: (lease: CultMeshAuthorityLease) => void): () => void {
this.#subscribers.add(callback);
return () => {
this.#subscribers.delete(callback);
};
}
public upsert(lease: CultMeshAuthorityLease): void {
requireNonEmpty(lease.leaseId, "lease.leaseId");
requireNonEmpty(lease.verseId, "lease.verseId");
requireNonEmpty(lease.peerId, "lease.peerId");
if (lease.expiresAt <= lease.validFrom) {
throw new Error("CultMesh authority lease expiry must be after validFrom.");
}
this.#leases.set(lease.leaseId, lease);
for (const subscriber of [...this.#subscribers]) {
subscriber(lease);
}
}
public get(leaseId: string): CultMeshAuthorityLease | undefined {
requireNonEmpty(leaseId, "leaseId");
return this.#leases.get(leaseId);
}
public isAuthorized(
peer: CultMeshPeerCard,
role: string,
shardId?: string,
at = new Date(),
): boolean {
requireNonEmpty(role, "role");
if (!peer.authorityLeaseId) {
return false;
}
const lease = this.#leases.get(peer.authorityLeaseId);
if (!lease) {
return false;
}
return (
at >= lease.validFrom &&
at < lease.expiresAt &&
lease.verseId === peer.verseId &&
lease.peerId === peer.peerId &&
lease.roles.includes(role) &&
peer.roles?.includes(role) === true &&
(!shardId ||
!lease.shardIds ||
lease.shardIds.length === 0 ||
lease.shardIds.includes(shardId))
);
}
}
export type CultMeshStreamKind = "audio" | "video" | "tensor" | "bytes";
export type CultMeshStreamBodyTransport =
| "shared-memory"
| "shared-d3d12-texture"
| "shared-d3d11-texture"
| "dma-buf"
| "iosurface"
| "ahardwarebuffer"
| "cultcache-page"
| "inline-bytes";
export type CultMeshStreamAccess = "read" | "write" | "read-write";
export interface CultMeshStreamClock {
clockDomainId: string;
sourceId?: string;
sampleRate?: number;
offsetToVerseTimeNs?: number;
confidence?: number;
evidenceKind?: string;
}
export interface CultMeshAudioStreamFormat {
sampleRate: number;
channels: number;
sampleFormat: "float32" | "int16" | "int24" | "int32";
framesPerPacket?: number;
}
export interface CultMeshVideoStreamFormat {
width: number;
height: number;
pixelFormat: string;
framesPerSecond?: number;
planeCount?: number;
}
export interface CultMeshStreamDescriptor {
streamId: string;
verseId: string;
ownerPeerId: string;
kind: CultMeshStreamKind;
label?: string;
clock: CultMeshStreamClock;
audio?: CultMeshAudioStreamFormat;
video?: CultMeshVideoStreamFormat;
preferredTransports: readonly CultMeshStreamBodyTransport[];
requiredAccess?: CultMeshStreamAccess;
maxInFlightFrames?: number;
metadataSchemaId?: string;
}
export interface CultMeshStreamConsumerProfile {
peerId: string;
verseId: string;
supportedTransports: readonly CultMeshStreamBodyTransport[];
acceptedKinds?: readonly CultMeshStreamKind[];
canImportGpuHandles?: boolean;
canMapSharedMemory?: boolean;
maxInFlightFrames?: number;
}
export interface CultMeshStreamNegotiation {
streamId: string;
producerPeerId: string;
consumerPeerId: string;
transport: CultMeshStreamBodyTransport;
access: CultMeshStreamAccess;
maxInFlightFrames: number;
copyBudget: "zero-copy-target" | "one-copy-fallback" | "opaque-runtime";
}
export interface CultMeshStreamFrameHandle {
streamId: string;
sequence: bigint;
timestampNs: bigint;
durationNs?: bigint;
transport: CultMeshStreamBodyTransport;
byteLength?: number;
nativeHandle?: string;
resourceKey?: string;
pageRef?: string;
fenceHandle?: string;
fenceValue?: bigint;
unavoidableCopyCount?: number;
metadata?: Record<string, unknown>;
}
export class CultMeshStreamCatalog {
readonly #streams = new Map<string, CultMeshStreamDescriptor>();
readonly #latestFrames = new Map<string, CultMeshStreamFrameHandle>();
readonly #streamSubscribers = new Set<(stream: CultMeshStreamDescriptor) => void>();
readonly #frameSubscribers = new Set<(frame: CultMeshStreamFrameHandle) => void>();
public get streams(): readonly CultMeshStreamDescriptor[] {
return [...this.#streams.values()].sort((left, right) =>
left.streamId.localeCompare(right.streamId),
);
}
public watch(callback: (stream: CultMeshStreamDescriptor) => void): () => void {
this.#streamSubscribers.add(callback);
return () => {
this.#streamSubscribers.delete(callback);
};
}
public watchFrames(callback: (frame: CultMeshStreamFrameHandle) => void): () => void {
this.#frameSubscribers.add(callback);
return () => {
this.#frameSubscribers.delete(callback);
};
}
public declare(stream: CultMeshStreamDescriptor): CultMeshStreamDescriptor {
requireNonEmpty(stream.streamId, "stream.streamId");
requireNonEmpty(stream.verseId, "stream.verseId");
requireNonEmpty(stream.ownerPeerId, "stream.ownerPeerId");
requireNonEmpty(stream.clock.clockDomainId, "stream.clock.clockDomainId");
if (stream.preferredTransports.length === 0) {
throw new Error("stream.preferredTransports must not be empty.");
}
this.#streams.set(stream.streamId, stream);
for (const subscriber of [...this.#streamSubscribers]) {
subscriber(stream);
}
return stream;
}
public get(streamId: string): CultMeshStreamDescriptor | undefined {
requireNonEmpty(streamId, "streamId");
return this.#streams.get(streamId);
}
public find(
verseId: string,
kind?: CultMeshStreamKind,
): readonly CultMeshStreamDescriptor[] {
requireNonEmpty(verseId, "verseId");
return this.streams.filter(
(stream) => stream.verseId === verseId && (!kind || stream.kind === kind),
);
}
public negotiate(
streamId: string,
consumer: CultMeshStreamConsumerProfile,
): CultMeshStreamNegotiation {
const stream = this.get(streamId);
if (!stream) {
throw new Error(`Unknown CultMesh stream '${streamId}'.`);
}
if (consumer.verseId !== stream.verseId) {
throw new Error("stream and consumer must belong to the same Verse.");
}
if (
consumer.acceptedKinds &&
!consumer.acceptedKinds.includes(stream.kind)
) {
throw new Error(`consumer does not accept ${stream.kind} streams.`);
}
const transport = stream.preferredTransports.find((candidate) =>
consumer.supportedTransports.includes(candidate),
);
if (!transport) {
throw new Error("stream and consumer have no compatible body transport.");
}
return {
streamId: stream.streamId,
producerPeerId: stream.ownerPeerId,
consumerPeerId: consumer.peerId,
transport,
access: stream.requiredAccess ?? "read",
maxInFlightFrames: Math.min(
stream.maxInFlightFrames ?? Number.MAX_SAFE_INTEGER,
consumer.maxInFlightFrames ?? Number.MAX_SAFE_INTEGER,
),
copyBudget: copyBudgetFor(transport),
};
}
public publishFrame(frame: CultMeshStreamFrameHandle): CultMeshStreamFrameHandle {
requireNonEmpty(frame.streamId, "frame.streamId");
if (!this.#streams.has(frame.streamId)) {
throw new Error(`Unknown CultMesh stream '${frame.streamId}'.`);
}
this.#latestFrames.set(frame.streamId, frame);
for (const subscriber of [...this.#frameSubscribers]) {
subscriber(frame);
}
return frame;
}
public latestFrame(streamId: string): CultMeshStreamFrameHandle | undefined {
requireNonEmpty(streamId, "streamId");
return this.#latestFrames.get(streamId);
}
}
export class CultMesh {
public static vec2(x: number, y: number): CultMeshVec2 {
return cultMeshVec2(x, y);
}
public static rect(min: CultMeshVec2, max: CultMeshVec2): CultMeshRect {
return cultMeshRect(min, max);
}
public static rectFromBounds(
minX: number,
minY: number,
maxX: number,
maxY: number,
): CultMeshRect {
return cultMeshRectFromBounds(minX, minY, maxX, maxY);
}
public static viewportRequest(
viewport: CultMeshRect,
controlledEntityIndices?: readonly number[],
): CultMeshViewportRequest {
return cultMeshViewportRequest(viewport, controlledEntityIndices);
}
public static routeHint(
kind: CultMeshLocalityKind = "automatic",
description?: string,
): CultMeshRouteHint {
return cultMeshRouteHint(kind, description);
}
public static routeRecord(routeHint?: CultMeshRouteHint): CultMeshRouteRecord;
public static routeRecord(kind?: string, description?: string): CultMeshRouteRecord;
public static routeRecord(
routeOrKind?: CultMeshRouteHint | string,
description = "",
): CultMeshRouteRecord {
return typeof routeOrKind === "string" || routeOrKind === undefined
? cultMeshRouteRecord(routeOrKind, description)
: cultMeshRouteRecord(routeOrKind);
}
public static routeFromRecord(
record: Partial<CultMeshRouteRecord>,
fallback: CultMeshRouteHint = cultMeshRouteHint(),
): CultMeshRouteHint {
return cultMeshRouteFromRecord(record, fallback);
}
public static authorityClaim(
role: string,
options: { shardId?: string; leaseId?: string } = {},
): CultMeshAuthorityClaim {
return cultMeshAuthorityClaim(role, options);
}
public static verseContext(
verseId: string,
runtimeId: string,
options: {
routeHint?: CultMeshRouteHint;
claims?: readonly CultMeshAuthorityClaim[];
} = {},
): CultMeshVerseContext {
return cultMeshVerseContext(verseId, runtimeId, options);
}
public static verse(
verseId: string,
runtimeId: string,
options: {
routeHint?: CultMeshRouteHint;
claims?: readonly CultMeshAuthorityClaim[];
} = {},
): CultMeshVerse {
return cultMeshVerse(verseId, runtimeId, options);
}
public static async connectVerse(
verseId: string,
runtimeId: string,
options: {
routeHint?: CultMeshRouteHint;
claims?: readonly CultMeshAuthorityClaim[];
} = {},
): Promise<CultMeshVerse> {
return cultMeshVerse(verseId, runtimeId, options);
}
public static operationContextFromVerse(
context: CultMeshVerseContext,
options: { idempotencyKey?: string } = {},
): CultMeshOperationContext {
return cultMeshOperationContextFromVerse(context, options);
}
public static queryContextFromVerse(
context: CultMeshVerseContext,
): CultMeshQueryContext {
return cultMeshQueryContextFromVerse(context);
}
public static bindOperation<TRequest, TResponse>(
verse: CultMeshVerseContext | CultMeshVerse,
operation: CultMeshOperationHandle<TRequest, TResponse>,
): CultMeshBoundOperationHandle<TRequest, TResponse> {
return cultMeshBindOperation(verse, operation);
}
public static bindQuery<TParameters, TResult>(
verse: CultMeshVerseContext | CultMeshVerse,
query: CultMeshQuerySurface<TParameters, TResult>,
): CultMeshBoundQuerySurface<TParameters, TResult> {
return cultMeshBindQuery(verse, query);
}
public static bindLiveFeed<TParameters, TResult>(
verse: CultMeshVerseContext | CultMeshVerse,
feed: CultMeshLiveFeed<TParameters, TResult>,
): CultMeshBoundLiveFeed<TParameters, TResult> {
return cultMeshBindLiveFeed(verse, feed);
}
public static operationContext(
runtimeId: string,
options: {
claims?: readonly CultMeshAuthorityClaim[];
routeHint?: CultMeshRouteHint;
idempotencyKey?: string;
} = {},
): CultMeshOperationContext {
return cultMeshOperationContext(runtimeId, options);
}
public static operationContextFor(runtimeId: string): CultMeshOperationContextBuilder {
return cultMeshOperationContextFor(runtimeId);
}
public static queryContext(
runtimeId: string,
options: { routeHint?: CultMeshRouteHint } = {},
): CultMeshQueryContext {
return cultMeshQueryContext(runtimeId, options);
}
public static queryContextFor(runtimeId: string): CultMeshQueryContextBuilder {
return cultMeshQueryContextFor(runtimeId);
}
public static operationReceipt(
operationId: string,
accepted: boolean,
options: { route?: CultMeshRouteHint; diagnostic?: string } = {},
): CultMeshOperationReceipt {
return cultMeshOperationReceipt(operationId, accepted, options);
}
public static operation<TRequest, TResponse>(
operationId: string,
invokeOperation: (
request: TRequest,
context: CultMeshOperationContext,
) => Promise<TResponse>,
): CultMeshOperationHandle<TRequest, TResponse> {
return cultMeshOperation(operationId, invokeOperation);
}
public static query<TParameters, TResult>(
queryId: string,
executeQuery: (
parameters: TParameters,
context: CultMeshQueryContext,
) => Promise<TResult>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchQuery?: CultMeshQueryWatcher<TParameters, TResult>;
} = {},
): CultMeshQuerySurface<TParameters, TResult> {
return cultMeshQuery(queryId, executeQuery, options);
}
public static describeQuerySurface(
query: {
readonly queryId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
},
): CultMeshQuerySurfaceDiagnostic {
return cultMeshDescribeQuerySurface(query);
}
public static describeOperationHandle(
operation: {
readonly operationId: string;
},
): CultMeshOperationHandleDiagnostic {
return cultMeshDescribeOperationHandle(operation);
}
public static describeStatePointer(
pointer: {
readonly pointerId: string;
readonly routeHint?: CultMeshRouteHint;
readonly sources?: readonly CultMeshProjectionSource[];
},
): CultMeshStatePointerDiagnostic {
return cultMeshDescribeStatePointer(pointer);
}
public static liveFeed<TParameters, TResult>(
feedId: string,
snapshotFeed: (
parameters: TParameters,
context: CultMeshQueryContext,
) => Promise<TResult>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchFeed?: CultMeshLiveFeedWatcher<TParameters, TResult>;
} = {},
): CultMeshLiveFeed<TParameters, TResult> {
return cultMeshLiveFeed(feedId, snapshotFeed, options);
}
public static document<TDocument>(
documentId: string,
schema: CultMeshDocumentSchemaDescriptor,
snapshotDocument: (context: CultMeshQueryContext) => Promise<TDocument>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchDocument?: CultMeshDocumentWatcher<TDocument>;
replaceDocument?: CultMeshDocumentReplacer<TDocument>;
submitPrediction?: CultMeshDocumentPredictionSubmitter<TDocument>;
} = {},
): CultMeshDocumentHandle<TDocument> {
return cultMeshDocument(documentId, schema, snapshotDocument, options);
}
public static documentFromCache<TDefinition extends AnyCultCacheDocumentDefinition>(
cache: CultCache,
definition: TDefinition,
key: string,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>> {
return cultMeshDocumentFromCache(cache, definition, key, options);
}
public static documentFromStore(
store: CacheBackingStore,
schemaId: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
},
): CultMeshDocumentHandle<unknown>;
public static documentFromStore<TDefinition extends AnyCultCacheDocumentDefinition>(
store: CacheBackingStore,
definition: TDefinition,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>>;
public static documentFromStore(
store: CacheBackingStore,
schemaOrDefinition: string | AnyCultCacheDocumentDefinition,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
} = {},
): CultMeshDocumentHandle<unknown> {
return typeof schemaOrDefinition === "string"
? cultMeshDocumentFromStore(store, schemaOrDefinition, options)
: cultMeshDocumentFromStore(store, schemaOrDefinition, options);
}
public static documentFromSingleFile(
path: string,
schemaId: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
},
): CultMeshDocumentHandle<unknown>;
public static documentFromSingleFile<TDefinition extends AnyCultCacheDocumentDefinition>(
path: string,
definition: TDefinition,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>>;
public static documentFromSingleFile(
path: string,
schemaOrDefinition: string | AnyCultCacheDocumentDefinition,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
pollMs?: number;
} = {},
): CultMeshDocumentHandle<unknown> {
return typeof schemaOrDefinition === "string"
? cultMeshDocumentFromSingleFile(path, schemaOrDefinition, options)
: cultMeshDocumentFromSingleFile(path, schemaOrDefinition, options);
}
public static documentFromPeerSnapshot(
peer: CultNetPeer | (() => CultNetPeer | Promise<CultNetPeer>),
schemaId: string,
recordKey: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
},
): CultMeshDocumentHandle<unknown>;
public static documentFromPeerSnapshot<TDefinition extends AnyCultCacheDocumentDefinition>(
peer: CultNetPeer | (() => CultNetPeer | Promise<CultNetPeer>),
definition: TDefinition,
recordKey: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>>;
public static documentFromPeerSnapshot(
peer: CultNetPeer | (() => CultNetPeer | Promise<CultNetPeer>),
schemaOrDefinition: string | AnyCultCacheDocumentDefinition,
recordKey: string,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
} = {},
): CultMeshDocumentHandle<unknown> {
return typeof schemaOrDefinition === "string"
? cultMeshDocumentFromPeerSnapshot(peer, schemaOrDefinition, recordKey, options)
: cultMeshDocumentFromPeerSnapshot(peer, schemaOrDefinition, recordKey, options);
}
public static documentFromPublication(
source: CultMeshDocumentPublicationSource,
schemaId: string,
recordKey: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
},
): CultMeshDocumentHandle<unknown>;
public static documentFromPublication<TDefinition extends AnyCultCacheDocumentDefinition>(
source: CultMeshDocumentPublicationSource,
definition: TDefinition,
recordKey: string,
options?: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>>;
public static documentFromPublication(
source: CultMeshDocumentPublicationSource,
schemaOrDefinition: string | AnyCultCacheDocumentDefinition,
recordKey: string,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
sourceId?: string;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
} = {},
): CultMeshDocumentHandle<unknown> {
return typeof schemaOrDefinition === "string"
? cultMeshDocumentFromPublication(source, schemaOrDefinition, recordKey, options)
: cultMeshDocumentFromPublication(source, schemaOrDefinition, recordKey, options);
}
public static globalDocumentFromCache<TDefinition extends AnyCultCacheDocumentDefinition>(
cache: CultCache,
definition: TDefinition,
options: {
documentId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshDocumentHandle<CultCacheDocumentValue<TDefinition>> {
return cultMeshGlobalDocumentFromCache(cache, definition, options);
}
public static reactiveDocument<TDefinition extends AnyCultCacheDocumentDefinition>(
node: CultMeshNode,
definition: TDefinition,
key: string,
options: {
context?: CultMeshQueryContext | string;
watch?: boolean;
debounceMs?: number;
onError?: (error: unknown) => void;
documentId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshReactiveDocument<CultCacheDocumentValue<TDefinition> & object> {
return node.reactiveDocument(definition, key, options);
}
public static syncDocumentFromPeerSnapshot<TDefinition extends AnyCultCacheDocumentDefinition>(
node: CultMeshNode,
peer: CultNetPeer | (() => CultNetPeer | Promise<CultNetPeer>),
definition: TDefinition,
key: string,
options: {
timeoutMs?: number;
messageIdPrefix?: string;
} = {},
): Promise<CultCacheDocumentValue<TDefinition>> {
return node.syncDocumentFromPeerSnapshot(peer, definition, key, options);
}
public static syncDocumentFromPublication<TDefinition extends AnyCultCacheDocumentDefinition>(
node: CultMeshNode,
source: CultMeshDocumentPublicationSource,
definition: TDefinition,
key: string,
options: {
timeoutMs?: number;
messageIdPrefix?: string;
} = {},
): Promise<CultCacheDocumentValue<TDefinition>> {
return node.syncDocumentFromPublication(source, definition, key, options);
}
public static syncDocumentsFromPublication(
node: CultMeshNode,
source: CultMeshDocumentPublicationSourceResolver,
bindings: readonly CultMeshPublicationDocumentBinding[],
options: {
routeHint?: CultMeshRouteHint;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
} = {},
): Promise<CultMeshDocumentCatalog> {
return node.syncDocumentsFromPublication(source, bindings, options);
}
public static documents(
...documents: readonly CultMeshDocumentHandle<any>[]
): CultMeshDocumentCatalog {
return cultMeshDocuments(...documents);
}
public static publicationDocument(
schema: string | AnyCultCacheDocumentDefinition,
recordKey: string,
options: {
documentId?: string;
sourceId?: string;
source?: CultMeshDocumentPublicationSource;
} = {},
): CultMeshPublicationDocumentBinding {
return cultMeshPublicationDocument(schema, recordKey, options);
}
public static documentsFromPublication(
source: CultMeshDocumentPublicationSourceResolver,
bindings: readonly CultMeshPublicationDocumentBinding[],
options: {
routeHint?: CultMeshRouteHint;
timeoutMs?: number;
pollMs?: number;
messageIdPrefix?: string;
} = {},
): CultMeshDocumentCatalog {
return cultMeshDocumentsFromPublication(source, bindings, options);
}
public static bindDocument<TDocument>(
verse: CultMeshVerseContext | CultMeshVerse,
document: CultMeshDocumentHandle<TDocument>,
): CultMeshBoundDocumentHandle<TDocument> {
return cultMeshBindDocument(verse, document);
}
public static collection<TDocument>(
collectionId: string,
schema: CultMeshDocumentSchemaDescriptor,
snapshotCollection: (context: CultMeshQueryContext) => Promise<CultMeshCollectionSnapshot<TDocument>>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
watchCollection?: CultMeshCollectionWatcher<TDocument>;
} = {},
): CultMeshCollectionHandle<TDocument> {
return cultMeshCollection(collectionId, schema, snapshotCollection, options);
}
public static collections(
...collections: readonly CultMeshCollectionHandle<any>[]
): CultMeshCollectionCatalog {
return cultMeshCollections(...collections);
}
public static collectionFromCache<TDefinition extends AnyCultCacheDocumentDefinition>(
cache: CultCache,
definition: TDefinition,
options: {
collectionId?: string;
routeHint?: CultMeshRouteHint;
pollMs?: number;
} = {},
): CultMeshCollectionHandle<CultCacheDocumentValue<TDefinition>> {
return cultMeshCollectionFromCache(cache, definition, options);
}
public static bindCollection<TDocument>(
verse: CultMeshVerseContext | CultMeshVerse,
collection: CultMeshCollectionHandle<TDocument>,
): CultMeshBoundCollectionHandle<TDocument> {
return cultMeshBindCollection(verse, collection);
}
public static describeLiveFeed(
feed: {
readonly feedId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
},
): CultMeshLiveFeedDiagnostic {
return cultMeshDescribeLiveFeed(feed);
}
public static pollingQueryWatcher<TParameters, TResult>(
executeQuery: (
parameters: TParameters,
context: CultMeshQueryContext,
) => Promise<TResult>,
options: CultMeshPollingWatchOptions<TResult> = {},
): CultMeshQueryWatcher<TParameters, TResult> {
return cultMeshPollingQueryWatcher(executeQuery, options);
}
public static statePointer<T>(
pointerId: string,
resolvePointer: (() => Promise<T | undefined>) | CultMeshStatePointerResolver<T>,
watchPointer?:
| ((callback: (value: T) => void) => CultMeshUnsubscribe)
| CultMeshStatePointerWatcher<T>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
} = {},
): CultMeshStatePointer<T> {
return cultMeshStatePointer(pointerId, resolvePointer, watchPointer, options);
}
public static mutableStatePointer<T>(
pointerId: string,
resolvePointer: (() => Promise<T | undefined>) | CultMeshStatePointerResolver<T>,
watchPointer:
| ((callback: (value: T) => void) => CultMeshUnsubscribe)
| CultMeshStatePointerWatcher<T>,
replacePointer:
| ((value: T) => Promise<void>)
| CultMeshMutableStatePointerReplacer<T>,
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
} = {},
): CultMeshMutableStatePointer<T> {
return cultMeshMutableStatePointer(
pointerId,
resolvePointer,
watchPointer,
replacePointer,
options,
);
}
public static bindStatePointer<T>(
verse: CultMeshVerseContext | CultMeshVerse,
pointer: CultMeshStatePointer<T>,
): CultMeshBoundStatePointer<T> {
return cultMeshBindStatePointer(verse, pointer);
}
public static bindMutableStatePointer<T>(
verse: CultMeshVerseContext | CultMeshVerse,
pointer: CultMeshMutableStatePointer<T>,
): CultMeshBoundMutableStatePointer<T> {
return cultMeshBindMutableStatePointer(verse, pointer);
}
public static projectionSource(
sourceId: string,
options: { schemaId?: string; description?: string } = {},
): CultMeshProjectionSource {
return cultMeshProjectionSource(sourceId, options);
}
public static stateRefResolver(
resolverId: string,
resolveRef:
| ((stateRef: string) => string | undefined)
| ((stateRef: string, context: CultMeshQueryContext) => string | undefined),
options: {
sources?: readonly CultMeshProjectionSource[];
routeHint?: CultMeshRouteHint;
} = {},
): CultMeshStateRefResolver {
return cultMeshStateRefResolver(resolverId, resolveRef, options);
}
public static describeStateRefResolver(
resolver: CultMeshStateRefResolver,
): CultMeshStateRefResolverDiagnostic {
return cultMeshDescribeStateRefResolver(resolver);
}
public static stateBinding(
targetProp: string,
pointer:
| {
readonly pointerId: string;
readonly routeHint?: CultMeshRouteHint;
readonly sources?: readonly CultMeshProjectionSource[];
}
| string,
options: {
sourceId?: string;
schemaId?: string;
routeHint?: CultMeshRouteHint;
} = {},
): CultMeshStateBindingDescriptor {
return cultMeshStateBinding(targetProp, pointer, options);
}
public static stateBindingRecord(
binding?: Partial<CultMeshStateBindingDescriptor>,
): CultMeshStateBindingRecord;
public static stateBindingRecord(
targetProp?: string,
pointerId?: string,
sourceId?: string,
schemaId?: string,
routeKind?: string,
routeDescription?: string,
): CultMeshStateBindingRecord;
public static stateBindingRecord(
bindingOrTargetProp?: Partial<CultMeshStateBindingDescriptor> | string,
pointerId?: string,
sourceId?: string,
schemaId?: string,
routeKind?: string,
routeDescription?: string,
): CultMeshStateBindingRecord {
return typeof bindingOrTargetProp === "object" || bindingOrTargetProp === undefined
? cultMeshStateBindingRecord(bindingOrTargetProp)
: cultMeshStateBindingRecord(
bindingOrTargetProp,
pointerId,
sourceId,
schemaId,
routeKind,
routeDescription,
);
}
public static stateBindingFromRecord(
record: Partial<CultMeshStateBindingRecord>,
options: {
fallbackRouteHint?: CultMeshRouteHint;
fallbackTargetProp?: string;
} = {},
): CultMeshStateBindingDescriptor {
return cultMeshStateBindingFromRecord(record, options);
}
public static operationBinding(
operation:
| {
readonly operationId: string;
}
| string,
options: {
label?: string;
schemaId?: string;
routeHint?: CultMeshRouteHint;
} = {},
): CultMeshOperationBindingDescriptor {
return cultMeshOperationBinding(operation, options);
}
public static operationBindingRecord(
binding?: Partial<CultMeshOperationBindingDescriptor>,
): CultMeshOperationBindingRecord;
public static operationBindingRecord(
operationId?: string,
label?: string,
schemaId?: string,
routeKind?: string,
routeDescription?: string,
): CultMeshOperationBindingRecord;
public static operationBindingRecord(
bindingOrOperationId?: Partial<CultMeshOperationBindingDescriptor> | string,
label?: string,
schemaId?: string,
routeKind?: string,
routeDescription?: string,
): CultMeshOperationBindingRecord {
return typeof bindingOrOperationId === "object" || bindingOrOperationId === undefined
? cultMeshOperationBindingRecord(bindingOrOperationId)
: cultMeshOperationBindingRecord(
bindingOrOperationId,
label,
schemaId,
routeKind,
routeDescription,
);
}
public static operationBindingFromRecord(
record: Partial<CultMeshOperationBindingRecord>,
options: {
fallbackRouteHint?: CultMeshRouteHint;
fallbackOperationId?: string;
} = {},
): CultMeshOperationBindingDescriptor {
return cultMeshOperationBindingFromRecord(record, options);
}
public static operationInvocation(
operation:
| {
readonly operationId: string;
readonly schemaId?: string;
readonly routeHint?: CultMeshRouteHint;
}
| string,
options: {
schemaId?: string;
routeHint?: CultMeshRouteHint;
idempotencyKey?: string;
} = {},
): CultMeshOperationInvocationDescriptor {
return cultMeshOperationInvocation(operation, options);
}
public static operationInvocationRecord(
invocation?: CultMeshOperationInvocationDescriptor,
options: {
fallbackOperationId?: string;
fallbackSchemaId?: string;
fallbackRouteHint?: CultMeshRouteHint;
fallbackIdempotencyKey?: string;
} = {},
): CultMeshOperationInvocationRecord {
return cultMeshOperationInvocationRecord(invocation, options);
}
public static operationInvocationFromRecord(
record: Partial<CultMeshOperationInvocationRecord>,
options: {
fallbackOperationId?: string;
fallbackSchemaId?: string;
fallbackRouteHint?: CultMeshRouteHint;
fallbackIdempotencyKey?: string;
} = {},
): CultMeshOperationInvocationDescriptor {
return cultMeshOperationInvocationFromRecord(record, options);
}
public static operationPayload(
fields: Readonly<Record<string, string | number | boolean | undefined>> = {},
): CultMeshOperationPayload {
return cultMeshOperationPayload(fields);
}
public static projectionRecipe<TParameters, TResult>(
projectionId: string,
sources: readonly CultMeshProjectionSource[],
projectProjection: (
parameters: TParameters,
context: CultMeshQueryContext,
) => Promise<TResult>,
options: {
routeHint?: CultMeshRouteHint;
watchProjection?: (
parameters: TParameters,
context: CultMeshQueryContext,
callback: (value: TResult) => void,
) => CultMeshUnsubscribe;
} = {},
): CultMeshProjectionRecipe<TParameters, TResult> {
return cultMeshProjectionRecipe(
projectionId,
sources,
projectProjection,
options,
);
}
public static describeProjectionRecipe(
recipe: {
readonly projectionId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
},
): CultMeshProjectionRecipeDiagnostic {
return cultMeshDescribeProjectionRecipe(recipe);
}
public static describeSurface(
surface:
| {
readonly queryId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
| {
readonly operationId: string;
}
| {
readonly projectionId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
| {
readonly feedId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
| {
readonly pointerId: string;
}
| {
readonly documentId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
| {
readonly collectionId: string;
readonly routeHint: CultMeshRouteHint;
readonly sources: readonly CultMeshProjectionSource[];
}
| CultMeshNativeSliceViewDescriptor,
): CultMeshSurfaceDiagnostic {
return cultMeshDescribeSurface(surface);
}
public static surfaceDiagnostic(
kind: CultMeshSurfaceKind,
surfaceId: string,
options: {
routeHint?: CultMeshRouteHint;
sources?: readonly CultMeshProjectionSource[];
} = {},
): CultMeshSurfaceDiagnostic {
return cultMeshSurfaceDiagnostic(kind, surfaceId, options);
}
public static describeSurfaceCatalog(
catalogId: string,
surfaces: readonly CultMeshSurfaceDiagnostic[],
): CultMeshSurfaceCatalogDiagnostic {
return cultMeshDescribeSurfaceCatalog(catalogId, surfaces);
}
public static findSurface(
catalog: CultMeshSurfaceCatalogDiagnostic,
surfaceId: string,
): CultMeshSurfaceDiagnostic | undefined {
return cultMeshFindSurface(catalog, surfaceId);
}
public static surfacesByKind(
catalog: CultMeshSurfaceCatalogDiagnostic,
kind: CultMeshSurfaceKind,
): CultMeshSurfaceDiagnostic[] {
return cultMeshSurfacesByKind(catalog, kind);
}
public static surfaceCatalogIndex(
catalog: CultMeshSurfaceCatalogDiagnostic,
): CultMeshSurfaceCatalogIndexDiagnostic {
return cultMeshSurfaceCatalogIndex(catalog);
}
public static nativeSliceColumn(
name: string,
valueType: string,
elementSizeBytes: number,
): CultMeshNativeSliceColumn {
return cultMeshNativeSliceColumn(name, valueType, elementSizeBytes);
}
public static nativeSliceView(
viewId: string,
schemaId: string,
rowCount: number,
columns: readonly CultMeshNativeSliceColumn[],
options: { route?: CultMeshRouteHint; nativeHandle?: string } = {},
): CultMeshNativeSliceViewDescriptor {
return cultMeshNativeSliceView(viewId, schemaId, rowCount, columns, options);
}
public static denseRowStrideBytes(view: CultMeshNativeSliceViewDescriptor): number {
return cultMeshDenseRowStrideBytes(view);
}
public static findNativeSliceColumn(
view: CultMeshNativeSliceViewDescriptor,
name: string,
): CultMeshNativeSliceColumn | undefined {
return cultMeshFindNativeSliceColumn(view, name);
}
public static describeNativeSliceView(
view: CultMeshNativeSliceViewDescriptor,
): CultMeshNativeSliceViewDiagnostic {
return cultMeshDescribeNativeSliceView(view);
}
public static async createNode(
cachePath: string,
options: CultMeshNodeOptions = {},
): Promise<CultMeshNode> {
requireNonEmpty(cachePath, "cachePath");
const store =
options.store ?? new SingleFileMessagePackBackingStore(cachePath);
const cache = CultCache.builder().withGenericStore(store).build();
const documents = new CultNetDocumentRegistry(options.bindings);
for (const definition of options.documents ?? []) {
cache.registerDocumentType(definition);
if (!documents.get(definition.type)) {
documents.register(defineCultNetDocumentBinding({ definition }));
}
}
if (options.pullOnStart !== false) {
await cache.pullAllBackingStores();
}
return new CultMeshNode(cache, store, documents);
}
public static startNode(
cachePath: string,
options: CultMeshNodeOptions = {},
): Promise<CultMeshNode> {
return CultMesh.createNode(cachePath, options);
}
public static createVerseCatalog<TVerse = unknown>(): CultMeshVerseCatalog<TVerse> {
return new CultMeshVerseCatalog<TVerse>();
}
public static createPeerCatalog(): CultMeshPeerCatalog {
return new CultMeshPeerCatalog();
}
public static createAuthorityLeaseCatalog(): CultMeshAuthorityLeaseCatalog {
return new CultMeshAuthorityLeaseCatalog();
}
public static createStreamCatalog(): CultMeshStreamCatalog {
return new CultMeshStreamCatalog();
}
public static createSchemaCatalog(): CultNetSchemaCatalog {
return new CultNetSchemaCatalog();
}
public static createBuiltInSchemaCatalog(
options: CultNetSchemaCatalogOptions = {},
): CultNetSchemaCatalog {
const catalog = new CultNetSchemaCatalog();
catalog.applyResponse(cultNetBuiltinSchemaRegistry.createCatalogResponse({
schemaVersion: "cultnet.schema_catalog_request.v0",
messageId: "cultmesh-ts-builtins",
includeSchemaJson: options.includeSchemaJson,
schemaIds: options.schemaIds ? [...options.schemaIds] : undefined,
kinds: options.kinds ? [...options.kinds] : undefined,
}));
return catalog;
}
public static createShardCatalog(): CultNetShardCatalog {
return new CultNetShardCatalog();
}
public static createRudpDocumentServer(
runtimeId: string,
connectionId: number,
options: CultMeshRudpDocumentServerOptions,
): CultMeshRudpDocumentServer {
requireNonEmpty(runtimeId, "runtimeId");
if (!options.documents) {
throw new Error("CultMesh RUDP document server requires a document registry.");
}
const host = options.bindHost ?? "127.0.0.1";
const port = options.bindPort ?? 0;
const bind = { host, port };
const socket = options.socket ?? createSocket(host.includes(":") ? "udp6" : "udp4");
const sessions = new Map<string, {
sessionId: string;
remote: { address: string; family: string; port: number };
session: CultNetRudpSession;
work: Promise<void>;
}>();
let sessionSequence = 0;
const resendPollMs = Math.max(10, options.resendPollMs ?? 25);
const sessionTimeoutMs = Math.max(1_000, options.sessionTimeoutMs ?? 30_000);
const wireContract = options.wireContract ?? "cultnet.schema.v0";
let resendTimer: NodeJS.Timeout | undefined;
function reportError(error: unknown): void {
const normalized = error instanceof Error ? error : new Error(String(error));
if (options.onError) {
options.onError(normalized);
return;
}
console.error(`CultMesh RUDP document server error: ${normalized.message}`);
}
socket.on("message", (wire, remote) => {
try {
const packet = decodeRudpPacket(wire);
const key = `${remote.address}:${remote.port}`;
const nowMs = Date.now();
let record = sessions.get(key);
if (packet.packetType === "connect") {
if (record) notifySessionClosed(record);
record = {
sessionId: `${runtimeId}:${++sessionSequence}`,
remote: { address: remote.address, family: remote.family, port: remote.port },
session: new CultNetRudpSession({
connectionId,
initialSequence: options.initialSequence,
resendDelayMs: options.resendDelayMs,
maxPendingReliablePackets: options.maxPendingReliablePackets,
}),
work: Promise.resolve(),
};
sessions.set(key, record);
socket.send(encodeRudpPacket(record.session.acceptConnect(packet, nowMs)), remote.port, remote.address);
return;
}
if (!record) {
return;
}
const result = record.session.receive(packet, nowMs);
if (result.reply) {
socket.send(encodeRudpPacket(result.reply), record.remote.port, record.remote.address);
}
for (const frame of result.delivered) {
if (frame.channelId !== "schema") {
continue;
}
record.work = record.work
.then(() => handleDocumentServerFrame(record, frame.payload))
.catch(reportError);
}
if (result.disconnected) {
sessions.delete(key);
notifySessionClosed(record);
return;
}
if (packet.packetType === "accept" || result.delivered.length > 0) {
socket.send(encodeRudpPacket(record.session.createAck()), record.remote.port, record.remote.address);
}
} catch (error) {
reportError(error);
}
});
socket.on("error", reportError);
async function handleDocumentServerFrame(
record: { sessionId: string; remote: { address: string; family: string; port: number }; session: CultNetRudpSession },
payload: Uint8Array,
): Promise<void> {
const message = parseCultNetMessage(decode(payload), wireContract);
switch (message.schemaVersion) {
case "cultnet.snapshot_request.v0": {
if (!options.getCache) {
sendSchemaMessage(record, {
schemaVersion: "cultnet.error.v0",
error: "CultMesh RUDP document server has no cache for snapshot requests.",
});
return;
}
try {
const cache = await options.getCache();
sendSchemaMessage(record, options.documents.createRawSnapshotResponse(cache, message.messageId, message));
} catch (error) {
sendSchemaMessage(record, {
schemaVersion: "cultnet.error.v0",
error: error instanceof Error ? error.message : String(error),
});
}
return;
}
case "cultnet.schema_catalog_request.v0":
sendSchemaMessage(record, cultNetBuiltinSchemaRegistry.createCatalogResponse(message));
return;
case "cultnet.document_put_raw.v0":
if (options.onDocumentPutRaw) {
await options.onDocumentPutRaw(normalizeRudpDocumentPut(message, record.remote));
}
return;
case "cultnet.operation_request.v0":
if (!options.onOperationRequest) {
sendSchemaMessage(record, {
schemaVersion: "cultnet.error.v0",
error: "CultMesh RUDP document server has no operation handler.",
});
return;
}
sendSchemaMessage(record, await options.onOperationRequest(message, publicSession(record)));
return;
default:
sendSchemaMessage(record, {
schemaVersion: "cultnet.error.v0",
error: `Unsupported CultMesh RUDP document request ${message.schemaVersion}.`,
});
}
}
function publicSession(record: {
sessionId: string;
remote: { address: string; family: string; port: number };
session: CultNetRudpSession;
}): CultMeshRudpServerSession {
return {
sessionId: record.sessionId,
remote: { ...record.remote },
send: message => sendSchemaMessage(record, message),
};
}
function notifySessionClosed(record: {
sessionId: string;
remote: { address: string; family: string; port: number };
session: CultNetRudpSession;
}): void {
if (!options.onSessionClosed) return;
void Promise.resolve(options.onSessionClosed(publicSession(record))).catch(reportError);
}
function sendSchemaMessage(
record: { remote: { address: string; port: number }; session: CultNetRudpSession },
message: CultNetMessage,
): void {
const payload = encode(encodeCultNetMessageForWire(message, wireContract));
for (const packet of record.session.sendMany("schema", payload, {
reliable: true,
ordered: true,
nowMs: Date.now(),
})) {
socket.send(encodeRudpPacket(packet), record.remote.port, record.remote.address);
}
}
return {
bind,
async start(): Promise<void> {
await new Promise<void>((resolve, reject) => {
socket.once("error", reject);
socket.bind(port, host, () => {
socket.off("error", reject);
const address = socket.address();
if (typeof address === "object") {
bind.port = address.port;
}
resolve();
});
});
resendTimer = setInterval(() => {
const nowMs = Date.now();
for (const [key, record] of sessions) {
if (record.session.checkTimeout(nowMs, sessionTimeoutMs)) {
sessions.delete(key);
notifySessionClosed(record);
continue;
}
for (const packet of record.session.dueResends(nowMs)) {
socket.send(encodeRudpPacket(packet), record.remote.port, record.remote.address);
}
}
}, resendPollMs);
resendTimer.unref?.();
},
close(): void {
if (resendTimer) {
clearInterval(resendTimer);
resendTimer = undefined;
}
for (const record of sessions.values()) notifySessionClosed(record);
sessions.clear();
socket.close();
},
};
}
public static parseRudpEndpoint(endpoint: string): CultMeshRudpEndpoint {
requireNonEmpty(endpoint, "endpoint");
const parsed = new URL(endpoint);
if (parsed.protocol.toLowerCase() !== "rudp:") {
throw new Error("RUDP endpoint must use the rudp:// scheme.");
}
if (!parsed.hostname || !parsed.port) {
throw new Error("RUDP endpoint must include a host and port.");
}
const port = Number.parseInt(parsed.port, 10);
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
throw new Error("RUDP endpoint port must be between 1 and 65535.");
}
const host = parsed.hostname;
const uriHost =
host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
return { host, port, uri: `rudp://${uriHost}:${port}` };
}
public static async publishRudpDocumentOnce<TDefinition extends AnyCultCacheDocumentDefinition>(
runtimeId: string,
connectionId: number,
endpoint: string | CultMeshRudpEndpoint,
binding: CultNetDocumentBinding<TDefinition>,
recordKey: string,
value: CultCacheDocumentValue<TDefinition>,
options: CultMeshRudpDocumentPublishOptions = {},
): Promise<void> {
requireNonEmpty(runtimeId, "runtimeId");
requireNonEmpty(recordKey, "recordKey");
const registry = new CultNetDocumentRegistry([binding]);
const peer = await CultMesh.createRudpPeer(runtimeId, connectionId, endpoint, options);
try {
peer.send(registry.createRawDocumentPutMessage(
binding,
options.messageId ?? `${runtimeId}:${binding.definition.type}:${recordKey}`,
recordKey,
value,
{
sourceRuntimeId: options.sourceRuntimeId ?? runtimeId,
sourceAgentId: options.sourceAgentId,
sourceRole: options.sourceRole,
tags: options.tags,
},
));
await new Promise((resolve) => setTimeout(resolve, Math.max(0, options.flushTimeoutMs ?? 150)));
} finally {
peer.close();
}
}
public static async createRudpServer(
runtimeId: string,
connectionId: number,
options: CultMeshRudpSocketOptions = {},
): Promise<CultNetRudpSocketTransportConnection> {
requireNonEmpty(runtimeId, "runtimeId");
const socket = options.socket ?? (await bindRudpSocket(options));
return new CultNetRudpSocketTransportConnection({
runtimeId,
socket,
mode: "server",
connectionId,
initialSequence: options.initialSequence,
resendDelayMs: options.resendDelayMs,
resendPollMs: options.resendPollMs,
transportId: options.transportId,
maxPayloadBytes: options.maxPayloadBytes,
maxFragmentBytes: options.maxFragmentBytes,
maxPendingReliablePackets: options.maxPendingReliablePackets,
reconnectPolicy: options.reconnectPolicy,
});
}
public static async createRudpClient(
runtimeId: string,
connectionId: number,
endpoint: string | CultMeshRudpEndpoint,
options: CultMeshRudpSocketOptions = {},
): Promise<CultNetRudpSocketTransportConnection> {
requireNonEmpty(runtimeId, "runtimeId");
const parsedEndpoint =
typeof endpoint === "string" ? CultMesh.parseRudpEndpoint(endpoint) : endpoint;
const socket = options.socket ?? (await bindRudpSocket(options));
return new CultNetRudpSocketTransportConnection({
runtimeId,
socket,
mode: "client",
remoteHost: parsedEndpoint.host,
remotePort: parsedEndpoint.port,
connectionId,
initialSequence: options.initialSequence,
resendDelayMs: options.resendDelayMs,
resendPollMs: options.resendPollMs,
transportId: options.transportId,
maxPayloadBytes: options.maxPayloadBytes,
maxFragmentBytes: options.maxFragmentBytes,
maxPendingReliablePackets: options.maxPendingReliablePackets,
reconnectPolicy: options.reconnectPolicy,
});
}
public static async createRudpClientForPeer(
runtimeId: string,
connectionId: number,
peer: CultMeshPeerCard,
options: CultMeshRudpSocketOptions = {},
): Promise<CultNetRudpSocketTransportConnection> {
const endpoint = peer.endpoints.find((value) =>
value.toLowerCase().startsWith("rudp://"),
);
if (!endpoint) {
throw new Error(`Peer ${peer.peerId} does not advertise a RUDP endpoint.`);
}
return CultMesh.createRudpClient(runtimeId, connectionId, endpoint, options);
}
public static async createRudpClientForAuthorizedPeer(
runtimeId: string,
connectionId: number,
peers: CultMeshPeerCatalog,
leases: CultMeshAuthorityLeaseCatalog,
verseId: string,
role: string,
options: CultMeshAuthorizedRudpSocketOptions = {},
): Promise<CultNetRudpSocketTransportConnection> {
const peer = peers.firstAuthorized(
verseId,
role,
leases,
options.shardId,
options.at,
);
if (!peer) {
throw new Error(`No authorized RUDP peer for role ${role} in Verse ${verseId}.`);
}
return CultMesh.createRudpClientForPeer(runtimeId, connectionId, peer, options);
}
public static async createRudpPeer(
runtimeId: string,
connectionId: number,
endpoint: string | CultMeshRudpEndpoint,
options: CultMeshRudpPeerOptions = {},
): Promise<CultNetPeer> {
const client = await CultMesh.createRudpClient(
runtimeId,
connectionId,
endpoint,
options,
);
client.connect(
options.connectPayload === undefined
? undefined
: Uint8Array.from(options.connectPayload),
);
await waitForRudpConnected(
client,
options.connectTimeoutMs ?? 1_000,
`RUDP peer ${runtimeId}`,
);
return new CultNetPeer(client, {
wireContract: options.wireContract ?? "cultnet.schema.v0",
});
}
public static async createRudpPeerForPeer(
runtimeId: string,
connectionId: number,
peer: CultMeshPeerCard,
options: CultMeshRudpPeerOptions = {},
): Promise<CultNetPeer> {
const endpoint = peer.endpoints.find((value) =>
value.toLowerCase().startsWith("rudp://"),
);
if (!endpoint) {
throw new Error(`Peer ${peer.peerId} does not advertise a RUDP endpoint.`);
}
return CultMesh.createRudpPeer(runtimeId, connectionId, endpoint, options);
}
public static async createRudpPeerForAuthorizedPeer(
runtimeId: string,
connectionId: number,
peers: CultMeshPeerCatalog,
leases: CultMeshAuthorityLeaseCatalog,
verseId: string,
role: string,
options: CultMeshAuthorizedRudpPeerOptions = {},
): Promise<CultNetPeer> {
const peer = peers.firstAuthorized(
verseId,
role,
leases,
options.shardId,
options.at,
);
if (!peer) {
throw new Error(`No authorized RUDP peer for role ${role} in Verse ${verseId}.`);
}
return CultMesh.createRudpPeerForPeer(runtimeId, connectionId, peer, options);
}
}
function cultMeshSchemaFromDefinition(
definition: AnyCultCacheDocumentDefinition,
): CultMeshDocumentSchemaDescriptor {
return {
type: definition.type,
schemaId: definition.schemaId ?? definition.type,
schemaName: definition.schemaName,
schemaVersion: definition.schemaVersion,
};
}
function normalizeCultMeshDocumentSchema(
schema: CultMeshDocumentSchemaDescriptor,
): CultMeshDocumentSchemaDescriptor {
const schemaId = schema.schemaId?.trim() || undefined;
const inferredSchemaName = schema.schemaName?.trim() || inferCultMeshSchemaName(schemaId);
const normalized = {
type: schema.type?.trim() || undefined,
schemaId,
schemaName: inferredSchemaName,
schemaVersion: schema.schemaVersion?.trim() || (inferredSchemaName ? schemaId : undefined),
};
if (!normalized.type && !normalized.schemaId && !normalized.schemaName) {
throw new Error("Document schema must include type, schemaId, or schemaName.");
}
return normalized;
}
function cultMeshSchemaNameVersionKey(
schema: CultMeshDocumentSchemaDescriptor,
): string | undefined {
return schema.schemaName
? `${schema.schemaName}@${schema.schemaVersion ?? ""}`
: undefined;
}
function cultMeshSchemasAreCompatible(
left: CultMeshDocumentSchemaDescriptor,
right: CultMeshDocumentSchemaDescriptor,
): boolean {
if (left.schemaId && right.schemaId && left.schemaId === right.schemaId) {
return true;
}
if (
left.schemaName &&
right.schemaName &&
left.schemaName === right.schemaName &&
(left.schemaVersion ?? "") === (right.schemaVersion ?? "")
) {
return true;
}
return Boolean(left.type && right.type && left.type === right.type);
}
function inferCultMeshSchemaName(schemaId: string | undefined): string | undefined {
if (!schemaId) {
return undefined;
}
const marker = schemaId.lastIndexOf(".v");
if (marker <= 0 || marker + 2 >= schemaId.length) {
return undefined;
}
const version = schemaId.slice(marker + 2);
return /^\d+$/.test(version) ? schemaId.slice(0, marker) : undefined;
}
function resolveCultMeshStoreDocumentRecord(
records: readonly CultCacheEnvelope[],
schema: CultMeshDocumentSchemaDescriptor,
schemaOrDefinition: string | AnyCultCacheDocumentDefinition,
): CultCacheEnvelope | undefined {
const schemaId = schema.schemaId ?? schema.type;
return (
records.find(candidate => schemaId !== undefined && candidate.schemaId === schemaId) ??
records.find(candidate =>
cultMeshEnvelopeMatchesSchema(candidate, schema) &&
(typeof schemaOrDefinition === "string" ||
cultMeshEnvelopeParsesAsDefinition(candidate, schemaOrDefinition))) ??
(typeof schemaOrDefinition === "string"
? undefined
: records.find(candidate => cultMeshEnvelopeParsesAsDefinition(candidate, schemaOrDefinition)))
);
}
function cultMeshEnvelopeMatchesSchema(
envelope: CultCacheEnvelope,
schema: CultMeshDocumentSchemaDescriptor,
): boolean {
const entry = envelope.catalogEntry;
if (!entry) {
return false;
}
if (
schema.schemaId &&
(entry.schemaId === schema.schemaId ||
envelope.schemaId === schema.schemaId ||
entry.compatibleSchemaIds?.includes(schema.schemaId))
) {
return true;
}
return Boolean(
schema.schemaName &&
entry.schemaName === schema.schemaName &&
(schema.schemaVersion ?? "") === (entry.schemaVersion ?? ""),
);
}
function cultMeshEnvelopeParsesAsDefinition(
envelope: CultCacheEnvelope,
definition: AnyCultCacheDocumentDefinition,
): boolean {
try {
definition.schema.parse(decode(envelope.payload));
return true;
} catch {
return false;
}
}
function cultMeshSchemaLabel(schema: CultMeshDocumentSchemaDescriptor): string {
return schema.schemaId ??
cultMeshSchemaNameVersionKey(schema) ??
schema.type ??
"<anonymous-schema>";
}
function requireNonEmpty(value: string, name: string): void {
if (!value || value.trim().length === 0) {
throw new Error(`${name} must be non-empty.`);
}
}
function normalizeRudpDocumentPut(
message: CultNetMessage,
remote: { address: string; family: string; port: number },
): CultMeshRudpDocumentPut {
if (message.schemaVersion !== "cultnet.document_put_raw.v0") {
throw new Error(`Expected cultnet.document_put_raw.v0, received ${message.schemaVersion}.`);
}
const document: CultNetRawDocumentRecord = message.document;
if (!document.schemaId || !document.recordKey) {
throw new Error("Raw document put is missing schemaId or recordKey.");
}
if (document.payloadEncoding !== "messagepack") {
throw new Error(`Unsupported raw payload encoding ${document.payloadEncoding}.`);
}
return {
schemaId: document.schemaId,
recordKey: document.recordKey,
storedAt: document.storedAt ?? new Date().toISOString(),
payload: decode(document.payload),
sourceRuntimeId: document.sourceRuntimeId ?? null,
sourceAgentId: document.sourceAgentId ?? null,
sourceRole: document.sourceRole ?? null,
tags: Array.isArray(document.tags) ? document.tags : [],
remote,
};
}
async function requestCultNetRawSnapshotDocument(
peerOrProvider: CultNetPeer | (() => CultNetPeer | Promise<CultNetPeer>),
schemaId: string,
recordKey: string,
options: {
timeoutMs?: number;
messageIdPrefix?: string;
accepts?: (document: CultNetRawDocumentRecord) => boolean;
} = {},
): Promise<CultNetRawDocumentRecord> {
const peer = await resolveCultNetPeer(peerOrProvider);
const messageId = `${options.messageIdPrefix ?? "cultmesh-snapshot"}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
const response = await requestCultNetRawSnapshot(peer, [recordKey], {
messageId,
timeoutMs: options.timeoutMs,
});
const candidates = response.documents.filter(candidate => candidate.recordKey === recordKey);
const document =
candidates.find(candidate => candidate.schemaId === schemaId) ??
(options.accepts ? candidates.find(options.accepts) : undefined) ??
candidates[0];
if (!document) {
throw new Error(`CultMesh peer snapshot did not return ${schemaId} at ${recordKey}.`);
}
return document;
}
async function requestCultNetRawSnapshot(
peer: CultNetPeer,
recordKeys: readonly string[],
options: {
messageId: string;
timeoutMs?: number;
},
): Promise<CultNetSnapshotResponseRawMessage> {
return new Promise<CultNetSnapshotResponseRawMessage>((resolve, reject) => {
const cleanup = (): void => {
clearTimeout(timer);
peer.off("message", onMessage);
peer.off("invalidMessage", onInvalidMessage);
peer.off("error", onError);
peer.off("close", onClose);
};
const rejectWith = (error: Error): void => {
cleanup();
reject(error);
};
const onMessage = (message: CultNetMessage): void => {
if (isCultNetErrorMessage(message)) {
cleanup();
reject(new Error(message.error));
return;
}
if (message.schemaVersion !== "cultnet.snapshot_response_raw.v0" ||
message.messageId !== options.messageId) {
return;
}
cleanup();
resolve(message);
};
const onInvalidMessage = (error: Error): void => rejectWith(error);
const onError = (error: Error): void => rejectWith(error);
const onClose = (): void => rejectWith(new Error("CultNet peer closed before snapshot response."));
const timer = setTimeout(
() => rejectWith(new Error(`Timed out waiting for CultNet snapshot ${options.messageId}.`)),
options.timeoutMs ?? 4_000,
);
peer.on("message", onMessage);
peer.on("invalidMessage", onInvalidMessage);
peer.on("error", onError);
peer.on("close", onClose);
peer.sendSnapshotRequest({
schemaVersion: "cultnet.snapshot_request.v0",
messageId: options.messageId,
recordKeys: [...recordKeys],
});
});
}
async function resolveCultNetPeer(
peerOrProvider: CultNetPeer | (() => CultNetPeer | Promise<CultNetPeer>),
): Promise<CultNetPeer> {
return typeof peerOrProvider === "function"
? await peerOrProvider()
: peerOrProvider;
}
function decodeCultNetRawDocumentPayload(document: CultNetRawDocumentRecord): unknown {
if (document.payloadEncoding !== "messagepack") {
throw new Error(`Unsupported CultNet raw document payload encoding ${document.payloadEncoding}.`);
}
return decode(toUint8Array(document.payload));
}
function parseCultMeshDocumentValue<TDefinition extends AnyCultCacheDocumentDefinition>(
schemaOrDefinition: string | TDefinition,
value: unknown,
): unknown | CultCacheDocumentValue<TDefinition> {
return typeof schemaOrDefinition === "string"
? value
: schemaOrDefinition.schema.parse(value);
}
function isCultNetErrorMessage(message: CultNetMessage): message is CultNetErrorMessage {
return message.schemaVersion === "cultnet.error.v0";
}
function toUint8Array(value: unknown): Uint8Array {
if (value instanceof Uint8Array) {
return value;
}
if (Array.isArray(value)) {
return Uint8Array.from(value);
}
throw new Error("CultNet raw document payload was not binary.");
}
async function bindRudpSocket(options: CultMeshRudpSocketOptions): Promise<Socket> {
const socket = createSocket("udp4");
const host = options.bindHost ?? "127.0.0.1";
const port = options.bindPort ?? 0;
await new Promise<void>((resolve, reject) => {
socket.once("error", reject);
socket.bind(port, host, () => {
socket.off("error", reject);
resolve();
});
});
return socket;
}
async function waitForRudpConnected(
transport: CultNetRudpSocketTransportConnection,
timeoutMs: number,
description: string,
): Promise<void> {
if (transport.connected) {
return;
}
await new Promise<void>((resolve, reject) => {
let settled = false;
const cleanup = (): void => {
clearInterval(poll);
clearTimeout(timer);
transport.off("error", onError);
transport.off("close", onClose);
};
const finish = (error?: Error): void => {
if (settled) {
return;
}
settled = true;
cleanup();
if (error) {
reject(error);
} else {
resolve();
}
};
const check = (): void => {
if (transport.connected) {
finish();
}
};
const onError = (error: Error): void => finish(error);
const onClose = (): void =>
finish(new Error(`${description} closed before handshake completed.`));
const poll = setInterval(check, 5);
const timer = setTimeout(
() => finish(new Error(`Timed out waiting for ${description} handshake.`)),
timeoutMs,
);
poll.unref?.();
timer.unref?.();
transport.once("error", onError);
transport.once("close", onClose);
check();
});
}
function copyBudgetFor(
transport: CultMeshStreamBodyTransport,
): CultMeshStreamNegotiation["copyBudget"] {
switch (transport) {
case "shared-memory":
case "shared-d3d12-texture":
case "shared-d3d11-texture":
case "dma-buf":
case "iosurface":
case "ahardwarebuffer":
return "zero-copy-target";
case "cultcache-page":
return "one-copy-fallback";
case "inline-bytes":
return "opaque-runtime";
}
}
function nonBlankOr(value?: string, fallback = ""): string {
return value && value.trim() ? value : fallback;
}
function parseCultMeshLocalityKind(
value: string | undefined,
fallback: CultMeshLocalityKind,
): CultMeshLocalityKind {
switch ((value ?? "").trim().toLowerCase()) {
case "automatic":
return "automatic";
case "inprocess":
case "in-process":
return "in-process";
case "sharedmemory":
case "shared-memory":
return "shared-memory";
case "ipc":
return "ipc";
case "network":
return "network";
case "wasm":
return "wasm";
default:
return fallback;
}
}