Implement durable typed journals, subscriptions, and Reader document connections
Add checked native journal/subscription bindings, managed-reference retention, bounded history and backpressure, atomic delivery receipts, and fenced hosting. Preserve source publication prediction and replica reconciliation; subscription attachment, controls, and delivery remain authoritative in this initial version. Connect independent manual/Gutenberg document producers to Reader bookshelves through typed adapters. Add connection inspection, shared provider selection, authoring documentation, and restart/rejection/fan-out integration coverage. Verified compiler suite (287 passed, two existing skips), Nix runtime and PostgreSQL qualification, five example assemblies, headless Reader integration, Chromium Reader integration, resource pins, formatting, and lint. No pinned workspace runtime was deployed.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import {
|
||||
valueType,
|
||||
type JournalPrimitive,
|
||||
type InterfaceOperation,
|
||||
type InterfaceRevision,
|
||||
type ObjectExpectation,
|
||||
type OperationId,
|
||||
type ValueType,
|
||||
} from "./types.js";
|
||||
import { valueTypesEqual } from "./validation.js";
|
||||
|
||||
export const subscriptionStatusType: ValueType = {
|
||||
kind: "record",
|
||||
fields: {
|
||||
status: valueType.string,
|
||||
generation: valueType.uint64,
|
||||
cursor: valueType.uint64,
|
||||
failure: {
|
||||
kind: "optional",
|
||||
value: {
|
||||
kind: "record",
|
||||
fields: { publication: valueType.string, code: valueType.string, message: valueType.string },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/** Native subscription configuration is selected by the checked attach contract.
|
||||
* No library/interface ID or handler method name is reserved by Camino. */
|
||||
export function subscriptionContract(
|
||||
payload: ValueType,
|
||||
parameters: ValueType,
|
||||
attach: InterfaceOperation,
|
||||
interfaces: readonly InterfaceRevision[],
|
||||
): { source: ObjectExpectation; handler: ObjectExpectation; operation: OperationId } {
|
||||
const fail = (message: string): never => {
|
||||
throw new Error(message);
|
||||
};
|
||||
if (
|
||||
attach.mode !== "call" ||
|
||||
attach.scope === "class" ||
|
||||
!valueTypesEqual(attach.outputType, valueType.unit) ||
|
||||
attach.inputType.kind !== "record"
|
||||
)
|
||||
return fail("Subscription attachment must be an object call returning unit");
|
||||
const fields = attach.inputType.fields;
|
||||
if (
|
||||
Object.keys(fields).sort().join(",") !== "handler,parameters,source,start" ||
|
||||
fields.source?.kind !== "object-ref" ||
|
||||
fields.handler?.kind !== "object-ref" ||
|
||||
fields.handler.expectation.kind !== "interface" ||
|
||||
!valueTypesEqual(fields.parameters!, parameters)
|
||||
)
|
||||
return fail("Attachment needs typed source/handler references, matching parameters, and a start policy");
|
||||
const start = fields.start;
|
||||
if (
|
||||
start?.kind !== "variant" ||
|
||||
Object.keys(start.cases).sort().join(",") !== "afterCursor,future,retained" ||
|
||||
!valueTypesEqual(start.cases.future!.value, valueType.unit) ||
|
||||
!valueTypesEqual(start.cases.retained!.value, valueType.unit) ||
|
||||
!valueTypesEqual(start.cases.afterCursor!.value, valueType.string)
|
||||
)
|
||||
return fail("Subscription start requires future:unit, retained:unit and afterCursor:string variant cases");
|
||||
const handler = fields.handler.expectation;
|
||||
const expected: ValueType = { kind: "record", fields: { event: payload, parameters } };
|
||||
const candidates =
|
||||
interfaces
|
||||
.find((iface) => iface.revisionId === handler.interfaceRevisionId)
|
||||
?.members.flatMap((member) => member.operations)
|
||||
.filter(
|
||||
(operation) =>
|
||||
operation.mode === "call" &&
|
||||
operation.scope !== "class" &&
|
||||
operation.execution?.kind === "transaction" &&
|
||||
operation.execution.targets.includes("server") &&
|
||||
valueTypesEqual(operation.inputType, expected) &&
|
||||
valueTypesEqual(operation.outputType, valueType.unit),
|
||||
) ?? [];
|
||||
if (candidates.length !== 1)
|
||||
return fail("Handler interface must select one unambiguous server transaction from {event, parameters} to unit");
|
||||
return { source: fields.source.expectation, handler, operation: candidates[0]!.id };
|
||||
}
|
||||
|
||||
/** Journals persist checked values, including managed references. They cannot
|
||||
* persist process handles, opaque messages or edit commands. */
|
||||
export function journalPayloadType(type: ValueType): boolean {
|
||||
switch (type.kind) {
|
||||
case "scalar":
|
||||
case "object-ref":
|
||||
return true;
|
||||
case "builtin":
|
||||
return type.name === "unit";
|
||||
case "list":
|
||||
case "optional":
|
||||
return journalPayloadType(type.value);
|
||||
case "record":
|
||||
return Object.values(type.fields).every(journalPayloadType);
|
||||
case "variant":
|
||||
return Object.values(type.cases).every((entry) => journalPayloadType(entry.value));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export function journalPrimitiveSignature(
|
||||
payload: ValueType,
|
||||
primitive: JournalPrimitive,
|
||||
): Pick<InterfaceOperation, "mode" | "inputType" | "outputType"> {
|
||||
const optional = (value: ValueType): ValueType => ({ kind: "optional", value });
|
||||
if (primitive === "publish")
|
||||
return {
|
||||
mode: "call",
|
||||
inputType: payload,
|
||||
outputType: { kind: "record", fields: { publication: valueType.string } },
|
||||
};
|
||||
if (primitive === "head") return { mode: "call", inputType: valueType.unit, outputType: optional(valueType.uint64) };
|
||||
return {
|
||||
mode: "call",
|
||||
inputType: { kind: "record", fields: { after: optional(valueType.string), limit: valueType.uint32 } },
|
||||
outputType: {
|
||||
kind: "record",
|
||||
fields: {
|
||||
entries: {
|
||||
kind: "list",
|
||||
value: { kind: "record", fields: { publication: valueType.string, position: valueType.uint64, payload } },
|
||||
},
|
||||
next: optional(valueType.string),
|
||||
asOfHead: optional(valueType.uint64),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user