Export template source from b7b156d0ca1549a52cbea95609b23714449b3cc8:quixos-instance/packages/presentation-runtime
This commit is contained in:
+204
@@ -0,0 +1,204 @@
|
||||
import { QxReject, type ExactReference, type MutationKey } from "@quixos/camino-replica-core";
|
||||
import { createRegistry, generic, type Contexts } from "./gen/qx.js";
|
||||
const metadata = (ref: ExactReference) => ({
|
||||
...ref,
|
||||
expectation: { kind: "interface" as const, id: "interface:org.quixos.presentation-state@1" as const },
|
||||
});
|
||||
const same = (a: ExactReference | null, b: ExactReference | null) =>
|
||||
a === null || b === null ? a === b : a.workspace === b.workspace && a.object === b.object;
|
||||
|
||||
// These source contracts also exist in assemblies with no closed navigation
|
||||
// application. Generated Contexts contains only the applications actually used.
|
||||
type NavigationInput = Contexts["attach"]["input"] & { occurrence: string };
|
||||
type MetadataRequest = { receiver: ReturnType<typeof metadata>; input: null };
|
||||
type NavigationMetadata = {
|
||||
"occurrence.get"(request: MetadataRequest): Promise<string>;
|
||||
"source.resolve"(request: MetadataRequest): Promise<ExactReference>;
|
||||
"parent.resolve"(request: MetadataRequest): Promise<ExactReference | null>;
|
||||
"attach.call"(
|
||||
request: { receiver: ReturnType<typeof metadata>; input: Contexts["attach"]["input"] },
|
||||
key: MutationKey,
|
||||
): Promise<null>;
|
||||
};
|
||||
type CreateContext<Navigation extends ExactReference> = {
|
||||
receiver: ExactReference;
|
||||
input: NavigationInput;
|
||||
ports: {
|
||||
metadata: NavigationMetadata;
|
||||
factory: { "create.call"(input: { occurrence: string }, key: MutationKey): Promise<Navigation> };
|
||||
};
|
||||
};
|
||||
type EnsureContext = CreateContext<ExactReference> & {
|
||||
ports: {
|
||||
presentationMount: {
|
||||
"find.call"(request: {
|
||||
receiver: NavigationInput["presentationMount"];
|
||||
input: string;
|
||||
}): Promise<ReturnType<typeof metadata> | null>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
async function attach({ receiver, input, ports }: Contexts["attach"]) {
|
||||
const owner = (await ports.owner.resolve())[0]?.target ?? null;
|
||||
const parent = (await ports.parent.resolve())[0]?.target ?? null;
|
||||
if (owner) {
|
||||
if (!same(owner, input.presentationMount)) throw new QxReject("PRESENTATION_STATE_ALREADY_ATTACHED");
|
||||
if (!same(parent, input.parent)) throw new QxReject("OCCURRENCE_PARENT_MISMATCH");
|
||||
return null;
|
||||
}
|
||||
if (parent) throw new QxReject("UNATTACHED_PRESENTATION_PARENT");
|
||||
if (input.parent) {
|
||||
const parentOwner = await ports.metadata["presentationMount.resolve"]({ receiver: input.parent, input: null });
|
||||
if (!same(parentOwner, input.presentationMount)) throw new QxReject("PRESENTATION_PARENT_MISMATCH");
|
||||
await ports.parent.connect(input.parent, { key: "parent" });
|
||||
} else {
|
||||
if (await ports.presentationMount["root.resolve"]({ receiver: input.presentationMount, input: null }))
|
||||
throw new QxReject("PRESENTATION_ROOT_EXISTS");
|
||||
await ports.rootOf.connect(input.presentationMount, { key: "root" });
|
||||
}
|
||||
// The parent must already be attached. No operation reparents attached state,
|
||||
// so attachment cannot introduce a cycle, including through pending births.
|
||||
await ports.presentationMount["attach.call"](
|
||||
{ receiver: input.presentationMount, input: metadata(receiver) },
|
||||
{ key: "owner" },
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
async function create<Navigation extends ExactReference>({
|
||||
receiver,
|
||||
input,
|
||||
ports,
|
||||
}: CreateContext<Navigation>): Promise<Navigation> {
|
||||
if (!input.occurrence || input.occurrence.length > 128) throw new QxReject("INVALID_OCCURRENCE");
|
||||
const navigation = await ports.factory["create.call"]({ occurrence: input.occurrence }, { key: "navigation" });
|
||||
if ((await ports.metadata["occurrence.get"]({ receiver: metadata(navigation), input: null })) !== input.occurrence)
|
||||
throw new QxReject("PRESENTATION_OCCURRENCE_MISMATCH");
|
||||
if (!same(await ports.metadata["source.resolve"]({ receiver: metadata(navigation), input: null }), receiver))
|
||||
throw new QxReject("PRESENTATION_SOURCE_MISMATCH");
|
||||
await ports.metadata["attach.call"](
|
||||
{ receiver: metadata(navigation), input: { presentationMount: input.presentationMount, parent: input.parent } },
|
||||
{ key: "attach" },
|
||||
);
|
||||
return navigation;
|
||||
}
|
||||
export const registry = createRegistry({
|
||||
copyEmpty: async ({ input, ports }) => {
|
||||
if (input !== null) throw new QxReject("PRESENTATION_HAS_NO_STATE");
|
||||
return {
|
||||
...(await ports.presentationMount.create({ key: "copy" })),
|
||||
expectation: { kind: "interface", id: "interface:org.quixos.presentation-mount@1" },
|
||||
};
|
||||
},
|
||||
findEmpty: async () => null,
|
||||
attachEmpty: async () => {
|
||||
throw new QxReject("PRESENTATION_STATE_NOT_INSTALLED");
|
||||
},
|
||||
...generic.allocate(allocate),
|
||||
copy,
|
||||
...generic.create(create),
|
||||
find: async ({ input, ports }) =>
|
||||
(await ports.members.resolve()).find((entry) => entry.mapKey === input)?.target ?? null,
|
||||
...generic.ensure(async ({ receiver, input, ports }: EnsureContext) => {
|
||||
const existing = await ports.presentationMount["find.call"]({
|
||||
receiver: input.presentationMount,
|
||||
input: input.occurrence,
|
||||
});
|
||||
if (existing) {
|
||||
if (!same(await ports.metadata["source.resolve"]({ receiver: existing, input: null }), receiver))
|
||||
throw new QxReject("PRESENTATION_SOURCE_MISMATCH");
|
||||
if (!same(await ports.metadata["parent.resolve"]({ receiver: existing, input: null }), input.parent))
|
||||
throw new QxReject("OCCURRENCE_PARENT_MISMATCH");
|
||||
} else {
|
||||
await create<ExactReference>({ receiver, input, ports });
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
attach,
|
||||
claim: async ({ receiver, input, ports }) => {
|
||||
const request = { receiver: input, input: null };
|
||||
const owner = await ports.metadata["presentationMount.resolve"](request);
|
||||
if (owner) throw new QxReject("PRESENTATION_STATE_ALREADY_ATTACHED");
|
||||
const parent = await ports.metadata["parent.resolve"](request);
|
||||
if (parent) {
|
||||
if (!same(await ports.metadata["presentationMount.resolve"]({ receiver: parent, input: null }), receiver))
|
||||
throw new QxReject("PRESENTATION_PARENT_MISMATCH");
|
||||
} else if (!same((await ports.root.resolve())[0]?.target ?? null, input)) {
|
||||
throw new QxReject("PRESENTATION_ROOT_MISMATCH");
|
||||
}
|
||||
const occurrence = await ports.metadata["occurrence.get"](request);
|
||||
if (!occurrence || occurrence.length > 128) throw new QxReject("INVALID_OCCURRENCE");
|
||||
await ports.members.connect({ target: input, mapKey: occurrence }, { key: "member" });
|
||||
return null;
|
||||
},
|
||||
createPresentationMount: async ({ ports }) => ({
|
||||
...(await ports.presentationMount.create({ key: "presentationMount" })),
|
||||
expectation: { kind: "interface", id: "interface:org.quixos.presentation-mount@1" },
|
||||
}),
|
||||
});
|
||||
|
||||
async function allocate<Navigation extends ExactReference>({
|
||||
input,
|
||||
ports,
|
||||
}: {
|
||||
input: NavigationInput;
|
||||
ports: { source: { "create.call"(input: NavigationInput, key: MutationKey): Promise<Navigation> } };
|
||||
}) {
|
||||
return metadata(await ports.source["create.call"](input, { key: "allocate" }));
|
||||
}
|
||||
async function copy({ receiver, input, ports }: Contexts["copy"]) {
|
||||
const members = (await ports.members.resolve()).map((entry) => entry.target);
|
||||
if (members.length > 512) throw new QxReject("PRESENTATION_COPY_LIMIT");
|
||||
const originalRoot = (await ports.root.resolve())[0]?.target ?? null;
|
||||
const requestedRoot = input ?? originalRoot;
|
||||
if (!requestedRoot && members.length) throw new QxReject("PRESENTATION_ROOT_MISSING");
|
||||
if (requestedRoot && !members.some((member) => same(member, requestedRoot)))
|
||||
throw new QxReject("PRESENTATION_COPY_ROOT_MISMATCH");
|
||||
const byId = new Map(members.map((member) => [member.object, member]));
|
||||
const children = new Map<string, typeof members>();
|
||||
for (const member of members) {
|
||||
const owner = await ports.metadata["presentationMount.resolve"]({ receiver: member, input: null });
|
||||
if (!same(owner, receiver)) throw new QxReject("PRESENTATION_OWNER_MISMATCH");
|
||||
const parent = await ports.metadata["parent.resolve"]({ receiver: member, input: null });
|
||||
if (parent) {
|
||||
if (!byId.has(parent.object)) throw new QxReject("PRESENTATION_PARENT_MISMATCH");
|
||||
const siblings = children.get(parent.object) ?? [];
|
||||
siblings.push(member);
|
||||
children.set(parent.object, siblings);
|
||||
} else if (!same(member, originalRoot)) throw new QxReject("PRESENTATION_ROOT_MISMATCH");
|
||||
}
|
||||
const queue = requestedRoot ? [requestedRoot] : [];
|
||||
const visited = new Set<string>();
|
||||
for (let index = 0; index < queue.length; index++) {
|
||||
const item = queue[index]!;
|
||||
if (visited.has(item.object)) throw new QxReject("PRESENTATION_CYCLE");
|
||||
visited.add(item.object);
|
||||
queue.push(...(children.get(item.object) ?? []).sort((a, b) => a.object.localeCompare(b.object)));
|
||||
}
|
||||
if (!input && queue.length !== members.length) throw new QxReject("PRESENTATION_DISCONNECTED_STATE");
|
||||
const presentationMount = {
|
||||
...(await ports.presentationMount.create({ key: "presentationMount" })),
|
||||
expectation: { kind: "interface" as const, id: "interface:org.quixos.presentation-mount@1" as const },
|
||||
};
|
||||
const mapping: { source: (typeof members)[number]; target: (typeof members)[number] }[] = [];
|
||||
const copied = new Map<string, (typeof members)[number]>();
|
||||
for (const [index, source] of queue.entries()) {
|
||||
const originalParent = await ports.metadata["parent.resolve"]({ receiver: source, input: null });
|
||||
const parent = same(source, requestedRoot) ? null : copied.get(originalParent!.object)!;
|
||||
if (!same(source, requestedRoot) && !parent) throw new QxReject("PRESENTATION_COPY_PARENT_MISSING");
|
||||
const occurrence = await ports.metadata["occurrence.get"]({ receiver: source, input: null });
|
||||
const target = await ports.metadata["allocate.call"](
|
||||
{ receiver: source, input: { presentationMount, occurrence, parent } },
|
||||
{ key: "allocate:" + index },
|
||||
);
|
||||
copied.set(source.object, target);
|
||||
mapping.push({ source, target });
|
||||
}
|
||||
for (const [index, item] of mapping.entries())
|
||||
await ports.metadata["copyValues.call"](
|
||||
{ receiver: item.source, input: { target: item.target, mapping } },
|
||||
{ key: "values:" + index },
|
||||
);
|
||||
return presentationMount;
|
||||
}
|
||||
Reference in New Issue
Block a user