@almadar/shell-runtime 0.1.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-2WCVOMZ2.js +112 -0
- package/dist/chunk-57SBCUSM.js +62 -0
- package/dist/{chunk-Q2H75UYT.js → chunk-A2JJT4GA.js} +1 -37
- package/dist/chunk-BAS26HBN.js +10 -0
- package/dist/{chunk-REPX5QPI.js → chunk-BOVU4TCQ.js} +11 -3
- package/dist/chunk-BY5G4225.js +6 -0
- package/dist/chunk-DFDNHVDW.js +284 -0
- package/dist/{chunk-NATGCIVM.js → chunk-JVSREWQO.js} +378 -130
- package/dist/{chunk-DYOJGLIX.js → chunk-LKWYCYX5.js} +2 -5
- package/dist/chunk-LYFIQPXX.js +1 -0
- package/dist/{chunk-ODJCZ27I.js → chunk-N7HYNKK6.js} +35 -2
- package/dist/{chunk-4J2QEIUV.js → chunk-NTFQ36UN.js} +4 -30
- package/dist/chunk-NU6LVKGS.js +15 -0
- package/dist/{chunk-VNMG7NKY.js → chunk-OQVSN7DP.js} +21 -26
- package/dist/chunk-P2N6K2IV.js +20 -0
- package/dist/{chunk-QQRDUMB2.js → chunk-QCJWPXLH.js} +11 -11
- package/dist/{chunk-QELZ6TEJ.js → chunk-UD2CXYSJ.js} +39 -3
- package/dist/{chunk-UJHOUZAL.js → chunk-V4EMDPCO.js} +2 -5
- package/dist/chunk-VKQA2DJX.js +409 -0
- package/dist/chunk-VNGXRYFF.js +9 -0
- package/dist/{chunk-HNOAZWFH.js → chunk-WGCWJOT2.js} +2 -0
- package/dist/chunk-XZGVOBY6.js +33 -0
- package/dist/chunk-ZUSM4VW4.js +197 -0
- package/dist/client/EventTransport.d.ts +23 -7
- package/dist/client/EventTransport.js +1 -2
- package/dist/client/awaiting.js +0 -1
- package/dist/client/channel-transport.d.ts +6 -6
- package/dist/client/channel-transport.js +0 -1
- package/dist/client/circuit-store.d.ts +16 -5
- package/dist/client/circuit-store.js +2 -3
- package/dist/client/client-kernel.d.ts +36 -15
- package/dist/client/declared-input-dispatch.d.ts +19 -2
- package/dist/client/declared-input-dispatch.js +1 -2
- package/dist/client/inbound-dispatch.d.ts +21 -2
- package/dist/client/inbound-dispatch.js +1 -2
- package/dist/client/local-fold.d.ts +15 -8
- package/dist/client/local-fold.js +1 -2
- package/dist/client/render-sigils.js +0 -1
- package/dist/client/state-book.d.ts +9 -16
- package/dist/client/state-book.js +1 -2
- package/dist/client/trait-mount-error.js +0 -1
- package/dist/credentials/env-vars.d.ts +5 -1
- package/dist/credentials/env-vars.js +1 -3
- package/dist/credentials/scope.d.ts +8 -7
- package/dist/credentials/scope.js +0 -1
- package/dist/credentials/vault.d.ts +142 -0
- package/dist/credentials/vault.js +1 -0
- package/dist/effects/in-flight-calls.js +0 -1
- package/dist/effects/on-device-model.js +0 -1
- package/dist/events/EventBus.d.ts +1 -4
- package/dist/events/EventBus.js +0 -1
- package/dist/events/emit-stamp.js +0 -1
- package/dist/index.d.ts +25 -42
- package/dist/index.js +27 -45
- package/dist/kernel/decode.d.ts +10 -10
- package/dist/kernel/decode.js +1 -2
- package/dist/kernel/engine.d.ts +133 -31
- package/dist/kernel/engine.js +7 -9
- package/dist/kernel/frame-applier.d.ts +8 -7
- package/dist/kernel/frame-applier.js +1 -2
- package/dist/kernel/host.d.ts +148 -16
- package/dist/kernel/host.js +4 -7
- package/dist/kernel/lane.d.ts +1 -22
- package/dist/kernel/lane.js +1 -2
- package/dist/kernel/os-fetch-port.d.ts +7 -0
- package/dist/kernel/os-fetch-port.js +3 -0
- package/dist/kernel/os-port.d.ts +30 -8
- package/dist/kernel/os-port.js +1 -2
- package/dist/kernel/program-effects.d.ts +11 -28
- package/dist/kernel/program-effects.js +1 -2
- package/dist/kernel/secret-ports.d.ts +139 -0
- package/dist/kernel/secret-ports.js +2 -0
- package/dist/kernel/wire.d.ts +113 -12
- package/dist/loader/fetch-loader.d.ts +26 -0
- package/dist/loader/fetch-loader.js +1 -0
- package/dist/loader/lazy-page.js +0 -1
- package/dist/loader/schema-loader.d.ts +1 -17
- package/dist/persistence/native-memory.js +0 -1
- package/dist/runtime/entities/declaredCollections.d.ts +6 -0
- package/dist/runtime/entities/declaredCollections.js +1 -0
- package/dist/runtime/evaluation/local-store-transport.d.ts +16 -23
- package/dist/runtime/evaluation/local-store-transport.js +9 -13
- package/dist/runtime/evaluation/residence-transport.d.ts +3 -3
- package/dist/runtime/evaluation/residence-transport.js +1 -2
- package/dist/runtime/evaluation/routed-persistence.js +0 -1
- package/dist/runtime/events/identity/routing.d.ts +2 -53
- package/dist/runtime/events/identity/routing.js +1 -2
- package/dist/runtime/types.d.ts +5 -205
- package/dist/runtime/types.js +1 -2
- package/dist/service/default-call-service.js +0 -1
- package/dist/service/service-provider.js +0 -1
- package/package.json +8 -5
- package/dist/chunk-2JU7Y4KU.js +0 -21
- package/dist/chunk-46RWK2B5.js +0 -28
- package/dist/chunk-4ZICQ5PM.js +0 -192
- package/dist/chunk-53IPZXDP.js +0 -76
- package/dist/chunk-5D63EMO5.js +0 -16
- package/dist/chunk-CPSQXEJ6.js +0 -55
- package/dist/chunk-CQP5XAX6.js +0 -173
- package/dist/chunk-CROS2GEX.js +0 -49
- package/dist/chunk-D4XJTBLB.js +0 -217
- package/dist/chunk-D5USD2EX.js +0 -232
- package/dist/chunk-EV56K4GB.js +0 -63
- package/dist/chunk-FQ6IZMIU.js +0 -10
- package/dist/chunk-HMK6YMQS.js +0 -135
- package/dist/chunk-IHQLE5M7.js +0 -62
- package/dist/chunk-JFWFDUTR.js +0 -35
- package/dist/chunk-MHYSHSRG.js +0 -54
- package/dist/chunk-MLKGABMK.js +0 -7
- package/dist/chunk-NAECVIAR.js +0 -322
- package/dist/chunk-Q7JK4KY7.js +0 -9
- package/dist/chunk-RBUMDYFS.js +0 -681
- package/dist/chunk-SAS5KKCR.js +0 -43
- package/dist/chunk-SWPDU752.js +0 -28
- package/dist/chunk-T775TUCD.js +0 -18
- package/dist/chunk-VMOBW7RK.js +0 -21
- package/dist/chunk-WNPNNYXY.js +0 -22
- package/dist/chunk-XB5GYODW.js +0 -28
- package/dist/chunk-YL2SO5TP.js +0 -334
- package/dist/chunk-YO4LWS3I.js +0 -606
- package/dist/chunk-ZAQSS47X.js +0 -157
- package/dist/chunk-ZCQUUJIF.js +0 -129
- package/dist/client/ServerBridge.d.ts +0 -126
- package/dist/client/ServerBridge.js +0 -3
- package/dist/credentials/seed.d.ts +0 -21
- package/dist/credentials/seed.js +0 -2
- package/dist/credentials/store.d.ts +0 -110
- package/dist/credentials/store.js +0 -2
- package/dist/index-hT1xCdTq.d.ts +0 -46
- package/dist/kernel/beacon.d.ts +0 -32
- package/dist/kernel/beacon.js +0 -2
- package/dist/kernel/http.d.ts +0 -302
- package/dist/kernel/http.js +0 -6
- package/dist/kernel/oauth-effects.d.ts +0 -25
- package/dist/kernel/oauth-effects.js +0 -7
- package/dist/kernel/oauth-port.d.ts +0 -31
- package/dist/kernel/oauth-port.js +0 -2
- package/dist/loader/http-loader.d.ts +0 -111
- package/dist/loader/http-loader.js +0 -2
- package/dist/runtime/effects/composition/compose-behaviors.d.ts +0 -58
- package/dist/runtime/effects/composition/compose-behaviors.js +0 -4
- package/dist/runtime/effects/composition/event-wiring.d.ts +0 -38
- package/dist/runtime/effects/composition/event-wiring.js +0 -2
- package/dist/runtime/effects/composition/index.d.ts +0 -5
- package/dist/runtime/effects/composition/index.js +0 -6
- package/dist/runtime/effects/composition/layout-strategy.d.ts +0 -35
- package/dist/runtime/effects/composition/layout-strategy.js +0 -2
- package/dist/runtime/effects/composition/pipe.d.ts +0 -25
- package/dist/runtime/effects/composition/pipe.js +0 -2
- package/dist/runtime/entities/mockStoreSeed.d.ts +0 -56
- package/dist/runtime/entities/mockStoreSeed.js +0 -3
- package/dist/runtime/entities/seedBrowserStore.d.ts +0 -28
- package/dist/runtime/entities/seedBrowserStore.js +0 -2
- package/dist/runtime/entities/std-mock-seed.d.ts +0 -87
- package/dist/runtime/entities/std-mock-seed.js +0 -2
- package/dist/runtime/time/TickScheduler.d.ts +0 -77
- package/dist/runtime/time/TickScheduler.js +0 -3
- package/dist/runtime/time/cron.d.ts +0 -25
- package/dist/runtime/time/cron.js +0 -2
- package/dist/runtime/time/duration.d.ts +0 -20
- package/dist/runtime/time/duration.js +0 -2
- package/dist/runtime/traits/OrbitalTraitParsing.d.ts +0 -34
- package/dist/runtime/traits/OrbitalTraitParsing.js +0 -2
- package/dist/runtime/traits/listen-targets.d.ts +0 -16
- package/dist/runtime/traits/listen-targets.js +0 -3
- package/dist/runtime/traits/trait-index.d.ts +0 -35
- package/dist/runtime/traits/trait-index.js +0 -3
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { base64Standard } from './chunk-ZUSM4VW4.js';
|
|
2
|
+
|
|
3
|
+
// src/kernel/os-fetch-port.ts
|
|
4
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
var fail = (kind, message) => ({ error: { kind, message: `os/fetch: ${message}` } });
|
|
6
|
+
function bytesOf(base64) {
|
|
7
|
+
const text = atob(base64);
|
|
8
|
+
const bytes = new Uint8Array(text.length);
|
|
9
|
+
for (let i = 0; i < text.length; i += 1) bytes[i] = text.charCodeAt(i);
|
|
10
|
+
return bytes;
|
|
11
|
+
}
|
|
12
|
+
function headersOf(value) {
|
|
13
|
+
const headers = new Headers();
|
|
14
|
+
if (value === void 0) return headers;
|
|
15
|
+
if (!Array.isArray(value)) return "headers must be a list of { name, value }";
|
|
16
|
+
for (const header of value) {
|
|
17
|
+
if (!isRecord(header) || typeof header.name !== "string" || typeof header.value !== "string") return "headers must be a list of { name, value }";
|
|
18
|
+
headers.append(header.name, header.value);
|
|
19
|
+
}
|
|
20
|
+
return headers;
|
|
21
|
+
}
|
|
22
|
+
var failed = (kind, op, message) => ({ error: { kind, message: `${op}: ${message}` } });
|
|
23
|
+
var isGpuOp = (op) => op.startsWith("gpu/");
|
|
24
|
+
var noGpu = (op) => failed("unsupported", op, "this host installed no gpu answerer");
|
|
25
|
+
var fetchOnly = (op) => failed("unsupported", op, "a browser host answers only os/fetch, and os/recv and os/close on the handle of a streamed one");
|
|
26
|
+
function createFetchOsHost(performFetch) {
|
|
27
|
+
const pending = /* @__PURE__ */ new Set();
|
|
28
|
+
const bodies = /* @__PURE__ */ new Map();
|
|
29
|
+
let nextHandle = 1;
|
|
30
|
+
const openOf = (op, handle) => {
|
|
31
|
+
const open = typeof handle === "number" ? bodies.get(handle) : void 0;
|
|
32
|
+
return open ?? failed("closed", op, "the handle is not an open streamed response");
|
|
33
|
+
};
|
|
34
|
+
async function receive(args) {
|
|
35
|
+
const open = openOf("os/recv", args[0]);
|
|
36
|
+
if (!("reader" in open)) return open;
|
|
37
|
+
const count = args[1];
|
|
38
|
+
if (typeof count !== "number" || !Number.isInteger(count) || count < 1) return failed("invalid", "os/recv", "the count must be a positive integer");
|
|
39
|
+
try {
|
|
40
|
+
if (open.held.length === 0) {
|
|
41
|
+
const next = await open.reader.read();
|
|
42
|
+
if (next.done) return { ok: { data: base64Standard(new Uint8Array(0)), eof: true } };
|
|
43
|
+
open.held = next.value;
|
|
44
|
+
}
|
|
45
|
+
const data = open.held.subarray(0, count);
|
|
46
|
+
open.held = open.held.subarray(count);
|
|
47
|
+
return { ok: { data: base64Standard(data), eof: false } };
|
|
48
|
+
} catch (raised) {
|
|
49
|
+
return failed("io", "os/recv", raised instanceof Error ? raised.message : "the response body failed");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function release(args) {
|
|
53
|
+
const open = openOf("os/close", args[0]);
|
|
54
|
+
if (!("reader" in open)) return open;
|
|
55
|
+
if (typeof args[0] === "number") bodies.delete(args[0]);
|
|
56
|
+
pending.delete(open.control);
|
|
57
|
+
open.control.abort();
|
|
58
|
+
await open.reader.cancel().catch(() => void 0);
|
|
59
|
+
return { ok: {} };
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
async answer(op, args) {
|
|
63
|
+
if (isGpuOp(op)) return noGpu(op);
|
|
64
|
+
if (op === "os/recv") return receive(args);
|
|
65
|
+
if (op === "os/close") return release(args);
|
|
66
|
+
if (op !== "os/fetch") return fetchOnly(op);
|
|
67
|
+
const [method, url, request] = args;
|
|
68
|
+
if (typeof method !== "string" || typeof url !== "string") return fail("invalid", "the method and the url must be text");
|
|
69
|
+
if (request !== void 0 && !isRecord(request)) return fail("invalid", "the request must be { headers?, body?, timeoutMs?, stream? }");
|
|
70
|
+
const headers = headersOf(request?.headers);
|
|
71
|
+
if (typeof headers === "string") return fail("invalid", headers);
|
|
72
|
+
const body = request?.body;
|
|
73
|
+
if (body !== void 0 && typeof body !== "string") return fail("invalid", "the body must be bytes");
|
|
74
|
+
const timeoutMs = request?.timeoutMs;
|
|
75
|
+
if (timeoutMs !== void 0 && (typeof timeoutMs !== "number" || !(timeoutMs > 0))) return fail("invalid", "timeoutMs must be a positive number");
|
|
76
|
+
const stream = request?.stream;
|
|
77
|
+
if (stream !== void 0 && typeof stream !== "boolean") return fail("invalid", "stream must be a boolean");
|
|
78
|
+
const control = new AbortController();
|
|
79
|
+
pending.add(control);
|
|
80
|
+
const timer = typeof timeoutMs === "number" ? setTimeout(() => control.abort(), timeoutMs) : void 0;
|
|
81
|
+
let kept = false;
|
|
82
|
+
try {
|
|
83
|
+
const response = await performFetch(url, { method, headers, signal: control.signal, ...body !== void 0 ? { body: bytesOf(body) } : {} });
|
|
84
|
+
const received = [];
|
|
85
|
+
response.headers.forEach((value, name) => received.push({ name, value }));
|
|
86
|
+
if (stream === true) {
|
|
87
|
+
const handle = nextHandle;
|
|
88
|
+
nextHandle += 1;
|
|
89
|
+
const reader = (response.body ?? new ReadableStream({ start: (sink) => sink.close() })).getReader();
|
|
90
|
+
bodies.set(handle, { reader, control, held: new Uint8Array(0) });
|
|
91
|
+
kept = true;
|
|
92
|
+
return { ok: { status: response.status, headers: received, body: base64Standard(new Uint8Array(0)), handle } };
|
|
93
|
+
}
|
|
94
|
+
return { ok: { status: response.status, headers: received, body: base64Standard(new Uint8Array(await response.arrayBuffer())) } };
|
|
95
|
+
} catch (raised) {
|
|
96
|
+
if (control.signal.aborted) return fail("timeout", `no answer within ${String(timeoutMs)} ms`);
|
|
97
|
+
return fail("io", raised instanceof Error ? raised.message : "the fetch failed");
|
|
98
|
+
} finally {
|
|
99
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
100
|
+
if (!kept) pending.delete(control);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
close() {
|
|
104
|
+
for (const control of pending) control.abort();
|
|
105
|
+
pending.clear();
|
|
106
|
+
for (const open of bodies.values()) void open.reader.cancel().catch(() => void 0);
|
|
107
|
+
bodies.clear();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { createFetchOsHost };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// src/loader/fetch-loader.ts
|
|
2
|
+
var FetchSchemaLoader = class {
|
|
3
|
+
/** `base`: the URL relative imports resolve against when no importer is named (the page's own URL by default). */
|
|
4
|
+
constructor(base) {
|
|
5
|
+
this.base = base;
|
|
6
|
+
}
|
|
7
|
+
base;
|
|
8
|
+
cache = /* @__PURE__ */ new Map();
|
|
9
|
+
resolvePath(importPath, fromPath) {
|
|
10
|
+
try {
|
|
11
|
+
return { success: true, data: new URL(importPath, new URL(fromPath ?? this.base, this.base)).href };
|
|
12
|
+
} catch (error) {
|
|
13
|
+
return { success: false, error: `Cannot resolve ${importPath}${fromPath !== void 0 ? ` from ${fromPath}` : ""}: ${error instanceof Error ? error.message : String(error)}` };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
load(importPath, fromPath) {
|
|
17
|
+
const resolved = this.resolvePath(importPath, fromPath);
|
|
18
|
+
if (!resolved.success) return Promise.resolve(resolved);
|
|
19
|
+
const url = resolved.data;
|
|
20
|
+
const held = this.cache.get(url);
|
|
21
|
+
if (held !== void 0) return held;
|
|
22
|
+
const loading = fetchSchema(url, importPath);
|
|
23
|
+
loading.then((result) => {
|
|
24
|
+
if (!result.success) this.cache.delete(url);
|
|
25
|
+
}, () => this.cache.delete(url));
|
|
26
|
+
this.cache.set(url, loading);
|
|
27
|
+
return loading;
|
|
28
|
+
}
|
|
29
|
+
async loadOrbital(importPath, orbitalName, fromPath) {
|
|
30
|
+
const loaded = await this.load(importPath, fromPath);
|
|
31
|
+
if (!loaded.success) return loaded;
|
|
32
|
+
const { schema, sourcePath } = loaded.data;
|
|
33
|
+
const orbital = orbitalName === void 0 ? schema.orbitals[0] : schema.orbitals.find((o) => o.name === orbitalName);
|
|
34
|
+
if (orbital === void 0) {
|
|
35
|
+
return { success: false, error: `${sourcePath} has no orbital ${orbitalName ?? "(none)"}; found: ${schema.orbitals.map((o) => o.name).join(", ")}` };
|
|
36
|
+
}
|
|
37
|
+
return { success: true, data: { orbital, orbitals: schema.orbitals, sourcePath, importPath } };
|
|
38
|
+
}
|
|
39
|
+
clearCache() {
|
|
40
|
+
this.cache.clear();
|
|
41
|
+
}
|
|
42
|
+
getCacheStats() {
|
|
43
|
+
return { size: this.cache.size };
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
async function fetchSchema(url, importPath) {
|
|
47
|
+
let response;
|
|
48
|
+
try {
|
|
49
|
+
response = await fetch(url);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return { success: false, error: `${url}: ${error instanceof Error ? error.message : String(error)}` };
|
|
52
|
+
}
|
|
53
|
+
if (!response.ok) return { success: false, error: `${url}: ${response.status} ${response.statusText}` };
|
|
54
|
+
try {
|
|
55
|
+
const schema = await response.json();
|
|
56
|
+
return { success: true, data: { schema, sourcePath: url, importPath } };
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return { success: false, error: `${url} is not a program: ${error instanceof Error ? error.message : String(error)}` };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { FetchSchemaLoader };
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { normalizeEventKey, LIFECYCLE_EVENTS } from '@almadar/core';
|
|
2
|
-
|
|
3
1
|
// src/client/state-book.ts
|
|
4
2
|
function findInitialState(trait) {
|
|
5
3
|
const markedInitial = trait.states?.find((s) => s.isInitial)?.name;
|
|
@@ -14,17 +12,6 @@ function createInitialTraitState(trait) {
|
|
|
14
12
|
context: {}
|
|
15
13
|
};
|
|
16
14
|
}
|
|
17
|
-
function findMatchingTransitions(trait, currentState, eventKey, eventId) {
|
|
18
|
-
return (trait.transitions ?? []).filter((t) => {
|
|
19
|
-
const fromMatches = Array.isArray(t.from) ? t.from.includes(currentState) : t.from === currentState;
|
|
20
|
-
if (!fromMatches) return false;
|
|
21
|
-
if (eventId && t.eventId) return t.eventId === eventId;
|
|
22
|
-
return t.event === eventKey;
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
function findTransition(trait, currentState, eventKey, eventId) {
|
|
26
|
-
return findMatchingTransitions(trait, currentState, eventKey, eventId)[0];
|
|
27
|
-
}
|
|
28
15
|
var SINGLETON_SCOPE = "__singleton__";
|
|
29
16
|
var key = (traitName, entityId) => `${traitName}::${entityId ?? SINGLETON_SCOPE}`;
|
|
30
17
|
var StateBook = class {
|
|
@@ -51,29 +38,6 @@ var StateBook = class {
|
|
|
51
38
|
}
|
|
52
39
|
return out;
|
|
53
40
|
}
|
|
54
|
-
/** Whether the trait has an arm on the event from its current state. */
|
|
55
|
-
canHandleEvent(traitName, eventKey, entityId, eventId) {
|
|
56
|
-
const trait = this.traits.get(traitName);
|
|
57
|
-
const state = this.getState(traitName, entityId);
|
|
58
|
-
if (trait === void 0 || state === void 0) return false;
|
|
59
|
-
return findTransition(trait, state.currentState, normalizeEventKey(eventKey), eventId) !== void 0;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* The lifecycle event an embedded child can be repainted with: the one it handles from its current state,
|
|
63
|
-
* provided every matching arm stays in that state and only renders.
|
|
64
|
-
*/
|
|
65
|
-
repaintLifecycleEvent(traitName, entityId) {
|
|
66
|
-
const trait = this.traits.get(traitName);
|
|
67
|
-
const state = this.getState(traitName, entityId);
|
|
68
|
-
if (trait === void 0 || state === void 0) return void 0;
|
|
69
|
-
for (const event of LIFECYCLE_EVENTS) {
|
|
70
|
-
const arms = findMatchingTransitions(trait, state.currentState, event);
|
|
71
|
-
if (arms.length === 0) continue;
|
|
72
|
-
const repaints = arms.every((t) => t.to === state.currentState && (t.effects ?? []).every((effect) => Array.isArray(effect) && effect[0] === "render-ui"));
|
|
73
|
-
return repaints ? event : void 0;
|
|
74
|
-
}
|
|
75
|
-
return void 0;
|
|
76
|
-
}
|
|
77
41
|
/** Record the state the kernel reports. No-op for a trait not on the page. */
|
|
78
42
|
seedState(traitName, currentState, entityId) {
|
|
79
43
|
const trait = this.traits.get(traitName);
|
|
@@ -89,4 +53,4 @@ var StateBook = class {
|
|
|
89
53
|
}
|
|
90
54
|
};
|
|
91
55
|
|
|
92
|
-
export { StateBook, createInitialTraitState, findInitialState
|
|
56
|
+
export { StateBook, createInitialTraitState, findInitialState };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/runtime/entities/declaredCollections.ts
|
|
2
|
+
function bindDeclaredCollections(store, entities) {
|
|
3
|
+
for (const entity of entities) {
|
|
4
|
+
if (entity.name && entity.collection && entity.browser !== true && entity.persistence !== "runtime") {
|
|
5
|
+
store.bindCollection?.(entity.name, entity.collection);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { bindDeclaredCollections };
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
// src/kernel/os-port.ts
|
|
2
|
+
function kernelOsTls(server) {
|
|
3
|
+
return {
|
|
4
|
+
complete: (key, outcome) => {
|
|
5
|
+
const step = JSON.parse(server().tlsComplete(key, JSON.stringify(outcome)));
|
|
6
|
+
return step;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
}
|
|
2
10
|
function kernelOsCompletions(kernel) {
|
|
3
11
|
const deferring = JSON.parse(kernel.osDeferringOps());
|
|
4
12
|
const streaming = JSON.parse(kernel.osStreamOps());
|
|
@@ -13,7 +21,7 @@ function kernelOsCompletions(kernel) {
|
|
|
13
21
|
}
|
|
14
22
|
function kernelOsGrants(matchers, server) {
|
|
15
23
|
return {
|
|
16
|
-
allows: (need) => server().osAllows(JSON.stringify(need)),
|
|
24
|
+
allows: (need, orbital) => server().osAllows(JSON.stringify(need), orbital),
|
|
17
25
|
globMatches: (glob, path) => matchers.osGlobMatches(glob, path),
|
|
18
26
|
isPublicAddress: (ip) => matchers.isPublicAddress(ip),
|
|
19
27
|
anchor: (anchors) => {
|
|
@@ -50,8 +58,8 @@ function anchoredForms(anchors, path) {
|
|
|
50
58
|
function anchoredGrants(grants, anchors) {
|
|
51
59
|
return {
|
|
52
60
|
...grants,
|
|
53
|
-
allows: (need) => need.need === "fs" ? anchoredForms(anchors, need.path).some((path) => grants.allows({ ...need, path })) : grants.allows(need)
|
|
61
|
+
allows: (need, orbital) => need.need === "fs" ? anchoredForms(anchors, need.path).some((path) => grants.allows({ ...need, path }, orbital)) : grants.allows(need, orbital)
|
|
54
62
|
};
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
export { anchoredGrants, expandAnchor, kernelOsCompletions, kernelOsGrants };
|
|
65
|
+
export { anchoredGrants, expandAnchor, kernelOsCompletions, kernelOsGrants, kernelOsTls };
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { isJsonObject, credentialStorageKey, EntityRowSchema } from '@almadar/core';
|
|
2
|
+
|
|
3
|
+
// src/credentials/vault.ts
|
|
4
|
+
var CredentialExchangeError = class extends Error {
|
|
5
|
+
constructor(kind, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.kind = kind;
|
|
8
|
+
this.name = "CredentialExchangeError";
|
|
9
|
+
}
|
|
10
|
+
kind;
|
|
11
|
+
};
|
|
12
|
+
var FAILURE_KINDS = ["unavailable", "unsigned", "denied", "invalid_grant", "invalid_client", "transport", "malformed", "refused"];
|
|
13
|
+
function failureOf(value) {
|
|
14
|
+
if (value === void 0 || !isJsonObject(value)) throw new Error("the kernel connect failure is not an object");
|
|
15
|
+
const kind = FAILURE_KINDS.find((known) => known === value.kind);
|
|
16
|
+
if (kind === void 0) throw new Error("the kernel connect failure has an unknown kind");
|
|
17
|
+
return { kind, message: textOf(value.message, "failure message") };
|
|
18
|
+
}
|
|
19
|
+
function standingOf(value) {
|
|
20
|
+
if (value === void 0 || !isJsonObject(value) || typeof value.connected !== "boolean") throw new Error("the kernel credential standing is malformed");
|
|
21
|
+
return { connected: value.connected, ...typeof value.label === "string" ? { label: value.label } : {} };
|
|
22
|
+
}
|
|
23
|
+
function requestOf(value) {
|
|
24
|
+
if (value === void 0 || !isJsonObject(value) || !Array.isArray(value.headers)) throw new Error("the kernel token request is malformed");
|
|
25
|
+
const headers = value.headers.map((pair) => {
|
|
26
|
+
if (!Array.isArray(pair) || pair.length !== 2) throw new Error("the kernel token request header is malformed");
|
|
27
|
+
return [textOf(pair[0], "header name"), textOf(pair[1], "header value")];
|
|
28
|
+
});
|
|
29
|
+
return { request: { endpoint: textOf(value.endpoint, "token endpoint"), headers, body: textOf(value.body, "token body") }, ticket: numberOf(value.ticket, "exchange ticket") };
|
|
30
|
+
}
|
|
31
|
+
function parsed(json) {
|
|
32
|
+
return JSON.parse(json);
|
|
33
|
+
}
|
|
34
|
+
function objectOf(json, what) {
|
|
35
|
+
const value = parsed(json);
|
|
36
|
+
if (!isJsonObject(value)) throw new Error(`the kernel ${what} is not an object`);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function textOf(value, what) {
|
|
40
|
+
if (typeof value !== "string") throw new Error(`the kernel ${what} is not text`);
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function numberOf(value, what) {
|
|
44
|
+
if (typeof value !== "number") throw new Error(`the kernel ${what} is not a number`);
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
function entryOf(value) {
|
|
48
|
+
if (!isJsonObject(value)) throw new Error("the kernel credential entry is not an object");
|
|
49
|
+
return {
|
|
50
|
+
service: textOf(value.service, "entry service"),
|
|
51
|
+
envVar: textOf(value.envVar, "entry name"),
|
|
52
|
+
last4: textOf(value.last4, "entry last4"),
|
|
53
|
+
updatedAt: numberOf(value.updatedAt, "entry updatedAt"),
|
|
54
|
+
...value.principal !== void 0 ? { principal: textOf(value.principal, "entry principal") } : {}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function writeOf(item) {
|
|
58
|
+
if (!isJsonObject(item)) throw new Error("the kernel credential write is not an object");
|
|
59
|
+
const id = textOf(item.id, "write id");
|
|
60
|
+
if (item.write === "delete") return { write: "delete", id };
|
|
61
|
+
const data = EntityRowSchema.parse(item.data);
|
|
62
|
+
if (item.write === "update") return { write: "update", id, data };
|
|
63
|
+
if (item.write === "create") return { write: "create", id, data: { ...data, id } };
|
|
64
|
+
throw new Error("the kernel credential write is neither create, update nor delete");
|
|
65
|
+
}
|
|
66
|
+
var CredentialVault = class _CredentialVault {
|
|
67
|
+
constructor(kernel, rows) {
|
|
68
|
+
this.kernel = kernel;
|
|
69
|
+
this.rows = rows;
|
|
70
|
+
}
|
|
71
|
+
kernel;
|
|
72
|
+
rows;
|
|
73
|
+
listeners = /* @__PURE__ */ new Set();
|
|
74
|
+
queue = Promise.resolve();
|
|
75
|
+
broken = false;
|
|
76
|
+
/** List the host's rows into the kernel and read them; rejects naming the rows it cannot read. */
|
|
77
|
+
static async open(kernel, rows) {
|
|
78
|
+
const vault = new _CredentialVault(kernel, rows);
|
|
79
|
+
await vault.reload();
|
|
80
|
+
return vault;
|
|
81
|
+
}
|
|
82
|
+
/** True when a valid master key is present (writes and decryption enabled). */
|
|
83
|
+
get enabled() {
|
|
84
|
+
return this.kernel.enabled;
|
|
85
|
+
}
|
|
86
|
+
/** Subscribe to credential changes; returns an unsubscribe function. */
|
|
87
|
+
subscribe(listener) {
|
|
88
|
+
this.listeners.add(listener);
|
|
89
|
+
return () => this.listeners.delete(listener);
|
|
90
|
+
}
|
|
91
|
+
notify(principal) {
|
|
92
|
+
for (const listener of this.listeners) listener(principal);
|
|
93
|
+
}
|
|
94
|
+
async reload() {
|
|
95
|
+
try {
|
|
96
|
+
this.kernel.load(JSON.stringify(await this.rows.list()));
|
|
97
|
+
this.broken = false;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
this.broken = true;
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
current() {
|
|
104
|
+
if (this.broken) throw new Error("the credential store could not be read or written; reload it");
|
|
105
|
+
return this.kernel;
|
|
106
|
+
}
|
|
107
|
+
async apply() {
|
|
108
|
+
const writes = parsed(this.kernel.takeWrites());
|
|
109
|
+
if (!Array.isArray(writes)) throw new Error("the kernel credential writes are not a list");
|
|
110
|
+
try {
|
|
111
|
+
for (const item of writes) {
|
|
112
|
+
const write = writeOf(item);
|
|
113
|
+
if (write.write === "create") await this.rows.create(write.data);
|
|
114
|
+
else if (write.write === "update") await this.rows.update(write.id, write.data);
|
|
115
|
+
else await this.rows.delete(write.id);
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
this.broken = true;
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** One operation at a time: the kernel runs it, then its writes reach the medium before the next begins. */
|
|
123
|
+
serial(run) {
|
|
124
|
+
const next = this.queue.then(run);
|
|
125
|
+
this.queue = next.then(
|
|
126
|
+
() => void 0,
|
|
127
|
+
() => void 0
|
|
128
|
+
);
|
|
129
|
+
return next;
|
|
130
|
+
}
|
|
131
|
+
exclusive(fresh, work, principal) {
|
|
132
|
+
return this.serial(async () => {
|
|
133
|
+
if (fresh || this.broken) await this.reload();
|
|
134
|
+
let result;
|
|
135
|
+
try {
|
|
136
|
+
result = work();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (this.kernel.takeWrites() !== "[]") this.broken = true;
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
await this.apply();
|
|
142
|
+
this.notify(principal);
|
|
143
|
+
return result;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/** Resolve one credential by name (warmed values only). */
|
|
147
|
+
resolve(envVar, principal) {
|
|
148
|
+
return this.current().resolve(envVar, principal);
|
|
149
|
+
}
|
|
150
|
+
/** The non-secret account label stored in the clear beside a credential's ciphertext; undefined when its row carries none. */
|
|
151
|
+
label(envVar, principal) {
|
|
152
|
+
return this.current().label(envVar, principal);
|
|
153
|
+
}
|
|
154
|
+
/** All app-wide values as an env-shaped map, for merging over the process env. */
|
|
155
|
+
snapshotEnv() {
|
|
156
|
+
const env = {};
|
|
157
|
+
for (const [name, value] of Object.entries(objectOf(this.current().snapshotEnv(), "environment"))) env[name] = textOf(value, "environment value");
|
|
158
|
+
return env;
|
|
159
|
+
}
|
|
160
|
+
/** Masked entries for display — never includes plaintext. */
|
|
161
|
+
entries(principal) {
|
|
162
|
+
const entries = parsed(this.current().entries(principal));
|
|
163
|
+
if (!Array.isArray(entries)) throw new Error("the kernel credential entries are not a list");
|
|
164
|
+
return entries.map(entryOf);
|
|
165
|
+
}
|
|
166
|
+
/** Whether the store can encrypt and the service and name of every app-wide row. */
|
|
167
|
+
listing() {
|
|
168
|
+
const listing = objectOf(this.current().listing(), "listing");
|
|
169
|
+
const entries = listing.entries;
|
|
170
|
+
if (!Array.isArray(entries) || typeof listing.enabled !== "boolean") throw new Error("the kernel credential listing is malformed");
|
|
171
|
+
return {
|
|
172
|
+
enabled: listing.enabled,
|
|
173
|
+
entries: entries.map((entry) => {
|
|
174
|
+
if (!isJsonObject(entry)) throw new Error("the kernel credential listing holds a row that is not an object");
|
|
175
|
+
const row = { service: textOf(entry.service, "listing service"), name: textOf(entry.name, "listing name") };
|
|
176
|
+
return entry.last4 === void 0 ? row : { ...row, last4: textOf(entry.last4, "listing last4") };
|
|
177
|
+
})
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** Upsert one credential; `label` is a non-secret account name kept in the clear beside it, left out it stays as it is. */
|
|
181
|
+
async set(service, envVar, value, principal, label) {
|
|
182
|
+
return entryOf(parsed(await this.exclusive(false, () => this.kernel.set(service, envVar, value, principal, label), principal)));
|
|
183
|
+
}
|
|
184
|
+
/** Replace the value of a credential already stored, keeping its service (a rotated refresh token). */
|
|
185
|
+
async replace(envVar, value, principal) {
|
|
186
|
+
return entryOf(parsed(await this.exclusive(false, () => this.kernel.replace(envVar, value, principal), principal)));
|
|
187
|
+
}
|
|
188
|
+
/** Keep `value` as the credential until it is forgotten, creating it when absent. */
|
|
189
|
+
async persist(envVar, value, principal, label) {
|
|
190
|
+
return entryOf(parsed(await this.exclusive(false, () => this.kernel.persist(envVar, value, principal, label), principal)));
|
|
191
|
+
}
|
|
192
|
+
/** Delete one credential row; whether one existed. */
|
|
193
|
+
remove(envVar, principal) {
|
|
194
|
+
return this.exclusive(false, () => this.kernel.remove(envVar, principal), principal);
|
|
195
|
+
}
|
|
196
|
+
flights = /* @__PURE__ */ new Map();
|
|
197
|
+
turns = /* @__PURE__ */ new Map();
|
|
198
|
+
async post(post, begun) {
|
|
199
|
+
try {
|
|
200
|
+
return await post(begun.request);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
this.kernel.connectAbandon(begun.ticket);
|
|
203
|
+
throw new CredentialExchangeError("transport", error instanceof Error ? error.message : "the token endpoint could not be reached");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async finish(ticket, reply) {
|
|
207
|
+
const settled = objectOf(await this.exclusive(false, () => this.kernel.connectFinish(ticket, reply.status, reply.body)), "connect outcome");
|
|
208
|
+
if (settled.error !== void 0) {
|
|
209
|
+
const failure = failureOf(settled.error);
|
|
210
|
+
throw new CredentialExchangeError(failure.kind, failure.message);
|
|
211
|
+
}
|
|
212
|
+
return standingOf(settled.ok);
|
|
213
|
+
}
|
|
214
|
+
/** Exchange `grant` at its token endpoint through `post` and keep the result as the credential: the kernel builds the request and settles the reply, the host only sends. */
|
|
215
|
+
async connect(grant, post) {
|
|
216
|
+
try {
|
|
217
|
+
const run = async () => {
|
|
218
|
+
const begun = objectOf(this.current().connectBegin(JSON.stringify(grant)), "connect request");
|
|
219
|
+
if (begun.error !== void 0) {
|
|
220
|
+
const failure = failureOf(begun.error);
|
|
221
|
+
throw new CredentialExchangeError(failure.kind, failure.message);
|
|
222
|
+
}
|
|
223
|
+
const request = requestOf(begun.ok);
|
|
224
|
+
return this.finish(request.ticket, await this.post(post, request));
|
|
225
|
+
};
|
|
226
|
+
if (grant.grant !== "refresh_token") return { ok: await run() };
|
|
227
|
+
const key = credentialStorageKey({ name: grant.name, ...grant.principal !== void 0 ? { principal: grant.principal } : {} });
|
|
228
|
+
const previous = this.turns.get(key);
|
|
229
|
+
const mine = (async () => {
|
|
230
|
+
await previous?.catch(() => void 0);
|
|
231
|
+
return run();
|
|
232
|
+
})();
|
|
233
|
+
this.turns.set(key, mine);
|
|
234
|
+
try {
|
|
235
|
+
return { ok: await mine };
|
|
236
|
+
} finally {
|
|
237
|
+
if (this.turns.get(key) === mine) this.turns.delete(key);
|
|
238
|
+
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (error instanceof CredentialExchangeError) return { error: { kind: error.kind, message: error.message } };
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/** The credential `envVar` for `principal`, refreshed first through `post` when it is past its expiry; concurrent resolves share one refresh. Undefined when nothing is held, nothing can renew it, or the provider refused (which drops the row). */
|
|
245
|
+
async resolveFresh(envVar, principal, post) {
|
|
246
|
+
const begun = objectOf(this.current().refreshBegin(envVar, principal), "refresh outcome");
|
|
247
|
+
if (begun.error !== void 0) {
|
|
248
|
+
const failure = failureOf(begun.error);
|
|
249
|
+
throw new CredentialExchangeError(failure.kind, failure.message);
|
|
250
|
+
}
|
|
251
|
+
const ok = begun.ok;
|
|
252
|
+
if (!isJsonObject(ok)) throw new Error("the kernel refresh outcome is malformed");
|
|
253
|
+
if (ok.exchange === void 0) return typeof ok.resolved === "string" ? ok.resolved : void 0;
|
|
254
|
+
const key = credentialStorageKey({ name: envVar, ...principal !== void 0 ? { principal } : {} });
|
|
255
|
+
const running = this.flights.get(key);
|
|
256
|
+
if (running !== void 0) {
|
|
257
|
+
this.current().connectAbandon(requestOf(ok.exchange).ticket);
|
|
258
|
+
return running;
|
|
259
|
+
}
|
|
260
|
+
const request = requestOf(ok.exchange);
|
|
261
|
+
const flight = (async () => {
|
|
262
|
+
try {
|
|
263
|
+
await this.finish(request.ticket, await this.post(post, request));
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error instanceof CredentialExchangeError && error.kind === "invalid_grant") return void 0;
|
|
266
|
+
throw error;
|
|
267
|
+
}
|
|
268
|
+
return this.resolve(envVar, principal);
|
|
269
|
+
})();
|
|
270
|
+
this.flights.set(key, flight);
|
|
271
|
+
try {
|
|
272
|
+
return await flight;
|
|
273
|
+
} finally {
|
|
274
|
+
this.flights.delete(key);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/** Re-encrypt every row under the current master key; a row the kernel cannot read fails it before any row changes. */
|
|
278
|
+
async rotate() {
|
|
279
|
+
const counts = objectOf(await this.exclusive(true, () => this.kernel.rotate()), "rotation");
|
|
280
|
+
return { rotated: numberOf(counts.rotated, "rotated"), alreadyCurrent: numberOf(counts.alreadyCurrent, "alreadyCurrent"), unreadable: numberOf(counts.unreadable, "unreadable") };
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export { CredentialExchangeError, CredentialVault };
|