Compare commits

..
14 changed files with 208 additions and 43 deletions
+4
View File
@@ -0,0 +1,4 @@
{
"monorepoCommit": "15b93e42825a3f8055f6305999a12c5b1cc73330",
"sourcePath": "quixos-instance/packages/web-studio-canvas-runtime"
}
+12
View File
@@ -0,0 +1,12 @@
{
"messages": {
"org.quixos.web-studio.CanvasPosition": {
"module": "../messages.js",
"export": "positionBinding"
},
"org.quixos.web-studio.CanvasSize": {
"module": "../messages.js",
"export": "sizeBinding"
}
}
}
Generated
+7 -7
View File
@@ -37,17 +37,17 @@
"quixosNixHelpers": {
"flake": false,
"locked": {
"lastModified": 1788543053,
"narHash": "sha256-3YReATWEVxZinYVi/l3cwOz+oFoZUrwj24kUBQTmUpQ=",
"ref": "refs/tags/quixos-reachability/56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a",
"rev": "56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a",
"revCount": 8,
"lastModified": 1789354913,
"narHash": "sha256-vCdz/2jZ15GpA7YkmjLGOuO/LoZIeI8v7joDD5EI0oE=",
"ref": "refs/tags/quixos-reachability/8b2a224698da3659dd9a81e237dfe79238ee2836",
"rev": "8b2a224698da3659dd9a81e237dfe79238ee2836",
"revCount": 18,
"type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git"
},
"original": {
"ref": "refs/tags/quixos-reachability/56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a",
"rev": "56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a",
"ref": "refs/tags/quixos-reachability/8b2a224698da3659dd9a81e237dfe79238ee2836",
"rev": "8b2a224698da3659dd9a81e237dfe79238ee2836",
"type": "git",
"url": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git"
}
+2 -1
View File
@@ -6,7 +6,7 @@
flake-utils.url = "github:numtide/flake-utils";
quixosNixHelpers = {
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git?ref=refs/tags/quixos-reachability/56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a&rev=56bbcd22d17f8c7f120b1e0b5bd1d5a86e91a80a";
url = "git+https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git?ref=refs/tags/quixos-reachability/8b2a224698da3659dd9a81e237dfe79238ee2836&rev=8b2a224698da3659dd9a81e237dfe79238ee2836";
flake = false;
};
};
@@ -18,6 +18,7 @@
quixosHelpers.mkCaminoTsYarnNixifyFlake {
inherit inputs nixpkgs flake-utils;
packageRoot = ./.;
bindingOptions = builtins.fromJSON (builtins.readFile ./bindings.json);
promptName = "web-studio-canvas-runtime";
bundle = {
entry = "dist/server.js";
+1 -1
View File
@@ -13,7 +13,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@quixos/camino-package-runtime": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git#commit=a998690446ecf647e3af78e369492c1a3663b66d"
"@quixos/camino-package-runtime": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git#commit=110e41b268c8ac309e6757f2af3331de417ff9b0"
},
"devDependencies": {
"@types/node": "^24",
+9 -1
View File
@@ -5,7 +5,15 @@ external interface WebStudioPlaceable revision "interface:org.quixos.web-studio.
package WebStudioCanvasRuntime id "package:org.quixos.web-studio:canvas-runtime" revision "package:org.quixos.web-studio:canvas-runtime@1" {
constructor createCanvas id "export:org.quixos.web-studio.canvas-runtime:create-canvas" constructs WebStudioCanvas : unit;
constructor createPlacement id "export:org.quixos.web-studio.canvas-runtime:create-placement" constructs WebStudioCanvasPlacement : message "org.quixos.web-studio.CreateCanvasPlacement" requires {
constructor createPlacement id "export:org.quixos.web-studio.canvas-runtime:create-placement" constructs WebStudioCanvasPlacement : record {
canvas_id: interface-ref<WebStudioCanvasInterface>;
subject_id: interface-ref<WebStudioPlaceable>;
x: double;
y: double;
width: double;
height: double;
z: int32;
} requires {
state position id "port:web-studio-placement-constructor:position" : message "org.quixos.web-studio.CanvasPosition" [write];
state size id "port:web-studio-placement-constructor:size" : message "org.quixos.web-studio.CanvasSize" [write];
state z id "port:web-studio-placement-constructor:z" : int32 [write];
+1 -1
View File
@@ -1,6 +1,6 @@
quixos-lock version 1 {
quixos source {
repository "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos.git";
commit "9c3ab572ecfbc83785038c3d9f174516df15d63f";
commit "15b93e42825a3f8055f6305999a12c5b1cc73330";
}
}
+16 -15
View File
@@ -1,11 +1,12 @@
import {
objectRef,
type RuntimeContext,
type QxObjectRef,
} from "@quixos/camino-package-runtime";
import type { Contexts } from "./gen/qx.js";
export type PlacementInput = {
canvas_id: string;
subject_id: string;
canvas_id: QxObjectRef;
subject_id: QxObjectRef;
x: number;
y: number;
width: number;
@@ -17,10 +18,11 @@ const finiteNumber = (value: unknown, fallback: number) =>
typeof value === "number" && Number.isFinite(value) ? value : fallback;
const objectId = (value: unknown, field: string) => {
if (typeof value !== "string" || !value.startsWith("obj:")) {
throw new Error(`${field} must be a Camino object ID`);
try {
return objectRef(value as QxObjectRef);
} catch {
throw new Error(`${field} must be a managed object reference`);
}
return value;
};
export const normalizePlacementInput = (
@@ -35,19 +37,18 @@ export const normalizePlacementInput = (
z: Math.trunc(finiteNumber(input.z, 0)),
});
export const createCanvas = (context: RuntimeContext) =>
objectRef(context.objectId);
export const createCanvas = ({ objectId }: Contexts["createCanvas"]) => objectId;
export const createPlacement = async (context: RuntimeContext) => {
export const createPlacement = async (context: Contexts["createPlacement"]) => {
const input = normalizePlacementInput(context.input);
await Promise.all([
context.state<{ x: number; y: number }>("port:web-studio-placement-constructor:position")
context.ports.position
.set({ x: input.x, y: input.y }),
context.state<{ width: number; height: number }>("port:web-studio-placement-constructor:size")
context.ports.size
.set({ width: input.width, height: input.height }),
context.state<number>("port:web-studio-placement-constructor:z").set(input.z),
context.ports.z.set(input.z),
]);
await context.edge("port:web-studio-placement-constructor:subject").connect(input.subject_id);
await context.edge("port:web-studio-placement-constructor:canvas").connect(input.canvas_id);
return objectRef(context.objectId);
await context.ports.subject.connect(context.input.subject_id);
await context.ports.canvas.connect(context.input.canvas_id);
return context.objectId;
};
+128
View File
@@ -0,0 +1,128 @@
// Generated by quixos-codegen-ts. Do not edit. Binding ABI version 1.
import { bindQxHandler, type BindingValue, type QxObjectRef, type QxHandler, type QxContextLifecycle, type QxHandlerSpec, type QxMessages } from "@quixos/camino-package-runtime";
import { positionBinding as message0 } from "../messages.js";
import { sizeBinding as message1 } from "../messages.js";
export const packageRevisionId = "package:org.quixos.web-studio:canvas-runtime@1";
export type Contexts = { "createCanvas": { "objectId": QxObjectRef<"atom:atom:org.quixos.web-studio:canvas">; "input": null; "ports": { } } & QxContextLifecycle<{ "objectId": QxObjectRef<"atom:atom:org.quixos.web-studio:canvas">; "input": null; "ports": { } } & {signal?: AbortSignal}>; "createPlacement": { "objectId": QxObjectRef<"atom:atom:org.quixos.web-studio:canvas-placement">; "input": { "canvas_id": QxObjectRef<"interface:interface:org.quixos.web-studio.canvas@1">; "subject_id": QxObjectRef<"interface:interface:org.quixos.web-studio.placeable@1">; "x": number; "y": number; "width": number; "height": number; "z": number }; "ports": { "position": { "set": (value: BindingValue<typeof message0>) => Promise<void> }; "size": { "set": (value: BindingValue<typeof message1>) => Promise<void> }; "z": { "set": (value: number) => Promise<void> }; "canvas": { "connect": (target: QxObjectRef<"interface:interface:org.quixos.web-studio.canvas@1">) => Promise<void> }; "subject": { "connect": (target: QxObjectRef<"interface:interface:org.quixos.web-studio.placeable@1">) => Promise<void> } } } & QxContextLifecycle<{ "objectId": QxObjectRef<"atom:atom:org.quixos.web-studio:canvas-placement">; "input": { "canvas_id": QxObjectRef<"interface:interface:org.quixos.web-studio.canvas@1">; "subject_id": QxObjectRef<"interface:interface:org.quixos.web-studio.placeable@1">; "x": number; "y": number; "width": number; "height": number; "z": number }; "ports": { "position": { "set": (value: BindingValue<typeof message0>) => Promise<void> }; "size": { "set": (value: BindingValue<typeof message1>) => Promise<void> }; "z": { "set": (value: number) => Promise<void> }; "canvas": { "connect": (target: QxObjectRef<"interface:interface:org.quixos.web-studio.canvas@1">) => Promise<void> }; "subject": { "connect": (target: QxObjectRef<"interface:interface:org.quixos.web-studio.placeable@1">) => Promise<void> } } } & {signal?: AbortSignal}> };
export type Implementation = { "createCanvas": QxHandler<Contexts["createCanvas"], QxObjectRef<"atom:atom:org.quixos.web-studio:canvas">>; "createPlacement": QxHandler<Contexts["createPlacement"], QxObjectRef<"atom:atom:org.quixos.web-studio:canvas-placement">> };
const messages = { "org.quixos.web-studio.CanvasPosition": message0, "org.quixos.web-studio.CanvasSize": message1 } satisfies QxMessages;
const specs = {
"createCanvas": {
"inputType": {
"kind": "builtin",
"name": "unit"
},
"outputType": {
"kind": "object-ref",
"expectation": {
"kind": "atom",
"atomId": "atom:org.quixos.web-studio:canvas"
}
},
"ports": {}
},
"createPlacement": {
"inputType": {
"kind": "record",
"fields": {
"canvas_id": {
"kind": "object-ref",
"expectation": {
"kind": "interface",
"interfaceRevisionId": "interface:org.quixos.web-studio.canvas@1"
}
},
"subject_id": {
"kind": "object-ref",
"expectation": {
"kind": "interface",
"interfaceRevisionId": "interface:org.quixos.web-studio.placeable@1"
}
},
"x": {
"kind": "scalar",
"name": "double"
},
"y": {
"kind": "scalar",
"name": "double"
},
"width": {
"kind": "scalar",
"name": "double"
},
"height": {
"kind": "scalar",
"name": "double"
},
"z": {
"kind": "scalar",
"name": "int32"
}
}
},
"outputType": {
"kind": "object-ref",
"expectation": {
"kind": "atom",
"atomId": "atom:org.quixos.web-studio:canvas-placement"
}
},
"ports": {
"position": {
"kind": "state",
"valueType": {
"kind": "message",
"descriptorId": "org.quixos.web-studio.CanvasPosition"
},
"primitives": [
"write"
],
"id": "port:web-studio-placement-constructor:position"
},
"size": {
"kind": "state",
"valueType": {
"kind": "message",
"descriptorId": "org.quixos.web-studio.CanvasSize"
},
"primitives": [
"write"
],
"id": "port:web-studio-placement-constructor:size"
},
"z": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "int32"
},
"primitives": [
"write"
],
"id": "port:web-studio-placement-constructor:z"
},
"canvas": {
"kind": "edge",
"id": "port:web-studio-placement-constructor:canvas",
"primitives": [
"connect"
]
},
"subject": {
"kind": "edge",
"id": "port:web-studio-placement-constructor:subject",
"primitives": [
"connect"
]
}
}
}
} satisfies Record<string, QxHandlerSpec>;
export const createRuntime = (implementation: Implementation) => ({
packageRevisionId,
exports: {
"export:org.quixos.web-studio.canvas-runtime:create-canvas": bindQxHandler(specs["createCanvas"], implementation["createCanvas"], messages),
"export:org.quixos.web-studio.canvas-runtime:create-placement": bindQxHandler(specs["createPlacement"], implementation["createPlacement"], messages),
},
});
+10
View File
@@ -0,0 +1,10 @@
import { encodeQxValue, decodeQxValue, type MessageBinding, type QxValueType } from "@quixos/camino-package-runtime";
const position: QxValueType = {kind: "record", fields: {x: {kind: "scalar", name: "double"}, y: {kind: "scalar", name: "double"}}};
const size: QxValueType = {kind: "record", fields: {width: {kind: "scalar", name: "double"}, height: {kind: "scalar", name: "double"}}};
export const positionBinding: MessageBinding<{x: number; y: number}> = {
encode: value => encodeQxValue(position, value, {}), decode: value => decodeQxValue(position, value, {}),
};
export const sizeBinding: MessageBinding<{width: number; height: number}> = {
encode: value => encodeQxValue(size, value, {}), decode: value => decodeQxValue(size, value, {}),
};
+3 -7
View File
@@ -1,10 +1,6 @@
import { servePackageRuntime } from "@quixos/camino-package-runtime";
import { createCanvas, createPlacement } from "./canvas.impl.js";
import { createRuntime, type Implementation } from "./gen/qx.js";
servePackageRuntime({
packageRevisionId: "package:org.quixos.web-studio:canvas-runtime@1",
exports: {
"export:org.quixos.web-studio.canvas-runtime:create-canvas": createCanvas,
"export:org.quixos.web-studio.canvas-runtime:create-placement": createPlacement,
},
});
const implementation: Implementation = { createCanvas, createPlacement };
servePackageRuntime(createRuntime(implementation));
+10 -5
View File
@@ -1,19 +1,23 @@
import assert from "node:assert/strict";
import test from "node:test";
import { normalizePlacementInput } from "../dist/canvas.impl.js";
import { protoValueToJs } from "@quixos/camino-package-runtime";
const reference = (objectId) => protoValueToJs({kind: {case: "refValue", value: {objectId}}});
const canvas = reference("obj:canvas"), subject = reference("obj:subject");
test("placement input is bounded and normalized", () => {
assert.deepEqual(normalizePlacementInput({
canvas_id: "obj:canvas",
subject_id: "obj:subject",
canvas_id: canvas,
subject_id: subject,
x: 12.5,
y: -30,
width: 12,
height: 80,
z: 3.8,
}), {
canvas_id: "obj:canvas",
subject_id: "obj:subject",
canvas_id: canvas,
subject_id: subject,
x: 12.5,
y: -30,
width: 220,
@@ -25,6 +29,7 @@ test("placement input is bounded and normalized", () => {
test("placement input requires Camino object references", () => {
assert.throws(
() => normalizePlacementInput({ canvas_id: "canvas", subject_id: "obj:subject" }),
/canvas_id must be a Camino object ID/,
/canvas_id must be a managed object reference/,
);
assert.throws(() => normalizePlacementInput({canvas_id: "obj:canvas", subject_id: subject}), /managed object reference/);
});
+1 -1
View File
@@ -159,7 +159,7 @@ cacheEntries = {
"@bufbuild/protobuf@npm:2.14.1" = { filename = "@bufbuild-protobuf-npm-2.14.1-78e9ea56a2-3f913aca03.zip"; hash = "sha512-P5E6ygPfhBnEsh2ADqVNf2yDalND5l1vaGJ57DVmxJB3KZOO8vlKXifZLo0/StzW4CcRi6Kbp2TSHhxjfN3HAg=="; };
"@connectrpc/connect-node@npm:2.1.2" = { filename = "@connectrpc-connect-node-npm-2.1.2-021434d5d5-ba8b0c9384.zip"; hash = "sha512-uosMk4TDgps3eqeoJtwM1Qpgk1J9LscBRgSRKDYzbqlgSitazqQrEEd/i+RWnknd7WbKD8d/X1sCcDXgKeE+qQ=="; };
"@connectrpc/connect@npm:2.1.2" = { filename = "@connectrpc-connect-npm-2.1.2-ab34825de3-4e15000890.zip"; hash = "sha512-ThUACJAjt1Lv1E+pQKWuHh+gDhvrxLoM7ScruTWSDYw7hbd0UOZErUQruMqaJ1sMFxNviWuo7/A2F4Ex2wHWFQ=="; };
"@quixos/camino-package-runtime@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git#commit=a998690446ecf647e3af78e369492c1a3663b66d" = { filename = "@quixos-camino-package-runtime-https-683bcbf33c-c90d5d5c53.zip"; hash = "sha512-yQ1dXFMR2k+xbkka2rrhsAcrQFZlJAZEYM0hqwGzdV5RJ+fhQEh3BEgoPiL2yxrxulU4zg/jsj8BTBFRp1feyg=="; };
"@quixos/camino-package-runtime@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git#commit=110e41b268c8ac309e6757f2af3331de417ff9b0" = { filename = "@quixos-camino-package-runtime-https-5a5a438f14-75b1ffcea9.zip"; hash = "sha512-dbH/zqm6gwFck1pSrJAEXNUl6wfHpOL+jj4IwdHCyf5VOMKInCLr73Yx9NYUvqn3zw3AOfdm/8bUeTz2Htxo/A=="; };
"@types/node@npm:24.13.3" = { filename = "@types-node-npm-24.13.3-b512a0bbeb-a5bc08f49b.zip"; hash = "sha512-pbwI9JuVgdzcqQ4CzXcZejx5mECAdmSULlzVFhpVPU0q6AZPfjKUQQ10jX0Ji1wYSL5/pQwG8pxBk6sWvk5Z6w=="; };
"@typescript/typescript-linux-arm64@npm:7.0.2" = { filename = "@typescript-typescript-linux-arm64-npm-7.0.2-9cacfc53a0-10c0.zip"; hash = "sha512-s4c7nGT/lQIw6x0IfrL0b3ECy/D0g7UoDLdiIWJoM6EGXk5N/dmXdxUdA8Cr3hV/3nO2BYKTQ6Ai8/zGhiSQ3A=="; };
"@typescript/typescript-linux-x64@npm:7.0.2" = { filename = "@typescript-typescript-linux-x64-npm-7.0.2-8dd7ff8d24-10c0.zip"; hash = "sha512-k9EUK5CZ3OOPnyslp8SAAonHJpnpPmlaulppI/NmFyCzNs3UN/TLPUkyQia3KIxgbyTzKoXI5L5fl8Uj9bJ7Cg=="; };
+4 -4
View File
@@ -31,14 +31,14 @@ __metadata:
languageName: node
linkType: hard
"@quixos/camino-package-runtime@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git#commit=a998690446ecf647e3af78e369492c1a3663b66d":
"@quixos/camino-package-runtime@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git#commit=110e41b268c8ac309e6757f2af3331de417ff9b0":
version: 0.1.0
resolution: "@quixos/camino-package-runtime@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git#commit=a998690446ecf647e3af78e369492c1a3663b66d"
resolution: "@quixos/camino-package-runtime@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git#commit=110e41b268c8ac309e6757f2af3331de417ff9b0"
dependencies:
"@bufbuild/protobuf": "npm:^2.12.1"
"@connectrpc/connect": "npm:^2.1.2"
"@connectrpc/connect-node": "npm:^2.1.2"
checksum: 10c0/c90d5d5c5311da4fb16e491adabae1b0072b40566524064460cd21ab01b3755e5127e7e14048770448283e22f6cb1af1ba5538ce0fe3b23f014c1151a757deca
checksum: 10c0/75b1ffcea9ba83015c935a52ac90045cd525eb07c7a4e2fe8e3e08c1d1c2c9fe5538c2889c22ebef7631f4d614bea9f7cf0dc039f766ffc6d4793cf61edc68fc
languageName: node
linkType: hard
@@ -46,7 +46,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@quixos/web-studio-canvas-runtime@workspace:."
dependencies:
"@quixos/camino-package-runtime": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git#commit=a998690446ecf647e3af78e369492c1a3663b66d"
"@quixos/camino-package-runtime": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-package-runtime.git#commit=110e41b268c8ac309e6757f2af3331de417ff9b0"
"@types/node": "npm:^24"
typescript: "npm:^7.0.2"
bin: