Format authored monorepo code with pinned language formatters
This commit is contained in:
+151
-64
@@ -1,108 +1,192 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { referenceFromWire } from "../dist/references.js";
|
||||
import { bindQxHandler, decodeQxValue, encodeQxValue, jsToProtoValue, liveValue, protoValueToJs, opaqueReactPropsBinding } from "../dist/index.js";
|
||||
import {
|
||||
bindQxHandler,
|
||||
decodeQxValue,
|
||||
encodeQxValue,
|
||||
jsToProtoValue,
|
||||
liveValue,
|
||||
protoValueToJs,
|
||||
opaqueReactPropsBinding,
|
||||
} from "../dist/index.js";
|
||||
const scalar = (name) => ({ kind: "scalar", name });
|
||||
const unit = { kind: "builtin", name: "unit" };
|
||||
test("opaque React props retain managed references while ordinary messages remain closed", () => {
|
||||
const descriptorId = "org.quixos.web-studio.ReactProps";
|
||||
const reference = referenceFromWire("obj:task");
|
||||
const type = {kind: "message", descriptorId};
|
||||
const messages = {[descriptorId]: opaqueReactPropsBinding};
|
||||
const decoded = decodeQxValue(type, encodeQxValue(type, {subject: reference, title: "Task"}, messages), messages);
|
||||
const type = { kind: "message", descriptorId };
|
||||
const messages = { [descriptorId]: opaqueReactPropsBinding };
|
||||
const decoded = decodeQxValue(type, encodeQxValue(type, { subject: reference, title: "Task" }, messages), messages);
|
||||
assert.ok(decoded.subject.equals(reference));
|
||||
assert.equal(decoded.title, "Task");
|
||||
assert.throws(() => opaqueReactPropsBinding.encode(null), /object/);
|
||||
assert.throws(() => opaqueReactPropsBinding.encode([]), /object/);
|
||||
assert.throws(() => encodeQxValue({kind: "message", descriptorId: "Ordinary"}, {subject: reference},
|
||||
{Ordinary: opaqueReactPropsBinding}), /Managed references/);
|
||||
assert.throws(
|
||||
() =>
|
||||
encodeQxValue(
|
||||
{ kind: "message", descriptorId: "Ordinary" },
|
||||
{ subject: reference },
|
||||
{ Ordinary: opaqueReactPropsBinding },
|
||||
),
|
||||
/Managed references/,
|
||||
);
|
||||
});
|
||||
test("declared record inputs preserve opaque references without opening message codecs", async () => {
|
||||
const target = referenceFromWire("obj:board");
|
||||
const type = {kind: "record", fields: {
|
||||
target: {kind: "object-ref", expectation: {kind: "atom", atomId: "board"}},
|
||||
x: scalar("double"), note: {kind: "optional", value: scalar("string")},
|
||||
}};
|
||||
const encoded = encodeQxValue(type, {target, x: 12}, {});
|
||||
const type = {
|
||||
kind: "record",
|
||||
fields: {
|
||||
target: { kind: "object-ref", expectation: { kind: "atom", atomId: "board" } },
|
||||
x: scalar("double"),
|
||||
note: { kind: "optional", value: scalar("string") },
|
||||
},
|
||||
};
|
||||
const encoded = encodeQxValue(type, { target, x: 12 }, {});
|
||||
const decoded = decodeQxValue(type, encoded, {});
|
||||
assert.ok(decoded.target.equals(target));
|
||||
assert.equal(decoded.x, 12);
|
||||
assert.equal(decoded.note, null);
|
||||
assert.throws(() => JSON.stringify(decoded), /cannot be serialized/);
|
||||
assert.throws(() => encodeQxValue(type, {target, x: 12, hidden: target}, {}), /Unexpected/);
|
||||
assert.throws(() => encodeQxValue(type, {x: 12}, {}), /Missing/);
|
||||
assert.throws(() => encodeQxValue(type, {target: "obj:board", x: 12}, {}), /opaque object reference/);
|
||||
const handler = bindQxHandler({inputType: type, outputType: unit, ports: {}}, async (context) => {
|
||||
assert.ok(context.input.target.equals(target));
|
||||
assert.equal(context.input.x, 12);
|
||||
}, {});
|
||||
await handler({objectId: target, inputProto: encoded.kind.value.fields});
|
||||
assert.throws(() => encodeQxValue(type, { target, x: 12, hidden: target }, {}), /Unexpected/);
|
||||
assert.throws(() => encodeQxValue(type, { x: 12 }, {}), /Missing/);
|
||||
assert.throws(() => encodeQxValue(type, { target: "obj:board", x: 12 }, {}), /opaque object reference/);
|
||||
const handler = bindQxHandler(
|
||||
{ inputType: type, outputType: unit, ports: {} },
|
||||
async (context) => {
|
||||
assert.ok(context.input.target.equals(target));
|
||||
assert.equal(context.input.x, 12);
|
||||
},
|
||||
{},
|
||||
);
|
||||
await handler({ objectId: target, inputProto: encoded.kind.value.fields });
|
||||
});
|
||||
test("typed sessions rebind ports and cancellation to each acquired invocation", async () => {
|
||||
const first = new AbortController(), second = new AbortController();
|
||||
const first = new AbortController(),
|
||||
second = new AbortController();
|
||||
let closed = false;
|
||||
const context = (value, signal) => ({objectId: referenceFromWire("obj:owner"), inputProto: {}, signal,
|
||||
state: () => ({live: async () => liveValue(jsToProtoValue(value))})});
|
||||
const raw = {...context(1n, first.signal), openSession: async () => ({id: "session:test",
|
||||
run: (work) => work(context(2n, second.signal)), close: async () => {closed = true;}})};
|
||||
const handler = bindQxHandler({inputType: unit, outputType: unit, ports: {counter: {kind: "state", id: "counter", valueType: scalar("int64"), primitives: ["read"]}}}, async (bound) => {
|
||||
assert.equal(bound.signal, first.signal);
|
||||
const session = await bound.openSession();
|
||||
await session.run(async (next) => {
|
||||
assert.equal(next.signal, second.signal);
|
||||
assert.equal(await next.ports.counter.get(), 2n);
|
||||
assert.equal(next.openSession, undefined);
|
||||
});
|
||||
await session.close();
|
||||
}, {});
|
||||
const context = (value, signal) => ({
|
||||
objectId: referenceFromWire("obj:owner"),
|
||||
inputProto: {},
|
||||
signal,
|
||||
state: () => ({ live: async () => liveValue(jsToProtoValue(value)) }),
|
||||
});
|
||||
const raw = {
|
||||
...context(1n, first.signal),
|
||||
openSession: async () => ({
|
||||
id: "session:test",
|
||||
run: (work) => work(context(2n, second.signal)),
|
||||
close: async () => {
|
||||
closed = true;
|
||||
},
|
||||
}),
|
||||
};
|
||||
const handler = bindQxHandler(
|
||||
{
|
||||
inputType: unit,
|
||||
outputType: unit,
|
||||
ports: { counter: { kind: "state", id: "counter", valueType: scalar("int64"), primitives: ["read"] } },
|
||||
},
|
||||
async (bound) => {
|
||||
assert.equal(bound.signal, first.signal);
|
||||
const session = await bound.openSession();
|
||||
await session.run(async (next) => {
|
||||
assert.equal(next.signal, second.signal);
|
||||
assert.equal(await next.ports.counter.get(), 2n);
|
||||
assert.equal(next.openSession, undefined);
|
||||
});
|
||||
await session.close();
|
||||
},
|
||||
{},
|
||||
);
|
||||
await handler(raw);
|
||||
assert.equal(closed, true);
|
||||
});
|
||||
test("binding codecs round trip nested bytes, 64-bit integers, nulls, and references", () => {
|
||||
const values = [[scalar("int64"), -(2n ** 63n)], [scalar("uint64"), 2n ** 64n - 1n],
|
||||
const values = [
|
||||
[scalar("int64"), -(2n ** 63n)],
|
||||
[scalar("uint64"), 2n ** 64n - 1n],
|
||||
[scalar("bytes"), new Uint8Array([0, 255])],
|
||||
[{ kind: "list", value: { kind: "optional", value: scalar("int64") } }, [null, 2n ** 60n]],
|
||||
[{ kind: "object-ref", expectation: { kind: "atom", atomId: "thing" } }, referenceFromWire("obj:thing")]];
|
||||
[{ kind: "object-ref", expectation: { kind: "atom", atomId: "thing" } }, referenceFromWire("obj:thing")],
|
||||
];
|
||||
for (const [type, value] of values) assert.deepEqual(decodeQxValue(type, encodeQxValue(type, value, {}), {}), value);
|
||||
});
|
||||
|
||||
test("opaque references pass declared RPC boundaries but cannot enter ordinary data", () => {
|
||||
const reference = referenceFromWire("obj:thing");
|
||||
const type = {kind: "object-ref", expectation: {kind: "atom", atomId: "thing"}};
|
||||
const type = { kind: "object-ref", expectation: { kind: "atom", atomId: "thing" } };
|
||||
const roundtrip = decodeQxValue(type, encodeQxValue(type, reference, {}), {});
|
||||
assert.equal(reference.equals(roundtrip), true);
|
||||
assert.equal(reference.equals(referenceFromWire("obj:other")), false);
|
||||
assert.throws(() => JSON.stringify({nested: [reference]}), /cannot be serialized/);
|
||||
assert.throws(() => JSON.stringify({ nested: [reference] }), /cannot be serialized/);
|
||||
assert.throws(() => String(reference), /cannot be coerced/);
|
||||
assert.throws(() => encodeQxValue(type, "obj:thing", {}), /opaque/);
|
||||
assert.throws(() => encodeQxValue(scalar("string"), reference, {}), /Managed references/);
|
||||
const message = {kind: "message", descriptorId: "Payload"};
|
||||
assert.throws(() => encodeQxValue(message, {nested: reference}, {Payload: {encode: jsToProtoValue}}), /Managed references/);
|
||||
assert.throws(() => encodeQxValue(message, {}, {Payload: {encode: () => jsToProtoValue(reference)}}), /Managed references/);
|
||||
assert.throws(() => decodeQxValue(message, jsToProtoValue(reference), {Payload: {decode: () => ({})}}), /Managed references/);
|
||||
const message = { kind: "message", descriptorId: "Payload" };
|
||||
assert.throws(
|
||||
() => encodeQxValue(message, { nested: reference }, { Payload: { encode: jsToProtoValue } }),
|
||||
/Managed references/,
|
||||
);
|
||||
assert.throws(
|
||||
() => encodeQxValue(message, {}, { Payload: { encode: () => jsToProtoValue(reference) } }),
|
||||
/Managed references/,
|
||||
);
|
||||
assert.throws(
|
||||
() => decodeQxValue(message, jsToProtoValue(reference), { Payload: { decode: () => ({}) } }),
|
||||
/Managed references/,
|
||||
);
|
||||
});
|
||||
test("typed state and interface ports preserve declared values and exact operation IDs", async () => {
|
||||
const spec = { inputType: scalar("int64"), outputType: scalar("bytes"), ports: {
|
||||
data: { kind: "state", id: "state-id", valueType: scalar("int64"), primitives: ["read", "write"] },
|
||||
reader: { kind: "interface", id: "interface-id", operations: { "payload.get": { id: "get-id", inputType: unit, outputType: scalar("bytes") } } },
|
||||
} };
|
||||
const spec = {
|
||||
inputType: scalar("int64"),
|
||||
outputType: scalar("bytes"),
|
||||
ports: {
|
||||
data: { kind: "state", id: "state-id", valueType: scalar("int64"), primitives: ["read", "write"] },
|
||||
reader: {
|
||||
kind: "interface",
|
||||
id: "interface-id",
|
||||
operations: { "payload.get": { id: "get-id", inputType: unit, outputType: scalar("bytes") } },
|
||||
},
|
||||
},
|
||||
};
|
||||
let written;
|
||||
const handler = bindQxHandler(spec, async ({ input, ports }) => {
|
||||
assert.equal(input, 2n ** 60n);
|
||||
assert.equal(await ports.data.get(), 9n);
|
||||
assert.deepEqual((await ports.data.live()).$quixosValue, jsToProtoValue(9n));
|
||||
assert.ok(ports.reader.objectId.equals(referenceFromWire("obj:reader")));
|
||||
assert.deepEqual((await ports.reader.live["payload.get"]()).$quixosValue, jsToProtoValue(new Uint8Array([7])));
|
||||
await ports.data.set(input);
|
||||
return ports.reader["payload.get"]();
|
||||
}, {});
|
||||
const result = await handler({ inputProto: { value: jsToProtoValue(2n ** 60n) }, objectId: "obj",
|
||||
state: (id) => { assert.equal(id, "state-id"); return {
|
||||
live: async () => liveValue(jsToProtoValue(9n)), set: async (value) => { written = value; },
|
||||
}; },
|
||||
interface: (id) => { assert.equal(id, "interface-id"); return { objectId: referenceFromWire("obj:reader"), live: async (operation, input) => {
|
||||
assert.equal(operation, "get-id"); assert.deepEqual(input, {}); return liveValue(jsToProtoValue(new Uint8Array([7])));
|
||||
} }; },
|
||||
const handler = bindQxHandler(
|
||||
spec,
|
||||
async ({ input, ports }) => {
|
||||
assert.equal(input, 2n ** 60n);
|
||||
assert.equal(await ports.data.get(), 9n);
|
||||
assert.deepEqual((await ports.data.live()).$quixosValue, jsToProtoValue(9n));
|
||||
assert.ok(ports.reader.objectId.equals(referenceFromWire("obj:reader")));
|
||||
assert.deepEqual((await ports.reader.live["payload.get"]()).$quixosValue, jsToProtoValue(new Uint8Array([7])));
|
||||
await ports.data.set(input);
|
||||
return ports.reader["payload.get"]();
|
||||
},
|
||||
{},
|
||||
);
|
||||
const result = await handler({
|
||||
inputProto: { value: jsToProtoValue(2n ** 60n) },
|
||||
objectId: "obj",
|
||||
state: (id) => {
|
||||
assert.equal(id, "state-id");
|
||||
return {
|
||||
live: async () => liveValue(jsToProtoValue(9n)),
|
||||
set: async (value) => {
|
||||
written = value;
|
||||
},
|
||||
};
|
||||
},
|
||||
interface: (id) => {
|
||||
assert.equal(id, "interface-id");
|
||||
return {
|
||||
objectId: referenceFromWire("obj:reader"),
|
||||
live: async (operation, input) => {
|
||||
assert.equal(operation, "get-id");
|
||||
assert.deepEqual(input, {});
|
||||
return liveValue(jsToProtoValue(new Uint8Array([7])));
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
assert.equal(written.$quixosValue.kind.value, String(2n ** 60n));
|
||||
assert.deepEqual(result.$quixosValue.kind.value, new Uint8Array([7]));
|
||||
@@ -110,8 +194,11 @@ test("typed state and interface ports preserve declared values and exact operati
|
||||
test("external message bindings and derived event types are used at the boundary", async () => {
|
||||
const message = { kind: "message", descriptorId: "Payload" };
|
||||
const messages = { Payload: { encode: jsToProtoValue, decode: protoValueToJs } };
|
||||
const handler = bindQxHandler({ inputType: message, outputType: { kind: "builtin", name: "watch-handle" }, eventType: message, ports: {} },
|
||||
{ kind: "derived", get: ({ input }) => ({ value: input.title }) }, messages);
|
||||
const handler = bindQxHandler(
|
||||
{ inputType: message, outputType: { kind: "builtin", name: "watch-handle" }, eventType: message, ports: {} },
|
||||
{ kind: "derived", get: ({ input }) => ({ value: input.title }) },
|
||||
messages,
|
||||
);
|
||||
assert.equal(handler.kind, "derived");
|
||||
const result = await handler.get({ objectId: "obj", inputProto: { title: jsToProtoValue("hello") } });
|
||||
assert.deepEqual(protoValueToJs(result.$quixosValue), { value: "hello" });
|
||||
|
||||
@@ -1,30 +1,47 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import crypto from 'node:crypto';
|
||||
import {spawnSync} from 'node:child_process';
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import crypto from "node:crypto";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
test('supervised SDK reads a credential file and proves instance identity without an environment token', async t => {
|
||||
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'qx-sdk-credential-'));
|
||||
t.after(() => fs.rm(directory, {recursive:true, force:true}));
|
||||
const token = crypto.randomBytes(32).toString('hex');
|
||||
const filename = path.join(directory, 'process-token');
|
||||
await fs.writeFile(filename, token, {mode:0o600});
|
||||
const env = {...process.env, CAMINO_RUNTIME_AUTH_TOKEN_FILE: filename,
|
||||
CAMINO_RUNTIME_AUTH_REQUIRED:'1', QUIXOS_RUNTIME_INSTANCE_ID:'instance:test'};
|
||||
test("supervised SDK reads a credential file and proves instance identity without an environment token", async (t) => {
|
||||
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "qx-sdk-credential-"));
|
||||
t.after(() => fs.rm(directory, { recursive: true, force: true }));
|
||||
const token = crypto.randomBytes(32).toString("hex");
|
||||
const filename = path.join(directory, "process-token");
|
||||
await fs.writeFile(filename, token, { mode: 0o600 });
|
||||
const env = {
|
||||
...process.env,
|
||||
CAMINO_RUNTIME_AUTH_TOKEN_FILE: filename,
|
||||
CAMINO_RUNTIME_AUTH_REQUIRED: "1",
|
||||
QUIXOS_RUNTIME_INSTANCE_ID: "instance:test",
|
||||
};
|
||||
delete env.CAMINO_RUNTIME_AUTH_TOKEN;
|
||||
const result = spawnSync(process.execPath, ['--input-type=module', '-e', `
|
||||
import {createPackageRuntimeRoutes} from ${JSON.stringify(new URL('../dist/index.js', import.meta.url).href)};
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
"--input-type=module",
|
||||
"-e",
|
||||
`
|
||||
import {createPackageRuntimeRoutes} from ${JSON.stringify(new URL("../dist/index.js", import.meta.url).href)};
|
||||
createPackageRuntimeRoutes({packageRevisionId:'package:test',exports:{}})({
|
||||
service(_type, implementation) { console.log(JSON.stringify(implementation.handshake({nonce:'challenge'}))); }
|
||||
});
|
||||
`], {env, encoding:'utf8'});
|
||||
`,
|
||||
],
|
||||
{ env, encoding: "utf8" },
|
||||
);
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
const handshake = JSON.parse(result.stdout);
|
||||
assert.equal(handshake.authenticationProof, crypto.createHmac('sha256', token)
|
||||
.update(JSON.stringify(['challenge','instance:test','package:test'])).digest('hex'));
|
||||
assert.ok(handshake.capabilities.includes('epoch-grants-v1'));
|
||||
assert.equal(
|
||||
handshake.authenticationProof,
|
||||
crypto
|
||||
.createHmac("sha256", token)
|
||||
.update(JSON.stringify(["challenge", "instance:test", "package:test"]))
|
||||
.digest("hex"),
|
||||
);
|
||||
assert.ok(handshake.capabilities.includes("epoch-grants-v1"));
|
||||
assert.ok(!result.stdout.includes(token));
|
||||
});
|
||||
|
||||
+20
-8
@@ -1,13 +1,25 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {createMigrationContext, migrationObjectId} from "../dist/migration.js";
|
||||
import { createMigrationContext, migrationObjectId } from "../dist/migration.js";
|
||||
test("migration context offers restricted ports and deterministic helper identities", () => {
|
||||
const input = {schemaVersion: 1, executionId: "operation:transition", exportId: "migrate", ports: [
|
||||
{name: "old", binding: "slot:name", view: "old", access: ["read"], states: [{objectId: "obj:one", value: "Alice"}]},
|
||||
{name: "new", binding: "slot:name", view: "new", access: ["write"]}, {name: "newRead", binding: "slot:name", view: "new", access: ["read"]},
|
||||
{name: "helpers", binding: "atom:helper", view: "new", access: ["create"]},
|
||||
]};
|
||||
const {context, result} = createMigrationContext(input);
|
||||
const input = {
|
||||
schemaVersion: 1,
|
||||
executionId: "operation:transition",
|
||||
exportId: "migrate",
|
||||
ports: [
|
||||
{
|
||||
name: "old",
|
||||
binding: "slot:name",
|
||||
view: "old",
|
||||
access: ["read"],
|
||||
states: [{ objectId: "obj:one", value: "Alice" }],
|
||||
},
|
||||
{ name: "new", binding: "slot:name", view: "new", access: ["write"] },
|
||||
{ name: "newRead", binding: "slot:name", view: "new", access: ["read"] },
|
||||
{ name: "helpers", binding: "atom:helper", view: "new", access: ["create"] },
|
||||
],
|
||||
};
|
||||
const { context, result } = createMigrationContext(input);
|
||||
assert.equal(context.read("old", "obj:one"), "Alice");
|
||||
assert.throws(() => context.write("old", "obj:one", "Bob"), /does not grant/);
|
||||
assert.throws(() => context.read("new", "obj:one"), /does not grant/);
|
||||
@@ -20,5 +32,5 @@ test("migration context offers restricted ports and deterministic helper identit
|
||||
assert.equal(helper, migrationObjectId(input.executionId, "helpers", "one"));
|
||||
assert.equal(context.create("helpers", "one"), helper);
|
||||
assert.equal(result().creates.length, 1);
|
||||
assert.deepEqual(input.ports[0].states, [{objectId: "obj:one", value: "Alice"}]);
|
||||
assert.deepEqual(input.ports[0].states, [{ objectId: "obj:one", value: "Alice" }]);
|
||||
});
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {relationshipMap, relationshipList, relationshipSet} from "../dist/index.js";
|
||||
import {referenceFromWire} from "../dist/references.js";
|
||||
import { relationshipMap, relationshipList, relationshipSet } from "../dist/index.js";
|
||||
import { referenceFromWire } from "../dist/references.js";
|
||||
test("relationship helpers preserve handles and require explicit collection revisions", async () => {
|
||||
let current = {revision: 0n, entries: []}, serial = 0;
|
||||
const port = {collection: async () => ({revision: current.revision, entries: current.entries.map((entry) => ({...entry}))}),
|
||||
let current = { revision: 0n, entries: [] },
|
||||
serial = 0;
|
||||
const port = {
|
||||
collection: async () => ({ revision: current.revision, entries: current.entries.map((entry) => ({ ...entry })) }),
|
||||
replace: async (entries, revision) => {
|
||||
assert.equal(revision, current.revision);
|
||||
current = {revision: revision + 1n, entries: entries.map((entry) => ({...entry, edgeId: entry.edgeId ?? `edge:${++serial}`}))};
|
||||
current = {
|
||||
revision: revision + 1n,
|
||||
entries: entries.map((entry) => ({ ...entry, edgeId: entry.edgeId ?? `edge:${++serial}` })),
|
||||
};
|
||||
return port.collection();
|
||||
}};
|
||||
const a = referenceFromWire("obj:a"), b = referenceFromWire("obj:b");
|
||||
},
|
||||
};
|
||||
const a = referenceFromWire("obj:a"),
|
||||
b = referenceFromWire("obj:b");
|
||||
const map = relationshipMap(port);
|
||||
await map.set("a1", a, 0n);
|
||||
assert.equal((await map.get("a1")).value.equals(a), true);
|
||||
|
||||
+231
-182
@@ -21,11 +21,7 @@ import {
|
||||
ValueSchema,
|
||||
ValueSourceSchema,
|
||||
} from "../dist/camino/api_pb.js";
|
||||
import {
|
||||
EdgeDependencySchema,
|
||||
InjectedDependencySchema,
|
||||
PackageExportRefSchema,
|
||||
} from "../dist/quixos/refs_pb.js";
|
||||
import { EdgeDependencySchema, InjectedDependencySchema, PackageExportRefSchema } from "../dist/quixos/refs_pb.js";
|
||||
import { InvokeCapabilityResponseSchema, OrchestratorRuntime } from "../dist/quixos/orch_pb.js";
|
||||
import { DerivedDependencySchema, PackageRuntime } from "../dist/quixos/runtime_pb.js";
|
||||
|
||||
@@ -93,26 +89,30 @@ test("runtime context exposes only explicitly injected ports", async () => {
|
||||
resolveEdge: async () => ({ edges: [] }),
|
||||
connectEdge: async () => ({}),
|
||||
};
|
||||
const context = createRuntimeContext(camino, {}, {
|
||||
objectId: "obj:task",
|
||||
input: {},
|
||||
dependencies: [
|
||||
create(InjectedDependencySchema, {
|
||||
portId: "port:title",
|
||||
binding: { case: "stateSlotId", value: "slot:task:title" },
|
||||
}),
|
||||
create(InjectedDependencySchema, {
|
||||
portId: "port:children",
|
||||
binding: {
|
||||
case: "edge",
|
||||
value: create(EdgeDependencySchema, {
|
||||
edgeTypeId: "edge:task:children",
|
||||
projectionId: "projection:task:children",
|
||||
}),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
const context = createRuntimeContext(
|
||||
camino,
|
||||
{},
|
||||
{
|
||||
objectId: "obj:task",
|
||||
input: {},
|
||||
dependencies: [
|
||||
create(InjectedDependencySchema, {
|
||||
portId: "port:title",
|
||||
binding: { case: "stateSlotId", value: "slot:task:title" },
|
||||
}),
|
||||
create(InjectedDependencySchema, {
|
||||
portId: "port:children",
|
||||
binding: {
|
||||
case: "edge",
|
||||
value: create(EdgeDependencySchema, {
|
||||
edgeTypeId: "edge:task:children",
|
||||
projectionId: "projection:task:children",
|
||||
}),
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(await context.state("port:title").get(), "obj:task/slot:task:title");
|
||||
await context.state("port:title").set("Changed");
|
||||
@@ -126,23 +126,30 @@ test("runtime context exposes only explicitly injected ports", async () => {
|
||||
|
||||
test("injected ports preserve an explicitly traversed object through PackageRuntime RPC", async () => {
|
||||
const reads = [];
|
||||
const caminoServer = await listen((router) => router.service(CaminoService, {
|
||||
readState: (request) => {
|
||||
reads.push(request);
|
||||
return { value: jsToProtoValue("Project name") };
|
||||
},
|
||||
}));
|
||||
const runtimeServer = await listen(createPackageRuntimeRoutes({
|
||||
packageRevisionId: "package:test@1",
|
||||
caminoUrl: caminoServer.url,
|
||||
exports: {
|
||||
"export:test:value": (context) => context.state("port:value").get(),
|
||||
},
|
||||
}));
|
||||
const client = createClient(PackageRuntime, createConnectTransport({
|
||||
baseUrl: runtimeServer.url,
|
||||
httpVersion: "1.1",
|
||||
}));
|
||||
const caminoServer = await listen((router) =>
|
||||
router.service(CaminoService, {
|
||||
readState: (request) => {
|
||||
reads.push(request);
|
||||
return { value: jsToProtoValue("Project name") };
|
||||
},
|
||||
}),
|
||||
);
|
||||
const runtimeServer = await listen(
|
||||
createPackageRuntimeRoutes({
|
||||
packageRevisionId: "package:test@1",
|
||||
caminoUrl: caminoServer.url,
|
||||
exports: {
|
||||
"export:test:value": (context) => context.state("port:value").get(),
|
||||
},
|
||||
}),
|
||||
);
|
||||
const client = createClient(
|
||||
PackageRuntime,
|
||||
createConnectTransport({
|
||||
baseUrl: runtimeServer.url,
|
||||
httpVersion: "1.1",
|
||||
}),
|
||||
);
|
||||
try {
|
||||
const response = await client.invoke({
|
||||
export: create(PackageExportRefSchema, {
|
||||
@@ -150,11 +157,13 @@ test("injected ports preserve an explicitly traversed object through PackageRunt
|
||||
exportId: "export:test:value",
|
||||
}),
|
||||
objectId: "obj:component",
|
||||
dependencies: [create(InjectedDependencySchema, {
|
||||
portId: "port:value",
|
||||
objectId: "obj:project",
|
||||
binding: { case: "stateSlotId", value: "slot:project:name" },
|
||||
})],
|
||||
dependencies: [
|
||||
create(InjectedDependencySchema, {
|
||||
portId: "port:value",
|
||||
objectId: "obj:project",
|
||||
binding: { case: "stateSlotId", value: "slot:project:name" },
|
||||
}),
|
||||
],
|
||||
});
|
||||
assert.equal(response.ok, true);
|
||||
assert.equal(protoValueToJs(response.result), "Project name");
|
||||
@@ -178,32 +187,40 @@ test("interface views invoke a related object and propagate transitive live depe
|
||||
}),
|
||||
}),
|
||||
});
|
||||
const orchServer = await listen((router) => router.service(OrchestratorRuntime, {
|
||||
invokeCapability: (request) => {
|
||||
invocations.push(request);
|
||||
return create(InvokeCapabilityResponseSchema, {
|
||||
ok: true,
|
||||
result: sourceValue,
|
||||
dependencies: [create(DerivedDependencySchema, {
|
||||
kind: "state",
|
||||
objectId: "obj:project",
|
||||
attachmentId: "slot:project:name",
|
||||
})],
|
||||
});
|
||||
},
|
||||
}));
|
||||
const runtimeServer = await listen(createPackageRuntimeRoutes({
|
||||
packageRevisionId: "package:test@1",
|
||||
orchUrl: orchServer.url,
|
||||
exports: {
|
||||
"export:test:value": derived((context) =>
|
||||
context.interface("port:named").live("operation:named:name:get")),
|
||||
},
|
||||
}));
|
||||
const client = createClient(PackageRuntime, createConnectTransport({
|
||||
baseUrl: runtimeServer.url,
|
||||
httpVersion: "1.1",
|
||||
}));
|
||||
const orchServer = await listen((router) =>
|
||||
router.service(OrchestratorRuntime, {
|
||||
invokeCapability: (request) => {
|
||||
invocations.push(request);
|
||||
return create(InvokeCapabilityResponseSchema, {
|
||||
ok: true,
|
||||
result: sourceValue,
|
||||
dependencies: [
|
||||
create(DerivedDependencySchema, {
|
||||
kind: "state",
|
||||
objectId: "obj:project",
|
||||
attachmentId: "slot:project:name",
|
||||
}),
|
||||
],
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
const runtimeServer = await listen(
|
||||
createPackageRuntimeRoutes({
|
||||
packageRevisionId: "package:test@1",
|
||||
orchUrl: orchServer.url,
|
||||
exports: {
|
||||
"export:test:value": derived((context) => context.interface("port:named").live("operation:named:name:get")),
|
||||
},
|
||||
}),
|
||||
);
|
||||
const client = createClient(
|
||||
PackageRuntime,
|
||||
createConnectTransport({
|
||||
baseUrl: runtimeServer.url,
|
||||
httpVersion: "1.1",
|
||||
}),
|
||||
);
|
||||
try {
|
||||
const response = await client.invoke({
|
||||
export: create(PackageExportRefSchema, {
|
||||
@@ -211,11 +228,13 @@ test("interface views invoke a related object and propagate transitive live depe
|
||||
exportId: "export:test:value",
|
||||
}),
|
||||
objectId: "obj:component",
|
||||
dependencies: [create(InjectedDependencySchema, {
|
||||
portId: "port:named",
|
||||
objectId: "obj:project",
|
||||
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
|
||||
})],
|
||||
dependencies: [
|
||||
create(InjectedDependencySchema, {
|
||||
portId: "port:named",
|
||||
objectId: "obj:project",
|
||||
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
|
||||
}),
|
||||
],
|
||||
});
|
||||
assert.equal(response.ok, true);
|
||||
assert.equal(invocations[0]?.objectId, "obj:project");
|
||||
@@ -232,64 +251,80 @@ test("interface views invoke a related object and propagate transitive live depe
|
||||
test("derived interface views reread after their transitive subscriptions become live", async () => {
|
||||
let current = "before subscription";
|
||||
let invocationCount = 0;
|
||||
const caminoServer = await listen((router) => router.service(CaminoService, {
|
||||
watchObject: async function* (request, context) {
|
||||
// Model a write racing the first nested capability read. Camino makes
|
||||
// the subscription live before yielding this snapshot.
|
||||
current = "after subscription";
|
||||
yield {
|
||||
objectId: request.objectId,
|
||||
snapshot: create(CaminoObjectSchema, {
|
||||
id: request.objectId,
|
||||
atomId: "atom:project",
|
||||
workspaceRevisionId: "workspace:test@1",
|
||||
}),
|
||||
};
|
||||
await new Promise((resolve) =>
|
||||
context.signal.addEventListener("abort", resolve, { once: true }));
|
||||
},
|
||||
}));
|
||||
const orchServer = await listen((router) => router.service(OrchestratorRuntime, {
|
||||
invokeCapability: () => {
|
||||
invocationCount += 1;
|
||||
return create(InvokeCapabilityResponseSchema, {
|
||||
ok: true,
|
||||
result: jsToProtoValue(current),
|
||||
dependencies: [create(DerivedDependencySchema, {
|
||||
kind: "state",
|
||||
objectId: "obj:project",
|
||||
attachmentId: "slot:project:name",
|
||||
})],
|
||||
});
|
||||
},
|
||||
}));
|
||||
const runtimeServer = await listen(createPackageRuntimeRoutes({
|
||||
packageRevisionId: "package:test@1",
|
||||
caminoUrl: caminoServer.url,
|
||||
orchUrl: orchServer.url,
|
||||
exports: {
|
||||
"export:test:value": derived((context) =>
|
||||
context.interface("port:named").invoke("operation:named:name:get")),
|
||||
},
|
||||
}));
|
||||
const client = createClient(PackageRuntime, createConnectTransport({
|
||||
baseUrl: runtimeServer.url,
|
||||
httpVersion: "1.1",
|
||||
}));
|
||||
const caminoServer = await listen((router) =>
|
||||
router.service(CaminoService, {
|
||||
watchObject: async function* (request, context) {
|
||||
// Model a write racing the first nested capability read. Camino makes
|
||||
// the subscription live before yielding this snapshot.
|
||||
current = "after subscription";
|
||||
yield {
|
||||
objectId: request.objectId,
|
||||
snapshot: create(CaminoObjectSchema, {
|
||||
id: request.objectId,
|
||||
atomId: "atom:project",
|
||||
workspaceRevisionId: "workspace:test@1",
|
||||
}),
|
||||
};
|
||||
await new Promise((resolve) => context.signal.addEventListener("abort", resolve, { once: true }));
|
||||
},
|
||||
}),
|
||||
);
|
||||
const orchServer = await listen((router) =>
|
||||
router.service(OrchestratorRuntime, {
|
||||
invokeCapability: () => {
|
||||
invocationCount += 1;
|
||||
return create(InvokeCapabilityResponseSchema, {
|
||||
ok: true,
|
||||
result: jsToProtoValue(current),
|
||||
dependencies: [
|
||||
create(DerivedDependencySchema, {
|
||||
kind: "state",
|
||||
objectId: "obj:project",
|
||||
attachmentId: "slot:project:name",
|
||||
}),
|
||||
],
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
const runtimeServer = await listen(
|
||||
createPackageRuntimeRoutes({
|
||||
packageRevisionId: "package:test@1",
|
||||
caminoUrl: caminoServer.url,
|
||||
orchUrl: orchServer.url,
|
||||
exports: {
|
||||
"export:test:value": derived((context) => context.interface("port:named").invoke("operation:named:name:get")),
|
||||
},
|
||||
}),
|
||||
);
|
||||
const client = createClient(
|
||||
PackageRuntime,
|
||||
createConnectTransport({
|
||||
baseUrl: runtimeServer.url,
|
||||
httpVersion: "1.1",
|
||||
}),
|
||||
);
|
||||
const controller = new AbortController();
|
||||
try {
|
||||
const stream = client.watch({
|
||||
export: create(PackageExportRefSchema, {
|
||||
packageRevisionId: "package:test@1",
|
||||
exportId: "export:test:value",
|
||||
}),
|
||||
objectId: "obj:component",
|
||||
dependencies: [create(InjectedDependencySchema, {
|
||||
portId: "port:named",
|
||||
objectId: "obj:project",
|
||||
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
|
||||
})],
|
||||
}, { signal: controller.signal })[Symbol.asyncIterator]();
|
||||
const stream = client
|
||||
.watch(
|
||||
{
|
||||
export: create(PackageExportRefSchema, {
|
||||
packageRevisionId: "package:test@1",
|
||||
exportId: "export:test:value",
|
||||
}),
|
||||
objectId: "obj:component",
|
||||
dependencies: [
|
||||
create(InjectedDependencySchema, {
|
||||
portId: "port:named",
|
||||
objectId: "obj:project",
|
||||
binding: { case: "interfaceRevisionId", value: "interface:named@1" },
|
||||
}),
|
||||
],
|
||||
},
|
||||
{ signal: controller.signal },
|
||||
)
|
||||
[Symbol.asyncIterator]();
|
||||
const initial = await stream.next();
|
||||
assert.equal(protoValueToJs(initial.value?.value), "after subscription");
|
||||
assert.equal(invocationCount, 2);
|
||||
@@ -305,56 +340,70 @@ test("derived watches subscribe before reading and emit only real changes", asyn
|
||||
let current = "before";
|
||||
let subscribed = false;
|
||||
const changes = [];
|
||||
const caminoServer = await listen((router) => router.service(CaminoService, {
|
||||
readState: () => {
|
||||
assert.equal(subscribed, true, "the dependency stream must be live before the state read");
|
||||
return { value: jsToProtoValue(current) };
|
||||
},
|
||||
watchObject: async function* (request, context) {
|
||||
subscribed = true;
|
||||
yield {
|
||||
objectId: request.objectId,
|
||||
snapshot: create(CaminoObjectSchema, {
|
||||
id: request.objectId,
|
||||
atomId: "atom:test",
|
||||
workspaceRevisionId: "workspace:test@1",
|
||||
}),
|
||||
};
|
||||
while (!context.signal.aborted) {
|
||||
const changed = await new Promise((resolve) => {
|
||||
const finish = () => resolve(true);
|
||||
changes.push(finish);
|
||||
context.signal.addEventListener("abort", () => resolve(false), { once: true });
|
||||
});
|
||||
if (!changed) return;
|
||||
yield { objectId: request.objectId };
|
||||
}
|
||||
},
|
||||
}));
|
||||
const runtimeServer = await listen(createPackageRuntimeRoutes({
|
||||
packageRevisionId: "package:test@1",
|
||||
caminoUrl: caminoServer.url,
|
||||
exports: {
|
||||
"export:test:value": derived((context) => context.state("port:value").get()),
|
||||
},
|
||||
}));
|
||||
const client = createClient(PackageRuntime, createConnectTransport({
|
||||
baseUrl: runtimeServer.url,
|
||||
httpVersion: "1.1",
|
||||
}));
|
||||
const caminoServer = await listen((router) =>
|
||||
router.service(CaminoService, {
|
||||
readState: () => {
|
||||
assert.equal(subscribed, true, "the dependency stream must be live before the state read");
|
||||
return { value: jsToProtoValue(current) };
|
||||
},
|
||||
watchObject: async function* (request, context) {
|
||||
subscribed = true;
|
||||
yield {
|
||||
objectId: request.objectId,
|
||||
snapshot: create(CaminoObjectSchema, {
|
||||
id: request.objectId,
|
||||
atomId: "atom:test",
|
||||
workspaceRevisionId: "workspace:test@1",
|
||||
}),
|
||||
};
|
||||
while (!context.signal.aborted) {
|
||||
const changed = await new Promise((resolve) => {
|
||||
const finish = () => resolve(true);
|
||||
changes.push(finish);
|
||||
context.signal.addEventListener("abort", () => resolve(false), { once: true });
|
||||
});
|
||||
if (!changed) return;
|
||||
yield { objectId: request.objectId };
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
const runtimeServer = await listen(
|
||||
createPackageRuntimeRoutes({
|
||||
packageRevisionId: "package:test@1",
|
||||
caminoUrl: caminoServer.url,
|
||||
exports: {
|
||||
"export:test:value": derived((context) => context.state("port:value").get()),
|
||||
},
|
||||
}),
|
||||
);
|
||||
const client = createClient(
|
||||
PackageRuntime,
|
||||
createConnectTransport({
|
||||
baseUrl: runtimeServer.url,
|
||||
httpVersion: "1.1",
|
||||
}),
|
||||
);
|
||||
const controller = new AbortController();
|
||||
try {
|
||||
const stream = client.watch({
|
||||
export: create(PackageExportRefSchema, {
|
||||
packageRevisionId: "package:test@1",
|
||||
exportId: "export:test:value",
|
||||
}),
|
||||
objectId: "obj:test",
|
||||
dependencies: [create(InjectedDependencySchema, {
|
||||
portId: "port:value",
|
||||
binding: { case: "stateSlotId", value: "slot:test:value" },
|
||||
})],
|
||||
}, { signal: controller.signal })[Symbol.asyncIterator]();
|
||||
const stream = client
|
||||
.watch(
|
||||
{
|
||||
export: create(PackageExportRefSchema, {
|
||||
packageRevisionId: "package:test@1",
|
||||
exportId: "export:test:value",
|
||||
}),
|
||||
objectId: "obj:test",
|
||||
dependencies: [
|
||||
create(InjectedDependencySchema, {
|
||||
portId: "port:value",
|
||||
binding: { case: "stateSlotId", value: "slot:test:value" },
|
||||
}),
|
||||
],
|
||||
},
|
||||
{ signal: controller.signal },
|
||||
)
|
||||
[Symbol.asyncIterator]();
|
||||
const initial = await stream.next();
|
||||
assert.equal(protoValueToJs(initial.value?.value), "before");
|
||||
assert.equal(initial.value?.initial, true);
|
||||
|
||||
Reference in New Issue
Block a user