@almadar/shell-runtime 0.0.0-stage → 1.0.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-4SEK5ER2.js +8 -0
- package/dist/chunk-57SBCUSM.js +62 -0
- package/dist/chunk-625EP7W2.js +108 -0
- package/dist/chunk-64IH2SMY.js +22 -0
- package/dist/chunk-7SDV3QWR.js +67 -0
- package/dist/chunk-7WB6VWRK.js +798 -0
- package/dist/chunk-A2JJT4GA.js +56 -0
- package/dist/chunk-BAS26HBN.js +10 -0
- package/dist/chunk-BOVU4TCQ.js +65 -0
- package/dist/chunk-BY5G4225.js +6 -0
- package/dist/chunk-DFDNHVDW.js +284 -0
- package/dist/chunk-ELBLT2HZ.js +1 -0
- package/dist/chunk-EZZYLTWY.js +1 -0
- package/dist/chunk-FWA5K7Q2.js +48 -0
- package/dist/chunk-HC6ERSAH.js +55 -0
- package/dist/chunk-HLOUDLPI.js +48 -0
- package/dist/chunk-I3TKINSO.js +398 -0
- package/dist/chunk-IOPUS6Q5.js +15 -0
- package/dist/chunk-LKWYCYX5.js +22 -0
- package/dist/chunk-LYFIQPXX.js +1 -0
- package/dist/chunk-LZIOTBFX.js +1 -0
- package/dist/chunk-MU335ZA4.js +63 -0
- package/dist/chunk-MZKIL5IN.js +74 -0
- package/dist/chunk-N7HYNKK6.js +83 -0
- package/dist/chunk-NTFQ36UN.js +52 -0
- package/dist/chunk-NU6LVKGS.js +15 -0
- package/dist/chunk-O57GKTYU.js +30 -0
- package/dist/chunk-P2N6K2IV.js +20 -0
- package/dist/chunk-P53CKOME.js +1 -0
- package/dist/chunk-QCJWPXLH.js +40 -0
- package/dist/chunk-SKLMWHQD.js +109 -0
- package/dist/chunk-SOQDEPME.js +131 -0
- package/dist/chunk-UBN5JJKZ.js +26 -0
- package/dist/chunk-UD2CXYSJ.js +244 -0
- package/dist/chunk-UP6GGGAN.js +34 -0
- package/dist/chunk-V4EMDPCO.js +33 -0
- package/dist/chunk-VNGXRYFF.js +9 -0
- package/dist/chunk-WGCWJOT2.js +21 -0
- package/dist/chunk-X6W44WHO.js +26 -0
- package/dist/chunk-XXWNMJI6.js +15 -0
- package/dist/chunk-ZUSM4VW4.js +197 -0
- package/dist/client/EventTransport.d.ts +147 -0
- package/dist/client/EventTransport.js +1 -0
- package/dist/client/awaiting.d.ts +28 -0
- package/dist/client/awaiting.js +1 -0
- package/dist/client/channel-transport.d.ts +69 -0
- package/dist/client/channel-transport.js +1 -0
- package/dist/client/circuit-store.d.ts +87 -0
- package/dist/client/circuit-store.js +3 -0
- package/dist/client/client-kernel.d.ts +134 -0
- package/dist/client/client-kernel.js +1 -0
- package/dist/client/declared-input-dispatch.d.ts +22 -0
- package/dist/client/declared-input-dispatch.js +1 -0
- package/dist/client/inbound-dispatch.d.ts +24 -0
- package/dist/client/inbound-dispatch.js +1 -0
- package/dist/client/local-fold.d.ts +33 -0
- package/dist/client/local-fold.js +1 -0
- package/dist/client/render-sigils.d.ts +24 -0
- package/dist/client/render-sigils.js +1 -0
- package/dist/client/state-book.d.ts +30 -0
- package/dist/client/state-book.js +1 -0
- package/dist/client/trait-mount-error.d.ts +9 -0
- package/dist/client/trait-mount-error.js +1 -0
- package/dist/client/trait-types.d.ts +67 -0
- package/dist/client/trait-types.js +1 -0
- package/dist/credentials/env-vars.d.ts +18 -0
- package/dist/credentials/env-vars.js +1 -0
- package/dist/credentials/scope.d.ts +51 -0
- package/dist/credentials/scope.js +1 -0
- package/dist/credentials/vault.d.ts +142 -0
- package/dist/credentials/vault.js +1 -0
- package/dist/effects/in-flight-calls.d.ts +36 -0
- package/dist/effects/in-flight-calls.js +1 -0
- package/dist/effects/on-device-model.d.ts +22 -0
- package/dist/effects/on-device-model.js +1 -0
- package/dist/events/EventBus.d.ts +87 -0
- package/dist/events/EventBus.js +1 -0
- package/dist/events/emit-stamp.d.ts +47 -0
- package/dist/events/emit-stamp.js +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +41 -0
- package/dist/kernel/decode.d.ts +13 -0
- package/dist/kernel/decode.js +1 -0
- package/dist/kernel/engine.d.ts +195 -0
- package/dist/kernel/engine.js +9 -0
- package/dist/kernel/frame-applier.d.ts +44 -0
- package/dist/kernel/frame-applier.js +1 -0
- package/dist/kernel/host.d.ts +206 -0
- package/dist/kernel/host.js +6 -0
- package/dist/kernel/lane.d.ts +7 -0
- package/dist/kernel/lane.js +1 -0
- 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 +155 -0
- package/dist/kernel/os-port.js +1 -0
- package/dist/kernel/program-effects.d.ts +48 -0
- package/dist/kernel/program-effects.js +1 -0
- package/dist/kernel/secret-ports.d.ts +139 -0
- package/dist/kernel/secret-ports.js +2 -0
- package/dist/kernel/wire.d.ts +104 -0
- package/dist/kernel/wire.js +1 -0
- package/dist/loader/fetch-loader.d.ts +26 -0
- package/dist/loader/fetch-loader.js +1 -0
- package/dist/loader/lazy-page.d.ts +12 -0
- package/dist/loader/lazy-page.js +1 -0
- package/dist/loader/schema-loader.d.ts +144 -0
- package/dist/loader/schema-loader.js +1 -0
- package/dist/persistence/native-memory.d.ts +28 -0
- package/dist/persistence/native-memory.js +1 -0
- 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 +59 -0
- package/dist/runtime/evaluation/local-store-transport.js +14 -0
- package/dist/runtime/evaluation/residence-transport.d.ts +15 -0
- package/dist/runtime/evaluation/residence-transport.js +1 -0
- package/dist/runtime/evaluation/routed-persistence.d.ts +9 -0
- package/dist/runtime/evaluation/routed-persistence.js +1 -0
- package/dist/runtime/events/identity/routing.d.ts +33 -0
- package/dist/runtime/events/identity/routing.js +1 -0
- package/dist/runtime/types.d.ts +374 -0
- package/dist/runtime/types.js +1 -0
- package/dist/service/default-call-service.d.ts +23 -0
- package/dist/service/default-call-service.js +2 -0
- package/dist/service/service-provider.d.ts +37 -0
- package/dist/service/service-provider.js +1 -0
- package/package.json +49 -3
- package/README.md +0 -3
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
import { isProgramEffect } from './chunk-WGCWJOT2.js';
|
|
2
|
+
import { systemRandom, base64Standard } from './chunk-ZUSM4VW4.js';
|
|
3
|
+
import { OnDeviceModelFailure, promptOnDevice, embedOnDevice } from './chunk-FWA5K7Q2.js';
|
|
4
|
+
import { untilAborted } from './chunk-MZKIL5IN.js';
|
|
5
|
+
import { isJsonObject, WriteDeclinedError, QueryDeclinedError, TabHostFailure, RowSetRefSchema, toPayloadValue, credentialStatusConnected } from '@almadar/core';
|
|
6
|
+
|
|
7
|
+
function callerViewer(caller) {
|
|
8
|
+
return caller === void 0 ? void 0 : { id: caller.principal, ...caller.role !== void 0 ? { role: caller.role } : {} };
|
|
9
|
+
}
|
|
10
|
+
function issuePayload(issue) {
|
|
11
|
+
const payload = { code: issue.code, message: issue.message };
|
|
12
|
+
if (issue.path !== void 0) payload.path = issue.path;
|
|
13
|
+
if (issue.line !== void 0) payload.line = issue.line;
|
|
14
|
+
if (issue.column !== void 0) payload.column = issue.column;
|
|
15
|
+
return payload;
|
|
16
|
+
}
|
|
17
|
+
var HostAskError = class extends Error {
|
|
18
|
+
constructor(kind, message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.kind = kind;
|
|
21
|
+
}
|
|
22
|
+
kind;
|
|
23
|
+
};
|
|
24
|
+
function programDocumentJson(document) {
|
|
25
|
+
return typeof document === "string" ? document : toPayloadValue(document);
|
|
26
|
+
}
|
|
27
|
+
function kernelEventRequestParser(kernel) {
|
|
28
|
+
return (payload, options) => {
|
|
29
|
+
try {
|
|
30
|
+
return { ok: kernel.parseEventRequest(JSON.stringify(payload), JSON.stringify(options)) };
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async function settle(work) {
|
|
37
|
+
try {
|
|
38
|
+
return { ok: await work() };
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function serviceParams(args) {
|
|
44
|
+
if (args === null) return void 0;
|
|
45
|
+
if (!isJsonObject(args)) throw new Error("call-service: the arguments are not an object");
|
|
46
|
+
return args;
|
|
47
|
+
}
|
|
48
|
+
function failureMessage(error) {
|
|
49
|
+
return error instanceof Error ? error.message : String(error);
|
|
50
|
+
}
|
|
51
|
+
async function callService(ports, request, scope) {
|
|
52
|
+
const { service, method, args, key, caller, site } = request;
|
|
53
|
+
const identity = caller !== void 0 ? { principal: caller.principal, ...caller.role !== void 0 ? { role: caller.role } : {} } : {};
|
|
54
|
+
const orbital = site?.orbital ?? scope.orbital;
|
|
55
|
+
const onMessage = site?.emit.onMessage;
|
|
56
|
+
const message = (payload) => {
|
|
57
|
+
if (site === void 0 || onMessage === void 0) return;
|
|
58
|
+
ports.deliverLive({ orbital, trait: site.trait, event: onMessage }, payload, scope.originClientId);
|
|
59
|
+
};
|
|
60
|
+
const call = async (signal, outsideRequest) => {
|
|
61
|
+
const host = site !== void 0 ? ports.servicePorts({ orbital, trait: site.trait }, callerViewer(caller), message, outsideRequest === true ? void 0 : scope.assertReentry) : void 0;
|
|
62
|
+
const context = caller !== void 0 || signal !== void 0 || host !== void 0 || outsideRequest !== void 0 ? { ...identity, ...host !== void 0 ? { host } : {}, ...signal !== void 0 ? { signal } : {}, ...outsideRequest !== void 0 ? { outsideRequest } : {} } : void 0;
|
|
63
|
+
const result = await ports.callService(service, method, serviceParams(args), context);
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
const deferred = site?.followOn === true ? ports.deferCallOutcome() : void 0;
|
|
67
|
+
if (site !== void 0 && deferred !== void 0) {
|
|
68
|
+
startDeferredCall(deferred, site, key, call, { ports, orbital, scope });
|
|
69
|
+
return { kind: "serviceDeferred" };
|
|
70
|
+
}
|
|
71
|
+
const run = async () => {
|
|
72
|
+
if (key === void 0) return { kind: "service", result: { ok: await call() } };
|
|
73
|
+
const outcome = await ports.inFlightCalls.run(key, (signal) => untilAborted(signal, call(signal)));
|
|
74
|
+
return outcome.cancelled ? { kind: "serviceCancelled" } : { kind: "service", result: { ok: outcome.value } };
|
|
75
|
+
};
|
|
76
|
+
try {
|
|
77
|
+
return await run();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return { kind: "service", result: { error: failureMessage(error) } };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function startDeferredCall(defer, site, key, call, context) {
|
|
83
|
+
const { ports, orbital, scope } = context;
|
|
84
|
+
const deliver = (event, payload) => defer({
|
|
85
|
+
orbital,
|
|
86
|
+
traitName: site.trait,
|
|
87
|
+
event,
|
|
88
|
+
payload,
|
|
89
|
+
entityId: scope.entityId,
|
|
90
|
+
originClientId: scope.originClientId,
|
|
91
|
+
locale: scope.locale
|
|
92
|
+
});
|
|
93
|
+
const outcome = key === void 0 ? call(void 0, true).then((value) => ({ cancelled: false, value })) : ports.inFlightCalls.run(key, (signal) => untilAborted(signal, call(signal, true)));
|
|
94
|
+
void outcome.then(
|
|
95
|
+
(settled) => {
|
|
96
|
+
if (settled.cancelled) {
|
|
97
|
+
if (site.emit.cancelled !== void 0 && key !== void 0) deliver(site.emit.cancelled, { key });
|
|
98
|
+
} else if (site.emit.success !== void 0) {
|
|
99
|
+
deliver(site.emit.success, keyed(settled.value === null ? void 0 : toEventPayload(settled.value), key));
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
(error) => {
|
|
103
|
+
if (site.emit.failure !== void 0) deliver(site.emit.failure, keyed({ error: failureMessage(error) }, key));
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
function keyed(payload, key) {
|
|
108
|
+
return payload !== void 0 && key !== void 0 ? { ...payload, key } : payload;
|
|
109
|
+
}
|
|
110
|
+
function toEventPayload(value) {
|
|
111
|
+
return isJsonObject(value) ? value : void 0;
|
|
112
|
+
}
|
|
113
|
+
async function substrate(ports, request, scope) {
|
|
114
|
+
const { key, site } = request;
|
|
115
|
+
const sink = ports.deferCallOutcome();
|
|
116
|
+
const complete = ports.substrateCompletion;
|
|
117
|
+
if (key === void 0 || site === void 0 || sink === void 0 || complete === void 0) return substrateInPlace(ports, request, scope);
|
|
118
|
+
const source = site.emit.onMessage !== void 0 ? ports.substrateStream?.(request.op, request.args, scope) : void 0;
|
|
119
|
+
if (ports.inFlightCalls.inFlight(key)) return { kind: "substrate", result: { error: { message: source !== void 0 ? `key ${key} already has a stream open` : `key ${key} already has a completion pending`, errors: [] } } };
|
|
120
|
+
const deliver = (fired) => {
|
|
121
|
+
if (fired !== null) sink({ orbital: site.orbital ?? scope.orbital, traitName: site.trait, event: fired.event, payload: isJsonObject(fired.payload) ? fired.payload : void 0, entityId: site.entityId, originClientId: scope.originClientId, locale: scope.locale });
|
|
122
|
+
};
|
|
123
|
+
const failed = (error) => deliver(complete(site, key, request.op, request.args, { error: { message: failureMessage(error), errors: [] } }));
|
|
124
|
+
if (source !== void 0 && ports.substrateMessage !== void 0) {
|
|
125
|
+
const message = ports.substrateMessage;
|
|
126
|
+
void ports.inFlightCalls.run(key, (signal) => new Promise((resolve) => {
|
|
127
|
+
let unsubscribe;
|
|
128
|
+
let ended = false;
|
|
129
|
+
const end = () => {
|
|
130
|
+
ended = true;
|
|
131
|
+
unsubscribe?.();
|
|
132
|
+
signal.removeEventListener("abort", end);
|
|
133
|
+
resolve();
|
|
134
|
+
};
|
|
135
|
+
signal.addEventListener("abort", end, { once: true });
|
|
136
|
+
unsubscribe = source.subscribe((value) => {
|
|
137
|
+
if (signal.aborted) return;
|
|
138
|
+
const fired = message(site, key, value);
|
|
139
|
+
deliver(fired);
|
|
140
|
+
}, end);
|
|
141
|
+
if (ended) unsubscribe();
|
|
142
|
+
})).catch(failed);
|
|
143
|
+
return { kind: "serviceDeferred" };
|
|
144
|
+
}
|
|
145
|
+
const pending = ports.inFlightCalls.run(key, (signal) => untilAborted(signal, substrateInPlace(ports, request, { ...scope, assertReentry: void 0 })));
|
|
146
|
+
void pending.then((outcome) => {
|
|
147
|
+
if (outcome.cancelled || outcome.value.kind !== "substrate") return;
|
|
148
|
+
deliver(complete(site, key, request.op, request.args, outcome.value.result));
|
|
149
|
+
}, failed);
|
|
150
|
+
return { kind: "serviceDeferred" };
|
|
151
|
+
}
|
|
152
|
+
function optionalKey(op, args) {
|
|
153
|
+
if (args.length === 0) return { key: void 0 };
|
|
154
|
+
const [key] = args;
|
|
155
|
+
if (args.length === 1 && typeof key === "string" && key !== "") return { key };
|
|
156
|
+
return { error: `${op}: the key must be one non-empty text` };
|
|
157
|
+
}
|
|
158
|
+
async function performHostAsk(actions, op, args) {
|
|
159
|
+
const fail = (message) => ({ error: message });
|
|
160
|
+
if (op === "auth/sign-out") {
|
|
161
|
+
if (args.length !== 0) return fail("auth/sign-out takes no arguments");
|
|
162
|
+
if (actions?.signOut === void 0) return fail("no host sign-out is configured for auth/sign-out");
|
|
163
|
+
try {
|
|
164
|
+
await actions.signOut();
|
|
165
|
+
return { ok: null };
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const audio = actions?.audio;
|
|
171
|
+
if (audio === void 0) return fail(`no host audio is configured for ${op}`);
|
|
172
|
+
let ask;
|
|
173
|
+
if (op === "audio/stop-music") {
|
|
174
|
+
if (args.length !== 0) return fail("audio/stop-music takes no arguments");
|
|
175
|
+
ask = { op };
|
|
176
|
+
} else {
|
|
177
|
+
const parsed = optionalKey(op, args);
|
|
178
|
+
if ("error" in parsed) return fail(parsed.error);
|
|
179
|
+
if (op === "audio/play-sound") {
|
|
180
|
+
if (parsed.key === void 0) return fail("audio/play-sound: the key must be one non-empty text");
|
|
181
|
+
ask = { op, key: parsed.key };
|
|
182
|
+
} else if (op === "audio/stop-sound" || op === "audio/change-music") {
|
|
183
|
+
ask = { op, key: parsed.key };
|
|
184
|
+
} else {
|
|
185
|
+
return fail(`no host action is configured for ${op}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
audio(ask);
|
|
190
|
+
return { ok: null };
|
|
191
|
+
} catch (error) {
|
|
192
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
var SIGN_IN_FIELDS = {
|
|
196
|
+
google: ["method"],
|
|
197
|
+
email: ["method", "email", "password"],
|
|
198
|
+
emailLink: ["method", "email", "continueUrl", "link"],
|
|
199
|
+
customToken: ["method", "token"]
|
|
200
|
+
};
|
|
201
|
+
function parseSignInRequest(args) {
|
|
202
|
+
const [request] = args;
|
|
203
|
+
if (args.length !== 1 || !isJsonObject(request)) return { error: "auth/sign-in takes one request object" };
|
|
204
|
+
const method = request.method;
|
|
205
|
+
const allowed = typeof method === "string" ? SIGN_IN_FIELDS[method] : void 0;
|
|
206
|
+
if (typeof method !== "string" || allowed === void 0) return { error: "auth/sign-in: method must be google, email, emailLink or customToken" };
|
|
207
|
+
const text = (field) => {
|
|
208
|
+
const value = request[field];
|
|
209
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
210
|
+
};
|
|
211
|
+
for (const field of Object.keys(request)) {
|
|
212
|
+
if (!allowed.includes(field)) return { error: `auth/sign-in: ${field} is not a field of the ${method} method` };
|
|
213
|
+
}
|
|
214
|
+
if (method === "google") return { request: { method } };
|
|
215
|
+
if (method === "email") {
|
|
216
|
+
const email2 = text("email");
|
|
217
|
+
const password = text("password");
|
|
218
|
+
return email2 !== void 0 && password !== void 0 ? { request: { method, email: email2, password } } : { error: "auth/sign-in: email takes an email and a password" };
|
|
219
|
+
}
|
|
220
|
+
if (method === "customToken") {
|
|
221
|
+
const token = text("token");
|
|
222
|
+
return token !== void 0 ? { request: { method, token } } : { error: "auth/sign-in: customToken takes a token" };
|
|
223
|
+
}
|
|
224
|
+
const email = text("email");
|
|
225
|
+
const continueUrl = text("continueUrl");
|
|
226
|
+
const link = text("link");
|
|
227
|
+
if (email === void 0 || continueUrl === void 0 === (link === void 0)) {
|
|
228
|
+
return { error: "auth/sign-in: emailLink takes an email and exactly one of continueUrl (send the link) or link (complete the sign-in)" };
|
|
229
|
+
}
|
|
230
|
+
return continueUrl !== void 0 ? { request: { method: "emailLink", email, continueUrl } } : { request: { method: "emailLink", email, link } };
|
|
231
|
+
}
|
|
232
|
+
function parseConsentRequest(args) {
|
|
233
|
+
const [request] = args;
|
|
234
|
+
if (args.length !== 1 || !isJsonObject(request)) return { error: "auth/consent takes one request object" };
|
|
235
|
+
for (const field of Object.keys(request)) {
|
|
236
|
+
if (field !== "authorizeUrl") return { error: `auth/consent: ${field} is not a field of the request` };
|
|
237
|
+
}
|
|
238
|
+
const authorizeUrl = request.authorizeUrl;
|
|
239
|
+
return typeof authorizeUrl === "string" && authorizeUrl !== "" ? { request: { authorizeUrl } } : { error: "auth/consent: authorizeUrl must be one non-empty text" };
|
|
240
|
+
}
|
|
241
|
+
function consentFromRedirect(redirectUrl) {
|
|
242
|
+
let params;
|
|
243
|
+
try {
|
|
244
|
+
params = new URL(redirectUrl).searchParams;
|
|
245
|
+
} catch {
|
|
246
|
+
throw new HostAskError("invalid", "the consent redirect is not a URL");
|
|
247
|
+
}
|
|
248
|
+
const code = params.get("code");
|
|
249
|
+
const state = params.get("state");
|
|
250
|
+
if (code === null || code === "" || state === null || state === "") throw new HostAskError("denied", "the consent redirect carried no code and state");
|
|
251
|
+
return { code, state };
|
|
252
|
+
}
|
|
253
|
+
async function performConsent(actions, args) {
|
|
254
|
+
const parsed = parseConsentRequest(args);
|
|
255
|
+
if ("error" in parsed) return { error: parsed.error, kind: "invalid" };
|
|
256
|
+
if (actions?.consent === void 0) return { error: "no host consent is configured for auth/consent", kind: "unavailable" };
|
|
257
|
+
try {
|
|
258
|
+
const { code, state } = await actions.consent(parsed.request);
|
|
259
|
+
return { ok: { code, state } };
|
|
260
|
+
} catch (error) {
|
|
261
|
+
if (error instanceof HostAskError) return { error: error.message, kind: error.kind };
|
|
262
|
+
return { error: error instanceof Error ? error.message : String(error), kind: "unavailable" };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function performHostAnswer(actions, op, args) {
|
|
266
|
+
if (op === "auth/consent") return performConsent(actions, args);
|
|
267
|
+
if (op !== "auth/sign-in") {
|
|
268
|
+
const outcome = await performHostAsk(actions, op, args);
|
|
269
|
+
return "error" in outcome ? { error: outcome.error, kind: "unavailable" } : { ok: {} };
|
|
270
|
+
}
|
|
271
|
+
const parsed = parseSignInRequest(args);
|
|
272
|
+
if ("error" in parsed) return { error: parsed.error, kind: "invalid" };
|
|
273
|
+
if (actions?.signIn === void 0) return { error: "no host sign-in is configured for auth/sign-in", kind: "unavailable" };
|
|
274
|
+
try {
|
|
275
|
+
const answer = await actions.signIn(parsed.request);
|
|
276
|
+
const fields = {};
|
|
277
|
+
if ("sent" in answer) fields.sent = true;
|
|
278
|
+
else {
|
|
279
|
+
fields.uid = answer.uid;
|
|
280
|
+
if (answer.email !== void 0) fields.email = answer.email;
|
|
281
|
+
if (answer.displayName !== void 0) fields.displayName = answer.displayName;
|
|
282
|
+
}
|
|
283
|
+
return { ok: fields };
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (error instanceof HostAskError) return { error: error.message, kind: error.kind };
|
|
286
|
+
return { error: error instanceof Error ? error.message : String(error), kind: "unavailable" };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function hostAskReturn(completion, outcome) {
|
|
290
|
+
const keyed2 = completion.key !== void 0 ? { key: completion.key } : {};
|
|
291
|
+
const event = "ok" in outcome ? completion.success : completion.failure;
|
|
292
|
+
if (event === void 0) return void 0;
|
|
293
|
+
const payload = "ok" in outcome ? { ...outcome.ok, ...keyed2 } : { error: outcome.error, kind: outcome.kind, ...keyed2 };
|
|
294
|
+
return { trait: completion.trait, ...completion.entityId !== void 0 ? { entityId: completion.entityId } : {}, event, payload };
|
|
295
|
+
}
|
|
296
|
+
async function hostAction(ports, request) {
|
|
297
|
+
const outcome = await performHostAsk(ports.hostActions, request.op, request.args);
|
|
298
|
+
return "error" in outcome ? { kind: "substrate", result: { error: { message: outcome.error, errors: [] } } } : { kind: "substrate", result: { ok: null } };
|
|
299
|
+
}
|
|
300
|
+
async function substrateInPlace(ports, request, scope) {
|
|
301
|
+
if (request.op === "auth/sign-in" || request.op === "auth/consent") return { kind: "substrate", result: { error: { message: `${request.op} runs on the client host: a server-role kernel cannot ask it`, errors: [] } } };
|
|
302
|
+
if (request.op.startsWith("audio/") || request.op === "auth/sign-out") return hostAction(ports, request);
|
|
303
|
+
if (request.op === "program/live") return { kind: "substrate", result: { error: { message: "program/live streams to its onMessage route: give it a key and an onMessage route", errors: [] } } };
|
|
304
|
+
const handler = ports.programEffect();
|
|
305
|
+
const failure = (message, errors = []) => {
|
|
306
|
+
const detail = { message, errors };
|
|
307
|
+
return { kind: "substrate", result: { error: detail } };
|
|
308
|
+
};
|
|
309
|
+
if (request.op === "program/dispatch" || request.op === "program/inputs" || request.op === "program/resolve-rows") {
|
|
310
|
+
const lent = ports.servicePorts({ orbital: scope.orbital, trait: "" }, void 0, () => {
|
|
311
|
+
}, scope.assertReentry);
|
|
312
|
+
try {
|
|
313
|
+
const delivered = await deliverAgentAsk(request.op, request.args, lent, ports, scope.originClientId);
|
|
314
|
+
if (delivered.ok) return { kind: "substrate", result: { ok: delivered.result } };
|
|
315
|
+
return failure(delivered.error, delivered.errors ?? []);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (handler === void 0) return failure(`no program host configured for ${request.op}`);
|
|
321
|
+
if (!isProgramEffect(request.op)) return failure(`no program host configured for ${request.op}`);
|
|
322
|
+
try {
|
|
323
|
+
const outcome = await handler(request.op, request.args, void 0);
|
|
324
|
+
if (outcome.ok) return { kind: "substrate", result: { ok: programDocumentJson(outcome.result) } };
|
|
325
|
+
return failure(outcome.error, (outcome.errors ?? []).map((issue) => issuePayload(issue)));
|
|
326
|
+
} catch (error) {
|
|
327
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function deliverAgentAsk(op, args, host, ports, clientId) {
|
|
331
|
+
const [first, second, third] = args;
|
|
332
|
+
if (op === "program/resolve-rows") {
|
|
333
|
+
if (args.length !== 2 || first === void 0 || second === void 0) return { ok: false, error: "program/resolve-rows: the host is asked for a row-set reference and the viewer slot, which is null: a program names no viewer" };
|
|
334
|
+
const reference = RowSetRefSchema.safeParse(first);
|
|
335
|
+
if (!reference.success) {
|
|
336
|
+
const error = `program/resolve-rows: not a row-set reference: ${reference.error.message}`;
|
|
337
|
+
return { ok: false, error, errors: [{ code: "INVALID_REQUEST", message: error, status: 400 }] };
|
|
338
|
+
}
|
|
339
|
+
if (second !== null) return { ok: false, error: "program/resolve-rows: rows resolve for the requesting session's identity (EXEC-IDENTITY-1); a program names no viewer, so the viewer argument is null" };
|
|
340
|
+
if (host.resolveRows === void 0) return { ok: false, error: "program/resolve-rows: this host resolves no row sets" };
|
|
341
|
+
const page = await host.resolveRows(reference.data, clientId);
|
|
342
|
+
return { ok: true, result: toPayloadValue(page) };
|
|
343
|
+
}
|
|
344
|
+
if (op === "program/inputs") {
|
|
345
|
+
const steps = args.length === 1 && isJsonObject(first) ? first["trace"] : void 0;
|
|
346
|
+
if (typeof steps !== "number") return { ok: false, error: "program/inputs: the host is asked for { trace } only" };
|
|
347
|
+
if (host.trace === void 0) return { ok: false, error: "program/inputs: this host keeps no dispatch trace" };
|
|
348
|
+
return { ok: true, result: host.trace(steps) };
|
|
349
|
+
}
|
|
350
|
+
if (args.length === 1 && isJsonObject(first)) {
|
|
351
|
+
const { inbound, body, headers } = first;
|
|
352
|
+
if (typeof inbound !== "string" || typeof body !== "string" || !isJsonObject(headers)) return { ok: false, error: "program/dispatch: the host is asked for { inbound, body, headers }" };
|
|
353
|
+
if (host.deliverInbound === void 0) return { ok: false, error: "program/dispatch: this host does not deliver inbound requests" };
|
|
354
|
+
const named = {};
|
|
355
|
+
for (const [key, value] of Object.entries(headers)) if (typeof value === "string") named[key] = value;
|
|
356
|
+
const answer = await host.deliverInbound(inbound, { headers: named, rawBody: body });
|
|
357
|
+
return { ok: true, result: { status: answer.status, body: answer.body } };
|
|
358
|
+
}
|
|
359
|
+
if (typeof first !== "string" || second === void 0 || !isJsonObject(second)) return { ok: false, error: "program/dispatch: the host is asked for a qualified address and a payload record" };
|
|
360
|
+
const payload = second;
|
|
361
|
+
if (isJsonObject(third)) {
|
|
362
|
+
if (host.dispatchRequest === void 0) return { ok: false, error: "program/dispatch: this host does not serve event requests" };
|
|
363
|
+
const stream = third["stream"] === true;
|
|
364
|
+
if (stream && host.supportsDispatchStream !== true) return { ok: false, error: "program/dispatch: this host does not answer stream items" };
|
|
365
|
+
const parse = ports.parseEventRequest;
|
|
366
|
+
if (parse === void 0) return { ok: false, error: "program/dispatch: this host has no kernel to parse an event request" };
|
|
367
|
+
const parsed = parse(payload, third);
|
|
368
|
+
if ("error" in parsed) return { ok: false, error: parsed.error, errors: [{ code: "INVALID_REQUEST", message: parsed.error, status: 400 }] };
|
|
369
|
+
const orbital = typeof third["orbital"] === "string" ? third["orbital"] : "";
|
|
370
|
+
const answered = await host.dispatchRequest(orbital, parsed.ok, third["input"] === true, stream);
|
|
371
|
+
if (!answered.ok) return { ok: false, error: answered.error, errors: [{ code: answered.code, message: answered.error, status: answered.status }] };
|
|
372
|
+
if (!stream) return { ok: true, result: answered.response };
|
|
373
|
+
if (answered.streamItems === void 0) return { ok: false, error: "program/dispatch: this host does not answer stream items" };
|
|
374
|
+
return { ok: true, result: { ...answered.response, items: answered.streamItems } };
|
|
375
|
+
}
|
|
376
|
+
const event = first.lastIndexOf(".");
|
|
377
|
+
const trait = first.lastIndexOf(".", event - 1);
|
|
378
|
+
if (event < 0 || trait < 0) return { ok: false, error: `program/dispatch: '${first}' is not a qualified address` };
|
|
379
|
+
const response = await host.dispatchInput(first.slice(0, trait), { targetTrait: first.slice(trait + 1, event), event: first.slice(event + 1), payload });
|
|
380
|
+
return { ok: true, result: response };
|
|
381
|
+
}
|
|
382
|
+
var CREDENTIAL_CONNECT_UNAVAILABLE = "this host holds no credential store that can connect";
|
|
383
|
+
var SECRET_PORTS_UNAVAILABLE = "SECRET_PORTS_UNAVAILABLE: this host was given no secret ports";
|
|
384
|
+
async function onDeviceModel(ports, request, scope) {
|
|
385
|
+
const { key } = request;
|
|
386
|
+
const ask = async (signal) => request.kind === "prompt" ? { kind: "prompt", result: { ok: await promptOnDevice(ports.onDevice, request.text, request.schema, key ?? null, signal) } } : { kind: "embed", result: { ok: await embedOnDevice(ports.onDevice, request.texts, key ?? null, signal) } };
|
|
387
|
+
const run = async () => {
|
|
388
|
+
if (key === void 0) return ask();
|
|
389
|
+
const outcome = await ports.inFlightCalls.run(key, (signal) => untilAborted(signal, ask(signal)));
|
|
390
|
+
return outcome.cancelled ? { kind: "serviceCancelled" } : outcome.value;
|
|
391
|
+
};
|
|
392
|
+
try {
|
|
393
|
+
return await run();
|
|
394
|
+
} catch (error) {
|
|
395
|
+
const failure = error instanceof OnDeviceModelFailure ? { kind: error.kind, message: error.message } : { kind: "failed", message: failureMessage(error) };
|
|
396
|
+
return request.kind === "prompt" ? { kind: "prompt", result: { error: failure } } : { kind: "embed", result: { error: failure } };
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
var NO_TAB_HOST = "browser/tab: no host answers browser tabs";
|
|
400
|
+
async function tab(ports, request, scope) {
|
|
401
|
+
const { ask, key } = request;
|
|
402
|
+
const port = ports.tabs;
|
|
403
|
+
if (port === void 0) return { kind: "tab", result: { error: { kind: "unavailable", message: NO_TAB_HOST } } };
|
|
404
|
+
const deliver = (payload) => {
|
|
405
|
+
if (ask.kind !== "watch") return;
|
|
406
|
+
const target = ask.deliver;
|
|
407
|
+
const lent = ports.servicePorts({ orbital: target.orbital, trait: target.trait }, void 0, () => {
|
|
408
|
+
});
|
|
409
|
+
void lent.dispatchInput(target.orbital, { targetTrait: target.trait, event: target.event, payload });
|
|
410
|
+
};
|
|
411
|
+
const perform = async (signal) => {
|
|
412
|
+
try {
|
|
413
|
+
return { kind: "tab", result: { ok: await port.perform(ask, { ...signal !== void 0 ? { signal } : {}, deliver }) } };
|
|
414
|
+
} catch (error) {
|
|
415
|
+
if (error instanceof TabHostFailure) return { kind: "tab", result: { error: { kind: error.kind, message: error.message } } };
|
|
416
|
+
return { kind: "tab", result: { error: { kind: "failed", message: failureMessage(error) } } };
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
const run = async () => {
|
|
420
|
+
if (key === void 0) return perform();
|
|
421
|
+
const outcome = await ports.inFlightCalls.run(key, (signal) => untilAborted(signal, perform(signal)));
|
|
422
|
+
return outcome.cancelled ? { kind: "serviceCancelled" } : outcome.value;
|
|
423
|
+
};
|
|
424
|
+
return run();
|
|
425
|
+
}
|
|
426
|
+
async function pageFacts(ports, request) {
|
|
427
|
+
return { kind: "pageFacts", result: await settle(async () => ports.pageFacts?.(request.fields) ?? { available: false, hostname: "", referrer: "", screenWidth: null, href: "" }) };
|
|
428
|
+
}
|
|
429
|
+
function finishTls(ports, os, key, raw) {
|
|
430
|
+
return new Promise((resolve) => completeTls(ports, os, key, raw, resolve));
|
|
431
|
+
}
|
|
432
|
+
function completeTls(ports, os, key, raw, finish) {
|
|
433
|
+
const tls = ports.osTls;
|
|
434
|
+
if (tls === void 0) {
|
|
435
|
+
finish(raw);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const step = (outcome, parked) => {
|
|
439
|
+
const next = tls.complete(key, outcome);
|
|
440
|
+
if (next === null) {
|
|
441
|
+
finish(parked ? { error: { kind: "tls", message: "the TLS operation vanished" } } : outcome);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if ("done" in next) {
|
|
445
|
+
finish(next.done);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
void os.answer(next.ask.op, next.ask.args).then((answered) => step(answered, true));
|
|
449
|
+
};
|
|
450
|
+
step(raw, false);
|
|
451
|
+
}
|
|
452
|
+
var CANONICAL_BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
453
|
+
var SEGMENT_ENCODERS = {
|
|
454
|
+
base64: (value) => base64Standard(new TextEncoder().encode(value)),
|
|
455
|
+
uri: (value) => encodeURIComponent(value),
|
|
456
|
+
form: (value) => encodeURIComponent(value).replace(/[!'()~]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`).replace(/%20/g, "+")
|
|
457
|
+
};
|
|
458
|
+
async function withCredentialSegments(ports, op, args, principal) {
|
|
459
|
+
const segments = args[1];
|
|
460
|
+
if (op !== "os/send" || !Array.isArray(segments)) return { args };
|
|
461
|
+
const parts = [];
|
|
462
|
+
for (const [at, segment] of segments.entries()) {
|
|
463
|
+
const invalid = (what) => ({ error: { kind: "invalid", message: `${op}: segment ${at + 1} ${what}` } });
|
|
464
|
+
if (!isJsonObject(segment)) return invalid("is not a { bytes, credential } object");
|
|
465
|
+
const text = segment["bytes"];
|
|
466
|
+
const name = segment["credential"];
|
|
467
|
+
if (typeof text !== "string" || typeof name !== "string") return invalid("needs a bytes text and a credential name");
|
|
468
|
+
if (!CANONICAL_BASE64.test(text)) return invalid("bytes are not standard base64");
|
|
469
|
+
parts.push(Uint8Array.from(atob(text), (c) => c.charCodeAt(0)));
|
|
470
|
+
const requested = segment["encode"];
|
|
471
|
+
const encoder = requested === void 0 ? void 0 : typeof requested === "string" && Object.hasOwn(SEGMENT_ENCODERS, requested) ? SEGMENT_ENCODERS[requested] : null;
|
|
472
|
+
if (encoder === null) return invalid(`encode must be one of ${Object.keys(SEGMENT_ENCODERS).join(", ")}`);
|
|
473
|
+
const requestedScope = segment["scope"];
|
|
474
|
+
if (requestedScope !== void 0 && requestedScope !== "app" && requestedScope !== "user") return invalid("scope must be one of app, user");
|
|
475
|
+
if (name === "") continue;
|
|
476
|
+
if (requestedScope === "user" && principal === void 0) return { error: { kind: "denied", message: `${op}: segment ${at + 1} needs a signed-in viewer` } };
|
|
477
|
+
const store = ports.secretPorts?.secrets;
|
|
478
|
+
if (store === void 0) return { error: { kind: "denied", message: `${op}: no credential source is configured` } };
|
|
479
|
+
let held;
|
|
480
|
+
try {
|
|
481
|
+
held = await store.get(name, requestedScope === "user" ? principal : void 0);
|
|
482
|
+
} catch (error) {
|
|
483
|
+
return { error: { kind: "io", message: `${op}: credential ${name}: ${error instanceof Error ? error.message : String(error)}` } };
|
|
484
|
+
}
|
|
485
|
+
if (held === void 0 || held === null) return { error: { kind: "denied", message: `${op}: credential ${name} is not held` } };
|
|
486
|
+
parts.push(new TextEncoder().encode(encoder === void 0 ? held : encoder(held)));
|
|
487
|
+
}
|
|
488
|
+
const bytes = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
489
|
+
let offset = 0;
|
|
490
|
+
for (const part of parts) {
|
|
491
|
+
bytes.set(part, offset);
|
|
492
|
+
offset += part.length;
|
|
493
|
+
}
|
|
494
|
+
return { args: [args[0] ?? null, base64Standard(bytes), ...args.slice(2)] };
|
|
495
|
+
}
|
|
496
|
+
async function osAnswer(ports, request, scope) {
|
|
497
|
+
const { os } = ports;
|
|
498
|
+
if (os === void 0) return { kind: "os", result: { error: { kind: "denied", message: `${request.op}: no os host configured` } } };
|
|
499
|
+
const { op, key, site, orbital: asking } = request;
|
|
500
|
+
const completions = ports.osCompletions;
|
|
501
|
+
const sink = ports.deferCallOutcome();
|
|
502
|
+
const message = ports.substrateMessage;
|
|
503
|
+
const streaming = key !== void 0 && site !== void 0 && completions !== void 0 && sink !== void 0 && message !== void 0 && os.stream !== void 0 && completions.streams(op);
|
|
504
|
+
const deferring = !streaming && key !== void 0 && site !== void 0 && completions !== void 0 && sink !== void 0 && os.defer !== void 0 && completions.defers(op);
|
|
505
|
+
const lease = (streaming || deferring) && key !== void 0 ? ports.inFlightCalls.reserve(key) : void 0;
|
|
506
|
+
if ((streaming || deferring) && lease === void 0) {
|
|
507
|
+
return { kind: "os", result: { error: { kind: "invalid", message: `${op}: key ${key} already has ${streaming ? "a stream open" : "a completion pending"}` } } };
|
|
508
|
+
}
|
|
509
|
+
const segmented = await withCredentialSegments(ports, op, request.args, request.principal);
|
|
510
|
+
if ("error" in segmented) {
|
|
511
|
+
lease?.release();
|
|
512
|
+
return { kind: "os", result: segmented };
|
|
513
|
+
}
|
|
514
|
+
if (lease?.signal.aborted === true) {
|
|
515
|
+
lease.release();
|
|
516
|
+
return { kind: "serviceCancelled" };
|
|
517
|
+
}
|
|
518
|
+
const { args } = segmented;
|
|
519
|
+
if (lease !== void 0 && streaming && key !== void 0 && site !== void 0 && completions !== void 0 && sink !== void 0 && message !== void 0 && os.stream !== void 0) {
|
|
520
|
+
const orbital = site.orbital ?? scope.orbital;
|
|
521
|
+
const stop = () => {
|
|
522
|
+
os.cancel?.(key);
|
|
523
|
+
lease.release();
|
|
524
|
+
};
|
|
525
|
+
lease.signal.addEventListener("abort", stop, { once: true });
|
|
526
|
+
const deliver = (fired) => {
|
|
527
|
+
if (fired === null || lease.signal.aborted) return false;
|
|
528
|
+
sink({ orbital, traitName: site.trait, event: fired.event, payload: isJsonObject(fired.payload) ? fired.payload : void 0, entityId: site.entityId, originClientId: scope.originClientId, locale: scope.locale });
|
|
529
|
+
return true;
|
|
530
|
+
};
|
|
531
|
+
const started = await os.stream(op, args, key, (value) => deliver(message(site, key, value)), (error) => {
|
|
532
|
+
lease.release();
|
|
533
|
+
lease.signal.removeEventListener("abort", stop);
|
|
534
|
+
deliver(completions.event(site, key, { error }));
|
|
535
|
+
}, asking);
|
|
536
|
+
if ("error" in started) {
|
|
537
|
+
lease.release();
|
|
538
|
+
lease.signal.removeEventListener("abort", stop);
|
|
539
|
+
}
|
|
540
|
+
return { kind: "os", result: started };
|
|
541
|
+
}
|
|
542
|
+
if (lease !== void 0 && deferring && key !== void 0 && site !== void 0 && completions !== void 0 && sink !== void 0 && os.defer !== void 0) {
|
|
543
|
+
const orbital = site.orbital ?? scope.orbital;
|
|
544
|
+
const cancel = () => {
|
|
545
|
+
os.cancel?.(key);
|
|
546
|
+
lease.release();
|
|
547
|
+
};
|
|
548
|
+
lease.signal.addEventListener("abort", cancel, { once: true });
|
|
549
|
+
const finish = (outcome) => {
|
|
550
|
+
lease.release();
|
|
551
|
+
lease.signal.removeEventListener("abort", cancel);
|
|
552
|
+
if (lease.signal.aborted) return;
|
|
553
|
+
const fired = completions.event(site, key, outcome);
|
|
554
|
+
if (fired === null) return;
|
|
555
|
+
sink({
|
|
556
|
+
orbital,
|
|
557
|
+
traitName: site.trait,
|
|
558
|
+
event: fired.event,
|
|
559
|
+
payload: isJsonObject(fired.payload) ? fired.payload : void 0,
|
|
560
|
+
entityId: site.entityId,
|
|
561
|
+
originClientId: scope.originClientId,
|
|
562
|
+
locale: scope.locale
|
|
563
|
+
});
|
|
564
|
+
};
|
|
565
|
+
const started = os.defer(op, args, key, (raw) => {
|
|
566
|
+
completeTls(ports, os, key, raw, finish);
|
|
567
|
+
}, asking);
|
|
568
|
+
if ("error" in started) {
|
|
569
|
+
lease.release();
|
|
570
|
+
lease.signal.removeEventListener("abort", cancel);
|
|
571
|
+
}
|
|
572
|
+
return "error" in started ? { kind: "os", result: started } : { kind: "serviceDeferred" };
|
|
573
|
+
}
|
|
574
|
+
return { kind: "os", result: await os.answer(op, args, asking) };
|
|
575
|
+
}
|
|
576
|
+
async function secret(ports, request) {
|
|
577
|
+
const store = ports.secretPorts?.secrets;
|
|
578
|
+
if (store === void 0) return { kind: "secret", result: { error: SECRET_PORTS_UNAVAILABLE } };
|
|
579
|
+
return { kind: "secret", result: await settle(async () => await store.get(request.name, request.principal) ?? null) };
|
|
580
|
+
}
|
|
581
|
+
async function secretSet(ports, request) {
|
|
582
|
+
const store = ports.secretPorts?.secrets;
|
|
583
|
+
if (store === void 0) return { kind: "secretSet", result: { error: SECRET_PORTS_UNAVAILABLE } };
|
|
584
|
+
return { kind: "secretSet", result: await settle(async () => {
|
|
585
|
+
await store.set(request.name, request.value, request.ttlMs, request.principal);
|
|
586
|
+
return null;
|
|
587
|
+
}) };
|
|
588
|
+
}
|
|
589
|
+
async function secretRotate(ports, request) {
|
|
590
|
+
const store = ports.secretPorts?.secrets;
|
|
591
|
+
if (store === void 0) return { kind: "secretRotate", result: { error: SECRET_PORTS_UNAVAILABLE } };
|
|
592
|
+
const rotate = store.rotate;
|
|
593
|
+
if (rotate === void 0) return { kind: "secretRotate", result: { error: "this host keeps no rotatable credentials" } };
|
|
594
|
+
return { kind: "secretRotate", result: await settle(async () => {
|
|
595
|
+
await rotate.call(store, request.name, request.value, request.principal);
|
|
596
|
+
return null;
|
|
597
|
+
}) };
|
|
598
|
+
}
|
|
599
|
+
function random(ports, request) {
|
|
600
|
+
const source = ports.secretPorts?.random ?? systemRandom;
|
|
601
|
+
try {
|
|
602
|
+
return { kind: "random", result: { ok: base64Standard(source(request.bytes)) } };
|
|
603
|
+
} catch (error) {
|
|
604
|
+
return { kind: "random", result: { error: failureMessage(error) } };
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
async function credentialStatus(ports, request) {
|
|
608
|
+
const store = ports.secretPorts?.secrets;
|
|
609
|
+
if (store === void 0) return { kind: "credentialStatus", result: { error: SECRET_PORTS_UNAVAILABLE } };
|
|
610
|
+
return { kind: "credentialStatus", result: await settle(async () => {
|
|
611
|
+
const connected = credentialStatusConnected(await store.get(request.name, request.principal), request.principal);
|
|
612
|
+
const label = connected ? await store.label?.(request.name, request.principal) : void 0;
|
|
613
|
+
return { connected, ...label !== void 0 ? { label } : {} };
|
|
614
|
+
}) };
|
|
615
|
+
}
|
|
616
|
+
async function credentialForget(ports, request) {
|
|
617
|
+
const store = ports.secretPorts?.secrets;
|
|
618
|
+
if (store === void 0) return { kind: "credentialForget", result: { error: SECRET_PORTS_UNAVAILABLE } };
|
|
619
|
+
return { kind: "credentialForget", result: await settle(async () => {
|
|
620
|
+
await store.forget(request.name, request.principal);
|
|
621
|
+
return null;
|
|
622
|
+
}) };
|
|
623
|
+
}
|
|
624
|
+
async function credentialConnect(ports, request) {
|
|
625
|
+
const connect = ports.secretPorts?.connect;
|
|
626
|
+
if (connect === void 0) return { kind: "credentialConnect", result: { error: { kind: "unavailable", message: CREDENTIAL_CONNECT_UNAVAILABLE } } };
|
|
627
|
+
try {
|
|
628
|
+
return { kind: "credentialConnect", result: await connect(request.grant) };
|
|
629
|
+
} catch (error) {
|
|
630
|
+
return { kind: "credentialConnect", result: { error: { kind: "unavailable", message: failureMessage(error) } } };
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
async function credentialEnvTails(ports, request) {
|
|
634
|
+
const env = ports.secretPorts?.env;
|
|
635
|
+
if (env === void 0) return { kind: "credentialEnvTails", result: { ok: { kind: "unsupported" } } };
|
|
636
|
+
return { kind: "credentialEnvTails", result: await settle(async () => ({ kind: "held", tails: env(request.names) })) };
|
|
637
|
+
}
|
|
638
|
+
async function credentialAdmin(ports, work) {
|
|
639
|
+
const port = ports.secretPorts?.credentials;
|
|
640
|
+
if (port === void 0) return { error: SECRET_PORTS_UNAVAILABLE };
|
|
641
|
+
return settle(() => work(port));
|
|
642
|
+
}
|
|
643
|
+
function rowData(value) {
|
|
644
|
+
if (!isJsonObject(value)) throw new Error("persist: the row is not an object");
|
|
645
|
+
return value;
|
|
646
|
+
}
|
|
647
|
+
async function queryRows(persistence, request) {
|
|
648
|
+
const { entityType, query, project } = request;
|
|
649
|
+
try {
|
|
650
|
+
if (project === "ids") {
|
|
651
|
+
if (!persistence.queryIds) return { kind: "queryUnsupported", reason: "no-ids-capability" };
|
|
652
|
+
return { kind: "idPage", result: { ok: await persistence.queryIds(entityType, query) } };
|
|
653
|
+
}
|
|
654
|
+
if (!persistence.queryPage) return { kind: "queryUnsupported", reason: "no-query-capability" };
|
|
655
|
+
return { kind: "page", result: { ok: await persistence.queryPage(entityType, query) } };
|
|
656
|
+
} catch (error) {
|
|
657
|
+
if (error instanceof QueryDeclinedError) return { kind: "queryUnsupported", reason: error.reason };
|
|
658
|
+
return { kind: project === "ids" ? "idPage" : "page", result: { error: failureMessage(error) } };
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
async function commitRows(persistence, request) {
|
|
662
|
+
if (!persistence.commitRows) return { kind: "committed", result: { error: "this store does not commit rows" } };
|
|
663
|
+
try {
|
|
664
|
+
return { kind: "committed", result: { ok: await persistence.commitRows(request.rows) } };
|
|
665
|
+
} catch (error) {
|
|
666
|
+
return { kind: "committed", result: { error: failureMessage(error) } };
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
async function writeRows(persistence, request) {
|
|
670
|
+
const { batch } = request;
|
|
671
|
+
try {
|
|
672
|
+
if (!persistence.writeBatch) return { kind: "writeUnsupported", reason: "no-write-capability" };
|
|
673
|
+
return { kind: "wrote", result: { ok: await persistence.writeBatch(batch) } };
|
|
674
|
+
} catch (error) {
|
|
675
|
+
if (error instanceof WriteDeclinedError) return { kind: "writeUnsupported", reason: error.reason };
|
|
676
|
+
return { kind: "wrote", result: { error: failureMessage(error) } };
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function declareCapabilities(persistence, declare, entityTypes) {
|
|
680
|
+
for (const entityType of entityTypes) {
|
|
681
|
+
const query = persistence.queryPage ? persistence.queryCapabilities?.(entityType) : void 0;
|
|
682
|
+
const write = persistence.writeWhere || persistence.writeBatch || persistence.commitRows ? persistence.writeCapabilities?.(entityType) : void 0;
|
|
683
|
+
if (query === void 0 && write === void 0) continue;
|
|
684
|
+
declare(entityType, JSON.stringify(query ?? null), JSON.stringify(write ?? null));
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
async function answerRequest(ports, request, scope) {
|
|
688
|
+
const { persistence } = ports;
|
|
689
|
+
switch (request.kind) {
|
|
690
|
+
case "readRow":
|
|
691
|
+
return {
|
|
692
|
+
kind: "row",
|
|
693
|
+
result: await settle(async () => {
|
|
694
|
+
const row = await persistence.getById(request.entityType, request.id);
|
|
695
|
+
return row;
|
|
696
|
+
})
|
|
697
|
+
};
|
|
698
|
+
case "listRows":
|
|
699
|
+
return {
|
|
700
|
+
kind: "rows",
|
|
701
|
+
result: await settle(async () => await persistence.list(request.entityType))
|
|
702
|
+
};
|
|
703
|
+
case "queryRows":
|
|
704
|
+
return queryRows(persistence, request);
|
|
705
|
+
case "writeRows":
|
|
706
|
+
return writeRows(persistence, request);
|
|
707
|
+
case "commitRows":
|
|
708
|
+
return commitRows(persistence, request);
|
|
709
|
+
case "createRow":
|
|
710
|
+
return {
|
|
711
|
+
kind: "row",
|
|
712
|
+
result: await settle(async () => {
|
|
713
|
+
const data = rowData(request.data);
|
|
714
|
+
const { id } = await persistence.create(request.entityType, data);
|
|
715
|
+
return await persistence.getById(request.entityType, id) ?? { ...data, id };
|
|
716
|
+
})
|
|
717
|
+
};
|
|
718
|
+
case "writeRow":
|
|
719
|
+
return {
|
|
720
|
+
kind: "row",
|
|
721
|
+
result: await settle(async () => {
|
|
722
|
+
const data = rowData(request.data);
|
|
723
|
+
const existing = await persistence.getById(request.entityType, request.id);
|
|
724
|
+
if (existing === null) await persistence.create(request.entityType, { ...data, id: request.id });
|
|
725
|
+
else await persistence.update(request.entityType, request.id, { ...data, id: request.id });
|
|
726
|
+
return await persistence.getById(request.entityType, request.id) ?? { ...existing, ...data, id: request.id };
|
|
727
|
+
})
|
|
728
|
+
};
|
|
729
|
+
case "deleteRow":
|
|
730
|
+
return {
|
|
731
|
+
kind: "deleted",
|
|
732
|
+
result: await settle(async () => {
|
|
733
|
+
await persistence.delete(request.entityType, request.id);
|
|
734
|
+
return null;
|
|
735
|
+
})
|
|
736
|
+
};
|
|
737
|
+
case "callService":
|
|
738
|
+
return callService(ports, request, scope);
|
|
739
|
+
case "cancelCall":
|
|
740
|
+
ports.inFlightCalls.cancel(request.key);
|
|
741
|
+
ports.os?.cancel?.(request.key);
|
|
742
|
+
return { kind: "cancelled", result: { ok: null } };
|
|
743
|
+
case "substrate":
|
|
744
|
+
return substrate(ports, request, scope);
|
|
745
|
+
case "pageFacts":
|
|
746
|
+
return pageFacts(ports, request);
|
|
747
|
+
case "prompt":
|
|
748
|
+
case "embed":
|
|
749
|
+
return onDeviceModel(ports, request);
|
|
750
|
+
case "tab":
|
|
751
|
+
return tab(ports, request);
|
|
752
|
+
case "secret":
|
|
753
|
+
return secret(ports, request);
|
|
754
|
+
case "secretSet":
|
|
755
|
+
return secretSet(ports, request);
|
|
756
|
+
case "secretRotate":
|
|
757
|
+
return secretRotate(ports, request);
|
|
758
|
+
case "random":
|
|
759
|
+
return random(ports, request);
|
|
760
|
+
case "os":
|
|
761
|
+
return osAnswer(ports, request, scope);
|
|
762
|
+
case "clock":
|
|
763
|
+
return { kind: "clock", result: { ok: (ports.now ?? Date.now)() } };
|
|
764
|
+
case "sealed":
|
|
765
|
+
return { kind: "sealed", result: { error: `${request.op}: a sealed operator is answered by the kernel, never by a host` } };
|
|
766
|
+
case "deliver":
|
|
767
|
+
return { kind: "deliver", result: { error: { kind: "down", id: request.envelope.id } } };
|
|
768
|
+
case "guest":
|
|
769
|
+
return { kind: "substrate", result: { error: { message: "program/dispatch: this host starts no guest kernels", errors: [{ kind: "unsupported", message: "program/dispatch: this host starts no guest kernels" }] } } };
|
|
770
|
+
case "credentialStatus":
|
|
771
|
+
return credentialStatus(ports, request);
|
|
772
|
+
case "credentialForget":
|
|
773
|
+
return credentialForget(ports, request);
|
|
774
|
+
case "credentialConnect":
|
|
775
|
+
return credentialConnect(ports, request);
|
|
776
|
+
case "credentialStoreList":
|
|
777
|
+
return { kind: request.kind, result: await credentialAdmin(ports, (port) => port.list()) };
|
|
778
|
+
case "credentialEnvTails":
|
|
779
|
+
return credentialEnvTails(ports, request);
|
|
780
|
+
case "credentialStoreRead":
|
|
781
|
+
return { kind: request.kind, result: await credentialAdmin(ports, (port) => port.read(request.name)) };
|
|
782
|
+
case "credentialStoreWrite":
|
|
783
|
+
return { kind: request.kind, result: await credentialAdmin(ports, async (port) => {
|
|
784
|
+
await port.write(request.service, request.name, request.value);
|
|
785
|
+
return null;
|
|
786
|
+
}) };
|
|
787
|
+
case "credentialStoreRemove":
|
|
788
|
+
return { kind: request.kind, result: await credentialAdmin(ports, (port) => port.remove(request.name)) };
|
|
789
|
+
case "credentialStoreRotate":
|
|
790
|
+
return { kind: request.kind, result: await credentialAdmin(ports, (port) => port.rotate()) };
|
|
791
|
+
case "credentialModeRead":
|
|
792
|
+
return { kind: request.kind, result: await credentialAdmin(ports, (port) => port.mode()) };
|
|
793
|
+
case "credentialModeWrite":
|
|
794
|
+
return { kind: request.kind, result: await credentialAdmin(ports, (port) => port.setMode(request.mode)) };
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
export { HostAskError, answerRequest, completeTls, consentFromRedirect, declareCapabilities, finishTls, hostAskReturn, kernelEventRequestParser, parseConsentRequest, parseSignInRequest, performHostAnswer, performHostAsk, writeRows };
|