Add generic CultMesh provider client

This commit is contained in:
Meta Vens 2026-07-10 12:38:47 +01:00
commit d8fdf6a275
5 changed files with 273 additions and 0 deletions

View file

@ -8,6 +8,12 @@ Providers supply advertisements, surface and embedded-document reads, command
submission, asset resolution, and any product daemon lifecycle. EveElectron submission, asset resolution, and any product daemon lifecycle. EveElectron
does not import provider state or plugin semantic implementations. does not import provider state or plugin semantic implementations.
`EveCultMeshProviderClient` takes an Odin-resolved peer target and advertisement
record reference, then follows only provider-published references for surfaces,
commands, receipts, and embedded documents. CultMesh and MessagePack are
constructor dependencies supplied from the application's installed CultLib
packages; the runtime owns protocol flow without pinning a sibling checkout.
```powershell ```powershell
npm install npm install
npm test npm test

View file

@ -0,0 +1,199 @@
const providerSchema = "gamecult.eve.provider_advertisement.v1";
const commandSchema = "gamecult.eve.command.v1";
const connectionId = 0x45564545;
export class EveCultMeshProviderClient {
#peer = null;
constructor(target, dependencies, options = {}) {
this.target = normalizeTarget(target);
this.mesh = requireFunctionSet(dependencies?.CultMesh, [
"createAuthorityLeaseCatalog",
"createPeerCatalog",
"createRudpPeerForAuthorizedPeer",
"documentFromPublication",
"verse",
], "CultMesh");
if (typeof dependencies?.encode !== "function") throw new Error("A MessagePack encode function is required.");
this.encode = dependencies.encode;
this.runtimeId = options.runtimeId || "eve-electron";
this.timeoutMs = options.timeoutMs ?? 5000;
this.context = this.mesh.verse("eve.remote", this.runtimeId).context;
}
async close() {
this.#peer?.close();
this.#peer = null;
}
async providerAdvertisement() {
const value = await this.#document(providerSchema, this.target.advertisementRecordRef, "provider");
const advertisement = normalizeProviderAdvertisement(value);
if (this.target.providerId && advertisement.providerId !== this.target.providerId) {
throw new Error(`Resolved provider ${advertisement.providerId} does not match ${this.target.providerId}.`);
}
return advertisement;
}
async surface(surfaceId = "") {
const advertisement = await this.providerAdvertisement();
const advertised = selectSurface(advertisement, surfaceId);
const document = normalizeSurfaceDocument(await this.#document(advertised.schema, advertised.recordRef, "surface"));
if (document.surface.id !== advertised.surfaceId) {
throw new Error(`Surface record ${advertised.recordRef} published ${document.surface.id}, expected ${advertised.surfaceId}.`);
}
return document;
}
async document({ schemaId, recordRef }) {
if (!schemaId || !recordRef) throw new Error("Embedded documents require schemaId and recordRef.");
return this.#document(schemaId, recordRef, "embedded-document");
}
async submitCommand(request) {
const advertisement = await this.providerAdvertisement();
const surface = selectSurface(advertisement, request?.surfaceId);
const interaction = surface.worldInteraction;
if (!interaction?.commandRecordRef) throw new Error(`Surface ${surface.surfaceId} advertises no command record reference.`);
const command = String(request?.command || "").trim();
if (!command) throw new Error("Command is required.");
const issuedAtUtc = request.issuedAtUtc || new Date().toISOString();
const commandId = request.commandId || `${this.runtimeId}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
const payload = [commandSchema, commandId, advertisement.providerId, surface.surfaceId, command, issuedAtUtc, request.clientId || this.runtimeId, objectValue(request.payload)];
const peer = await this.#getPeer();
peer.send({
schemaVersion: "cultnet.document_put_raw.v0",
messageId: commandId,
document: {
schemaId: commandSchema,
recordKey: childRecordRef(interaction.commandRecordRef, commandId),
storedAt: issuedAtUtc,
payloadEncoding: "messagepack",
payload: this.encode(payload),
sourceRuntimeId: this.runtimeId,
sourceRole: "eve-runtime",
tags: ["eve", "surface-command"],
},
});
return {
commandId,
providerId: advertisement.providerId,
surfaceId: surface.surfaceId,
state: "submitted",
receiptSchema: interaction.receiptSchema,
receiptRecordRef: interaction.receiptRecordRef
? childRecordRef(interaction.receiptRecordRef, commandId)
: "",
};
}
async receipt(submission) {
if (!submission?.receiptSchema || !submission?.receiptRecordRef) {
throw new Error("The command submission has no advertised receipt location.");
}
return this.#document(submission.receiptSchema, submission.receiptRecordRef, "receipt");
}
async #document(schemaId, recordRef, kind) {
const handle = this.mesh.documentFromPublication(
{ kind: "peer-snapshot", peer: () => this.#getPeer(), endpoint: this.#endpoint() },
schemaId,
recordRef,
{
documentId: recordRef,
routeHint: this.context.routeHint,
sourceId: recordRef,
timeoutMs: this.timeoutMs,
pollMs: 100,
messageIdPrefix: `${this.runtimeId}:${kind}`,
},
);
return handle.latest(this.context);
}
async #getPeer() {
if (this.#peer) return this.#peer;
const peers = this.mesh.createPeerCatalog();
const leases = this.mesh.createAuthorityLeaseCatalog();
const leaseId = `${this.target.peerId}:authority`;
peers.upsert({ peerId: this.target.peerId, verseId: this.target.verseId, endpoints: this.target.endpoints, roles: [this.target.role], authorityLeaseId: leaseId });
leases.upsert({ leaseId, verseId: this.target.verseId, peerId: this.target.peerId, roles: [this.target.role], validFrom: new Date(Date.now() - 60000), expiresAt: new Date(Date.now() + 60000) });
this.#peer = await this.mesh.createRudpPeerForAuthorizedPeer(this.runtimeId, connectionId, peers, leases, this.target.verseId, this.target.role, { connectTimeoutMs: this.timeoutMs, maxFragmentBytes: 1200, maxPendingReliablePackets: 512 });
this.#peer.on?.("close", () => { this.#peer = null; });
return this.#peer;
}
#endpoint() {
const endpoint = this.target.endpoints.find(value => value.toLowerCase().startsWith("rudp://"));
if (!endpoint) throw new Error(`Provider ${this.target.providerId || this.target.peerId} has no Odin-resolved RUDP endpoint.`);
return endpoint;
}
}
export function normalizeProviderAdvertisement(value) {
const providerId = stringField(value, "providerId", 1);
if (!providerId) throw new Error("Provider advertisement is missing providerId.");
const surfaces = arrayField(value, "surfaces", 11).map(normalizeAdvertisedSurface).filter(surface => surface.surfaceId);
return {
schema: stringField(value, "schema", 0), providerId,
serviceId: stringField(value, "serviceId", 2), verseId: stringField(value, "verseId", 3),
title: stringField(value, "title", 4), kind: stringField(value, "kind", 5),
cultMeshAddress: stringField(value, "cultMeshAddress", 6), surfaces,
};
}
export function normalizeSurfaceDocument(value) {
const portable = stringField(value, "schema", 1) === "gamecult.eve.surface.v1";
const surface = field(value, "surface", portable ? 7 : 5);
const id = stringField(surface, "id", 0);
if (!id) throw new Error("Eve surface document is missing surface.id.");
return {
schema: "gamecult.eve.surface.v1",
providerId: stringField(value, "providerId", portable ? 2 : 0),
providerKind: stringField(value, "providerKind", portable ? 3 : 1),
title: stringField(value, "title", portable ? 4 : 2),
version: numberField(value, "version", portable ? 5 : 3),
updatedAtUtc: stringField(value, "updatedAtUtc", portable ? 6 : 4),
surface: { id, root: field(surface, "root", 1), styles: arrayField(surface, "styles", 2) },
commands: arrayField(value, "commands", portable ? 8 : 6),
};
}
function normalizeAdvertisedSurface(value) {
const interaction = field(value, "worldInteraction", 6);
return {
surfaceId: stringField(value, "surfaceId", 0), schema: stringField(value, "schema", 1),
recordRef: stringField(value, "recordRef", 2), transport: stringField(value, "transport", 3),
status: stringField(value, "status", 4), surfaceKind: stringField(value, "surfaceKind", 5),
worldInteraction: interaction ? {
projectionKind: stringField(interaction, "projectionKind", 0),
stateSchemas: arrayField(interaction, "stateSchemas", 1),
commandBoundary: stringField(interaction, "commandBoundary", 2),
commandRecordRef: stringField(interaction, "commandRecordRef", 3),
receiptSchema: stringField(interaction, "receiptSchema", 4),
receiptRecordRef: stringField(interaction, "receiptRecordRef", 5),
assetManifestRecordRef: stringField(interaction, "assetManifestRecordRef", 6),
loweringTargets: arrayField(interaction, "loweringTargets", 7), ownership: stringField(interaction, "ownership", 8),
} : null,
};
}
function selectSurface(advertisement, surfaceId) {
const selected = surfaceId ? advertisement.surfaces.find(value => value.surfaceId === surfaceId) : advertisement.surfaces[0];
if (!selected) throw new Error(`Provider ${advertisement.providerId} does not advertise surface ${surfaceId || "(first)"}.`);
if (selected.transport !== "cultmesh-record" || !selected.recordRef) throw new Error(`Surface ${selected.surfaceId} is not a readable CultMesh record.`);
return selected;
}
function normalizeTarget(value) {
if (!value?.advertisementRecordRef) throw new Error("An Odin-resolved advertisementRecordRef is required.");
return { providerId: String(value.providerId || ""), advertisementRecordRef: String(value.advertisementRecordRef), peerId: String(value.peerId || "provider"), verseId: String(value.verseId || "provider"), role: String(value.role || "provider"), endpoints: Array.isArray(value.endpoints) ? value.endpoints.map(String) : [] };
}
function childRecordRef(parent, child) { return `${String(parent).replace(/\/$/u, "")}/${encodeURIComponent(child)}`; }
function field(value, name, slot) { return value && typeof value === "object" ? (value[name] ?? value[slot]) : undefined; }
function stringField(value, name, slot) { const result = field(value, name, slot); return typeof result === "string" ? result : ""; }
function numberField(value, name, slot) { const result = field(value, name, slot); return typeof result === "number" && Number.isFinite(result) ? result : 0; }
function arrayField(value, name, slot) { const result = field(value, name, slot); return Array.isArray(result) ? result : []; }
function objectValue(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : {}; }
function requireFunctionSet(value, names, label) { if (!value) throw new Error(`${label} is required.`); for (const name of names) if (typeof value[name] !== "function") throw new Error(`${label}.${name} is required.`); return value; }

13
src/index.d.ts vendored
View file

@ -1,5 +1,18 @@
import type { BrowserWindow, BrowserWindowConstructorOptions, IpcMain, Shell } from "electron"; import type { BrowserWindow, BrowserWindowConstructorOptions, IpcMain, Shell } from "electron";
export declare class EveCultMeshProviderClient {
constructor(target: object, dependencies: { CultMesh: object; encode: (value: unknown) => Uint8Array }, options?: { runtimeId?: string; timeoutMs?: number });
close(): Promise<void>;
providerAdvertisement(): Promise<object>;
surface(surfaceId?: string): Promise<object>;
document(request: { schemaId: string; recordRef: string }): Promise<unknown>;
submitCommand(request: object): Promise<object>;
receipt(submission: object): Promise<unknown>;
}
export declare function normalizeProviderAdvertisement(value: unknown): object;
export declare function normalizeSurfaceDocument(value: unknown): object;
export interface EveElectronWindowOptions extends BrowserWindowConstructorOptions { export interface EveElectronWindowOptions extends BrowserWindowConstructorOptions {
browserWindow?: BrowserWindowConstructorOptions; browserWindow?: BrowserWindowConstructorOptions;
openExternal?: boolean; openExternal?: boolean;

View file

@ -1,2 +1,3 @@
export * from "./eve-electron-shell.mjs"; export * from "./eve-electron-shell.mjs";
export * from "./cultmesh-provider-client.mjs";
export * from "./window-host.mjs"; export * from "./window-host.mjs";

View file

@ -0,0 +1,54 @@
import assert from "node:assert/strict";
import test from "node:test";
import { EveCultMeshProviderClient } from "../src/cultmesh-provider-client.mjs";
const advertisement = [
"gamecult.eve.provider_advertisement.v1", "test-provider", "test.service", "test.verse",
"Test Provider", "game.runtime", "cultmesh://test/provider", "2026-07-10T00:00:00Z",
["live", "2026-07-10T00:00:00Z", 15000], [], [], [[
"world", "gamecult.eve.surface.v1", "records/world", "cultmesh-record", "available", "interactive-world",
["provider-world", [], "commands", "records/commands", "gamecult.eve.command_receipt.v1", "records/receipts", "records/assets", ["electron-shell"], "provider owns truth"],
]], [],
];
const surface = ["surface-state", "gamecult.eve.surface.v1", "test-provider", "daemon", "World", 3, "now", ["world", { id: "root", kind: "world.scene", children: [] }, []], []];
function harness() {
const sent = [];
const records = new Map([["records/provider", advertisement], ["records/world", surface], ["records/receipts/cmd-1", { state: "accepted" }]]);
const peer = { send: message => sent.push(message), close() {}, on() {} };
const mesh = {
createPeerCatalog: () => ({ upsert() {} }), createAuthorityLeaseCatalog: () => ({ upsert() {} }),
createRudpPeerForAuthorizedPeer: async () => peer,
documentFromPublication: (_source, _schema, recordRef) => ({ latest: async () => {
if (!records.has(recordRef)) throw new Error(`missing ${recordRef}`);
return records.get(recordRef);
} }),
verse: () => ({ context: { routeHint: { transport: "network" } } }),
};
const client = new EveCultMeshProviderClient({
providerId: "test-provider", advertisementRecordRef: "records/provider", peerId: "test-peer",
verseId: "test.verse", role: "provider", endpoints: ["rudp://127.0.0.1:7777"],
}, { CultMesh: mesh, encode: value => Uint8Array.from([value.length]) }, { runtimeId: "test-runtime" });
return { client, sent };
}
test("follows provider-advertised record references", async () => {
const { client } = harness();
const provider = await client.providerAdvertisement();
assert.equal(provider.surfaces[0].recordRef, "records/world");
assert.equal((await client.surface("world")).surface.id, "world");
});
test("submits at the advertised command boundary and reads the advertised receipt", async () => {
const { client, sent } = harness();
const submission = await client.submitCommand({ surfaceId: "world", command: "move", commandId: "cmd-1", payload: { x: 1 } });
assert.equal(sent[0].document.recordKey, "records/commands/cmd-1");
assert.equal(sent[0].document.sourceRole, "eve-runtime");
assert.equal(submission.receiptRecordRef, "records/receipts/cmd-1");
assert.deepEqual(await client.receipt(submission), { state: "accepted" });
});
test("refuses unadvertised surfaces", async () => {
const { client } = harness();
await assert.rejects(() => client.surface("missing"), /does not advertise/);
});