@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/frame.cjs
ADDED
|
@@ -0,0 +1,977 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/frame/index.tsx
|
|
32
|
+
var frame_exports = {};
|
|
33
|
+
__export(frame_exports, {
|
|
34
|
+
APPLETS_REACT_ENTRY: () => APPLETS_REACT_ENTRY,
|
|
35
|
+
appletScope: () => appletScope,
|
|
36
|
+
mountApplet: () => mountApplet,
|
|
37
|
+
mountAppletAsync: () => mountAppletAsync
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(frame_exports);
|
|
40
|
+
var React7 = __toESM(require("react"), 1);
|
|
41
|
+
var import_code_runtime = require("@ai-matrx/code-runtime");
|
|
42
|
+
|
|
43
|
+
// src/react/index.ts
|
|
44
|
+
var react_exports = {};
|
|
45
|
+
__export(react_exports, {
|
|
46
|
+
Applet: () => Applet,
|
|
47
|
+
AppletFrame: () => AppletFrame,
|
|
48
|
+
DataPage: () => DataPage,
|
|
49
|
+
Kind: () => Kind,
|
|
50
|
+
Link: () => Link,
|
|
51
|
+
Pages: () => Pages,
|
|
52
|
+
defineActions: () => defineActions,
|
|
53
|
+
matchPage: () => matchPage,
|
|
54
|
+
navigate: () => navigate,
|
|
55
|
+
newQueryClient: () => newQueryClient,
|
|
56
|
+
useAction: () => useAction,
|
|
57
|
+
useColumns: () => useColumns,
|
|
58
|
+
useJob: () => useJob,
|
|
59
|
+
useJobs: () => useJobs,
|
|
60
|
+
usePage: () => usePage,
|
|
61
|
+
useRow: () => useRow,
|
|
62
|
+
useRows: () => useRows,
|
|
63
|
+
useSources: () => useSources,
|
|
64
|
+
useValue: () => useValue
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// src/react/context.tsx
|
|
68
|
+
var React = __toESM(require("react"), 1);
|
|
69
|
+
var import_react_query = require("@tanstack/react-query");
|
|
70
|
+
var import_react = require("@ai-matrx/records/data/react");
|
|
71
|
+
|
|
72
|
+
// src/types.ts
|
|
73
|
+
var PAGE_ACTION_STREAM = "actions";
|
|
74
|
+
var NAV_STREAM = "nav";
|
|
75
|
+
var runStream = (requestId) => `run:${requestId}`;
|
|
76
|
+
var valueStream = (name) => `value:${name}`;
|
|
77
|
+
function fail(code, message, retryable = false, diagnostic) {
|
|
78
|
+
const error = { code, message, retryable };
|
|
79
|
+
if (diagnostic !== void 0) error.diagnostic = diagnostic;
|
|
80
|
+
return { ok: false, error };
|
|
81
|
+
}
|
|
82
|
+
function fromRecordsError(error) {
|
|
83
|
+
const out = { code: error.code, message: error.message, retryable: error.code === "unreachable" || error.code === "timed_out" };
|
|
84
|
+
if (error.diagnostic !== void 0) out.diagnostic = error.diagnostic;
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
function toAppletError(err, code = "internal") {
|
|
88
|
+
if (err && typeof err === "object") {
|
|
89
|
+
const o = err;
|
|
90
|
+
if (typeof o.code === "string" && typeof o.message === "string") {
|
|
91
|
+
const out = { code: o.code, message: o.message, retryable: o.retryable === true };
|
|
92
|
+
if (typeof o.diagnostic === "string") out.diagnostic = o.diagnostic;
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
if (err instanceof Error) return { code, message: err.message, retryable: false };
|
|
96
|
+
}
|
|
97
|
+
return { code, message: String(err), retryable: false };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/react/context.tsx
|
|
101
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
102
|
+
var EventHub = class {
|
|
103
|
+
constructor(channel) {
|
|
104
|
+
this.channel = channel;
|
|
105
|
+
}
|
|
106
|
+
channel;
|
|
107
|
+
streams = /* @__PURE__ */ new Map();
|
|
108
|
+
bySubscription = /* @__PURE__ */ new Map();
|
|
109
|
+
detach = null;
|
|
110
|
+
subscribe(stream, listener) {
|
|
111
|
+
if (!this.detach) this.detach = this.channel.receive((m) => this.deliver(m));
|
|
112
|
+
let entry = this.streams.get(stream);
|
|
113
|
+
if (!entry) {
|
|
114
|
+
const created = { listeners: /* @__PURE__ */ new Set(), subscriptionId: null, ready: Promise.resolve() };
|
|
115
|
+
created.ready = this.channel.events.on(stream, null).then(
|
|
116
|
+
({ subscriptionId }) => {
|
|
117
|
+
if (this.streams.get(stream) !== created) {
|
|
118
|
+
void this.channel.events.off(subscriptionId);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
created.subscriptionId = subscriptionId;
|
|
122
|
+
this.bySubscription.set(subscriptionId, stream);
|
|
123
|
+
},
|
|
124
|
+
(err) => {
|
|
125
|
+
void this.channel.reportError({ ...toAppletError(err), where: `events.on(${stream})` });
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
this.streams.set(stream, created);
|
|
129
|
+
entry = created;
|
|
130
|
+
}
|
|
131
|
+
const current = entry;
|
|
132
|
+
current.listeners.add(listener);
|
|
133
|
+
return {
|
|
134
|
+
ready: current.ready,
|
|
135
|
+
unsubscribe: () => {
|
|
136
|
+
current.listeners.delete(listener);
|
|
137
|
+
if (current.listeners.size > 0 || this.streams.get(stream) !== current) return;
|
|
138
|
+
this.streams.delete(stream);
|
|
139
|
+
if (current.subscriptionId) {
|
|
140
|
+
this.bySubscription.delete(current.subscriptionId);
|
|
141
|
+
void this.channel.events.off(current.subscriptionId);
|
|
142
|
+
}
|
|
143
|
+
if (this.streams.size === 0 && this.detach) {
|
|
144
|
+
this.detach();
|
|
145
|
+
this.detach = null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
deliver(message) {
|
|
151
|
+
const stream = this.bySubscription.get(message.subscriptionId);
|
|
152
|
+
if (!stream) return;
|
|
153
|
+
for (const l of [...this.streams.get(stream)?.listeners ?? []]) l(message.payload);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
function frameDataEvents(hub) {
|
|
157
|
+
return {
|
|
158
|
+
on(event, listener) {
|
|
159
|
+
return hub.subscribe(event, (payload) => listener(payload)).unsubscribe;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function dataScopeFor(channel, hub) {
|
|
164
|
+
return { port: channel.data, portKey: channel.hostId, events: frameDataEvents(hub) };
|
|
165
|
+
}
|
|
166
|
+
var CONTEXT_KEY = /* @__PURE__ */ Symbol.for("ai-matrx.applets.frameContext");
|
|
167
|
+
function frameContext() {
|
|
168
|
+
const g = globalThis;
|
|
169
|
+
let ctx = g[CONTEXT_KEY];
|
|
170
|
+
if (!ctx) {
|
|
171
|
+
ctx = React.createContext(null);
|
|
172
|
+
ctx.displayName = "AppletFrame";
|
|
173
|
+
g[CONTEXT_KEY] = ctx;
|
|
174
|
+
}
|
|
175
|
+
return ctx;
|
|
176
|
+
}
|
|
177
|
+
function newQueryClient() {
|
|
178
|
+
return new import_react_query.QueryClient({
|
|
179
|
+
defaultOptions: {
|
|
180
|
+
// Events keep the cache true (rows:<alias>, value:<name>, run:<id>), so a key is requested once.
|
|
181
|
+
queries: { staleTime: Number.POSITIVE_INFINITY, retry: false, refetchOnWindowFocus: false, refetchOnReconnect: false },
|
|
182
|
+
mutations: { retry: false }
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
var DETACHED_KEY = /* @__PURE__ */ Symbol.for("ai-matrx.applets.detached");
|
|
187
|
+
var NO_FRAME = {
|
|
188
|
+
code: "no_frame",
|
|
189
|
+
message: "This part of the Applet is not inside an AppletFrame.",
|
|
190
|
+
retryable: false
|
|
191
|
+
};
|
|
192
|
+
var NO_FRAME_DATA = { code: "not_supported", message: NO_FRAME.message, diagnostic: NO_FRAME.code };
|
|
193
|
+
function detachedChannel() {
|
|
194
|
+
const refuse = async () => ({ ok: false, error: NO_FRAME });
|
|
195
|
+
const refuseData = async () => ({ ok: false, error: NO_FRAME_DATA });
|
|
196
|
+
const reject = async () => {
|
|
197
|
+
throw NO_FRAME;
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
hostId: "detached",
|
|
201
|
+
viewer: reject,
|
|
202
|
+
record: reject,
|
|
203
|
+
child: reject,
|
|
204
|
+
kinds: { definition: refuse, component: refuse },
|
|
205
|
+
pages: { definition: refuse },
|
|
206
|
+
data: {
|
|
207
|
+
sources: refuseData,
|
|
208
|
+
columns: refuseData,
|
|
209
|
+
list: refuseData,
|
|
210
|
+
get: refuseData,
|
|
211
|
+
create: refuseData,
|
|
212
|
+
update: refuseData,
|
|
213
|
+
archive: refuseData,
|
|
214
|
+
restore: refuseData
|
|
215
|
+
},
|
|
216
|
+
intelligence: {
|
|
217
|
+
describe: refuse,
|
|
218
|
+
run: refuse,
|
|
219
|
+
result: refuse,
|
|
220
|
+
answer: refuse,
|
|
221
|
+
rejoin: refuse,
|
|
222
|
+
listRuns: refuse,
|
|
223
|
+
createSoft: refuse
|
|
224
|
+
},
|
|
225
|
+
surface: {
|
|
226
|
+
mount: refuse,
|
|
227
|
+
unmount: async () => void 0,
|
|
228
|
+
getValue: async () => void 0,
|
|
229
|
+
setValue: reject,
|
|
230
|
+
listActions: async () => [],
|
|
231
|
+
runAction: refuse,
|
|
232
|
+
completeAction: async () => void 0
|
|
233
|
+
},
|
|
234
|
+
nav: { go: reject, current: async () => ({ path: [], params: {} }) },
|
|
235
|
+
events: { on: reject, off: async () => void 0 },
|
|
236
|
+
reportError: async () => void 0,
|
|
237
|
+
receive: () => () => void 0,
|
|
238
|
+
forHost: () => null
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function detachedFrame() {
|
|
242
|
+
const g = globalThis;
|
|
243
|
+
let frame = g[DETACHED_KEY];
|
|
244
|
+
if (!frame) {
|
|
245
|
+
const channel = detachedChannel();
|
|
246
|
+
const hub = new EventHub(channel);
|
|
247
|
+
frame = { channel, hub, queryClient: newQueryClient(), dataScope: dataScopeFor(channel, hub), actions: /* @__PURE__ */ new Map(), parent: null, attached: false };
|
|
248
|
+
g[DETACHED_KEY] = frame;
|
|
249
|
+
}
|
|
250
|
+
return frame;
|
|
251
|
+
}
|
|
252
|
+
function useFrame() {
|
|
253
|
+
return React.useContext(frameContext()) ?? detachedFrame();
|
|
254
|
+
}
|
|
255
|
+
function useOptionalFrame() {
|
|
256
|
+
return React.useContext(frameContext());
|
|
257
|
+
}
|
|
258
|
+
var keys = {
|
|
259
|
+
root: (hostId) => ["applets", hostId],
|
|
260
|
+
record: (hostId) => ["applets", hostId, "record"],
|
|
261
|
+
viewer: (hostId) => ["applets", hostId, "viewer"],
|
|
262
|
+
nav: (hostId) => ["applets", hostId, "nav"]
|
|
263
|
+
};
|
|
264
|
+
function recordQuery(channel) {
|
|
265
|
+
return { queryKey: keys.record(channel.hostId), queryFn: () => channel.record() };
|
|
266
|
+
}
|
|
267
|
+
function viewerQuery(channel) {
|
|
268
|
+
return { queryKey: keys.viewer(channel.hostId), queryFn: () => channel.viewer() };
|
|
269
|
+
}
|
|
270
|
+
var FRAMES_KEY = /* @__PURE__ */ Symbol.for("ai-matrx.applets.frames");
|
|
271
|
+
function liveFrames() {
|
|
272
|
+
const g = globalThis;
|
|
273
|
+
let list = g[FRAMES_KEY];
|
|
274
|
+
if (!list) {
|
|
275
|
+
list = [];
|
|
276
|
+
g[FRAMES_KEY] = list;
|
|
277
|
+
}
|
|
278
|
+
return list;
|
|
279
|
+
}
|
|
280
|
+
function AppletFrame({ host, queryClient, children, ...renderers }) {
|
|
281
|
+
const parent = useOptionalFrame();
|
|
282
|
+
const Context = frameContext();
|
|
283
|
+
const [frame] = React.useState(() => {
|
|
284
|
+
const hub = new EventHub(host);
|
|
285
|
+
const value = {
|
|
286
|
+
channel: host,
|
|
287
|
+
hub,
|
|
288
|
+
queryClient: queryClient ?? parent?.queryClient ?? newQueryClient(),
|
|
289
|
+
dataScope: dataScopeFor(host, hub),
|
|
290
|
+
actions: /* @__PURE__ */ new Map(),
|
|
291
|
+
parent,
|
|
292
|
+
attached: true
|
|
293
|
+
};
|
|
294
|
+
const inherit = (key) => {
|
|
295
|
+
const own = renderers[key] ?? (key === "renderKind" || key === "renderDataPage" ? parent?.[key] : void 0);
|
|
296
|
+
if (own !== void 0) value[key] = own;
|
|
297
|
+
};
|
|
298
|
+
inherit("renderKind");
|
|
299
|
+
inherit("renderDataPage");
|
|
300
|
+
inherit("renderApplet");
|
|
301
|
+
inherit("pageComponent");
|
|
302
|
+
return value;
|
|
303
|
+
});
|
|
304
|
+
React.useEffect(() => {
|
|
305
|
+
const frames = liveFrames();
|
|
306
|
+
frames.push(frame);
|
|
307
|
+
let cancelled = false;
|
|
308
|
+
let mountId = null;
|
|
309
|
+
void (async () => {
|
|
310
|
+
try {
|
|
311
|
+
const record = await frame.queryClient.fetchQuery(recordQuery(host));
|
|
312
|
+
const parentName = parent ? (await parent.queryClient.fetchQuery(recordQuery(parent.channel))).surfaceName : null;
|
|
313
|
+
if (cancelled) return;
|
|
314
|
+
const mounted = await host.surface.mount(record.surfaceName, parentName);
|
|
315
|
+
if (!mounted.ok) {
|
|
316
|
+
void host.reportError({ ...mounted.error, where: "surface.mount" });
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (cancelled) void host.surface.unmount(mounted.data.mountId);
|
|
320
|
+
else mountId = mounted.data.mountId;
|
|
321
|
+
} catch (err) {
|
|
322
|
+
void host.reportError({ ...toAppletError(err), where: "AppletFrame mount" });
|
|
323
|
+
}
|
|
324
|
+
})();
|
|
325
|
+
const { unsubscribe } = frame.hub.subscribe(PAGE_ACTION_STREAM, (payload) => {
|
|
326
|
+
void routePageAction(frame, payload);
|
|
327
|
+
});
|
|
328
|
+
return () => {
|
|
329
|
+
cancelled = true;
|
|
330
|
+
unsubscribe();
|
|
331
|
+
if (mountId) void host.surface.unmount(mountId);
|
|
332
|
+
const i = frames.indexOf(frame);
|
|
333
|
+
if (i >= 0) frames.splice(i, 1);
|
|
334
|
+
};
|
|
335
|
+
}, [frame, host, parent]);
|
|
336
|
+
const { port, events, portKey } = frame.dataScope;
|
|
337
|
+
const body = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Context.Provider, { value: frame, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.DataPortProvider, { port, portKey, queryClient: frame.queryClient, ...events ? { events } : {}, children }) });
|
|
338
|
+
return parent?.queryClient === frame.queryClient ? body : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_query.QueryClientProvider, { client: frame.queryClient, children: body });
|
|
339
|
+
}
|
|
340
|
+
async function routePageAction(frame, event) {
|
|
341
|
+
const { channel } = frame;
|
|
342
|
+
const handler = frame.actions.get(event.action);
|
|
343
|
+
if (!handler) {
|
|
344
|
+
await channel.surface.completeAction(event.runId, fail("action_unhandled", `This page has no handler for "${event.action}".`));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
let viewerId = null;
|
|
348
|
+
try {
|
|
349
|
+
viewerId = (await frame.queryClient.fetchQuery(viewerQuery(channel))).userId;
|
|
350
|
+
} catch {
|
|
351
|
+
viewerId = null;
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
const data = await handler(event.input, { runId: event.runId, by: event.by ?? "applet", viewerId });
|
|
355
|
+
await channel.surface.completeAction(event.runId, { ok: true, data });
|
|
356
|
+
} catch (err) {
|
|
357
|
+
await channel.surface.completeAction(event.runId, { ok: false, error: toAppletError(err) });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/react/data.ts
|
|
362
|
+
var React2 = __toESM(require("react"), 1);
|
|
363
|
+
var import_react2 = require("@ai-matrx/records/data/react");
|
|
364
|
+
async function unwrap(answer) {
|
|
365
|
+
const r = await answer;
|
|
366
|
+
if (!r.ok) throw r.error;
|
|
367
|
+
return r.data;
|
|
368
|
+
}
|
|
369
|
+
function asAppletError(err) {
|
|
370
|
+
return err == null ? null : toAppletError(err);
|
|
371
|
+
}
|
|
372
|
+
function queryStatus(q, saving) {
|
|
373
|
+
if (q.status === "error") return "error";
|
|
374
|
+
if (saving) return "saving";
|
|
375
|
+
if (q.status === "pending") return q.fetchStatus === "idle" ? "idle" : "loading";
|
|
376
|
+
return "ready";
|
|
377
|
+
}
|
|
378
|
+
function toResult(answer) {
|
|
379
|
+
return answer.ok ? answer : { ok: false, error: fromRecordsError(answer.error) };
|
|
380
|
+
}
|
|
381
|
+
function scopeOf(frame) {
|
|
382
|
+
return frame.attached ? void 0 : frame.dataScope;
|
|
383
|
+
}
|
|
384
|
+
function readStatus(loading, failed, saving) {
|
|
385
|
+
if (failed) return "error";
|
|
386
|
+
if (saving) return "saving";
|
|
387
|
+
return loading ? "loading" : "ready";
|
|
388
|
+
}
|
|
389
|
+
function useWriter(frame, alias) {
|
|
390
|
+
const mutations = (0, import_react2.useDataMutations)(alias, scopeOf(frame));
|
|
391
|
+
const [pending, setPending] = React2.useState(0);
|
|
392
|
+
const [writeError, setWriteError] = React2.useState(null);
|
|
393
|
+
const track = async (work) => {
|
|
394
|
+
setPending((n) => n + 1);
|
|
395
|
+
try {
|
|
396
|
+
const answer = toResult(await work());
|
|
397
|
+
setWriteError(answer.ok ? null : answer.error);
|
|
398
|
+
return answer;
|
|
399
|
+
} catch (err) {
|
|
400
|
+
const error = toAppletError(err);
|
|
401
|
+
setWriteError(error);
|
|
402
|
+
return { ok: false, error };
|
|
403
|
+
} finally {
|
|
404
|
+
setPending((n) => n - 1);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
return {
|
|
408
|
+
saving: pending > 0,
|
|
409
|
+
error: writeError,
|
|
410
|
+
create: (values) => track(() => mutations.create(values)),
|
|
411
|
+
update: (id, patch, baseVersion) => track(async () => {
|
|
412
|
+
if (baseVersion !== void 0) return mutations.update(id, patch, { baseVersion });
|
|
413
|
+
const first = await mutations.update(id, patch);
|
|
414
|
+
if (first.ok || first.error.code !== "stale_read") return first;
|
|
415
|
+
const read = await frame.channel.data.get(alias, id);
|
|
416
|
+
if (!read.ok) return read;
|
|
417
|
+
if (!read.data) return { ok: false, error: { code: "not_found", message: "This record is not here any more." } };
|
|
418
|
+
return mutations.update(id, patch, { baseVersion: read.data._version });
|
|
419
|
+
}),
|
|
420
|
+
archive: (id) => track(() => mutations.archive(id)),
|
|
421
|
+
restore: (id) => track(() => mutations.restore(id))
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function useRows(source, q) {
|
|
425
|
+
const frame = useFrame();
|
|
426
|
+
const list = (0, import_react2.useDataRows)(source, q ?? {}, scopeOf(frame));
|
|
427
|
+
const writer = useWriter(frame, source);
|
|
428
|
+
return {
|
|
429
|
+
rows: list.rows,
|
|
430
|
+
total: list.total,
|
|
431
|
+
status: readStatus(list.loading, list.error !== null, writer.saving),
|
|
432
|
+
error: (list.error ? fromRecordsError(list.error) : null) ?? writer.error,
|
|
433
|
+
hasMore: list.hasMore,
|
|
434
|
+
loadMore: list.loadMore,
|
|
435
|
+
newer: list.newer,
|
|
436
|
+
refresh: list.refresh,
|
|
437
|
+
create: writer.create,
|
|
438
|
+
update: (id, patch) => writer.update(id, patch, list.rows.find((r) => r._id === id)?._version),
|
|
439
|
+
archive: writer.archive,
|
|
440
|
+
restore: writer.restore
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function useRow(source, id) {
|
|
444
|
+
const frame = useFrame();
|
|
445
|
+
const one = (0, import_react2.useDataRow)(source, id, scopeOf(frame));
|
|
446
|
+
const writer = useWriter(frame, source);
|
|
447
|
+
const row = one.data ?? null;
|
|
448
|
+
return {
|
|
449
|
+
row,
|
|
450
|
+
status: readStatus(one.loading, one.error !== null, writer.saving),
|
|
451
|
+
error: (one.error ? fromRecordsError(one.error) : null) ?? writer.error,
|
|
452
|
+
update: (patch) => writer.update(id, patch, row?._version),
|
|
453
|
+
archive: () => writer.archive(id),
|
|
454
|
+
refresh: one.refresh
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function useColumns(source) {
|
|
458
|
+
const frame = useFrame();
|
|
459
|
+
const q = (0, import_react2.useDataColumns)(source, scopeOf(frame));
|
|
460
|
+
return { columns: q.data ?? [], status: readStatus(q.loading, q.error !== null, false), error: q.error ? fromRecordsError(q.error) : null };
|
|
461
|
+
}
|
|
462
|
+
function useSources() {
|
|
463
|
+
const frame = useFrame();
|
|
464
|
+
const q = (0, import_react2.useDataSources)(scopeOf(frame));
|
|
465
|
+
return { sources: q.data ?? [], status: readStatus(q.loading, q.error !== null, false), error: q.error ? fromRecordsError(q.error) : null };
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/react/jobs.ts
|
|
469
|
+
var React3 = __toESM(require("react"), 1);
|
|
470
|
+
var import_react_query2 = require("@tanstack/react-query");
|
|
471
|
+
var IDLE = { status: "idle", error: null, text: "", result: null, inputs: [] };
|
|
472
|
+
var TERMINAL = /* @__PURE__ */ new Set(["done", "complete", "completed", "succeeded", "success"]);
|
|
473
|
+
function reduce(state, action) {
|
|
474
|
+
switch (action.type) {
|
|
475
|
+
case "reset":
|
|
476
|
+
return { ...IDLE, status: action.status };
|
|
477
|
+
case "fail":
|
|
478
|
+
return { ...state, status: "error", error: action.error };
|
|
479
|
+
case "status":
|
|
480
|
+
return { ...state, status: action.status };
|
|
481
|
+
case "catch-up":
|
|
482
|
+
if (state.status === "done") return state;
|
|
483
|
+
return { ...state, status: "done", result: action.envelope, text: state.text || action.text };
|
|
484
|
+
case "event": {
|
|
485
|
+
const e = action.event;
|
|
486
|
+
switch (e.type) {
|
|
487
|
+
case "text":
|
|
488
|
+
return { ...state, text: state.text + e.delta };
|
|
489
|
+
case "status":
|
|
490
|
+
if (TERMINAL.has(e.status)) return { ...state, status: "done" };
|
|
491
|
+
if (e.status === "error" || e.status === "failed") return { ...state, status: "error" };
|
|
492
|
+
return state.status === "done" ? state : { ...state, status: "running" };
|
|
493
|
+
case "needs_input":
|
|
494
|
+
return { ...state, status: "needs_input", inputs: e.inputs };
|
|
495
|
+
case "result":
|
|
496
|
+
return { ...state, status: "done", result: e.envelope };
|
|
497
|
+
case "error":
|
|
498
|
+
return { ...state, status: "error", error: e.error };
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return state;
|
|
503
|
+
}
|
|
504
|
+
var runsKey = (hostId, key) => ["applets", hostId, "runs", key];
|
|
505
|
+
function useJob(alias) {
|
|
506
|
+
const frame = useFrame();
|
|
507
|
+
const { channel, queryClient: qc, hub } = frame;
|
|
508
|
+
const [state, dispatch] = React3.useReducer(reduce, IDLE);
|
|
509
|
+
const refRef = React3.useRef(null);
|
|
510
|
+
const unsubscribeRef = React3.useRef(null);
|
|
511
|
+
React3.useEffect(
|
|
512
|
+
() => () => {
|
|
513
|
+
unsubscribeRef.current?.();
|
|
514
|
+
unsubscribeRef.current = null;
|
|
515
|
+
},
|
|
516
|
+
[]
|
|
517
|
+
);
|
|
518
|
+
const record = (0, import_react_query2.useQuery)({ ...recordQuery(channel) }, qc);
|
|
519
|
+
const key = record.data?.mandates.find((m) => m.alias === alias)?.key ?? null;
|
|
520
|
+
const appletId = record.data?.id;
|
|
521
|
+
const runs = (0, import_react_query2.useQuery)(
|
|
522
|
+
{
|
|
523
|
+
queryKey: runsKey(channel.hostId, key ?? ""),
|
|
524
|
+
queryFn: () => unwrap(channel.intelligence.listRuns(key ?? "", appletId)),
|
|
525
|
+
enabled: key !== null
|
|
526
|
+
},
|
|
527
|
+
qc
|
|
528
|
+
);
|
|
529
|
+
const run = async (variables, opts) => {
|
|
530
|
+
unsubscribeRef.current?.();
|
|
531
|
+
unsubscribeRef.current = null;
|
|
532
|
+
dispatch({ type: "reset", status: "resolving" });
|
|
533
|
+
try {
|
|
534
|
+
const rec = await qc.fetchQuery(recordQuery(channel));
|
|
535
|
+
const mandate = rec.mandates.find((m) => m.alias === alias);
|
|
536
|
+
if (!mandate) {
|
|
537
|
+
const error = { code: "job_not_found", message: `This Applet has no job named "${alias}".`, retryable: false };
|
|
538
|
+
dispatch({ type: "fail", error });
|
|
539
|
+
return { ok: false, error };
|
|
540
|
+
}
|
|
541
|
+
const viewer = await qc.fetchQuery(viewerQuery(channel));
|
|
542
|
+
const req = {
|
|
543
|
+
key: mandate.key,
|
|
544
|
+
organizationId: viewer.activeOrganizationId,
|
|
545
|
+
appletId: rec.id,
|
|
546
|
+
surfaceName: rec.surfaceName
|
|
547
|
+
};
|
|
548
|
+
if (variables !== void 0) req.variables = variables;
|
|
549
|
+
if (opts?.userInput !== void 0) req.userInput = opts.userInput;
|
|
550
|
+
const started = await channel.intelligence.run(req);
|
|
551
|
+
if (!started.ok) {
|
|
552
|
+
dispatch({ type: "fail", error: started.error });
|
|
553
|
+
return started;
|
|
554
|
+
}
|
|
555
|
+
const ref = started.data;
|
|
556
|
+
refRef.current = ref;
|
|
557
|
+
dispatch({ type: "status", status: "running" });
|
|
558
|
+
let finished = false;
|
|
559
|
+
const sub = hub.subscribe(runStream(ref.requestId), (payload) => {
|
|
560
|
+
const event = payload;
|
|
561
|
+
dispatch({ type: "event", event });
|
|
562
|
+
if (!finished && (event.type === "result" || event.type === "error" || event.type === "status" && TERMINAL.has(event.status))) {
|
|
563
|
+
finished = true;
|
|
564
|
+
void qc.invalidateQueries({ queryKey: runsKey(channel.hostId, mandate.key) });
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
unsubscribeRef.current = sub.unsubscribe;
|
|
568
|
+
await sub.ready;
|
|
569
|
+
const now = await channel.intelligence.result(ref);
|
|
570
|
+
if (now.ok && !finished && (now.data.envelope !== null || TERMINAL.has(now.data.status))) {
|
|
571
|
+
finished = true;
|
|
572
|
+
dispatch({ type: "catch-up", text: now.data.text, envelope: now.data.envelope });
|
|
573
|
+
}
|
|
574
|
+
void qc.invalidateQueries({ queryKey: runsKey(channel.hostId, mandate.key) });
|
|
575
|
+
return started;
|
|
576
|
+
} catch (err) {
|
|
577
|
+
const error = toAppletError(err);
|
|
578
|
+
dispatch({ type: "fail", error });
|
|
579
|
+
return { ok: false, error };
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
const answer = async (values) => {
|
|
583
|
+
const ref = refRef.current;
|
|
584
|
+
if (!ref) return { ok: false, error: { code: "no_run", message: "There is no run waiting for an answer.", retryable: false } };
|
|
585
|
+
dispatch({ type: "status", status: "running" });
|
|
586
|
+
const answered = await channel.intelligence.answer(ref, values);
|
|
587
|
+
if (!answered.ok) dispatch({ type: "fail", error: answered.error });
|
|
588
|
+
return answered;
|
|
589
|
+
};
|
|
590
|
+
return { run, answer, runs: runs.data ?? [], ...state };
|
|
591
|
+
}
|
|
592
|
+
function useJobs() {
|
|
593
|
+
const { channel, queryClient } = useFrame();
|
|
594
|
+
const q = (0, import_react_query2.useQuery)(
|
|
595
|
+
{
|
|
596
|
+
queryKey: ["applets", channel.hostId, "jobs"],
|
|
597
|
+
queryFn: async () => {
|
|
598
|
+
const rec = await queryClient.fetchQuery(recordQuery(channel));
|
|
599
|
+
return Promise.all(
|
|
600
|
+
rec.mandates.map(async (m) => {
|
|
601
|
+
const d = await channel.intelligence.describe(m.key);
|
|
602
|
+
if (!d.ok) throw d.error;
|
|
603
|
+
return { alias: m.alias, key: m.key, label: d.data.label, goal: d.data.goal, outputKind: d.data.outputKind };
|
|
604
|
+
})
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
queryClient
|
|
609
|
+
);
|
|
610
|
+
return { jobs: q.data ?? [], status: queryStatus(q, false), error: asAppletError(q.error) };
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/react/surface.ts
|
|
614
|
+
var React4 = __toESM(require("react"), 1);
|
|
615
|
+
var import_react_query3 = require("@tanstack/react-query");
|
|
616
|
+
var valueKey = (hostId, name) => ["applets", hostId, "value", name];
|
|
617
|
+
function useValue(name) {
|
|
618
|
+
const frame = useFrame();
|
|
619
|
+
const { channel, queryClient: qc, hub } = frame;
|
|
620
|
+
const q = (0, import_react_query3.useQuery)({ queryKey: valueKey(channel.hostId, name), queryFn: async () => await channel.surface.getValue(name) ?? null }, qc);
|
|
621
|
+
React4.useEffect(() => {
|
|
622
|
+
const { unsubscribe } = hub.subscribe(valueStream(name), (payload) => {
|
|
623
|
+
qc.setQueryData(valueKey(channel.hostId, name), payload ?? null);
|
|
624
|
+
});
|
|
625
|
+
return unsubscribe;
|
|
626
|
+
}, [hub, qc, channel, name]);
|
|
627
|
+
const set = (next) => {
|
|
628
|
+
qc.setQueryData(valueKey(channel.hostId, name), next);
|
|
629
|
+
channel.surface.setValue(name, next).catch((err) => {
|
|
630
|
+
void channel.reportError({ ...toAppletError(err), where: `setValue(${name})` });
|
|
631
|
+
});
|
|
632
|
+
};
|
|
633
|
+
return [q.data ?? void 0, set];
|
|
634
|
+
}
|
|
635
|
+
function useAction(name, options) {
|
|
636
|
+
const { channel } = useFrame();
|
|
637
|
+
const [status, setStatus] = React4.useState("idle");
|
|
638
|
+
const [error, setError] = React4.useState(null);
|
|
639
|
+
const run = async (input) => {
|
|
640
|
+
setStatus("loading");
|
|
641
|
+
try {
|
|
642
|
+
const answer = await channel.surface.runAction(name, input ?? null, options?.by ?? "person");
|
|
643
|
+
if (answer.ok) {
|
|
644
|
+
setError(null);
|
|
645
|
+
setStatus("ready");
|
|
646
|
+
return answer;
|
|
647
|
+
}
|
|
648
|
+
const err = { code: answer.error.code, message: answer.error.message, retryable: answer.error.retryable };
|
|
649
|
+
if (answer.error.diagnostic !== void 0) err.diagnostic = answer.error.diagnostic;
|
|
650
|
+
setError(err);
|
|
651
|
+
setStatus("error");
|
|
652
|
+
return { ok: false, error: err };
|
|
653
|
+
} catch (thrown) {
|
|
654
|
+
const err = toAppletError(thrown);
|
|
655
|
+
setError(err);
|
|
656
|
+
setStatus("error");
|
|
657
|
+
return { ok: false, error: err };
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
return { run, status, error };
|
|
661
|
+
}
|
|
662
|
+
function defineActions(handlers) {
|
|
663
|
+
const frame = useFrame();
|
|
664
|
+
const latest = React4.useRef(handlers);
|
|
665
|
+
latest.current = handlers;
|
|
666
|
+
const names = Object.keys(handlers).sort().join("\0");
|
|
667
|
+
React4.useEffect(() => {
|
|
668
|
+
const registered = names.split("\0").filter(Boolean);
|
|
669
|
+
for (const name of registered) {
|
|
670
|
+
frame.actions.set(name, (input, ctx) => {
|
|
671
|
+
const handler = latest.current[name];
|
|
672
|
+
if (!handler) throw { code: "action_unhandled", message: `This page has no handler for "${name}".`, retryable: false };
|
|
673
|
+
return handler(input, ctx);
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
return () => {
|
|
677
|
+
for (const name of registered) frame.actions.delete(name);
|
|
678
|
+
};
|
|
679
|
+
}, [frame, names]);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/react/nav.tsx
|
|
683
|
+
var React5 = __toESM(require("react"), 1);
|
|
684
|
+
var import_react_query4 = require("@tanstack/react-query");
|
|
685
|
+
var import_controls = require("@ai-matrx/design-system/controls");
|
|
686
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
687
|
+
function parse(to) {
|
|
688
|
+
const [pathPart = "", queryPart = ""] = to.split("?");
|
|
689
|
+
const params = {};
|
|
690
|
+
for (const pair of queryPart.split("&").filter(Boolean)) {
|
|
691
|
+
const [k = "", v = ""] = pair.split("=");
|
|
692
|
+
params[decodeURIComponent(k)] = decodeURIComponent(v);
|
|
693
|
+
}
|
|
694
|
+
return { path: pathPart.split("/").filter(Boolean), params };
|
|
695
|
+
}
|
|
696
|
+
function go(frame, to) {
|
|
697
|
+
frame.queryClient.setQueryData(keys.nav(frame.channel.hostId), parse(to));
|
|
698
|
+
frame.channel.nav.go(to).catch((err) => {
|
|
699
|
+
void frame.channel.reportError({ ...toAppletError(err), where: `nav.go(${to})` });
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
function navigate(to) {
|
|
703
|
+
const roots = liveFrames().filter((f) => f.parent === null);
|
|
704
|
+
const frame = roots[roots.length - 1];
|
|
705
|
+
if (!frame) {
|
|
706
|
+
console.error(`[applets] navigate("${to}") was called with no AppletFrame mounted.`);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
go(frame, to);
|
|
710
|
+
}
|
|
711
|
+
function useLocation(frame) {
|
|
712
|
+
const { channel, queryClient: qc, hub } = frame;
|
|
713
|
+
const q = (0, import_react_query4.useQuery)({ queryKey: keys.nav(channel.hostId), queryFn: () => channel.nav.current() }, qc);
|
|
714
|
+
React5.useEffect(() => {
|
|
715
|
+
const { unsubscribe } = hub.subscribe(NAV_STREAM, (payload) => {
|
|
716
|
+
qc.setQueryData(keys.nav(channel.hostId), payload);
|
|
717
|
+
});
|
|
718
|
+
return unsubscribe;
|
|
719
|
+
}, [hub, qc, channel]);
|
|
720
|
+
return q.data ?? { path: [], params: {} };
|
|
721
|
+
}
|
|
722
|
+
function segments(path) {
|
|
723
|
+
return path.split("/").filter(Boolean);
|
|
724
|
+
}
|
|
725
|
+
function matchPage(pages, path) {
|
|
726
|
+
let best = null;
|
|
727
|
+
for (const page of pages) {
|
|
728
|
+
const want = segments(page.path);
|
|
729
|
+
if (want.length !== path.length) continue;
|
|
730
|
+
const params = {};
|
|
731
|
+
let score = 0;
|
|
732
|
+
let hit = true;
|
|
733
|
+
want.forEach((w, i) => {
|
|
734
|
+
const got = path[i] ?? "";
|
|
735
|
+
if (w.startsWith(":")) params[w.slice(1)] = got;
|
|
736
|
+
else if (w === got) score += 1;
|
|
737
|
+
else hit = false;
|
|
738
|
+
});
|
|
739
|
+
if (hit && (!best || score > best.score)) best = { page, params, score };
|
|
740
|
+
}
|
|
741
|
+
return best ? { page: best.page, params: best.params } : null;
|
|
742
|
+
}
|
|
743
|
+
function usePage() {
|
|
744
|
+
const frame = useFrame();
|
|
745
|
+
const location = useLocation(frame);
|
|
746
|
+
const record = (0, import_react_query4.useQuery)({ ...recordQuery(frame.channel) }, frame.queryClient);
|
|
747
|
+
const pages = record.data?.pages ?? [];
|
|
748
|
+
const matched = matchPage(pages, location.path) ?? (location.path.length === 0 && pages[0] ? { page: pages[0], params: {} } : null);
|
|
749
|
+
return {
|
|
750
|
+
path: location.path,
|
|
751
|
+
page: matched ? { path: matched.page.path, title: matched.page.title } : { path: `/${location.path.join("/")}`, title: "" },
|
|
752
|
+
params: { ...location.params, ...matched?.params ?? {} },
|
|
753
|
+
navigate: (to) => go(frame, to)
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function Link({ to, onClick, children, ...rest }) {
|
|
757
|
+
const frame = useFrame();
|
|
758
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
759
|
+
"a",
|
|
760
|
+
{
|
|
761
|
+
...rest,
|
|
762
|
+
href: to,
|
|
763
|
+
onClick: (event) => {
|
|
764
|
+
onClick?.(event);
|
|
765
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
766
|
+
event.preventDefault();
|
|
767
|
+
go(frame, to);
|
|
768
|
+
},
|
|
769
|
+
children
|
|
770
|
+
}
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
function topOf(pages, page) {
|
|
774
|
+
let current = page;
|
|
775
|
+
for (let guard = 0; current.parent && guard < pages.length; guard += 1) {
|
|
776
|
+
const up = pages.find((p) => p.path === current.parent);
|
|
777
|
+
if (!up) break;
|
|
778
|
+
current = up;
|
|
779
|
+
}
|
|
780
|
+
return current;
|
|
781
|
+
}
|
|
782
|
+
function Pages({ layout = "tabs", components }) {
|
|
783
|
+
const frame = useFrame();
|
|
784
|
+
const location = useLocation(frame);
|
|
785
|
+
const record = (0, import_react_query4.useQuery)({ ...recordQuery(frame.channel) }, frame.queryClient);
|
|
786
|
+
const pages = record.data?.pages ?? [];
|
|
787
|
+
const top = pages.filter((p) => !p.parent);
|
|
788
|
+
const matched = matchPage(pages, location.path)?.page ?? (location.path.length === 0 ? top[0] : void 0);
|
|
789
|
+
const selected = matched ? topOf(pages, matched).path : null;
|
|
790
|
+
const open = (path) => go(frame, path);
|
|
791
|
+
const Component = matched ? components?.[matched.file] ?? frame.pageComponent?.(matched.file) ?? null : null;
|
|
792
|
+
const body = matched ? Component ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Component, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { "data-applet-stand-in": "page", className: "text-sm text-muted-foreground", children: [
|
|
793
|
+
"Page file ",
|
|
794
|
+
matched.file,
|
|
795
|
+
" is not compiled in this frame"
|
|
796
|
+
] }) : record.isPending ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { "data-applet-stand-in": "no-page", className: "text-sm text-muted-foreground", children: [
|
|
797
|
+
"No page at /",
|
|
798
|
+
location.path.join("/")
|
|
799
|
+
] });
|
|
800
|
+
let navigation = null;
|
|
801
|
+
if (layout === "tabs" && top.length > 0) {
|
|
802
|
+
navigation = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls.Tabs, { "aria-label": "Pages", value: selected, onValueChange: open, data: top.map((p) => ({ value: p.path, label: p.title })) });
|
|
803
|
+
} else if (layout === "menu" && top.length > 0) {
|
|
804
|
+
navigation = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls.Select, { "aria-label": "Pages", value: selected ?? top[0]?.path ?? "", onValueChange: open, options: top.map((p) => ({ value: p.path, label: p.title })) });
|
|
805
|
+
} else if (layout === "sidebar") {
|
|
806
|
+
navigation = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("nav", { "aria-label": "Pages", className: "flex w-48 shrink-0 flex-col gap-1 border-r border-border p-2", children: top.map((p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
807
|
+
"button",
|
|
808
|
+
{
|
|
809
|
+
type: "button",
|
|
810
|
+
"aria-current": p.path === selected ? "page" : void 0,
|
|
811
|
+
"data-state": p.path === selected ? "on" : "off",
|
|
812
|
+
className: "rounded-md px-2 py-1 text-left text-sm text-foreground data-[state=off]:text-muted-foreground data-[state=on]:bg-muted",
|
|
813
|
+
onClick: () => open(p.path),
|
|
814
|
+
children: p.title
|
|
815
|
+
},
|
|
816
|
+
p.path
|
|
817
|
+
)) });
|
|
818
|
+
}
|
|
819
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { "data-applet-pages": layout, className: layout === "sidebar" ? "flex h-full min-h-0" : "flex h-full min-h-0 flex-col", children: [
|
|
820
|
+
navigation,
|
|
821
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "min-h-0 flex-1", children: body })
|
|
822
|
+
] });
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/react/nesting.tsx
|
|
826
|
+
var React6 = __toESM(require("react"), 1);
|
|
827
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
828
|
+
function StandIn({ what, children }) {
|
|
829
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { "data-applet-stand-in": what, className: "rounded-md border border-dashed border-border p-2 text-sm text-muted-foreground", children });
|
|
830
|
+
}
|
|
831
|
+
function Applet({ id }) {
|
|
832
|
+
const frame = useFrame();
|
|
833
|
+
const [child, setChild] = React6.useState(null);
|
|
834
|
+
const [error, setError] = React6.useState(null);
|
|
835
|
+
React6.useEffect(() => {
|
|
836
|
+
let cancelled = false;
|
|
837
|
+
frame.channel.child(id).then(
|
|
838
|
+
({ hostId }) => {
|
|
839
|
+
if (cancelled) return;
|
|
840
|
+
const scoped = frame.channel.forHost(hostId);
|
|
841
|
+
if (scoped) setChild(scoped);
|
|
842
|
+
else setError({ code: "host_not_found", message: `The host for Applet ${id} did not answer.`, retryable: true });
|
|
843
|
+
},
|
|
844
|
+
(err) => {
|
|
845
|
+
if (!cancelled) setError(toAppletError(err));
|
|
846
|
+
}
|
|
847
|
+
);
|
|
848
|
+
return () => {
|
|
849
|
+
cancelled = true;
|
|
850
|
+
};
|
|
851
|
+
}, [frame, id]);
|
|
852
|
+
if (error) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StandIn, { what: "applet-error", children: error.message });
|
|
853
|
+
if (!child) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { "data-applet-loading": id, "aria-busy": "true" });
|
|
854
|
+
if (!frame.renderApplet) return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(StandIn, { what: "applet", children: [
|
|
855
|
+
"Applet ",
|
|
856
|
+
id,
|
|
857
|
+
" needs mountApplet to render"
|
|
858
|
+
] });
|
|
859
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: frame.renderApplet(child) });
|
|
860
|
+
}
|
|
861
|
+
function Kind({ kind, value }) {
|
|
862
|
+
const frame = useFrame();
|
|
863
|
+
if (frame.renderKind) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: frame.renderKind(kind, value) });
|
|
864
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(StandIn, { what: `kind:${kind}`, children: [
|
|
865
|
+
"No renderer for kind ",
|
|
866
|
+
kind,
|
|
867
|
+
" in this frame"
|
|
868
|
+
] });
|
|
869
|
+
}
|
|
870
|
+
function DataPage({ id }) {
|
|
871
|
+
const frame = useFrame();
|
|
872
|
+
const [state, setState] = React6.useState(null);
|
|
873
|
+
React6.useEffect(() => {
|
|
874
|
+
let cancelled = false;
|
|
875
|
+
void frame.channel.pages.definition(id).then((r) => {
|
|
876
|
+
if (!cancelled) setState(r.ok ? { definition: r.data } : { error: r.error });
|
|
877
|
+
});
|
|
878
|
+
return () => {
|
|
879
|
+
cancelled = true;
|
|
880
|
+
};
|
|
881
|
+
}, [frame, id]);
|
|
882
|
+
if (!state) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { "data-applet-loading": id, "aria-busy": "true" });
|
|
883
|
+
if ("error" in state) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StandIn, { what: "data-page-error", children: state.error.message });
|
|
884
|
+
if (!frame.renderDataPage) return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(StandIn, { what: "data-page", children: [
|
|
885
|
+
"Page ",
|
|
886
|
+
id,
|
|
887
|
+
" needs a host page renderer"
|
|
888
|
+
] });
|
|
889
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: frame.renderDataPage(id, state.definition) });
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/frame/index.tsx
|
|
893
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
894
|
+
var APPLETS_REACT_ENTRY = "@ai-matrx/applets/react";
|
|
895
|
+
function appletScope(record, scope) {
|
|
896
|
+
const entries = [.../* @__PURE__ */ new Set([...record.scope.entries, ...scope.entries, "react", APPLETS_REACT_ENTRY])];
|
|
897
|
+
const overrides = { ...record.scope.overrides ?? {}, ...scope.overrides ?? {} };
|
|
898
|
+
return { entries, overrides, shadowDangerousGlobals: record.scope.shadowDangerousGlobals || scope.shadowDangerousGlobals };
|
|
899
|
+
}
|
|
900
|
+
function provideHooks() {
|
|
901
|
+
(0, import_code_runtime.provideScopeModules)({ [APPLETS_REACT_ENTRY]: () => react_exports });
|
|
902
|
+
}
|
|
903
|
+
function mountApplet(record, host, scope, options = {}) {
|
|
904
|
+
provideHooks();
|
|
905
|
+
const spec = appletScope(record, scope);
|
|
906
|
+
const origin = `applet:${record.id}@${record.version}`;
|
|
907
|
+
const unresolvedSeen = [];
|
|
908
|
+
const compileOptions = {
|
|
909
|
+
origin,
|
|
910
|
+
onUnresolved: (o, list) => {
|
|
911
|
+
const lines = list.map((u) => u.importPath ? `${u.identifier} from "${u.importPath}"` : u.identifier);
|
|
912
|
+
unresolvedSeen.push(...lines);
|
|
913
|
+
if (options.onUnresolved) options.onUnresolved(o, lines);
|
|
914
|
+
else if (lines.length) {
|
|
915
|
+
void host.reportError({ code: "unresolved_import", message: `Missing in this Applet: ${lines.join(", ")}`, retryable: false, where: o });
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
let compiled;
|
|
920
|
+
try {
|
|
921
|
+
compiled = (0, import_code_runtime.compile)(record.files, record.entry, spec, compileOptions);
|
|
922
|
+
} catch (err) {
|
|
923
|
+
return { ok: false, error: toAppletError(err, "compile_failed") };
|
|
924
|
+
}
|
|
925
|
+
if (!compiled.ok) return { ok: false, error: compiled.error };
|
|
926
|
+
const Entry = compiled.Component;
|
|
927
|
+
const pageCache = /* @__PURE__ */ new Map([[record.entry, Entry]]);
|
|
928
|
+
const pageComponent = (file) => {
|
|
929
|
+
if (!pageCache.has(file)) {
|
|
930
|
+
if (!(file in record.files)) pageCache.set(file, null);
|
|
931
|
+
else {
|
|
932
|
+
const page = (0, import_code_runtime.compile)(record.files, file, spec, { ...compileOptions, origin: `${origin}#${file}` });
|
|
933
|
+
if (!page.ok) void host.reportError({ ...page.error, where: `${origin}#${file}` });
|
|
934
|
+
pageCache.set(file, page.ok ? page.Component : null);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return pageCache.get(file) ?? null;
|
|
938
|
+
};
|
|
939
|
+
const renderApplet = (child) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NestedApplet, { channel: child, scope, options });
|
|
940
|
+
function MountedApplet() {
|
|
941
|
+
const renderers = { pageComponent, renderApplet };
|
|
942
|
+
if (options.renderKind) renderers.renderKind = options.renderKind;
|
|
943
|
+
if (options.renderDataPage) renderers.renderDataPage = options.renderDataPage;
|
|
944
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AppletFrame, { host, ...renderers, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Entry, {}) });
|
|
945
|
+
}
|
|
946
|
+
MountedApplet.displayName = `Applet(${record.slug})`;
|
|
947
|
+
return { ok: true, Component: MountedApplet, unresolved: compiled.unresolved };
|
|
948
|
+
}
|
|
949
|
+
async function mountAppletAsync(record, host, scope, options = {}) {
|
|
950
|
+
provideHooks();
|
|
951
|
+
await (0, import_code_runtime.prepareScope)(appletScope(record, scope));
|
|
952
|
+
return mountApplet(record, host, scope, options);
|
|
953
|
+
}
|
|
954
|
+
function NestedApplet({ channel, scope, options }) {
|
|
955
|
+
const [mounted, setMounted] = React7.useState(null);
|
|
956
|
+
React7.useEffect(() => {
|
|
957
|
+
let cancelled = false;
|
|
958
|
+
channel.record().then(
|
|
959
|
+
(record) => {
|
|
960
|
+
if (!cancelled) setMounted(mountApplet(record, channel, scope, options));
|
|
961
|
+
},
|
|
962
|
+
(err) => {
|
|
963
|
+
if (!cancelled) setMounted({ ok: false, error: toAppletError(err) });
|
|
964
|
+
}
|
|
965
|
+
);
|
|
966
|
+
return () => {
|
|
967
|
+
cancelled = true;
|
|
968
|
+
};
|
|
969
|
+
}, [channel, scope, options]);
|
|
970
|
+
if (!mounted) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-applet-loading": channel.hostId, "aria-busy": "true" });
|
|
971
|
+
if (!mounted.ok) {
|
|
972
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-applet-stand-in": "applet-error", className: "rounded-md border border-dashed border-border p-2 text-sm text-muted-foreground", children: mounted.error.message });
|
|
973
|
+
}
|
|
974
|
+
const Component = mounted.Component;
|
|
975
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Component, {});
|
|
976
|
+
}
|
|
977
|
+
//# sourceMappingURL=frame.cjs.map
|