Export template source from be30143a326d5d7d7b0cbabea5bc5ac0301ef5b9:quixos-instance/packages/presentation-runtime
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"monorepoCommit": "be30143a326d5d7d7b0cbabea5bc5ac0301ef5b9",
|
||||||
|
"sourcePath": "quixos-instance/packages/presentation-runtime"
|
||||||
|
}
|
||||||
Generated
+108
@@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"helpers": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1790852599,
|
||||||
|
"narHash": "sha256-MdNtZpcsoguLPSCpAy9Naz6fkVPrftbZQbz1Aj7Zm9A=",
|
||||||
|
"ref": "refs/tags/quixos-reachability/91679e8cc82133093cb0b4f9e519cbb39a5bba4f",
|
||||||
|
"rev": "91679e8cc82133093cb0b4f9e519cbb39a5bba4f",
|
||||||
|
"revCount": 54,
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "refs/tags/quixos-reachability/91679e8cc82133093cb0b4f9e519cbb39a5bba4f",
|
||||||
|
"rev": "91679e8cc82133093cb0b4f9e519cbb39a5bba4f",
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1783776592,
|
||||||
|
"narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"protocol": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1791261504,
|
||||||
|
"narHash": "sha256-QSk9DWR7n5KL6cMHzmSKKXNIyaFA0s8Nu+wXjhhlENI=",
|
||||||
|
"ref": "refs/tags/quixos-reachability/5b44c8154dc038efd87cf1cbdd2c00b66bfd4f87",
|
||||||
|
"rev": "5b44c8154dc038efd87cf1cbdd2c00b66bfd4f87",
|
||||||
|
"revCount": 184,
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"ref": "refs/tags/quixos-reachability/5b44c8154dc038efd87cf1cbdd2c00b66bfd4f87",
|
||||||
|
"rev": "5b44c8154dc038efd87cf1cbdd2c00b66bfd4f87",
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": [
|
||||||
|
"protocol",
|
||||||
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"helpers": "helpers",
|
||||||
|
"nixpkgs": [
|
||||||
|
"protocol",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"protocol": "protocol"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
inputs.protocol.url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git?ref=refs/tags/quixos-reachability/5b44c8154dc038efd87cf1cbdd2c00b66bfd4f87&rev=5b44c8154dc038efd87cf1cbdd2c00b66bfd4f87";
|
||||||
|
inputs.nixpkgs.follows = "protocol/nixpkgs";
|
||||||
|
inputs.flake-utils.follows = "protocol/flake-utils";
|
||||||
|
inputs.helpers = {
|
||||||
|
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git?ref=refs/tags/quixos-reachability/91679e8cc82133093cb0b4f9e519cbb39a5bba4f&rev=91679e8cc82133093cb0b4f9e519cbb39a5bba4f";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
|
outputs =
|
||||||
|
{
|
||||||
|
nixpkgs,
|
||||||
|
flake-utils,
|
||||||
|
helpers,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
camino = import (helpers + "/quixos-package-helpers.nix");
|
||||||
|
in
|
||||||
|
{
|
||||||
|
quixosPackages.checkedArtifacts =
|
||||||
|
context:
|
||||||
|
let
|
||||||
|
pkgs = camino.packageSet { inherit nixpkgs system context; };
|
||||||
|
dependencies =
|
||||||
|
if builtins.pathExists ./yarn-project.nix then
|
||||||
|
camino.mkCaminoYarnDependencies {
|
||||||
|
inherit pkgs;
|
||||||
|
src = ./.;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
in
|
||||||
|
(camino.mkCaminoTypeScriptPackage {
|
||||||
|
inherit pkgs context dependencies;
|
||||||
|
src = ./.;
|
||||||
|
entry = "src/portable.ts";
|
||||||
|
});
|
||||||
|
devShells.default = camino.mkCaminoYarnShell { inherit pkgs; };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "@quixos/presentation-runtime",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"packageManager": "yarn@4.18.0",
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "qx-workspace check"
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||||
|
import interface PresentationStates;
|
||||||
|
import interface PresentationSubject;
|
||||||
|
import interface PresentationStateValues;
|
||||||
|
import interface PresentationState;
|
||||||
|
import interface PresentationMount;
|
||||||
|
import interface PresentationStateFactory;
|
||||||
|
external atom PresentationMountRecord id "atom:org.quixos.presentation-mount";
|
||||||
|
package PresentationRuntime id "package:org.quixos.presentation-runtime" revision "package:org.quixos.presentation-runtime@1" {
|
||||||
|
operation copyEmpty id "presentation-runtime:copy-empty" : optional<interface-ref<PresentationState>> -> interface-ref<PresentationMount> mode call execution transaction targets [JavaScript,server] receiver any requires {creation presentationMount id "presentationMount" : PresentationMountRecord input unit;};
|
||||||
|
operation findEmpty id "presentation-runtime:find-empty" : string -> optional<interface-ref<PresentationState>> mode call execution snapshot targets [JavaScript,server] receiver any requires {};
|
||||||
|
operation attachEmpty id "presentation-runtime:attach-empty" : interface-ref<PresentationState> -> unit mode call execution transaction targets [JavaScript,server] receiver any requires {};
|
||||||
|
function createPresentationMount id "presentation-runtime:create" : unit -> interface-ref<PresentationMount> execution transaction targets [JavaScript,server] requires {
|
||||||
|
creation presentationMount id "presentationMount" : PresentationMountRecord input unit;
|
||||||
|
};
|
||||||
|
operation attach id "presentation-runtime:attach" : record {presentationMount:interface-ref<PresentationMount>;parent:optional<interface-ref<PresentationState>>;} -> unit mode call execution transaction targets [JavaScript,server] receiver any requires {
|
||||||
|
edge owner id "owner" : optional-one interface PresentationMount [resolve];
|
||||||
|
edge parent id "parent" : optional-one interface PresentationState [resolve,connect];
|
||||||
|
edge rootOf id "rootOf" : optional-one interface PresentationMount [connect];
|
||||||
|
interface metadata id "metadata" : PresentationState using [presentationMount.resolve] from input;
|
||||||
|
interface presentationMount id "presentationMount" : PresentationMount using [root.resolve,attach.call] from input;
|
||||||
|
};
|
||||||
|
operation claim id "presentation-runtime:claim" : interface-ref<PresentationState> -> unit mode call execution transaction targets [JavaScript,server] receiver any requires {
|
||||||
|
edge members id "members" : many-unique interface PresentationState keyed "string" [connect];
|
||||||
|
edge root id "root" : optional-one interface PresentationState [resolve];
|
||||||
|
interface metadata id "metadata" : PresentationState using [occurrence.get,presentationMount.resolve,parent.resolve] from input;
|
||||||
|
};
|
||||||
|
operation create<object Navigation implements PresentationState> id "presentation-runtime:create-state" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> mode call execution transaction targets [JavaScript,server] receiver any requires {
|
||||||
|
interface metadata id "state" : PresentationState using [occurrence.get,presentationMount.resolve,parent.resolve,source.resolve,attach.call] from input;
|
||||||
|
interface factory id "factory" : PresentationStateFactory<Navigation> using [create.call];
|
||||||
|
};
|
||||||
|
operation ensure<object Navigation implements PresentationState> id "presentation-runtime:ensure-state" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> unit mode call execution transaction targets [JavaScript,server] receiver any requires {
|
||||||
|
interface presentationMount id "presentationMount" : PresentationMount using [find.call] from input;
|
||||||
|
interface metadata id "state" : PresentationState using [occurrence.get,presentationMount.resolve,parent.resolve,source.resolve,attach.call] from input;
|
||||||
|
interface factory id "factory" : PresentationStateFactory<Navigation> using [create.call];
|
||||||
|
};
|
||||||
|
operation find id "presentation-runtime:find" : string -> optional<interface-ref<PresentationState>> mode call execution snapshot targets [JavaScript,server] receiver any requires {
|
||||||
|
edge members id "members" : many-unique interface PresentationState keyed "string" [resolve];
|
||||||
|
};
|
||||||
|
operation allocate<object Navigation implements PresentationState> id "presentation-runtime:allocate" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> interface-ref<PresentationState> mode call execution transaction targets [JavaScript,server] receiver any requires {interface source id "source" : PresentationStates<Navigation> using [create.call];};
|
||||||
|
operation copy id "presentation-runtime:copy" : optional<interface-ref<PresentationState>> -> interface-ref<PresentationMount> mode call execution transaction targets [JavaScript,server] receiver any requires {
|
||||||
|
edge members id "members" : many-unique interface PresentationState keyed "string" [resolve];
|
||||||
|
edge root id "root" : optional-one interface PresentationState [resolve];
|
||||||
|
interface metadata id "metadata" : PresentationState using [parent.resolve,occurrence.get,presentationMount.resolve,allocate.call,copyValues.call] from input;
|
||||||
|
creation presentationMount id "presentationMount" : PresentationMountRecord input unit;
|
||||||
|
};
|
||||||
|
query RootInputs id "presentation:root-inputs" root PresentationMount document "queries/root.graphql" operation "PresentationRoot" {replica;max rows 5000;max result-bytes 8388608;}
|
||||||
|
query RootView id "presentation:root-view" root PresentationMount document "queries/root.graphql" operation "PresentationRoot" {local from RootInputs;watch;max rows 5000;max result-bytes 8388608;}
|
||||||
|
query SubjectInputs id "presentation:subject-inputs" root PresentationSubject document "queries/subject.graphql" operation "PresentationSubject" {replica;}
|
||||||
|
query SubjectView id "presentation:subject-view" root PresentationSubject document "queries/subject.graphql" operation "PresentationSubject" {local from SubjectInputs;watch;}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
query PresentationRoot {
|
||||||
|
root {
|
||||||
|
members(all: 512) {
|
||||||
|
entries {
|
||||||
|
mapKey
|
||||||
|
node {
|
||||||
|
source { _camino { ref } }
|
||||||
|
parent { _camino { ref } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root {
|
||||||
|
_camino { ref }
|
||||||
|
occurrence
|
||||||
|
source { _camino { ref } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
query PresentationSubject {
|
||||||
|
root { _camino { ref } }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
quixos-lock version 1 {
|
||||||
|
quixos source { repository "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos.git"; commit "be30143a326d5d7d7b0cbabea5bc5ac0301ef5b9"; }
|
||||||
|
interface PresentationStates source { repository "https://repos.quixos.org/quixos-studio/interface-presentation-states.git"; commit "9828a789b25e8a009f6b55cb32cf7e9ce1e3ac3c"; }
|
||||||
|
interface PresentationSubject source { repository "https://repos.quixos.org/quixos-studio/interface-presentation-subject.git"; commit "26b1dc891485fa268570718bf283f440691654a7"; }
|
||||||
|
interface PresentationStateValues source { repository "https://repos.quixos.org/quixos-studio/interface-presentation-state-values.git"; commit "50e11ed980b909f428e99a9b30ede1bb24941ab7"; }
|
||||||
|
interface PresentationState source { repository "https://repos.quixos.org/quixos-studio/interface-presentation-state.git"; commit "94db4272bcca52fbb23325bfa305d7331ea08602"; }
|
||||||
|
interface PresentationMount source { repository "https://repos.quixos.org/quixos-studio/interface-presentation-mount.git"; commit "30566340643355d8509bf116c2c3d91f09173d98"; }
|
||||||
|
interface PresentationStateFactory source { repository "https://repos.quixos.org/quixos-studio/interface-presentation-state-factory.git"; commit "87685dc5e408f93a778a83e31463734b8df7709a"; }
|
||||||
|
}
|
||||||
+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