chore(vendor): import pi 0.85.1

Imported as a linear vendor commit so forge rebases do not replay the upstream root tree.

git-subtree-dir: third_party/pi

git-subtree-split: d981de1229ef899957bbe968bc8dcda02a21f477
This commit is contained in:
2026-09-14 10:48:47 -04:00
parent 5ebc7018c3
commit 4def357d09
1669 changed files with 397627 additions and 0 deletions

View File

@@ -0,0 +1,660 @@
import { awaitWithContext, BACKGROUND_CONTEXT } from "../context/index.ts";
import type { Op } from "../delta/index.ts";
import type {
Context,
JsonValue,
RemoteServiceBinding,
RemoteServiceBindingOptions,
RemoteServiceTransport,
Service,
ServiceInstanceAddress,
ServiceInstanceSnapshot,
ServiceMemberSnapshot,
ServiceProviderUpdate,
ServiceSubscription,
} from "../types.ts";
import { RemoteServiceError } from "./errors.ts";
import { ServiceSlot } from "./handle.ts";
import { InstanceDirectory, type InstanceDirectoryEntry } from "./instances.ts";
import { ReplicatedStateReplica, serviceDeliveryContext } from "./state.ts";
type ErrorReporter = (error: Error) => void;
type ServiceMemberKind = ServiceMemberSnapshot["kind"];
class MemberSlot {
readonly #serviceId: string;
readonly #member: string;
readonly #invoke: (args: readonly JsonValue[], context: Context) => Promise<JsonValue | undefined>;
readonly #state: ReplicatedStateReplica;
readonly #isActive: () => boolean;
readonly #assertAccess: () => void;
readonly value: unknown;
#kind: ServiceMemberKind | undefined;
#expectedKind: ServiceMemberKind | undefined;
constructor(
serviceId: string,
member: string,
invoke: (args: readonly JsonValue[], context: Context) => Promise<JsonValue | undefined>,
isActive: () => boolean,
assertAccess: () => void,
reportError: ErrorReporter,
) {
this.#serviceId = serviceId;
this.#member = member;
this.#invoke = invoke;
this.#isActive = isActive;
this.#assertAccess = assertAccess;
this.#state = new ReplicatedStateReplica(reportError);
const callable = (): void => {};
this.value = new Proxy(callable, {
apply: (_target, _thisArg, args) => this.#call(args),
get: (_target, property) => {
if (property === "value") {
this.#assertAccess();
this.#expect("state");
return this.#state.value;
}
if (property === "subscribe") return this.#subscribe.bind(this);
if (property === Symbol.toStringTag) return "RemoteServiceMember";
if (property === "then") return undefined;
return undefined;
},
});
}
setDescription(kind: ServiceMemberKind): void {
if (this.#kind !== undefined && this.#kind !== kind) {
throw new Error(`Remote service member ${this.#serviceId}.${this.#member} changed kind`);
}
this.#kind = kind;
if (this.#expectedKind !== undefined && this.#expectedKind !== kind) {
throw new RemoteServiceError(
"service_member_mismatch",
`Remote service member ${this.#serviceId}.${this.#member} is ${kind}, not ${this.#expectedKind}`,
);
}
}
hydrate(sequence: number, ops: readonly Op[], context: Context): void {
this.setDescription("state");
this.#state.hydrate(sequence, ops, context);
}
update(sequence: number, ops: readonly Op[], context: Context): void {
this.setDescription("state");
this.#state.update(sequence, ops, context);
}
clear(): void {
this.#state.clear();
}
#subscribe(listener: (value: JsonValue, context: Context) => void): () => void {
this.#assertAccess();
if (typeof listener !== "function") {
throw new TypeError("Replicated state subscription listener must be a function");
}
this.#expect("state");
return this.#state.subscribe(listener);
}
#expect(kind: ServiceMemberKind): void {
if (this.#expectedKind !== undefined && this.#expectedKind !== kind) {
throw new RemoteServiceError(
"service_member_mismatch",
`Remote service member ${this.#serviceId}.${this.#member} was used as two different kinds`,
);
}
this.#expectedKind = kind;
if (this.#kind !== undefined && this.#kind !== kind) {
throw new RemoteServiceError(
"service_member_mismatch",
`Remote service member ${this.#serviceId}.${this.#member} is ${this.#kind}, not ${kind}`,
);
}
}
#call(args: unknown[]): Promise<JsonValue | undefined> {
this.#assertAccess();
this.#expect("method");
if (!this.#isActive()) {
return Promise.reject(
new RemoteServiceError("service_stale_instance", `Remote service ${this.#serviceId} binding is closed`),
);
}
const context = args.at(-1);
if (!isContext(context)) {
return Promise.reject(
new RemoteServiceError(
"service_invalid_value",
`Remote service method ${this.#serviceId}.${this.#member} requires a trailing Context`,
),
);
}
const businessArgs = args.slice(0, -1) as JsonValue[];
return this.#invoke(businessArgs, context);
}
}
class ServiceFacade {
readonly #serviceId: string;
readonly #address: ServiceInstanceAddress | undefined;
readonly #transport: RemoteServiceTransport;
readonly #reportError: ErrorReporter;
readonly #slots = new Map<string, MemberSlot>();
readonly #descriptions = new Map<string, ServiceMemberKind>();
readonly #isActive: () => boolean;
readonly #assertAccess: () => void;
readonly proxy: object;
constructor(
serviceId: string,
address: ServiceInstanceAddress | undefined,
transport: RemoteServiceTransport,
isActive: () => boolean,
assertAccess: () => void,
reportError: ErrorReporter,
) {
this.#serviceId = serviceId;
this.#address = address;
this.#transport = transport;
this.#isActive = isActive;
this.#assertAccess = assertAccess;
this.#reportError = reportError;
this.proxy = new Proxy(Object.create(null) as object, {
get: (_target, property) => {
if (typeof property !== "string") return undefined;
return this.#slot(property).value;
},
});
}
install(snapshot: ServiceInstanceSnapshot, context: Context): void {
if (!sameAddress(snapshot.instance, this.#address))
throw new Error("Remote service snapshot has the wrong address");
const members = validateMembers(snapshot.members);
for (const name of this.#slots.keys()) {
if (!members.has(name)) {
throw new RemoteServiceError(
"service_member_not_found",
`Unknown remote service member ${this.#serviceId}.${name}`,
);
}
}
this.#descriptions.clear();
for (const [name, member] of members) this.#descriptions.set(name, member.kind);
for (const member of members.values()) {
const slot = this.#slots.get(member.name);
if (member.kind === "state") {
(slot ?? this.#slot(member.name)).hydrate(member.sequence, member.ops, context);
} else {
slot?.setDescription(member.kind);
}
}
}
update(member: string, sequence: number, ops: readonly Op[], context: Context): void {
if (this.#descriptions.get(member) !== "state") {
throw new Error(`Remote service update targets non-state member ${this.#serviceId}.${member}`);
}
this.#slot(member).update(sequence, ops, context);
}
clear(): void {
for (const slot of this.#slots.values()) slot.clear();
}
#slot(member: string): MemberSlot {
let slot = this.#slots.get(member);
if (slot !== undefined) return slot;
slot = new MemberSlot(
this.#serviceId,
member,
(args, context) =>
this.#transport.invoke(
{
serviceId: this.#serviceId,
...(this.#address === undefined ? {} : { instance: this.#address }),
member,
args,
},
context,
),
this.#isActive,
this.#assertAccess,
this.#reportError,
);
const kind = this.#descriptions.get(member);
if (kind !== undefined) slot.setDescription(kind);
this.#slots.set(member, slot);
return slot;
}
}
interface SingletonBinding {
facade: ServiceFacade;
subscription?: ServiceSubscription;
starting?: Promise<void>;
active: boolean;
revision: number;
}
interface KeyedInstance extends InstanceDirectoryEntry {
readonly facade: ServiceFacade;
}
class KeyedBinding<T> {
readonly #service: Service<T>;
readonly #transport: RemoteServiceTransport;
readonly #reportError: ErrorReporter;
readonly #assertAccess: () => void;
readonly #onEmpty: () => void;
readonly #instances: InstanceDirectory<KeyedInstance>;
#subscription: ServiceSubscription | undefined;
#starting: Promise<void> | undefined;
#closed = false;
#bound: boolean;
#revision = 0;
constructor(
service: Service<T>,
transport: RemoteServiceTransport,
reportError: ErrorReporter,
assertAccess: () => void,
onEmpty: () => void,
bound: boolean,
) {
this.#service = service;
this.#transport = transport;
this.#reportError = reportError;
this.#assertAccess = assertAccess;
this.#onEmpty = onEmpty;
this.#bound = bound;
this.#instances = new InstanceDirectory({ ready: false, onError: reportError });
}
observe(handler: (service: T, context: Context) => void | Promise<void>): () => void {
if (this.#closed) throw new Error("Remote keyed service binding is closed");
let stopped = false;
const stop = this.#instances.observe<T>((service, context) => {
const slot = new ServiceSlot(this.#service.id, true);
void slot.bind(service as object);
return handler(
slot.view<T>(() => {
this.#assertAccess();
if (stopped || context.abortSignal?.aborted) {
throw new RemoteServiceError(
"service_stale_instance",
`Remote service ${this.#service.id} observation is closed`,
);
}
}),
context,
);
});
if (this.#bound && this.#starting === undefined) {
const revision = this.#revision;
const starting = this.#start(revision);
this.#starting = starting;
void starting.catch((error: unknown) => {
if (!this.#closed && this.#revision === revision && this.#bound) this.#reportError(toError(error));
});
}
return () => {
if (stopped) return;
stopped = true;
stop();
if (this.#instances.observerCount === 0) this.#onEmpty();
};
}
async rebind(bound: boolean, context: Context): Promise<void> {
if (this.#closed) return;
this.#bound = bound;
const revision = ++this.#revision;
await this.#reset(context, false);
if (this.#closed || this.#revision !== revision || this.#bound !== bound) return;
if (bound && this.#instances.observerCount > 0) {
const starting = this.#start(revision);
this.#starting = starting;
await starting;
}
}
ready(): Promise<void> {
return this.#starting ?? Promise.resolve();
}
async close(context: Context): Promise<void> {
if (this.#closed) return;
this.#closed = true;
this.#revision += 1;
await this.#reset(context, true);
this.#instances.dispose();
}
async #reset(context: Context, waitForStarting: boolean): Promise<void> {
this.#instances.reset();
const starting = this.#starting;
this.#starting = undefined;
const subscription = this.#subscription;
this.#subscription = undefined;
await Promise.all([
waitForStarting ? starting?.catch(() => {}) : undefined,
subscription === undefined ? undefined : subscription.close(context),
]);
}
async #start(revision: number): Promise<void> {
const subscription = await this.#transport.subscribe(
this.#service.id,
"keyed",
(update, context) => {
if (this.#revision === revision) this.#update(update, context);
},
BACKGROUND_CONTEXT,
);
if (this.#closed || !this.#bound || this.#revision !== revision) {
await subscription.close(BACKGROUND_CONTEXT);
return;
}
this.#subscription = subscription;
if (subscription.snapshot.mode !== "keyed" || subscription.snapshot.serviceId !== this.#service.id) {
throw new Error(`Remote service ${this.#service.id} returned the wrong keyed snapshot`);
}
for (const snapshot of subscription.snapshot.instances) this.#spawn(snapshot, serviceDeliveryContext());
subscription.activate();
this.#instances.ready();
}
#update(update: ServiceProviderUpdate, context: Context): void {
if (this.#closed) return;
try {
switch (update.type) {
case "unavailable":
case "replaced":
throw new Error("Keyed service received a singleton lifecycle update");
case "spawned":
this.#spawn(update.instance, context);
break;
case "closed": {
const instance = this.#instances.get(update.instance.key);
if (instance?.generation === update.instance.generation) this.#instances.remove(instance);
break;
}
case "state": {
if (update.instance === undefined) throw new Error("Keyed state update has no instance address");
const instance = this.#instances.get(update.instance.key);
if (instance?.generation !== update.instance.generation) return;
instance.facade.update(update.member, update.sequence, update.ops, context);
break;
}
}
} catch (error) {
this.#reportError(toError(error));
}
}
#spawn(snapshot: ServiceInstanceSnapshot, context: Context): void {
const address = snapshot.instance;
if (address === undefined) throw new Error("Keyed service instance snapshot has no address");
let active = true;
const facade = new ServiceFacade(
this.#service.id,
address,
this.#transport,
() => active && !this.#closed,
this.#assertAccess,
this.#reportError,
);
facade.install(snapshot, context);
this.#instances.replace({
key: address.key,
generation: address.generation,
service: facade.proxy,
facade,
deactivate: () => {
active = false;
facade.clear();
},
});
}
}
export class RemoteServiceBindingImpl implements RemoteServiceBinding {
readonly #transport: RemoteServiceTransport;
readonly #allowlist = new Set<string>();
readonly #reportError: ErrorReporter;
readonly #modes = new Map<string, "singleton" | "keyed">();
readonly #assertAccess: () => void;
readonly #singletons = new Map<string, SingletonBinding>();
readonly #keyed = new Map<string, KeyedBinding<unknown>>();
#bound: boolean;
#readinessRevision = 0;
#bindingTransition = Promise.resolve();
#disposed = false;
constructor(options: RemoteServiceBindingOptions) {
this.#transport = options.transport;
const ids = options.services.map(({ id }) => id);
if (new Set(ids).size !== ids.length) throw new TypeError("Remote service binding has duplicate service IDs");
for (const id of ids) this.#allowlist.add(id);
this.#reportError = options.onError ?? (() => {});
this.#assertAccess = options.assertAccess ?? (() => {});
this.#bound = options.bound ?? true;
}
use<T>(service: Service<T>): T {
this.#assertRemotable(service);
this.#assertAvailable(service.id, "singleton");
let binding = this.#singletons.get(service.id);
if (binding !== undefined) return binding.facade.proxy as T;
binding = { facade: undefined as unknown as ServiceFacade, active: true, revision: 0 };
binding.facade = new ServiceFacade(
service.id,
undefined,
this.#transport,
() => binding!.active && !this.#disposed && this.#bound,
() => this.#assertHandleAccess(),
this.#reportError,
);
this.#singletons.set(service.id, binding);
this.#readinessRevision += 1;
if (this.#bound) {
const revision = binding.revision;
const starting = this.#startSingleton(service.id, binding, revision);
binding.starting = starting;
void starting.catch((error: unknown) => {
if (binding.active && binding.revision === revision && !this.#disposed && this.#bound) {
this.#reportError(toError(error));
}
});
}
return binding.facade.proxy as T;
}
observe<T>(service: Service<T>, handler: (service: T, context: Context) => void | Promise<void>): () => void {
this.#assertRemotable(service);
this.#assertAvailable(service.id, "keyed");
let binding = this.#keyed.get(service.id) as KeyedBinding<T> | undefined;
if (binding === undefined) {
binding = new KeyedBinding(
service,
this.#transport,
this.#reportError,
() => this.#assertHandleAccess(),
() => {
if (this.#keyed.get(service.id) !== binding) return;
this.#keyed.delete(service.id);
this.#readinessRevision += 1;
void binding!.close(BACKGROUND_CONTEXT).catch(this.#reportError);
},
this.#bound,
);
this.#keyed.set(service.id, binding as KeyedBinding<unknown>);
this.#readinessRevision += 1;
}
return binding.observe(handler);
}
async ready(context: Context): Promise<void> {
if (this.#disposed) throw new Error("Remote service binding is disposed");
while (true) {
const revision = this.#readinessRevision;
const starts = [
this.#bindingTransition,
...[...this.#singletons.values()].flatMap((binding) =>
binding.starting === undefined ? [] : [binding.starting],
),
...[...this.#keyed.values()].map((binding) => binding.ready()),
];
await awaitWithContext(
Promise.all(starts).then(() => undefined),
context,
);
if (this.#disposed) throw new Error("Remote service binding is disposed");
if (revision === this.#readinessRevision) return;
}
}
async rebind(bound: boolean, context: Context): Promise<void> {
if (this.#disposed) throw new Error("Remote service binding is disposed");
this.#bound = bound;
this.#readinessRevision += 1;
const transitions: Promise<void>[] = [];
for (const [serviceId, binding] of this.#singletons) {
binding.revision += 1;
binding.facade.clear();
const subscription = binding.subscription;
delete binding.subscription;
const revision = binding.revision;
const starting = (async () => {
await subscription?.close(context);
if (bound) await this.#startSingleton(serviceId, binding, revision);
})();
binding.starting = starting;
transitions.push(starting);
}
for (const binding of this.#keyed.values()) transitions.push(binding.rebind(bound, context));
const completion = (async () => {
const results = await Promise.allSettled(transitions);
const errors = results.filter((result): result is PromiseRejectedResult => result.status === "rejected");
if (errors.length > 0)
throw new AggregateError(
errors.map(({ reason }) => reason),
"Failed to rebind services",
);
})();
this.#bindingTransition = completion;
await completion;
}
async dispose(context: Context): Promise<void> {
if (this.#disposed) return;
this.#disposed = true;
const closes: Promise<void>[] = [];
for (const binding of this.#singletons.values()) {
binding.active = false;
binding.facade.clear();
if (binding.starting) closes.push(binding.starting.catch(() => {}));
if (binding.subscription) closes.push(Promise.resolve(binding.subscription.close(context)));
}
for (const binding of this.#keyed.values()) closes.push(binding.close(context));
this.#singletons.clear();
this.#keyed.clear();
const results = await Promise.allSettled(closes);
const errors = results.filter((result): result is PromiseRejectedResult => result.status === "rejected");
if (errors.length > 0)
throw new AggregateError(
errors.map(({ reason }) => reason),
"Failed to dispose services",
);
}
async #startSingleton(serviceId: string, binding: SingletonBinding, revision: number): Promise<void> {
const subscription = await this.#transport.subscribe(
serviceId,
"singleton",
(update, context) => {
if (!binding.active || binding.revision !== revision) return;
try {
if (update.type === "unavailable") {
binding.facade.clear();
} else if (update.type === "replaced") {
if (update.snapshot.instance !== undefined) {
throw new Error("Singleton replacement has an instance address");
}
binding.facade.install(update.snapshot, context);
} else if (update.type === "state" && update.instance === undefined) {
binding.facade.update(update.member, update.sequence, update.ops, context);
}
} catch (error) {
this.#reportError(toError(error));
}
},
BACKGROUND_CONTEXT,
);
if (!binding.active || this.#disposed || !this.#bound || binding.revision !== revision) {
await subscription.close(BACKGROUND_CONTEXT);
return;
}
binding.subscription = subscription;
const snapshot = subscription.snapshot;
if (snapshot.mode !== "singleton" || snapshot.serviceId !== serviceId || snapshot.instances.length !== 1) {
throw new Error(`Remote service ${serviceId} returned an invalid singleton snapshot`);
}
binding.facade.install(snapshot.instances[0]!, serviceDeliveryContext());
subscription.activate();
}
#assertHandleAccess(): void {
if (this.#disposed) throw new Error("Remote service binding is disposed");
this.#assertAccess();
}
#assertRemotable(service: { readonly id: string; readonly local: boolean }): void {
if (service.local) throw new RemoteServiceError("service_not_allowed", `Service ${service.id} is process-local`);
}
#assertAvailable(serviceId: string, mode: "singleton" | "keyed"): void {
if (this.#disposed) throw new Error("Remote service binding is disposed");
if (!this.#allowlist.has(serviceId)) {
throw new RemoteServiceError("service_not_allowed", `Remote service ${serviceId} is not allowlisted`);
}
const existing = this.#modes.get(serviceId);
if (existing !== undefined && existing !== mode) {
throw new RemoteServiceError(
"service_mode_mismatch",
`Remote service ${serviceId} is already used as ${existing}`,
);
}
this.#modes.set(serviceId, mode);
}
}
function validateMembers(members: readonly ServiceMemberSnapshot[]): ReadonlyMap<string, ServiceMemberSnapshot> {
const result = new Map<string, ServiceMemberSnapshot>();
for (const member of members) {
if (member.name.length === 0 || result.has(member.name)) {
throw new Error("Remote service has invalid member descriptions");
}
result.set(member.name, member);
}
return result;
}
function sameAddress(left: ServiceInstanceAddress | undefined, right: ServiceInstanceAddress | undefined): boolean {
if (left === undefined || right === undefined) return left === right;
return left.key === right.key && left.generation === right.generation;
}
function isContext(value: unknown): value is Context {
if (typeof value !== "object" || value === null) return false;
const candidate = value as Partial<Context>;
return typeof candidate.value === "function" && typeof candidate.toString === "function";
}
function toError(error: unknown): Error {
return error instanceof Error ? error : new Error(String(error));
}

View File

@@ -0,0 +1,26 @@
export const REMOTE_SERVICE_ERROR_CODES = [
"service_not_allowed",
"service_not_found",
"service_mode_mismatch",
"service_member_not_found",
"service_member_mismatch",
"service_instance_not_found",
"service_stale_instance",
"service_invalid_value",
] as const;
export type RemoteServiceErrorCode = (typeof REMOTE_SERVICE_ERROR_CODES)[number];
export function isRemoteServiceErrorCode(value: unknown): value is RemoteServiceErrorCode {
return typeof value === "string" && REMOTE_SERVICE_ERROR_CODES.includes(value as RemoteServiceErrorCode);
}
export class RemoteServiceError extends Error {
readonly code: RemoteServiceErrorCode;
constructor(code: RemoteServiceErrorCode, message: string) {
super(message);
this.name = "RemoteServiceError";
this.code = code;
}
}

View File

@@ -0,0 +1,113 @@
interface ResolvedValue {
readonly value: unknown;
readonly receiver: object;
}
type ValueResolver = () => ResolvedValue;
/** Host-owned mutable target with consumer-owned guarded views. */
export class ServiceSlot {
readonly #serviceId: string;
readonly #wrapObjects: boolean;
#implementation: object | undefined;
constructor(serviceId: string, wrapObjects: boolean) {
this.#serviceId = serviceId;
this.#wrapObjects = wrapObjects;
}
view<T>(assertAccess: () => void): T {
return new ServiceView(this, assertAccess, this.#wrapObjects).proxy as T;
}
bind(implementation: object): void {
this.#implementation = implementation;
}
unbind(): void {
this.#implementation = undefined;
}
resolve(property: PropertyKey, assertAccess: () => void): ResolvedValue {
assertAccess();
const implementation = this.#implementation;
if (implementation === undefined) throw new Error(`Service ${this.#serviceId} is disconnected`);
return {
value: Reflect.get(implementation, property, implementation),
receiver: implementation,
};
}
}
class ServiceView {
readonly #slot: ServiceSlot;
readonly #assertAccess: () => void;
readonly #wrapObjects: boolean;
readonly #members = new Map<PropertyKey, ValueView>();
readonly proxy: object;
constructor(slot: ServiceSlot, assertAccess: () => void, wrapObjects: boolean) {
this.#slot = slot;
this.#assertAccess = assertAccess;
this.#wrapObjects = wrapObjects;
this.proxy = new Proxy(Object.create(null) as object, {
get: (_target, property) => this.#getMember(property),
});
}
#getMember(property: PropertyKey): unknown {
const resolve = (): ResolvedValue => this.#slot.resolve(property, this.#assertAccess);
const current = resolve().value;
if (!isObject(current) || (typeof current !== "function" && !this.#wrapObjects)) return current;
let member = this.#members.get(property);
if (member === undefined) {
member = new ValueView(resolve, typeof current === "function");
this.#members.set(property, member);
}
return member.proxy;
}
}
class ValueView {
readonly #resolve: ValueResolver;
readonly #children = new Map<PropertyKey, ValueView>();
readonly proxy: object;
constructor(resolve: ValueResolver, callable: boolean) {
this.#resolve = resolve;
const target: object = callable ? () => undefined : Object.create(null);
this.proxy = new Proxy(target, {
apply: (_target, _thisArg, args) => this.#invoke(args),
get: (_target, property) => this.#get(property),
});
}
#invoke(args: unknown[]): unknown {
const { value, receiver } = this.#resolve();
if (typeof value !== "function") throw new TypeError("Service member is not callable");
return Reflect.apply(value, receiver, args);
}
#get(property: PropertyKey): unknown {
const resolve = (): ResolvedValue => {
const parent = this.#resolve();
if (!isObject(parent.value)) throw new TypeError("Service member does not have properties");
return {
value: Reflect.get(parent.value, property, parent.value),
receiver: parent.value,
};
};
const current = resolve().value;
if (typeof current !== "function") return current;
let child = this.#children.get(property);
if (child === undefined) {
child = new ValueView(resolve, true);
this.#children.set(property, child);
}
return child.proxy;
}
}
function isObject(value: unknown): value is object {
return (typeof value === "object" && value !== null) || typeof value === "function";
}

View File

@@ -0,0 +1,147 @@
import { BACKGROUND_CONTEXT, withCancel } from "../context/index.ts";
import type { Context } from "../types.ts";
export interface InstanceDirectoryEntry {
readonly key: string;
readonly generation: number;
readonly service: object;
deactivate(): void;
}
interface Observer {
readonly handler: (service: object, context: Context) => void | Promise<void>;
readonly tasks: Map<InstanceDirectoryEntry, { readonly cancel: (reason?: unknown) => void }>;
closed: boolean;
}
/** Owns keyed instance lifetime and the cancellable tasks observing those instances. */
export class InstanceDirectory<TEntry extends InstanceDirectoryEntry> {
readonly #entries = new Map<string, TEntry>();
readonly #observers = new Set<Observer>();
readonly #reportError: (error: Error) => void;
#ready: boolean;
#disposed = false;
constructor(options: { readonly ready: boolean; readonly onError: (error: Error) => void }) {
this.#ready = options.ready;
this.#reportError = options.onError;
}
get observerCount(): number {
return this.#observers.size;
}
get(key: string): TEntry | undefined {
return this.#entries.get(key);
}
insert(entry: TEntry): void {
this.#assertActive();
if (this.#entries.has(entry.key)) {
throw new Error(`Keyed service already has a live instance with key ${entry.key}`);
}
this.#entries.set(entry.key, entry);
if (this.#ready) this.#startAll(entry);
}
replace(entry: TEntry): void {
this.#assertActive();
const previous = this.#entries.get(entry.key);
if (previous !== undefined) {
if (previous.generation === entry.generation) {
throw new Error("Keyed service repeated a live generation");
}
this.#remove(previous);
}
this.#entries.set(entry.key, entry);
if (this.#ready) this.#startAll(entry);
}
remove(entry: TEntry): void {
if (this.#entries.get(entry.key) !== entry) return;
this.#remove(entry);
}
ready(): void {
this.#assertActive();
if (this.#ready) return;
this.#ready = true;
for (const entry of this.#entries.values()) this.#startAll(entry);
}
reset(): void {
if (this.#disposed) return;
this.#ready = false;
for (const entry of [...this.#entries.values()]) this.#remove(entry);
}
observe<T>(handler: (service: T, context: Context) => void | Promise<void>): () => void {
this.#assertActive();
const observer: Observer = {
handler: handler as Observer["handler"],
tasks: new Map(),
closed: false,
};
this.#observers.add(observer);
if (this.#ready) {
for (const entry of this.#entries.values()) this.#start(observer, entry);
}
return () => {
if (observer.closed) return;
observer.closed = true;
for (const task of observer.tasks.values()) task.cancel();
observer.tasks.clear();
this.#observers.delete(observer);
};
}
dispose(): void {
if (this.#disposed) return;
this.#disposed = true;
for (const observer of this.#observers) {
observer.closed = true;
for (const task of observer.tasks.values()) task.cancel();
observer.tasks.clear();
}
this.#observers.clear();
for (const entry of [...this.#entries.values()]) {
entry.deactivate();
}
this.#entries.clear();
}
#remove(entry: TEntry): void {
if (this.#entries.get(entry.key) !== entry) return;
this.#entries.delete(entry.key);
entry.deactivate();
for (const observer of this.#observers) {
observer.tasks.get(entry)?.cancel();
observer.tasks.delete(entry);
}
}
#startAll(entry: TEntry): void {
for (const observer of this.#observers) this.#start(observer, entry);
}
#start(observer: Observer, entry: TEntry): void {
if (observer.closed || observer.tasks.has(entry)) return;
const { context, cancel } = withCancel(BACKGROUND_CONTEXT);
observer.tasks.set(entry, { cancel });
try {
void Promise.resolve(observer.handler(entry.service, context)).catch((error: unknown) => {
if (!context.abortSignal?.aborted) this.#reportError(toError(error));
});
} catch (error) {
if (!context.abortSignal?.aborted) this.#reportError(toError(error));
}
}
#assertActive(): void {
if (this.#disposed) throw new Error("Keyed service directory is disposed");
}
}
function toError(error: unknown): Error {
return error instanceof Error ? error : new Error(String(error));
}

View File

@@ -0,0 +1,17 @@
import type { RemoteServiceTransport } from "../types.ts";
import type { RemoteServiceProvider } from "./provider.ts";
/** Connects a provider to a binding without changing remote service semantics. */
export function createLoopbackServiceTransport(provider: RemoteServiceProvider): RemoteServiceTransport {
return {
invoke: (call, context) => provider.invoke(call, context),
subscribe: async (serviceId, mode, listener) => {
const subscription = provider.subscribe(serviceId, mode, listener);
return {
snapshot: subscription.snapshot,
activate: () => subscription.activate(),
close: () => subscription.close(),
};
},
};
}

View File

@@ -0,0 +1,586 @@
import type {
Context,
JsonValue,
RemoteServiceContract,
Service,
ServiceCall,
ServiceCatalogueEntry,
ServiceInstanceAddress,
ServiceInstanceSnapshot,
ServiceMemberSnapshot,
ServiceMode,
ServiceProviderUpdate,
ServiceSubscription,
ServiceSubscriptionSnapshot,
} from "../types.ts";
import { RemoteServiceError } from "./errors.ts";
import { serviceDeliveryContext } from "./state.ts";
import { getReplicatedStateInternals, type ReplicatedStateInternals } from "./state-internals.ts";
import { decodeServiceControlCall } from "./wire.ts";
type RemoteMethod = (...args: unknown[]) => unknown;
type InstanceMember =
| { readonly kind: "method"; readonly method: RemoteMethod }
| { readonly kind: "state"; readonly state: ReplicatedStateInternals };
type ServiceMemberKind = InstanceMember["kind"];
interface ClassifiedRemoteServiceImplementation {
readonly implementation: object;
readonly members: Map<string, InstanceMember>;
}
interface ProviderInstance {
readonly address?: ServiceInstanceAddress;
readonly implementation: object;
readonly members: ReadonlyMap<string, InstanceMember>;
readonly removeMemberListeners: readonly (() => void)[];
active: boolean;
}
interface ProviderSubscriber {
readonly listener: (update: ServiceProviderUpdate, context: Context) => void;
readonly buffer: { readonly update: ServiceProviderUpdate; readonly context: Context }[];
active: boolean;
terminated: boolean;
closed: boolean;
}
interface ServiceProviderDefinition {
readonly service: { readonly id: string; readonly local?: boolean };
readonly mode: ServiceMode;
}
interface ServiceRegistration {
readonly serviceId: string;
readonly mode: ServiceMode;
singleton?: ProviderInstance;
singletonShape?: ReadonlyMap<string, ServiceMemberKind>;
readonly instances: Map<string, ProviderInstance>;
readonly generations: Map<string, number>;
readonly subscribers: Set<ProviderSubscriber>;
}
export type ServiceUpdatePublisher = (
subscriptionId: string,
update: ServiceProviderUpdate,
context: Context,
) => void | Promise<void>;
/** Hosts one provider for one remote consumer and owns that consumer's subscriptions. */
export interface RemoteServiceEndpoint {
invoke(call: ServiceCall, publish: ServiceUpdatePublisher, context: Context): Promise<JsonValue | undefined>;
dispose(): void;
}
export class RemoteServiceProvider {
readonly #catalogue: readonly ServiceCatalogueEntry[];
readonly #registrations = new Map<string, ServiceRegistration>();
#disposed = false;
constructor(entries: readonly (ServiceProviderDefinition | { readonly id: string })[]) {
const definitions = entries.map(
(entry): ServiceProviderDefinition => ("service" in entry ? entry : { service: entry, mode: "singleton" }),
);
for (const { service } of definitions) {
if ("local" in service && service.local === true) {
throw new TypeError(`Local service ${service.id} cannot be published remotely`);
}
}
const ids = definitions.map(({ service }) => service.id);
if (new Set(ids).size !== ids.length) throw new TypeError("Remote service catalogue contains duplicate IDs");
this.#catalogue = Object.freeze(
definitions.map(({ service, mode }) => Object.freeze({ serviceId: service.id, mode })),
);
for (const { service, mode } of definitions) {
this.#registrations.set(service.id, {
serviceId: service.id,
mode,
instances: new Map(),
generations: new Map(),
subscribers: new Set(),
});
}
}
get catalogue(): readonly ServiceCatalogueEntry[] {
return this.#catalogue;
}
provide<T>(service: Service<T>, implementation: NoInfer<RemoteServiceContract<T>>): void {
this.#assertActive();
this.#assertRemotable(service);
this.#assertAllowed(service.id);
const registration = this.#registration(service.id, "singleton");
if (registration.singleton !== undefined) {
throw new RemoteServiceError("service_mode_mismatch", `Remote service ${service.id} already has a provider`);
}
const classified = classifyRemoteServiceImplementation(registration.serviceId, implementation);
const shape = serviceMemberShape(classified.members);
this.#assertSingletonShape(registration, shape);
registration.singleton = this.#createInstance(registration, classified, undefined);
registration.singletonShape = shape;
}
/** Disconnect one singleton while preserving active subscriptions and remote facades. */
withdraw<T>(service: Service<T>): void {
this.#assertActive();
this.#assertRemotable(service);
this.#assertAllowed(service.id);
const registration = this.#registration(service.id, "singleton");
const previous = registration.singleton;
if (previous === undefined) return;
previous.active = false;
for (const remove of previous.removeMemberListeners) remove();
delete registration.singleton;
this.#emit(registration, { type: "unavailable" });
}
/** Check a singleton replacement without changing the active provider. */
validateReplacement<T>(service: Service<T>, implementation: NoInfer<RemoteServiceContract<T>>): void {
this.#assertActive();
this.#assertRemotable(service);
this.#assertAllowed(service.id);
const registration = this.#registration(service.id, "singleton");
const classified = classifyRemoteServiceImplementation(registration.serviceId, implementation);
this.#assertSingletonShape(registration, serviceMemberShape(classified.members));
}
/** Replace one singleton without making its stable remote facade unavailable. */
replace<T>(service: Service<T>, implementation: NoInfer<RemoteServiceContract<T>>): void {
this.#assertActive();
this.#assertRemotable(service);
this.#assertAllowed(service.id);
const registration = this.#registration(service.id, "singleton");
const classified = classifyRemoteServiceImplementation(registration.serviceId, implementation);
const shape = serviceMemberShape(classified.members);
this.#assertSingletonShape(registration, shape);
const replacement = this.#createInstance(registration, classified, undefined);
const previous = registration.singleton;
if (previous !== undefined) {
previous.active = false;
for (const remove of previous.removeMemberListeners) remove();
}
registration.singleton = replacement;
registration.singletonShape = shape;
this.#emit(registration, { type: "replaced", snapshot: this.#snapshotInstance(replacement) });
}
use<T>(service: Service<T>): T {
this.#assertActive();
this.#assertRemotable(service);
this.#assertAllowed(service.id);
const registration = this.#registrations.get(service.id);
if (registration?.mode !== "singleton" || registration.singleton === undefined) {
throw new RemoteServiceError("service_not_found", `Remote service ${service.id} has no local provider`);
}
return registration.singleton.implementation as T;
}
spawn<T>(service: Service<T>, key: string, implementation: NoInfer<RemoteServiceContract<T>>): () => void {
this.#assertActive();
this.#assertRemotable(service);
this.#assertAllowed(service.id);
if (key.length === 0) throw new TypeError("Remote service instance key must not be empty");
const registration = this.#registration(service.id, "keyed");
if (registration.instances.has(key)) {
throw new RemoteServiceError(
"service_mode_mismatch",
`Remote service ${service.id} already has a live instance with key ${key}`,
);
}
const generation = (registration.generations.get(key) ?? 0) + 1;
registration.generations.set(key, generation);
const address = { key, generation } satisfies ServiceInstanceAddress;
const classified = classifyRemoteServiceImplementation(registration.serviceId, implementation);
const instance = this.#createInstance(registration, classified, address);
registration.instances.set(key, instance);
this.#emit(registration, { type: "spawned", instance: this.#snapshotInstance(instance) });
let closed = false;
return () => {
if (closed) return;
closed = true;
if (registration.instances.get(key) !== instance) return;
instance.active = false;
for (const remove of instance.removeMemberListeners) remove();
registration.instances.delete(key);
this.#emit(registration, { type: "closed", instance: address });
};
}
async invoke(call: ServiceCall, context: Context): Promise<JsonValue | undefined> {
this.#assertActive();
this.#assertAllowed(call.serviceId);
const registration = this.#registrations.get(call.serviceId);
if (registration === undefined) {
throw new RemoteServiceError("service_not_found", `Unknown remote service ${call.serviceId}`);
}
const instance = this.#resolveInstance(registration, call.instance);
const member = instance.members.get(call.member);
if (member === undefined) {
throw new RemoteServiceError(
"service_member_not_found",
`Unknown remote service member ${call.serviceId}.${call.member}`,
);
}
if (member.kind !== "method") {
throw new RemoteServiceError(
"service_member_mismatch",
`Remote service member ${call.serviceId}.${call.member} is not a method`,
);
}
const result: unknown = await Reflect.apply(member.method, instance.implementation, [...call.args, context]);
return result as JsonValue | undefined;
}
subscribe(
serviceId: string,
mode: ServiceMode,
listener: (update: ServiceProviderUpdate, context: Context) => void,
): ServiceSubscription {
this.#assertActive();
this.#assertAllowed(serviceId);
const registration = this.#registration(serviceId, mode);
if (registration.mode === "singleton" && registration.singleton === undefined) {
throw new RemoteServiceError("service_not_found", `Remote service ${serviceId} has no provider`);
}
const subscriber: ProviderSubscriber = {
listener,
buffer: [],
active: false,
terminated: false,
closed: false,
};
this.#publishPending(registration);
registration.subscribers.add(subscriber);
const snapshot = this.#snapshot(registration);
return {
snapshot,
activate: () => {
if (subscriber.closed || subscriber.active) return;
subscriber.active = true;
const errors: unknown[] = [];
try {
for (const entry of subscriber.buffer.splice(0)) {
try {
listener(entry.update, entry.context);
} catch (error) {
errors.push(error);
}
}
} finally {
if (subscriber.terminated) subscriber.closed = true;
}
throwCollectedErrors(errors, "Failed to activate remote service subscription");
},
close: () => {
if (subscriber.closed) return;
subscriber.closed = true;
subscriber.buffer.length = 0;
registration.subscribers.delete(subscriber);
},
};
}
dispose(): void {
if (this.#disposed) return;
this.#disposed = true;
const errors: unknown[] = [];
for (const registration of this.#registrations.values()) {
const singleton = registration.singleton;
if (singleton !== undefined) {
singleton.active = false;
for (const remove of singleton.removeMemberListeners) remove();
delete registration.singleton;
try {
this.#emit(registration, { type: "unavailable" });
} catch (error) {
errors.push(error);
}
}
for (const [key, instance] of [...registration.instances]) {
instance.active = false;
for (const remove of instance.removeMemberListeners) remove();
registration.instances.delete(key);
try {
this.#emit(registration, { type: "closed", instance: instance.address! });
} catch (error) {
errors.push(error);
}
}
for (const subscriber of registration.subscribers) {
if (subscriber.active) {
subscriber.closed = true;
subscriber.buffer.length = 0;
} else {
subscriber.terminated = true;
}
}
registration.subscribers.clear();
}
this.#registrations.clear();
if (errors.length === 1) throw errors[0];
if (errors.length > 1) throw new AggregateError(errors, "Failed to dispose remote service provider");
}
#registration(serviceId: string, mode: ServiceMode): ServiceRegistration {
const registration = this.#registrations.get(serviceId);
if (registration === undefined) {
throw new RemoteServiceError("service_not_found", `Unknown remote service ${serviceId}`);
}
if (registration.mode !== mode) {
throw new RemoteServiceError(
"service_mode_mismatch",
`Remote service ${serviceId} is ${registration.mode}, not ${mode}`,
);
}
return registration;
}
#createInstance(
registration: ServiceRegistration,
classified: ClassifiedRemoteServiceImplementation,
address: ServiceInstanceAddress | undefined,
): ProviderInstance {
const removeMemberListeners: (() => void)[] = [];
const instance: ProviderInstance = {
...(address === undefined ? {} : { address }),
implementation: classified.implementation,
members: classified.members,
removeMemberListeners,
active: true,
};
for (const [name, member] of classified.members) {
if (member.kind !== "state") continue;
removeMemberListeners.push(
member.state.subscribe((ops, sequence, context) => {
if (!instance.active) return;
this.#emit(
registration,
{
type: "state",
...(address === undefined ? {} : { instance: address }),
member: name,
sequence,
ops,
},
context,
);
}),
);
}
return instance;
}
#assertSingletonShape(registration: ServiceRegistration, replacement: ReadonlyMap<string, ServiceMemberKind>): void {
const current = registration.singletonShape;
if (current === undefined || sameServiceMemberShape(current, replacement)) return;
throw new RemoteServiceError(
"service_member_mismatch",
`Remote service ${registration.serviceId} replacement must preserve its member shape`,
);
}
#resolveInstance(registration: ServiceRegistration, address: ServiceInstanceAddress | undefined): ProviderInstance {
if (registration.mode === "singleton") {
if (address !== undefined) {
throw new RemoteServiceError(
"service_mode_mismatch",
`Remote service ${registration.serviceId} is singleton`,
);
}
if (registration.singleton === undefined) {
throw new RemoteServiceError(
"service_not_found",
`Remote service ${registration.serviceId} has no provider`,
);
}
return registration.singleton;
}
if (address === undefined) {
throw new RemoteServiceError("service_mode_mismatch", `Remote service ${registration.serviceId} is keyed`);
}
const instance = registration.instances.get(address.key);
if (instance === undefined) {
throw new RemoteServiceError(
"service_instance_not_found",
`Remote service ${registration.serviceId} has no instance ${address.key}`,
);
}
if (instance.address?.generation !== address.generation) {
throw new RemoteServiceError(
"service_stale_instance",
`Remote service ${registration.serviceId} instance ${address.key} is stale`,
);
}
return instance;
}
#publishPending(registration: ServiceRegistration): void {
const context = serviceDeliveryContext();
const instances =
registration.mode === "singleton"
? registration.singleton === undefined
? []
: [registration.singleton]
: registration.instances.values();
for (const instance of instances) {
for (const member of instance.members.values()) {
if (member.kind === "state") member.state.publish(context);
}
}
}
#snapshot(registration: ServiceRegistration): ServiceSubscriptionSnapshot {
const instances =
registration.mode === "singleton"
? registration.singleton
? [this.#snapshotInstance(registration.singleton)]
: []
: [...registration.instances.values()]
.sort((left, right) => left.address!.key.localeCompare(right.address!.key))
.map((instance) => this.#snapshotInstance(instance));
return { serviceId: registration.serviceId, mode: registration.mode, instances };
}
#snapshotInstance(instance: ProviderInstance): ServiceInstanceSnapshot {
const members: ServiceMemberSnapshot[] = [];
for (const [name, member] of instance.members) {
if (member.kind === "method") {
members.push({ name, kind: "method" });
} else {
members.push({
name,
kind: "state",
sequence: member.state.sequence,
ops: [["r", member.state.value as JsonValue]],
});
}
}
return {
...(instance.address === undefined ? {} : { instance: instance.address }),
members,
};
}
#emit(registration: ServiceRegistration, update: ServiceProviderUpdate, context?: Context): void {
if (registration.subscribers.size === 0) return;
const deliveryContext = context ?? serviceDeliveryContext();
const errors: unknown[] = [];
for (const subscriber of registration.subscribers) {
if (subscriber.closed) continue;
const entry = { update, context: deliveryContext };
if (!subscriber.active) {
subscriber.buffer.push(entry);
continue;
}
try {
subscriber.listener(entry.update, entry.context);
} catch (error) {
errors.push(error);
}
}
throwCollectedErrors(errors, `Failed to publish remote service ${registration.serviceId} update`);
}
#assertRemotable(service: { readonly id: string; readonly local: boolean }): void {
if (service.local) throw new RemoteServiceError("service_not_allowed", `Service ${service.id} is process-local`);
}
#assertAllowed(serviceId: string): void {
if (!this.#registrations.has(serviceId)) {
throw new RemoteServiceError("service_not_allowed", `Remote service ${serviceId} is not allowlisted`);
}
}
#assertActive(): void {
if (this.#disposed) throw new Error("Remote service provider is disposed");
}
}
export function createRemoteServiceEndpoint(provider: RemoteServiceProvider): RemoteServiceEndpoint {
const subscriptions = new Map<string, ServiceSubscription>();
let disposed = false;
return {
async invoke(call, publish, context) {
if (disposed) throw new Error("Remote service endpoint is disposed");
const control = decodeServiceControlCall(call);
if (control?.type === "catalogue") return provider.catalogue as unknown as JsonValue;
if (control?.type === "subscribe") {
if (subscriptions.has(control.subscriptionId)) {
throw new Error("Service subscription ID is already active");
}
const subscription = provider.subscribe(control.serviceId, control.mode, (update, updateContext) => {
void Promise.resolve(publish(control.subscriptionId, update, updateContext)).catch(() => {});
});
subscriptions.set(control.subscriptionId, subscription);
subscription.activate();
return subscription.snapshot as unknown as JsonValue;
}
if (control?.type === "unsubscribe") {
const subscription = subscriptions.get(control.subscriptionId);
if (subscription === undefined) throw new Error("Service subscription was not found");
subscription.close();
subscriptions.delete(control.subscriptionId);
return undefined;
}
return provider.invoke(call, context);
},
dispose() {
if (disposed) return;
disposed = true;
for (const subscription of subscriptions.values()) subscription.close();
subscriptions.clear();
},
};
}
export function validateRemoteServiceImplementation(serviceId: string, implementation: unknown): void {
void classifyRemoteServiceImplementation(serviceId, implementation);
}
function classifyRemoteServiceImplementation(
serviceId: string,
implementation: unknown,
): ClassifiedRemoteServiceImplementation {
if (typeof implementation !== "object" || implementation === null || Array.isArray(implementation)) {
throw new TypeError(`Remote service ${serviceId} implementation must be an object`);
}
const members = new Map<string, InstanceMember>();
for (const name of Object.keys(implementation).sort()) {
const descriptor = Object.getOwnPropertyDescriptor(implementation, name);
if (descriptor === undefined || !("value" in descriptor)) {
throw new TypeError(`Remote service member ${serviceId}.${name} must be a data property`);
}
if (typeof descriptor.value === "function") {
members.set(name, { kind: "method", method: descriptor.value as RemoteMethod });
continue;
}
const state = getReplicatedStateInternals(descriptor.value);
if (state !== undefined) {
members.set(name, { kind: "state", state });
continue;
}
throw new TypeError(`Remote service member ${serviceId}.${name} is not remotely exposable`);
}
if (members.size === 0) throw new TypeError(`Remote service ${serviceId} has no members`);
return { implementation, members };
}
function throwCollectedErrors(errors: readonly unknown[], message: string): void {
if (errors.length === 1) throw errors[0];
if (errors.length > 1) throw new AggregateError(errors, message);
}
function serviceMemberShape(members: ReadonlyMap<string, InstanceMember>): ReadonlyMap<string, ServiceMemberKind> {
return new Map([...members].map(([name, member]) => [name, member.kind]));
}
function sameServiceMemberShape(
left: ReadonlyMap<string, ServiceMemberKind>,
right: ReadonlyMap<string, ServiceMemberKind>,
): boolean {
return left.size === right.size && [...left].every(([name, kind]) => right.get(name) === kind);
}

View File

@@ -0,0 +1,158 @@
import { type Decoder, decoder, type Encoder, encoder } from "../delta/index.ts";
import type {
ServiceInstanceAddress,
ServiceInstanceSnapshot,
ServiceProviderUpdate,
ServiceSubscriptionSnapshot,
} from "../types.ts";
import type { WireServiceProviderUpdate, WireServiceSubscriptionSnapshot } from "./wire.ts";
/** Stateful operation encoders for every replicated state in one service subscription. */
export interface ServiceStateEncoder {
encodeSnapshot(snapshot: ServiceSubscriptionSnapshot): WireServiceSubscriptionSnapshot;
encodeUpdate(update: ServiceProviderUpdate): WireServiceProviderUpdate;
}
/** Stateful operation decoders for every replicated state in one service subscription. */
export interface ServiceStateDecoder {
decodeSnapshot(snapshot: WireServiceSubscriptionSnapshot): ServiceSubscriptionSnapshot;
decodeUpdate(update: WireServiceProviderUpdate): ServiceProviderUpdate;
}
interface CodecEntry<C> {
readonly instance: ServiceInstanceAddress | undefined;
readonly codec: C;
}
class StateCodecRegistry<C> {
readonly #create: () => C;
readonly #entries = new Map<string, CodecEntry<C>>();
constructor(create: () => C) {
this.#create = create;
}
reset(): void {
this.#entries.clear();
}
add(instance: ServiceInstanceAddress | undefined, member: string): C {
const key = stateKey(instance, member);
if (this.#entries.has(key)) throw new Error(`Duplicate service state ${describeState(instance, member)}`);
const codec = this.#create();
this.#entries.set(key, { instance, codec });
return codec;
}
get(instance: ServiceInstanceAddress | undefined, member: string): C {
const codec = this.#entries.get(stateKey(instance, member))?.codec;
if (codec === undefined) throw new Error(`Unknown service state ${describeState(instance, member)}`);
return codec;
}
removeInstance(instance: ServiceInstanceAddress): void {
for (const [key, entry] of this.#entries) {
if (sameAddress(entry.instance, instance)) this.#entries.delete(key);
}
}
}
export function createServiceStateEncoder(): ServiceStateEncoder {
const codecs = new StateCodecRegistry<Encoder>(encoder);
return {
encodeSnapshot(snapshot) {
codecs.reset();
return {
...snapshot,
instances: snapshot.instances.map((instance) => encodeInstance(instance, codecs)),
};
},
encodeUpdate(update) {
switch (update.type) {
case "state":
return { ...update, ops: codecs.get(update.instance, update.member).encode(update.ops) };
case "replaced":
codecs.reset();
return { ...update, snapshot: encodeInstance(update.snapshot, codecs) };
case "spawned":
return { ...update, instance: encodeInstance(update.instance, codecs) };
case "unavailable":
codecs.reset();
return update;
case "closed":
codecs.removeInstance(update.instance);
return update;
}
},
};
}
export function createServiceStateDecoder(): ServiceStateDecoder {
const codecs = new StateCodecRegistry<Decoder>(decoder);
return {
decodeSnapshot(snapshot) {
codecs.reset();
return {
...snapshot,
instances: snapshot.instances.map((instance) => decodeInstance(instance, codecs)),
};
},
decodeUpdate(update) {
switch (update.type) {
case "state":
return { ...update, ops: codecs.get(update.instance, update.member).decode(update.ops) };
case "replaced":
codecs.reset();
return { ...update, snapshot: decodeInstance(update.snapshot, codecs) };
case "spawned":
return { ...update, instance: decodeInstance(update.instance, codecs) };
case "unavailable":
codecs.reset();
return update;
case "closed":
codecs.removeInstance(update.instance);
return update;
}
},
};
}
function encodeInstance(
instance: ServiceInstanceSnapshot,
codecs: StateCodecRegistry<Encoder>,
): WireServiceSubscriptionSnapshot["instances"][number] {
return {
...instance,
members: instance.members.map((member) =>
member.kind === "state"
? { ...member, ops: codecs.add(instance.instance, member.name).encode(member.ops) }
: member,
),
};
}
function decodeInstance(
instance: WireServiceSubscriptionSnapshot["instances"][number],
codecs: StateCodecRegistry<Decoder>,
): ServiceInstanceSnapshot {
return {
...instance,
members: instance.members.map((member) =>
member.kind === "state"
? { ...member, ops: codecs.add(instance.instance, member.name).decode(member.ops) }
: member,
),
};
}
function stateKey(instance: ServiceInstanceAddress | undefined, member: string): string {
return JSON.stringify([instance?.key ?? null, instance?.generation ?? null, member]);
}
function sameAddress(left: ServiceInstanceAddress | undefined, right: ServiceInstanceAddress): boolean {
return left?.key === right.key && left.generation === right.generation;
}
function describeState(instance: ServiceInstanceAddress | undefined, member: string): string {
return instance === undefined ? member : `${instance.key}@${instance.generation}.${member}`;
}

View File

@@ -0,0 +1,20 @@
import type { Op } from "../delta/index.ts";
import type { Context } from "../types.ts";
export interface ReplicatedStateInternals {
readonly sequence: number;
readonly value: unknown;
publish(context: Context): void;
subscribe(listener: (ops: readonly Op[], sequence: number, context: Context) => void): () => void;
}
const sources = new WeakMap<object, ReplicatedStateInternals>();
export function registerReplicatedStateInternals(value: object, internals: ReplicatedStateInternals): void {
sources.set(value, internals);
}
export function getReplicatedStateInternals(value: unknown): ReplicatedStateInternals | undefined {
if (typeof value !== "object" || value === null) return undefined;
return sources.get(value);
}

View File

@@ -0,0 +1,139 @@
import { BACKGROUND_CONTEXT } from "../context/index.ts";
import { applyImmutable, isBase, type Op, type Tracker, track } from "../delta/index.ts";
import type { Context, JsonValue, MutableReplicatedState, ReplicatedState, ReplicatedStateDelivery } from "../types.ts";
import { registerReplicatedStateInternals } from "./state-internals.ts";
export class MutableReplicatedStateImpl<T extends object> implements MutableReplicatedState<T> {
readonly #listeners = new Set<(value: T, context: Context, delivery: ReplicatedStateDelivery) => void>();
readonly #sourceListeners = new Set<(ops: readonly Op[], sequence: number, context: Context) => void>();
readonly #tracker: Tracker<T>;
#publishedValue: T;
#sequence = 0;
constructor(initial: T) {
this.#tracker = track(initial);
this.#publishedValue = applyImmutable(undefined, this.#tracker.flush()) as unknown as T;
const thisSource = this;
registerReplicatedStateInternals(this, {
get sequence() {
return thisSource.#sequence;
},
get value() {
return thisSource.#publishedValue;
},
publish: (context) => thisSource.publish(context),
subscribe: (listener) => {
thisSource.#sourceListeners.add(listener);
return () => thisSource.#sourceListeners.delete(listener);
},
});
}
get value(): T {
return this.#publishedValue;
}
get state(): T {
return this.#tracker.state;
}
publish(context: Context): void {
const ops = this.#tracker.flush();
if (ops.length === 0) return;
this.#sequence += 1;
this.#publishedValue = applyImmutable(this.#publishedValue as unknown as JsonValue, ops) as unknown as T;
for (const listener of [...this.#sourceListeners]) listener(ops, this.#sequence, context);
const delivery = { kind: "update", sequence: this.#sequence } as const;
for (const listener of [...this.#listeners]) listener(this.#publishedValue, context, delivery);
}
subscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void {
const context = serviceDeliveryContext();
this.publish(context);
this.#listeners.add(listener);
listener(this.#publishedValue, context, { kind: "hydrate", sequence: this.#sequence });
return () => this.#listeners.delete(listener);
}
}
/** A cold read-only state used by service consumers until a complete snapshot arrives. */
export class ReplicatedStateReplica<T extends JsonValue = JsonValue> implements ReplicatedState<T> {
readonly #listeners = new Set<(value: T, context: Context, delivery: ReplicatedStateDelivery) => void>();
readonly #reportError: (error: Error) => void;
#value: T | undefined;
#sequence: number | undefined;
constructor(reportError: (error: Error) => void) {
this.#reportError = reportError;
}
get value(): T | undefined {
return this.#value;
}
subscribe(listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void): () => void {
this.#listeners.add(listener);
if (this.#value !== undefined) {
this.#deliver(listener, this.#value, serviceDeliveryContext(), {
kind: "hydrate",
sequence: this.#sequence!,
});
}
return () => this.#listeners.delete(listener);
}
hydrate(sequence: number, ops: readonly Op[], context: Context): void {
if (!isBase(ops)) throw new Error("Replicated state snapshot is not a base operation batch");
const value = applyImmutable<T>(undefined, ops);
this.#sequence = sequence;
this.#value = value;
this.#deliverAll(context, { kind: "hydrate", sequence });
}
update(sequence: number, ops: readonly Op[], context: Context): void {
if (this.#sequence === undefined || this.#value === undefined) {
throw new Error("Replicated state received an update before hydration");
}
if (sequence !== this.#sequence + 1) {
this.clear();
throw new Error("Replicated state update sequence has a gap");
}
const value = applyImmutable(this.#value, ops);
this.#sequence = sequence;
this.#value = value;
this.#deliverAll(context, { kind: "update", sequence });
}
clear(): void {
this.#value = undefined;
this.#sequence = undefined;
}
#deliverAll(context: Context, delivery: ReplicatedStateDelivery): void {
if (this.#value === undefined) return;
for (const listener of this.#listeners) this.#deliver(listener, this.#value, context, delivery);
}
#deliver(
listener: (value: T, context: Context, delivery: ReplicatedStateDelivery) => void,
value: T,
context: Context,
delivery: ReplicatedStateDelivery,
): void {
try {
listener(value, context, delivery);
} catch (error) {
this.#reportError(toError(error));
}
}
}
/** @internal Context for synthetic service deliveries without a caller. */
export function serviceDeliveryContext(): Context {
// TODO: Add delivery-scoped cancellation or metadata if deliveries gain an owned lifecycle.
return BACKGROUND_CONTEXT;
}
function toError(error: unknown): Error {
return error instanceof Error ? error : new Error(String(error));
}

View File

@@ -0,0 +1,234 @@
import { assertValidOp, assertValidWireOp, type WireOp } from "../delta/index.ts";
import type {
ServiceCall,
ServiceCatalogueEntry,
ServiceInstanceAddress,
ServiceMode,
ServiceProviderUpdate,
ServiceSubscriptionSnapshot,
} from "../types.ts";
export type WireServiceMemberSnapshot =
| { readonly name: string; readonly kind: "method" }
| { readonly name: string; readonly kind: "state"; readonly sequence: number; readonly ops: readonly WireOp[] };
export type WireServiceInstanceSnapshot = {
readonly instance?: ServiceInstanceAddress;
readonly members: readonly WireServiceMemberSnapshot[];
};
export type WireServiceSubscriptionSnapshot = {
readonly serviceId: string;
readonly mode: ServiceMode;
readonly instances: readonly WireServiceInstanceSnapshot[];
};
export type WireServiceProviderUpdate =
| {
readonly type: "state";
readonly instance?: ServiceInstanceAddress;
readonly member: string;
readonly sequence: number;
readonly ops: readonly WireOp[];
}
| { readonly type: "unavailable" }
| { readonly type: "replaced"; readonly snapshot: WireServiceInstanceSnapshot }
| { readonly type: "spawned"; readonly instance: WireServiceInstanceSnapshot }
| { readonly type: "closed"; readonly instance: ServiceInstanceAddress };
const SERVICE_CONTROL_ID = "$chord.service";
const SERVICE_CATALOGUE_MEMBER = "catalogue";
const SERVICE_SUBSCRIBE_MEMBER = "subscribe";
const SERVICE_UNSUBSCRIBE_MEMBER = "unsubscribe";
export type ServiceControlCall =
| { readonly type: "catalogue" }
| {
readonly type: "subscribe";
readonly subscriptionId: string;
readonly serviceId: string;
readonly mode: ServiceMode;
}
| { readonly type: "unsubscribe"; readonly subscriptionId: string };
export function createServiceCatalogueCall(): ServiceCall {
return { serviceId: SERVICE_CONTROL_ID, member: SERVICE_CATALOGUE_MEMBER, args: [] };
}
export function createServiceSubscribeCall(subscriptionId: string, serviceId: string, mode: ServiceMode): ServiceCall {
return { serviceId: SERVICE_CONTROL_ID, member: SERVICE_SUBSCRIBE_MEMBER, args: [subscriptionId, serviceId, mode] };
}
export function createServiceUnsubscribeCall(subscriptionId: string): ServiceCall {
return { serviceId: SERVICE_CONTROL_ID, member: SERVICE_UNSUBSCRIBE_MEMBER, args: [subscriptionId] };
}
export function decodeServiceControlCall(call: ServiceCall): ServiceControlCall | undefined {
if (call.serviceId !== SERVICE_CONTROL_ID || call.instance !== undefined) return undefined;
if (call.member === SERVICE_CATALOGUE_MEMBER && call.args.length === 0) return { type: "catalogue" };
if (
call.member === SERVICE_SUBSCRIBE_MEMBER &&
call.args.length === 3 &&
isId(call.args[0]) &&
isId(call.args[1]) &&
(call.args[2] === "singleton" || call.args[2] === "keyed")
) {
return {
type: "subscribe",
subscriptionId: call.args[0],
serviceId: call.args[1],
mode: call.args[2],
};
}
if (call.member === SERVICE_UNSUBSCRIBE_MEMBER && call.args.length === 1 && isId(call.args[0])) {
return { type: "unsubscribe", subscriptionId: call.args[0] };
}
return undefined;
}
export function parseServiceCall(value: unknown): ServiceCall {
const call = record(value, "service call");
assertKeys(call, ["serviceId", "member", "args"], ["instance"], "service call");
if (!isId(call.serviceId) || !isId(call.member) || !Array.isArray(call.args)) {
throw new TypeError("Invalid service call");
}
if (call.instance !== undefined) assertAddress(call.instance);
return value as ServiceCall;
}
export function parseServiceCatalogue(value: unknown): readonly ServiceCatalogueEntry[] {
if (!Array.isArray(value)) throw new TypeError("Invalid service catalogue");
const ids = new Set<string>();
for (const candidate of value) {
const entry = record(candidate, "service catalogue entry");
assertKeys(entry, ["serviceId", "mode"], [], "service catalogue entry");
if (!isId(entry.serviceId) || !isMode(entry.mode) || ids.has(entry.serviceId)) {
throw new TypeError("Invalid service catalogue");
}
ids.add(entry.serviceId);
}
return value as unknown as readonly ServiceCatalogueEntry[];
}
export function parseServiceSubscriptionSnapshot(value: unknown): ServiceSubscriptionSnapshot {
assertSubscriptionSnapshot(value, assertValidOp);
return value as ServiceSubscriptionSnapshot;
}
export function parseWireServiceSubscriptionSnapshot(value: unknown): WireServiceSubscriptionSnapshot {
assertSubscriptionSnapshot(value, assertValidWireOp);
return value as WireServiceSubscriptionSnapshot;
}
export function parseServiceProviderUpdate(value: unknown): ServiceProviderUpdate {
assertProviderUpdate(value, assertValidOp);
return value as ServiceProviderUpdate;
}
export function parseWireServiceProviderUpdate(value: unknown): WireServiceProviderUpdate {
assertProviderUpdate(value, assertValidWireOp);
return value as WireServiceProviderUpdate;
}
function assertSubscriptionSnapshot(value: unknown, assertOp: (value: unknown) => void): void {
const snapshot = record(value, "service subscription snapshot");
assertKeys(snapshot, ["serviceId", "mode", "instances"], [], "service subscription snapshot");
if (!isId(snapshot.serviceId) || !isMode(snapshot.mode) || !Array.isArray(snapshot.instances)) {
throw new TypeError("Invalid service subscription snapshot");
}
for (const instance of snapshot.instances) assertInstance(instance, assertOp);
}
function assertProviderUpdate(value: unknown, assertOp: (value: unknown) => void): void {
const update = record(value, "service provider update");
switch (update.type) {
case "state":
assertKeys(update, ["type", "member", "sequence", "ops"], ["instance"], "state update");
if (!isId(update.member) || !isInteger(update.sequence, 1) || !Array.isArray(update.ops)) {
throw new TypeError("Invalid service state update");
}
if (update.instance !== undefined) assertAddress(update.instance);
for (const op of update.ops) assertOp(op);
return;
case "unavailable":
assertKeys(update, ["type"], [], "unavailable update");
return;
case "replaced":
assertKeys(update, ["type", "snapshot"], [], "replacement update");
assertInstance(update.snapshot, assertOp);
return;
case "spawned":
assertKeys(update, ["type", "instance"], [], "spawn update");
assertInstance(update.instance, assertOp);
return;
case "closed":
assertKeys(update, ["type", "instance"], [], "close update");
assertAddress(update.instance);
return;
default:
throw new TypeError("Invalid service provider update");
}
}
function assertInstance(value: unknown, assertOp: (value: unknown) => void): void {
const instance = record(value, "service instance snapshot");
assertKeys(instance, ["members"], ["instance"], "service instance snapshot");
if (instance.instance !== undefined) assertAddress(instance.instance);
if (!Array.isArray(instance.members)) throw new TypeError("Invalid service instance snapshot");
for (const candidate of instance.members) {
const member = record(candidate, "service member snapshot");
if (member.kind === "method") {
assertKeys(member, ["name", "kind"], [], "service method snapshot");
if (!isId(member.name)) throw new TypeError("Invalid service method snapshot");
continue;
}
if (member.kind === "state") {
assertKeys(member, ["name", "kind", "sequence", "ops"], [], "service state snapshot");
if (!isId(member.name) || !isInteger(member.sequence, 0) || !Array.isArray(member.ops)) {
throw new TypeError("Invalid service state snapshot");
}
for (const op of member.ops) assertOp(op);
continue;
}
throw new TypeError("Invalid service member snapshot");
}
}
function assertAddress(value: unknown): asserts value is ServiceInstanceAddress {
const address = record(value, "service instance address");
assertKeys(address, ["key", "generation"], [], "service instance address");
if (!isId(address.key) || !isInteger(address.generation, 1)) {
throw new TypeError("Invalid service instance address");
}
}
function record(value: unknown, description: string): Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new TypeError(`Invalid ${description}`);
}
return value as Record<string, unknown>;
}
function assertKeys(
value: Record<string, unknown>,
required: readonly string[],
optional: readonly string[],
description: string,
): void {
const allowed = new Set([...required, ...optional]);
if (required.some((key) => !Object.hasOwn(value, key)) || Object.keys(value).some((key) => !allowed.has(key))) {
throw new TypeError(`Invalid ${description}`);
}
}
function isId(value: unknown): value is string {
return typeof value === "string" && value.length > 0;
}
function isMode(value: unknown): value is ServiceMode {
return value === "singleton" || value === "keyed";
}
function isInteger(value: unknown, minimum: number): value is number {
return typeof value === "number" && Number.isInteger(value) && value >= minimum;
}