Thing/packages/eve-contracts/dist/index.js

135 lines
5.8 KiB
JavaScript

import Ajv2020 from "ajv/dist/2020.js";
import { eveContractSchemas } from "./generated/schemas.js";
export { eveContractSchemas } from "./generated/schemas.js";
export const EVE_PROVIDER_ADVERTISEMENT_SCHEMA = "gamecult.eve.provider_advertisement.v1";
export const EVE_SURFACE_SCHEMA = "gamecult.eve.surface.v1";
export const EVE_COMMAND_DESCRIPTOR_SCHEMA = "gamecult.eve.command.v1";
export const EVE_COMMAND_INVOCATION_SCHEMA = "gamecult.eve.command_invocation.v1";
export const EVE_COMMAND_RECEIPT_SCHEMA = "gamecult.eve.command_receipt.v1";
export const EVE_COMMAND_RESULT_SCHEMA = "gamecult.eve.command_result.v1";
export const EVE_INPUT_CAPABILITY_SCHEMA = "gamecult.eve.input_capability.v1";
const ajv = new Ajv2020({
allErrors: true,
allowUnionTypes: true,
strict: true,
strictRequired: false,
strictTypes: false,
});
for (const schema of Object.values(eveContractSchemas))
ajv.addSchema(schema);
const validators = Object.fromEntries(Object.entries(eveContractSchemas).map(([name, schema]) => [name, requiredValidator(schema.$id)]));
export class EveContractValidationError extends TypeError {
contract;
errors;
constructor(contract, errors) {
super(`Invalid Eve ${contract}: ${ajv.errorsText(errors, { separator: "; " })}`);
this.name = "EveContractValidationError";
this.contract = contract;
this.errors = errors;
}
}
export function isEveContract(contract, value) {
return Boolean(validators[contract](value));
}
export function parseEveContract(contract, value) {
const validator = validators[contract];
if (!validator(value))
throw new EveContractValidationError(contract, validator.errors ?? []);
return value;
}
export const isEveProviderAdvertisement = (value) => isEveContract("providerAdvertisement", value);
export const isEveSurfaceDocument = (value) => isEveContract("surface", value);
export const isEveCommandDescriptor = (value) => isEveContract("commandDescriptor", value);
export const isEveCommandInvocation = (value) => isEveContract("commandInvocation", value);
export const isEveCommandReceipt = (value) => isEveContract("commandReceipt", value);
export const isEveCommandResult = (value) => isEveContract("commandResult", value);
export const isEveInputCapability = (value) => isEveContract("inputCapability", value);
export const parseEveProviderAdvertisement = (value) => parseEveContract("providerAdvertisement", normalizeEveProviderAdvertisement(value));
export const parseEveSurfaceDocument = (value) => parseEveContract("surface", value);
export const parseEveCommandDescriptor = (value) => parseEveContract("commandDescriptor", value);
export const parseEveCommandInvocation = (value) => parseEveContract("commandInvocation", value);
export const parseEveCommandReceipt = (value) => parseEveContract("commandReceipt", value);
export const parseEveCommandResult = (value) => {
const result = parseEveContract("commandResult", value);
if (result.transientProjection)
parseEveSurfaceDocument(result.transientProjection);
return result;
};
export const parseEveInputCapability = (value) => parseEveContract("inputCapability", value);
function requiredValidator(schemaId) {
const validator = ajv.getSchema(schemaId);
if (!validator)
throw new Error(`Eve contract schema ${schemaId} was not registered.`);
return validator;
}
/**
* Converts the canonical positional MessagePack representation used by the C#
* Eve package into the JSON-shaped provider contract used at browser/model
* boundaries. This is serialization lowering, not a second advertisement DTO.
*/
export function normalizeEveProviderAdvertisement(value) {
if (!Array.isArray(value))
return value;
if (value.length < 13)
return value;
const [schema, providerId, serviceId, verseId, title, kind, cultMeshAddress, updatedAtUtc, freshness, schemas, witnesses, surfaces, commands, authorizedBodyProducerIds] = value;
return {
schema,
providerId,
serviceId,
verseId,
title,
kind,
cultMeshAddress,
updatedAtUtc,
freshness: normalizeFreshness(freshness),
schemas,
witnesses: Array.isArray(witnesses) ? witnesses.map(normalizeWitness) : witnesses,
surfaces: Array.isArray(surfaces) ? surfaces.map(normalizeSurfaceAdvertisement) : surfaces,
commands: Array.isArray(commands) ? commands.map(normalizeAdvertisedCommand) : commands,
...(Array.isArray(authorizedBodyProducerIds) ? { authorizedBodyProducerIds } : {}),
};
}
function normalizeFreshness(value) {
return Array.isArray(value)
? { state: value[0], lastSeenAtUtc: value[1], maxAgeMs: value[2] }
: value;
}
function normalizeWitness(value) {
return Array.isArray(value)
? { kind: value[0], ref: value[1], summary: value[2] }
: value;
}
function normalizeSurfaceAdvertisement(value) {
if (!Array.isArray(value))
return value;
return {
surfaceId: value[0],
schema: value[1],
recordRef: value[2],
transport: value[3],
status: value[4],
surfaceKind: value[5],
...(value[6] ? { worldInteraction: normalizeWorldInteraction(value[6]) } : {}),
};
}
function normalizeWorldInteraction(value) {
if (!Array.isArray(value))
return value;
return {
projectionKind: value[0],
stateSchemas: value[1],
commandBoundary: value[2],
commandRecordRef: value[3],
receiptSchema: value[4],
receiptRecordRef: value[5],
assetManifestRecordRef: value[6],
loweringTargets: value[7],
ownership: value[8],
};
}
function normalizeAdvertisedCommand(value) {
return Array.isArray(value)
? { command: value[0], surfaceId: value[1], transport: value[2], summary: value[3] }
: value;
}