@ai-matrx/applets 0.2.7
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/CHANGELOG.md +130 -0
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/context-Dz3PWsJd.d.cts +193 -0
- package/dist/context-Dz3PWsJd.d.ts +193 -0
- package/dist/frame.cjs +977 -0
- package/dist/frame.cjs.map +1 -0
- package/dist/frame.d.cts +42 -0
- package/dist/frame.d.ts +42 -0
- package/dist/frame.js +958 -0
- package/dist/frame.js.map +1 -0
- package/dist/host.cjs +664 -0
- package/dist/host.cjs.map +1 -0
- package/dist/host.d.cts +128 -0
- package/dist/host.d.ts +128 -0
- package/dist/host.js +641 -0
- package/dist/host.js.map +1 -0
- package/dist/index.cjs +66 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +200 -0
- package/dist/index.d.ts +200 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.cjs +699 -0
- package/dist/platform.cjs.map +1 -0
- package/dist/platform.d.cts +135 -0
- package/dist/platform.d.ts +135 -0
- package/dist/platform.js +680 -0
- package/dist/platform.js.map +1 -0
- package/dist/react.cjs +880 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +178 -0
- package/dist/react.d.ts +178 -0
- package/dist/react.js +854 -0
- package/dist/react.js.map +1 -0
- package/package.json +128 -0
package/dist/host.js
ADDED
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var PAGE_ACTION_STREAM = "actions";
|
|
3
|
+
var NAV_STREAM = "nav";
|
|
4
|
+
var runStream = (requestId) => `run:${requestId}`;
|
|
5
|
+
var valueStream = (name) => `value:${name}`;
|
|
6
|
+
function ok(data) {
|
|
7
|
+
return { ok: true, data };
|
|
8
|
+
}
|
|
9
|
+
function fail(code, message, retryable = false, diagnostic) {
|
|
10
|
+
const error = { code, message, retryable };
|
|
11
|
+
if (diagnostic !== void 0) error.diagnostic = diagnostic;
|
|
12
|
+
return { ok: false, error };
|
|
13
|
+
}
|
|
14
|
+
function toAppletError(err, code = "internal") {
|
|
15
|
+
if (err && typeof err === "object") {
|
|
16
|
+
const o = err;
|
|
17
|
+
if (typeof o.code === "string" && typeof o.message === "string") {
|
|
18
|
+
const out = { code: o.code, message: o.message, retryable: o.retryable === true };
|
|
19
|
+
if (typeof o.diagnostic === "string") out.diagnostic = o.diagnostic;
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
if (err instanceof Error) return { code, message: err.message, retryable: false };
|
|
23
|
+
}
|
|
24
|
+
return { code, message: String(err), retryable: false };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/host/memory.ts
|
|
28
|
+
var CHAIN = /* @__PURE__ */ Symbol("applets.memory.chain");
|
|
29
|
+
var SURFACE_NAME = /* @__PURE__ */ Symbol("applets.memory.surfaceName");
|
|
30
|
+
function mintId(world, prefix) {
|
|
31
|
+
world.nextId += 1;
|
|
32
|
+
return `${prefix}-${world.nextId.toString(36).padStart(4, "0")}`;
|
|
33
|
+
}
|
|
34
|
+
function parsePath(to) {
|
|
35
|
+
const [pathPart = "", queryPart = ""] = to.split("?");
|
|
36
|
+
const path = pathPart.split("/").filter(Boolean);
|
|
37
|
+
const params = {};
|
|
38
|
+
for (const pair of queryPart.split("&").filter(Boolean)) {
|
|
39
|
+
const [k = "", v = ""] = pair.split("=");
|
|
40
|
+
params[decodeURIComponent(k)] = decodeURIComponent(v);
|
|
41
|
+
}
|
|
42
|
+
return { path, params };
|
|
43
|
+
}
|
|
44
|
+
var clone = (value) => value === void 0 ? value : JSON.parse(JSON.stringify(value));
|
|
45
|
+
function matches(row, q) {
|
|
46
|
+
for (const w of q.where ?? []) {
|
|
47
|
+
const v = row[w.column];
|
|
48
|
+
switch (w.op) {
|
|
49
|
+
case "eq":
|
|
50
|
+
if (v !== w.value) return false;
|
|
51
|
+
break;
|
|
52
|
+
case "neq":
|
|
53
|
+
if (v === w.value) return false;
|
|
54
|
+
break;
|
|
55
|
+
case "lt":
|
|
56
|
+
if (!(v < w.value)) return false;
|
|
57
|
+
break;
|
|
58
|
+
case "lte":
|
|
59
|
+
if (!(v <= w.value)) return false;
|
|
60
|
+
break;
|
|
61
|
+
case "gt":
|
|
62
|
+
if (!(v > w.value)) return false;
|
|
63
|
+
break;
|
|
64
|
+
case "gte":
|
|
65
|
+
if (!(v >= w.value)) return false;
|
|
66
|
+
break;
|
|
67
|
+
case "contains":
|
|
68
|
+
if (!String(v ?? "").toLowerCase().includes(String(w.value ?? "").toLowerCase())) return false;
|
|
69
|
+
break;
|
|
70
|
+
case "in":
|
|
71
|
+
if (!Array.isArray(w.value) || !w.value.includes(v)) return false;
|
|
72
|
+
break;
|
|
73
|
+
case "empty":
|
|
74
|
+
if (!(v === null || v === void 0 || v === "")) return false;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (q.search) {
|
|
79
|
+
const needle = q.search.toLowerCase();
|
|
80
|
+
const hit = Object.entries(row).some(([k, v]) => !k.startsWith("_") && typeof v === "string" && v.toLowerCase().includes(needle));
|
|
81
|
+
if (!hit) return false;
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
function compare(a, b) {
|
|
86
|
+
if (a === b) return 0;
|
|
87
|
+
if (a === null || a === void 0) return 1;
|
|
88
|
+
if (b === null || b === void 0) return -1;
|
|
89
|
+
return a < b ? -1 : 1;
|
|
90
|
+
}
|
|
91
|
+
function createMemoryHost(spec) {
|
|
92
|
+
const world = {
|
|
93
|
+
hosts: /* @__PURE__ */ new Map(),
|
|
94
|
+
location: parsePath(spec.path ?? "/"),
|
|
95
|
+
nextId: 0,
|
|
96
|
+
calls: [],
|
|
97
|
+
errors: [],
|
|
98
|
+
latencyMs: spec.latencyMs ?? 0,
|
|
99
|
+
pendingActions: /* @__PURE__ */ new Map()
|
|
100
|
+
};
|
|
101
|
+
return buildHost(world, spec, "memory:root", null);
|
|
102
|
+
}
|
|
103
|
+
function buildHost(world, spec, hostId, parent) {
|
|
104
|
+
const record = spec.record;
|
|
105
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
106
|
+
const subscriptions = /* @__PURE__ */ new Map();
|
|
107
|
+
const tables = /* @__PURE__ */ new Map();
|
|
108
|
+
const values = new Map(Object.entries(spec.surface?.values ?? {}));
|
|
109
|
+
const runs = /* @__PURE__ */ new Map();
|
|
110
|
+
let mounted = null;
|
|
111
|
+
for (const [alias, t] of Object.entries(spec.tables ?? {})) {
|
|
112
|
+
const rows = (t.rows ?? []).map((values2) => ({
|
|
113
|
+
archived: false,
|
|
114
|
+
row: {
|
|
115
|
+
...clone(values2),
|
|
116
|
+
_id: typeof values2._id === "string" ? values2._id : mintId(world, "row"),
|
|
117
|
+
_version: typeof values2._version === "number" ? values2._version : 1,
|
|
118
|
+
_organization_id: t.organizationId ?? record.organizationId
|
|
119
|
+
}
|
|
120
|
+
}));
|
|
121
|
+
tables.set(alias, { spec: t, rows });
|
|
122
|
+
}
|
|
123
|
+
const wait = () => new Promise((resolve) => setTimeout(resolve, world.latencyMs));
|
|
124
|
+
const log = (port, method, args) => {
|
|
125
|
+
world.calls.push({ hostId, port, method, args: clone(args) });
|
|
126
|
+
};
|
|
127
|
+
const emit = (stream, payload) => {
|
|
128
|
+
for (const [subscriptionId, s] of subscriptions) {
|
|
129
|
+
if (s !== stream) continue;
|
|
130
|
+
const message = { subscriptionId, payload: clone(payload) };
|
|
131
|
+
for (const l of listeners) l(message);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const refuse = (code, message, diagnostic) => ({
|
|
135
|
+
ok: false,
|
|
136
|
+
error: diagnostic === void 0 ? { code, message } : { code, message, diagnostic }
|
|
137
|
+
});
|
|
138
|
+
const answer = (data2) => ({ ok: true, data: data2 });
|
|
139
|
+
const table = (alias) => {
|
|
140
|
+
const t = tables.get(alias);
|
|
141
|
+
if (!t) return refuse("not_found", `This Applet declares no source named "${alias}".`);
|
|
142
|
+
return answer(t);
|
|
143
|
+
};
|
|
144
|
+
const dataListeners = /* @__PURE__ */ new Map();
|
|
145
|
+
const bump = (alias, event) => {
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
for (const l of [...dataListeners.get(`rows:${alias}`) ?? []]) l(clone(event));
|
|
148
|
+
}, 0);
|
|
149
|
+
};
|
|
150
|
+
const findRow = (rows, id) => rows.find((r) => r.row._id === id);
|
|
151
|
+
const memoryData = {
|
|
152
|
+
subscribe(event, listener) {
|
|
153
|
+
let set = dataListeners.get(event);
|
|
154
|
+
if (!set) {
|
|
155
|
+
set = /* @__PURE__ */ new Set();
|
|
156
|
+
dataListeners.set(event, set);
|
|
157
|
+
}
|
|
158
|
+
set.add(listener);
|
|
159
|
+
const held = set;
|
|
160
|
+
return () => {
|
|
161
|
+
held.delete(listener);
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
async sources() {
|
|
165
|
+
log("data", "sources", []);
|
|
166
|
+
await wait();
|
|
167
|
+
const out = [...tables.entries()].map(([alias, t]) => ({
|
|
168
|
+
alias,
|
|
169
|
+
label: t.spec.label ?? alias,
|
|
170
|
+
kind: t.spec.kind ?? "table",
|
|
171
|
+
canCreate: t.spec.canCreate ?? true,
|
|
172
|
+
canUpdate: t.spec.canUpdate ?? true
|
|
173
|
+
}));
|
|
174
|
+
return answer(out);
|
|
175
|
+
},
|
|
176
|
+
async columns(alias) {
|
|
177
|
+
log("data", "columns", [alias]);
|
|
178
|
+
await wait();
|
|
179
|
+
const t = table(alias);
|
|
180
|
+
return t.ok ? answer(clone(t.data.spec.columns)) : t;
|
|
181
|
+
},
|
|
182
|
+
async list(alias, q, cursor) {
|
|
183
|
+
log("data", "list", [alias, q, cursor]);
|
|
184
|
+
await wait();
|
|
185
|
+
const t = table(alias);
|
|
186
|
+
if (!t.ok) return t;
|
|
187
|
+
let rows = t.data.rows.filter((r) => !r.archived).map((r) => r.row).filter((r) => matches(r, q));
|
|
188
|
+
for (const s of [...q.sort ?? []].reverse()) {
|
|
189
|
+
const dir = s.direction === "desc" ? -1 : 1;
|
|
190
|
+
rows = [...rows].sort((a, b) => dir * compare(a[s.column], b[s.column]));
|
|
191
|
+
}
|
|
192
|
+
const start = cursor ? rows.findIndex((r) => r._id === cursor) + 1 : 0;
|
|
193
|
+
if (cursor && start === 0) return refuse("invalid_argument", "That page cursor is not from this list.");
|
|
194
|
+
const size = q.pageSize ?? 50;
|
|
195
|
+
const page = rows.slice(start, start + size);
|
|
196
|
+
const last = page[page.length - 1];
|
|
197
|
+
return answer({ rows: clone(page), total: rows.length, cursor: start + size < rows.length && last ? last._id : null });
|
|
198
|
+
},
|
|
199
|
+
async get(alias, id) {
|
|
200
|
+
log("data", "get", [alias, id]);
|
|
201
|
+
await wait();
|
|
202
|
+
const t = table(alias);
|
|
203
|
+
if (!t.ok) return t;
|
|
204
|
+
const r = findRow(t.data.rows, id);
|
|
205
|
+
return answer(r && !r.archived ? clone(r.row) : null);
|
|
206
|
+
},
|
|
207
|
+
async create(alias, values2) {
|
|
208
|
+
log("data", "create", [alias, values2]);
|
|
209
|
+
await wait();
|
|
210
|
+
const t = table(alias);
|
|
211
|
+
if (!t.ok) return t;
|
|
212
|
+
if (t.data.spec.canCreate === false) return refuse("door", `New records cannot be added to ${t.data.spec.label ?? alias} here.`);
|
|
213
|
+
const refusal = t.data.spec.refuse?.({ op: "create", values: values2 });
|
|
214
|
+
if (refusal) return { ok: false, error: refusal };
|
|
215
|
+
const row = {
|
|
216
|
+
...clone(values2),
|
|
217
|
+
_id: mintId(world, "row"),
|
|
218
|
+
_version: 1,
|
|
219
|
+
_organization_id: t.data.spec.organizationId ?? record.organizationId
|
|
220
|
+
};
|
|
221
|
+
t.data.rows.unshift({ row, archived: false });
|
|
222
|
+
bump(alias, { ids: [row._id] });
|
|
223
|
+
return answer(clone(row));
|
|
224
|
+
},
|
|
225
|
+
async update(alias, id, patch, baseVersion) {
|
|
226
|
+
log("data", "update", [alias, id, patch, baseVersion]);
|
|
227
|
+
await wait();
|
|
228
|
+
const t = table(alias);
|
|
229
|
+
if (!t.ok) return t;
|
|
230
|
+
if (t.data.spec.canUpdate === false) return refuse("door", `${t.data.spec.label ?? alias} is read-only here.`);
|
|
231
|
+
const stored = findRow(t.data.rows, id);
|
|
232
|
+
if (!stored || stored.archived) return refuse("not_found", "This record is not here any more.");
|
|
233
|
+
if (stored.row._version !== baseVersion) {
|
|
234
|
+
return refuse(
|
|
235
|
+
"stale_write",
|
|
236
|
+
"Someone else changed this record after you opened it. Your change was not saved.",
|
|
237
|
+
`base ${baseVersion}, current ${stored.row._version}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
const refusal = t.data.spec.refuse?.({ op: "update", id, patch, baseVersion });
|
|
241
|
+
if (refusal) return { ok: false, error: refusal };
|
|
242
|
+
stored.row = { ...stored.row, ...clone(patch), _id: id, _version: stored.row._version + 1 };
|
|
243
|
+
bump(alias, { ids: [id] });
|
|
244
|
+
return answer(clone(stored.row));
|
|
245
|
+
},
|
|
246
|
+
async archive(alias, id) {
|
|
247
|
+
log("data", "archive", [alias, id]);
|
|
248
|
+
await wait();
|
|
249
|
+
const t = table(alias);
|
|
250
|
+
if (!t.ok) return t;
|
|
251
|
+
const stored = findRow(t.data.rows, id);
|
|
252
|
+
if (!stored) return refuse("not_found", "This record is not here any more.");
|
|
253
|
+
const refusal = t.data.spec.refuse?.({ op: "archive", id });
|
|
254
|
+
if (refusal) return { ok: false, error: refusal };
|
|
255
|
+
stored.archived = true;
|
|
256
|
+
stored.row = { ...stored.row, _version: stored.row._version + 1 };
|
|
257
|
+
bump(alias, { ids: [id] });
|
|
258
|
+
return answer(void 0);
|
|
259
|
+
},
|
|
260
|
+
async restore(alias, id) {
|
|
261
|
+
log("data", "restore", [alias, id]);
|
|
262
|
+
await wait();
|
|
263
|
+
const t = table(alias);
|
|
264
|
+
if (!t.ok) return t;
|
|
265
|
+
const stored = findRow(t.data.rows, id);
|
|
266
|
+
if (!stored) return refuse("not_found", "This record is not here any more.");
|
|
267
|
+
const refusal = t.data.spec.refuse?.({ op: "restore", id });
|
|
268
|
+
if (refusal) return { ok: false, error: refusal };
|
|
269
|
+
stored.archived = false;
|
|
270
|
+
stored.row = { ...stored.row, _version: stored.row._version + 1 };
|
|
271
|
+
bump(alias, { ids: [id] });
|
|
272
|
+
return answer(void 0);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
const dataPort = spec.data ?? memoryData;
|
|
276
|
+
const data = {
|
|
277
|
+
sources: () => dataPort.sources(),
|
|
278
|
+
columns: (alias) => dataPort.columns(alias),
|
|
279
|
+
list: (alias, q, cursor) => dataPort.list(alias, q, cursor),
|
|
280
|
+
get: (alias, id) => dataPort.get(alias, id),
|
|
281
|
+
create: (alias, values2) => dataPort.create(alias, values2),
|
|
282
|
+
update: (alias, id, patch, baseVersion) => dataPort.update(alias, id, patch, baseVersion),
|
|
283
|
+
archive: (alias, id) => dataPort.archive(alias, id),
|
|
284
|
+
restore: (alias, id) => dataPort.restore(alias, id)
|
|
285
|
+
};
|
|
286
|
+
const rowsUnsubscribe = /* @__PURE__ */ new Map();
|
|
287
|
+
const playRun = (requestId) => {
|
|
288
|
+
const run = runs.get(requestId);
|
|
289
|
+
if (!run) return;
|
|
290
|
+
const step = () => {
|
|
291
|
+
const event = run.rest.shift();
|
|
292
|
+
if (!event) {
|
|
293
|
+
if (run.status === "running") {
|
|
294
|
+
run.status = "done";
|
|
295
|
+
emit(runStream(requestId), { type: "status", status: "done" });
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (event.type === "text") run.text += event.delta;
|
|
300
|
+
if (event.type === "status") run.status = event.status;
|
|
301
|
+
if (event.type === "result") {
|
|
302
|
+
run.envelope = event;
|
|
303
|
+
run.status = "done";
|
|
304
|
+
}
|
|
305
|
+
if (event.type === "error") run.status = "error";
|
|
306
|
+
if (event.type === "needs_input") run.status = "needs_input";
|
|
307
|
+
emit(runStream(requestId), event);
|
|
308
|
+
if (event.type === "needs_input" || event.type === "result" || event.type === "error") return;
|
|
309
|
+
setTimeout(step, 0);
|
|
310
|
+
};
|
|
311
|
+
setTimeout(step, world.latencyMs);
|
|
312
|
+
};
|
|
313
|
+
const intelligence = {
|
|
314
|
+
async describe(key) {
|
|
315
|
+
log("intelligence", "describe", [key]);
|
|
316
|
+
await wait();
|
|
317
|
+
const m = spec.mandates?.[key];
|
|
318
|
+
if (!m) return fail("mandate_not_found", `No job named "${key}" exists.`);
|
|
319
|
+
return ok({
|
|
320
|
+
key,
|
|
321
|
+
label: m.label,
|
|
322
|
+
goal: m.goal,
|
|
323
|
+
outputKind: m.outputKind ?? null,
|
|
324
|
+
inputs: clone(m.inputs ?? []),
|
|
325
|
+
holder: { type: m.holder ?? "agent" }
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
async run(req) {
|
|
329
|
+
log("intelligence", "run", [req]);
|
|
330
|
+
await wait();
|
|
331
|
+
const m = spec.mandates?.[req.key];
|
|
332
|
+
if (!m) return fail("mandate_not_found", `No job named "${req.key}" exists.`);
|
|
333
|
+
const requestId = mintId(world, "req");
|
|
334
|
+
const ref = { requestId, conversationId: mintId(world, "conv"), mandateKey: req.key };
|
|
335
|
+
const runReq = { variables: clone(req.variables ?? {}) };
|
|
336
|
+
if (req.userInput !== void 0) runReq.userInput = req.userInput;
|
|
337
|
+
runs.set(requestId, {
|
|
338
|
+
ref,
|
|
339
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
340
|
+
status: "running",
|
|
341
|
+
text: "",
|
|
342
|
+
envelope: null,
|
|
343
|
+
rest: m.script(runReq),
|
|
344
|
+
req: runReq,
|
|
345
|
+
key: req.key
|
|
346
|
+
});
|
|
347
|
+
playRun(requestId);
|
|
348
|
+
return ok(ref);
|
|
349
|
+
},
|
|
350
|
+
async result(ref) {
|
|
351
|
+
log("intelligence", "result", [ref]);
|
|
352
|
+
await wait();
|
|
353
|
+
const run = runs.get(ref.requestId);
|
|
354
|
+
if (!run) return fail("run_not_found", "That run is not here.");
|
|
355
|
+
const envelope = run.envelope && run.envelope.type === "result" ? run.envelope.envelope : null;
|
|
356
|
+
return ok({ envelope: clone(envelope), text: run.text, status: run.status });
|
|
357
|
+
},
|
|
358
|
+
async answer(ref, answers) {
|
|
359
|
+
log("intelligence", "answer", [ref, answers]);
|
|
360
|
+
await wait();
|
|
361
|
+
const run = runs.get(ref.requestId);
|
|
362
|
+
if (!run) return fail("run_not_found", "That run is not here.");
|
|
363
|
+
if (run.status !== "needs_input") return fail("not_waiting", "This run is not waiting for an answer.");
|
|
364
|
+
const m = spec.mandates?.[run.key];
|
|
365
|
+
if (m?.afterAnswer) run.rest = m.afterAnswer(run.req, clone(answers));
|
|
366
|
+
run.status = "running";
|
|
367
|
+
playRun(ref.requestId);
|
|
368
|
+
return ok(void 0);
|
|
369
|
+
},
|
|
370
|
+
async rejoin(ref) {
|
|
371
|
+
log("intelligence", "rejoin", [ref]);
|
|
372
|
+
await wait();
|
|
373
|
+
return runs.has(ref.requestId) ? ok(void 0) : fail("run_not_found", "That run is not here.");
|
|
374
|
+
},
|
|
375
|
+
async listRuns(key) {
|
|
376
|
+
log("intelligence", "listRuns", [key]);
|
|
377
|
+
await wait();
|
|
378
|
+
const out = [...runs.values()].filter((r) => r.key === key).map((r) => ({ ref: clone(r.ref), startedAt: r.startedAt, status: r.status })).reverse();
|
|
379
|
+
return ok(out);
|
|
380
|
+
},
|
|
381
|
+
async createSoft(s) {
|
|
382
|
+
log("intelligence", "createSoft", [s]);
|
|
383
|
+
await wait();
|
|
384
|
+
if (spec.mandates?.[s.key]) return fail("already_exists", `A job named "${s.key}" already exists.`);
|
|
385
|
+
spec.mandates = {
|
|
386
|
+
...spec.mandates ?? {},
|
|
387
|
+
[s.key]: { label: s.label, goal: s.goal, outputKind: s.outputKind ?? null, script: () => [] }
|
|
388
|
+
};
|
|
389
|
+
return ok({ key: s.key });
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
const ownActions = () => spec.surface?.actions ?? [];
|
|
393
|
+
const toInfo = (a, declaredOn) => {
|
|
394
|
+
const info = {
|
|
395
|
+
name: a.name,
|
|
396
|
+
category: a.category ?? "applet",
|
|
397
|
+
label: a.label,
|
|
398
|
+
description: a.description,
|
|
399
|
+
runs: a.runs,
|
|
400
|
+
policy: a.policy,
|
|
401
|
+
triggeredBy: [...a.triggeredBy],
|
|
402
|
+
declaredOn
|
|
403
|
+
};
|
|
404
|
+
if (a.input !== void 0) info.input = a.input;
|
|
405
|
+
if (a.output !== void 0) info.output = a.output;
|
|
406
|
+
if (a.target !== void 0) info.target = a.target;
|
|
407
|
+
if (a.updatesValue !== void 0) info.updatesValue = a.updatesValue;
|
|
408
|
+
return info;
|
|
409
|
+
};
|
|
410
|
+
const chain = () => {
|
|
411
|
+
const out = /* @__PURE__ */ new Map();
|
|
412
|
+
for (const d of ownActions()) out.set(d.name, { decl: d, owner: self });
|
|
413
|
+
if (parent) {
|
|
414
|
+
for (const link of parent[CHAIN]()) if (!out.has(link.decl.name)) out.set(link.decl.name, link);
|
|
415
|
+
}
|
|
416
|
+
return [...out.values()];
|
|
417
|
+
};
|
|
418
|
+
const surface = {
|
|
419
|
+
async mount(name, parentName) {
|
|
420
|
+
log("surface", "mount", [name, parentName]);
|
|
421
|
+
await wait();
|
|
422
|
+
mounted = { mountId: mintId(world, "mount"), name, parent: parentName };
|
|
423
|
+
return ok({ mountId: mounted.mountId });
|
|
424
|
+
},
|
|
425
|
+
async unmount(mountId) {
|
|
426
|
+
log("surface", "unmount", [mountId]);
|
|
427
|
+
if (mounted?.mountId === mountId) mounted = null;
|
|
428
|
+
},
|
|
429
|
+
async getValue(name) {
|
|
430
|
+
log("surface", "getValue", [name]);
|
|
431
|
+
await wait();
|
|
432
|
+
return clone(values.get(name));
|
|
433
|
+
},
|
|
434
|
+
async setValue(name, v) {
|
|
435
|
+
log("surface", "setValue", [name, v]);
|
|
436
|
+
await wait();
|
|
437
|
+
values.set(name, clone(v));
|
|
438
|
+
emit(valueStream(name), v);
|
|
439
|
+
},
|
|
440
|
+
async listActions() {
|
|
441
|
+
log("surface", "listActions", []);
|
|
442
|
+
await wait();
|
|
443
|
+
return chain().map((l) => toInfo(l.decl, l.owner[SURFACE_NAME]));
|
|
444
|
+
},
|
|
445
|
+
async runAction(name, input, by) {
|
|
446
|
+
log("surface", "runAction", [name, input, by]);
|
|
447
|
+
await wait();
|
|
448
|
+
const link = chain().find((l) => l.decl.name === name);
|
|
449
|
+
if (!link) return fail("action_not_found", `This page has no action named "${name}".`);
|
|
450
|
+
const { decl, owner } = link;
|
|
451
|
+
if (!decl.triggeredBy.includes(by)) {
|
|
452
|
+
return fail("caller_refused", `"${decl.label}" cannot be started by ${by === "person" ? "a person" : `an ${by}`}.`);
|
|
453
|
+
}
|
|
454
|
+
if (decl.policy === "manual" && by !== "person") {
|
|
455
|
+
return fail("caller_refused", `Only a person can start "${decl.label}".`);
|
|
456
|
+
}
|
|
457
|
+
const viewer = await self.viewer();
|
|
458
|
+
const runId = mintId(world, "act");
|
|
459
|
+
let result;
|
|
460
|
+
switch (decl.runs) {
|
|
461
|
+
case "page": {
|
|
462
|
+
result = await new Promise((resolve) => {
|
|
463
|
+
world.pendingActions.set(runId, resolve);
|
|
464
|
+
const event = { action: name, input, runId, by };
|
|
465
|
+
owner.control.emit(PAGE_ACTION_STREAM, event);
|
|
466
|
+
});
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
case "server": {
|
|
470
|
+
const handler = spec.surface?.serverActions?.[decl.target ?? name];
|
|
471
|
+
if (!handler) {
|
|
472
|
+
result = fail("not_supported", `The server action "${decl.target ?? name}" is not available here.`);
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
result = ok(await handler(input, { runId, by, viewerId: viewer.userId }));
|
|
477
|
+
} catch (err) {
|
|
478
|
+
result = { ok: false, error: toAppletError(err) };
|
|
479
|
+
}
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
case "mandate": {
|
|
483
|
+
const key = record.mandates.find((m) => m.alias === decl.target)?.key ?? decl.target ?? "";
|
|
484
|
+
const variables = input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
485
|
+
result = await intelligence.run({ key, variables, organizationId: viewer.activeOrganizationId, appletId: record.id, surfaceName: record.surfaceName });
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
case "navigate": {
|
|
489
|
+
const params = input && typeof input === "object" ? input : {};
|
|
490
|
+
const to = (decl.target ?? "/").replace(/\{(\w+)\}/g, (_m, k) => encodeURIComponent(String(params[k] ?? "")));
|
|
491
|
+
await nav.go(to);
|
|
492
|
+
result = ok(to);
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (result.ok && decl.updatesValue) await surface.setValue(decl.updatesValue, result.data);
|
|
497
|
+
return result;
|
|
498
|
+
},
|
|
499
|
+
async completeAction(runId, result) {
|
|
500
|
+
log("surface", "completeAction", [runId, result]);
|
|
501
|
+
const resolve = world.pendingActions.get(runId);
|
|
502
|
+
world.pendingActions.delete(runId);
|
|
503
|
+
resolve?.(result);
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
const nav = {
|
|
507
|
+
async go(to) {
|
|
508
|
+
log("nav", "go", [to]);
|
|
509
|
+
world.location = parsePath(to);
|
|
510
|
+
for (const h of world.hosts.values()) h.control.emit(NAV_STREAM, world.location);
|
|
511
|
+
},
|
|
512
|
+
async current() {
|
|
513
|
+
log("nav", "current", []);
|
|
514
|
+
return clone(world.location);
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
let childCount = 0;
|
|
518
|
+
const self = {
|
|
519
|
+
kind: "memory",
|
|
520
|
+
hostId,
|
|
521
|
+
[CHAIN]: chain,
|
|
522
|
+
[SURFACE_NAME]: record.surfaceName,
|
|
523
|
+
async viewer() {
|
|
524
|
+
log("host", "viewer", []);
|
|
525
|
+
return {
|
|
526
|
+
userId: spec.viewer?.userId ?? "memory-viewer",
|
|
527
|
+
organizationIds: spec.viewer?.organizationIds ?? [record.organizationId],
|
|
528
|
+
activeOrganizationId: spec.viewer?.activeOrganizationId ?? record.organizationId,
|
|
529
|
+
guest: spec.viewer?.guest ?? false
|
|
530
|
+
};
|
|
531
|
+
},
|
|
532
|
+
async record() {
|
|
533
|
+
log("host", "record", []);
|
|
534
|
+
await wait();
|
|
535
|
+
return clone(record);
|
|
536
|
+
},
|
|
537
|
+
async child(appletId) {
|
|
538
|
+
log("host", "child", [appletId]);
|
|
539
|
+
await wait();
|
|
540
|
+
const childSpec = spec.children?.[appletId];
|
|
541
|
+
if (!childSpec) throw new Error(`[applets/memory] no child Applet "${appletId}" in this memory host's spec.`);
|
|
542
|
+
childCount += 1;
|
|
543
|
+
const childId = `${hostId}/${appletId}#${childCount}`;
|
|
544
|
+
buildHost(world, { latencyMs: world.latencyMs, ...childSpec }, childId, self);
|
|
545
|
+
return { hostId: childId };
|
|
546
|
+
},
|
|
547
|
+
kinds: {
|
|
548
|
+
async definition(slug) {
|
|
549
|
+
log("kinds", "definition", [slug]);
|
|
550
|
+
const k = spec.kinds?.[slug];
|
|
551
|
+
return k?.definition !== void 0 ? ok(clone(k.definition)) : fail("kind_not_found", `No kind named "${slug}".`);
|
|
552
|
+
},
|
|
553
|
+
async component(slug, platform) {
|
|
554
|
+
log("kinds", "component", [slug, platform]);
|
|
555
|
+
return ok(clone(spec.kinds?.[slug]?.component ?? null));
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
pages: {
|
|
559
|
+
async definition(pageId) {
|
|
560
|
+
log("pages", "definition", [pageId]);
|
|
561
|
+
const p = spec.dataPages?.[pageId];
|
|
562
|
+
return p !== void 0 ? ok(clone(p)) : fail("page_not_found", "That page is not here.");
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
data,
|
|
566
|
+
intelligence,
|
|
567
|
+
surface,
|
|
568
|
+
nav,
|
|
569
|
+
events: {
|
|
570
|
+
async on(stream, filter) {
|
|
571
|
+
log("events", "on", [stream, filter]);
|
|
572
|
+
const subscriptionId = mintId(world, "sub");
|
|
573
|
+
subscriptions.set(subscriptionId, stream);
|
|
574
|
+
if (stream.startsWith("rows:")) {
|
|
575
|
+
const stop = dataPort.subscribe(stream, (event) => {
|
|
576
|
+
const message = { subscriptionId, payload: clone(event) };
|
|
577
|
+
for (const l of listeners) l(message);
|
|
578
|
+
});
|
|
579
|
+
rowsUnsubscribe.set(subscriptionId, stop);
|
|
580
|
+
}
|
|
581
|
+
return { subscriptionId };
|
|
582
|
+
},
|
|
583
|
+
async off(subscriptionId) {
|
|
584
|
+
log("events", "off", [subscriptionId]);
|
|
585
|
+
subscriptions.delete(subscriptionId);
|
|
586
|
+
rowsUnsubscribe.get(subscriptionId)?.();
|
|
587
|
+
rowsUnsubscribe.delete(subscriptionId);
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
async reportError(err) {
|
|
591
|
+
log("host", "reportError", [err]);
|
|
592
|
+
world.errors.push(clone(err));
|
|
593
|
+
},
|
|
594
|
+
receive(listener) {
|
|
595
|
+
listeners.add(listener);
|
|
596
|
+
return () => {
|
|
597
|
+
listeners.delete(listener);
|
|
598
|
+
};
|
|
599
|
+
},
|
|
600
|
+
forHost(id) {
|
|
601
|
+
return world.hosts.get(id) ?? null;
|
|
602
|
+
},
|
|
603
|
+
control: {
|
|
604
|
+
dataPort,
|
|
605
|
+
get calls() {
|
|
606
|
+
return world.calls;
|
|
607
|
+
},
|
|
608
|
+
get errors() {
|
|
609
|
+
return world.errors;
|
|
610
|
+
},
|
|
611
|
+
rows(alias) {
|
|
612
|
+
return (tables.get(alias)?.rows ?? []).map((r) => ({ ...clone(r.row), ...r.archived ? { _archived: true } : {} }));
|
|
613
|
+
},
|
|
614
|
+
externalUpdate(alias, id, patch) {
|
|
615
|
+
const stored = findRow(tables.get(alias)?.rows ?? [], id);
|
|
616
|
+
if (!stored) throw new Error(`[applets/memory] no row ${id} in ${alias}`);
|
|
617
|
+
stored.row = { ...stored.row, ...clone(patch), _version: stored.row._version + 1 };
|
|
618
|
+
bump(alias, { ids: [id] });
|
|
619
|
+
return clone(stored.row);
|
|
620
|
+
},
|
|
621
|
+
externalCreate(alias, values2) {
|
|
622
|
+
const t = tables.get(alias);
|
|
623
|
+
if (!t) throw new Error(`[applets/memory] no table ${alias}`);
|
|
624
|
+
const row = { ...clone(values2), _id: mintId(world, "row"), _version: 1, _organization_id: t.spec.organizationId ?? record.organizationId };
|
|
625
|
+
t.rows.unshift({ row, archived: false });
|
|
626
|
+
bump(alias, { ids: [row._id] });
|
|
627
|
+
return clone(row);
|
|
628
|
+
},
|
|
629
|
+
emit,
|
|
630
|
+
location() {
|
|
631
|
+
return clone(world.location);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
world.hosts.set(hostId, self);
|
|
636
|
+
return self;
|
|
637
|
+
}
|
|
638
|
+
export {
|
|
639
|
+
createMemoryHost
|
|
640
|
+
};
|
|
641
|
+
//# sourceMappingURL=host.js.map
|