@mostra/react 0.0.0-stage → 0.2.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/LICENSE +202 -0
- package/README.md +16 -2
- package/dist/form.d.ts +20 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +259 -0
- package/dist/form.js.map +1 -0
- package/dist/hooks-Dh_l8KqJ.js +2737 -0
- package/dist/hooks-Dh_l8KqJ.js.map +1 -0
- package/dist/index.d.ts +412 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +199 -0
- package/dist/index.js.map +1 -0
- package/dist/mount-Bpg0M5ln.js +516 -0
- package/dist/mount-Bpg0M5ln.js.map +1 -0
- package/dist/router.d.ts +28 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +52 -0
- package/dist/router.js.map +1 -0
- package/package.json +117 -3
- package/src/form.ts +326 -0
- package/src/hooks.ts +530 -0
- package/src/index.ts +75 -0
- package/src/mount.ts +78 -0
- package/src/router.ts +84 -0
- package/src/runtime/boot.ts +306 -0
- package/src/runtime/bridge-limits.ts +30 -0
- package/src/runtime/errors.ts +53 -0
- package/src/runtime/journal.ts +219 -0
- package/src/runtime/server-client.ts +745 -0
- package/src/runtime/store.ts +1641 -0
- package/src/runtime/transport.ts +342 -0
- package/src/runtime/verify-probe.ts +67 -0
- package/src/server-hooks.ts +291 -0
- package/src/server-reference.ts +54 -0
- package/src/shell-bridge.ts +212 -0
- package/src/storage.ts +165 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { ArtifactManifest, ArtifactManifest as ArtifactManifest$1, Bridge, DeliveryTarget, FieldScope, RunProgress, RunStatus, SubmissionSummary, SubmissionSummary as SubmissionSummary$1, SubmitBlocker, SubmitBlocker as SubmitBlocker$1, UiInitializeResult, Viewer } from "@mostra/protocol";
|
|
2
|
+
import { ComponentType, ReactNode } from "react";
|
|
3
|
+
//#region src/runtime/store.d.ts
|
|
4
|
+
/** The submission state `useSubmit` reports (INTERFACES §2.3). */
|
|
5
|
+
type SubmitState = "open" | "submitting" | "submitted" | "delivered" | "pending-delivery" | "undeliverable" | "closed";
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/hooks.d.ts
|
|
8
|
+
/** Where the artifact runs (D111): Mostra's gateway, a static export, an MCP App, or `verify`. */
|
|
9
|
+
type ArtifactMode = "live" | "static" | "mcp-app" | "verify";
|
|
10
|
+
/**
|
|
11
|
+
* A Standard Schema validator (`~standard`), such as a Zod 4 schema: what `validate` takes besides
|
|
12
|
+
* a function.
|
|
13
|
+
*/
|
|
14
|
+
interface StandardSchema<T> {
|
|
15
|
+
/** The Standard Schema interface (version 1). */
|
|
16
|
+
readonly "~standard": {
|
|
17
|
+
/** Checks a value: issues when it is invalid. */
|
|
18
|
+
validate(value: unknown): {
|
|
19
|
+
readonly issues?: readonly {
|
|
20
|
+
readonly message: string;
|
|
21
|
+
}[];
|
|
22
|
+
} | Promise<unknown>;
|
|
23
|
+
/** The type the schema accepts. */
|
|
24
|
+
readonly types?: {
|
|
25
|
+
readonly input: T;
|
|
26
|
+
readonly output: T;
|
|
27
|
+
} | undefined;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Options of {@link useField} (D52). */
|
|
31
|
+
interface FieldOptions<T> {
|
|
32
|
+
/** The label people see in results and the shell's list of missing answers. */
|
|
33
|
+
readonly label?: string;
|
|
34
|
+
/** A submit needs a value: the shell and `useSubmit` list it until it has one. */
|
|
35
|
+
readonly required?: boolean;
|
|
36
|
+
/** Kept in the submission, left out of events and logs. */
|
|
37
|
+
readonly sensitive?: boolean;
|
|
38
|
+
/** A Zod (or any Standard Schema) schema, or a function returning a message for a bad value. */
|
|
39
|
+
readonly validate?: StandardSchema<T> | ((value: T) => string | undefined);
|
|
40
|
+
/** `viewer` (default): the viewer's draft. `artifact`: shared state every viewer sees (D63). */
|
|
41
|
+
readonly scope?: FieldScope;
|
|
42
|
+
}
|
|
43
|
+
/** What a field is doing (INTERFACES §2.3). */
|
|
44
|
+
interface FieldMeta<T> {
|
|
45
|
+
/**
|
|
46
|
+
* `saved`; `saving`; `offline` (not saved yet: no connection, or the gateway refused it, with
|
|
47
|
+
* `error`); `conflict` (another writer changed it: see `conflict`); `readonly` (submitted under
|
|
48
|
+
* `once`, a view-only link, or a copy of the page without a Mostra server).
|
|
49
|
+
*/
|
|
50
|
+
readonly save: "saved" | "saving" | "offline" | "conflict" | "readonly";
|
|
51
|
+
/** The validation message for the current value, or why the gateway refused it. */
|
|
52
|
+
readonly error?: string;
|
|
53
|
+
/** Another writer changed the field since the viewer's edit: keep theirs or mine. */
|
|
54
|
+
readonly conflict?: {
|
|
55
|
+
readonly theirs: T;
|
|
56
|
+
keepMine(): void;
|
|
57
|
+
takeTheirs(): void;
|
|
58
|
+
};
|
|
59
|
+
/** Who last wrote the field (artifact scope, D63). */
|
|
60
|
+
readonly updatedBy?: {
|
|
61
|
+
readonly kind: "viewer" | "agent" | "host";
|
|
62
|
+
readonly name?: string;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A persisted value with a stable field ID (D52). It behaves like `useState`: the value the viewer
|
|
67
|
+
* sees, a setter, and what the field is doing. Edits save to Mostra as the viewer works, survive
|
|
68
|
+
* reloads, closed tabs and other devices, reach other tabs, and carry over to newer versions by
|
|
69
|
+
* ID. Dotted IDs (`reply.m1`) nest in results.
|
|
70
|
+
*
|
|
71
|
+
* ```tsx
|
|
72
|
+
* const [layout, setLayout, meta] = useField("layout", "a", { label: "Layout", required: true });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function useField<T>(id: string, initial: T, options?: FieldOptions<NoInfer<T>>): [T, (next: T | ((previous: T) => T)) => void, FieldMeta<T>];
|
|
76
|
+
/** What `useSubmit` returns (INTERFACES §2.3). */
|
|
77
|
+
interface SubmitHandle {
|
|
78
|
+
/**
|
|
79
|
+
* Submits the viewer's draft through the artifact's own code (D40). Rejects with
|
|
80
|
+
* `MOSTRA_SUBMIT_CHANNEL_DENIED` where the host takes the shell's trusted Submit only,
|
|
81
|
+
* `MOSTRA_SUBMIT_BLOCKED` while a field blocks, `MOSTRA_SUBMIT_ALREADY` under a closed `once`.
|
|
82
|
+
*/
|
|
83
|
+
submit(): Promise<SubmissionSummary$1>;
|
|
84
|
+
/** Whether `submit()` can work here: the host allows the artifact channel and the draft is open. */
|
|
85
|
+
readonly allowed: boolean;
|
|
86
|
+
/** What blocks a submit now. */
|
|
87
|
+
readonly blockers: readonly SubmitBlocker$1[];
|
|
88
|
+
/** Where the submission stands (IN-13). */
|
|
89
|
+
readonly state: SubmitState;
|
|
90
|
+
/** The viewer's submission, once made. */
|
|
91
|
+
readonly submission?: {
|
|
92
|
+
readonly id: string;
|
|
93
|
+
readonly at: string;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** The artifact channel's submit, the blockers and the submission's state (D40, IN-13). */
|
|
97
|
+
export declare function useSubmit(): SubmitHandle;
|
|
98
|
+
/**
|
|
99
|
+
* Relabels the shell's trusted Submit while mounted, and asks the shell to confirm with `confirm`
|
|
100
|
+
* first (D06, SH-02). The artifact asks; the shell renders the control in its own origin, where
|
|
101
|
+
* the artifact can't reach it.
|
|
102
|
+
*/
|
|
103
|
+
export declare function useSubmitControl(options: {
|
|
104
|
+
readonly label?: string;
|
|
105
|
+
readonly confirm?: string;
|
|
106
|
+
}): void;
|
|
107
|
+
/** A named signal for the host's attention (IN-09), such as "Ping the agent". */
|
|
108
|
+
export declare function useSignal(name: string): {
|
|
109
|
+
send(payload?: unknown): Promise<void>;
|
|
110
|
+
};
|
|
111
|
+
/** A review set's candidate, as the trusted Choose names it. */
|
|
112
|
+
interface ChoiceCandidate {
|
|
113
|
+
/** The candidate's ID: the choice's value. */
|
|
114
|
+
readonly id: string;
|
|
115
|
+
/** The name the trusted Choose shows ("Choose Dense"), and results label it with. */
|
|
116
|
+
readonly label?: string;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A review set's choice (D65, D113): the field `review.<reviewSetId>.choice`. With `candidates`,
|
|
120
|
+
* the shell renders a trusted Choose for the candidate the page shows (`current`); `choose()`
|
|
121
|
+
* chooses from inside the page where the host allows the artifact channel (D40), else rejects
|
|
122
|
+
* with `MOSTRA_SUBMIT_CHANNEL_DENIED`.
|
|
123
|
+
*/
|
|
124
|
+
export declare function useChoice(reviewSetId: string, options?: {
|
|
125
|
+
readonly candidates?: readonly ChoiceCandidate[];
|
|
126
|
+
readonly current?: string;
|
|
127
|
+
}): {
|
|
128
|
+
readonly choice?: string;
|
|
129
|
+
choose(candidateId: string): Promise<void>;
|
|
130
|
+
};
|
|
131
|
+
/** Who the page acts as (D104): the home's local user, a tailnet user, or a link's person. */
|
|
132
|
+
export declare function useViewer(): {
|
|
133
|
+
readonly id: string;
|
|
134
|
+
readonly name?: string;
|
|
135
|
+
readonly source: "local" | "tailnet" | "token" | "host";
|
|
136
|
+
};
|
|
137
|
+
/** The artifact, the version and the view the page shows, and where it runs (D104, D111). */
|
|
138
|
+
export declare function useArtifact(): {
|
|
139
|
+
readonly id: string;
|
|
140
|
+
readonly versionId: string;
|
|
141
|
+
readonly version: number;
|
|
142
|
+
readonly viewId: string;
|
|
143
|
+
readonly mode: ArtifactMode;
|
|
144
|
+
};
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/shell-bridge.d.ts
|
|
147
|
+
/** The shell connection of this page, once it is established. */
|
|
148
|
+
interface ShellConnection {
|
|
149
|
+
/** The shell's origin. */
|
|
150
|
+
readonly shellOrigin: string;
|
|
151
|
+
/** What the shell said about where the page is shown, and the view (`host.mostra`). */
|
|
152
|
+
readonly host: UiInitializeResult;
|
|
153
|
+
/** The bridge, for the client runtime's messages. */
|
|
154
|
+
readonly bridge: Bridge;
|
|
155
|
+
/**
|
|
156
|
+
* The frame session's facts: its CSRF token, the link's scopes (D97, D98) and, from gateways
|
|
157
|
+
* since WP-07, the viewer it acts as.
|
|
158
|
+
*/
|
|
159
|
+
readonly session: {
|
|
160
|
+
readonly csrf: string;
|
|
161
|
+
readonly scopes: readonly string[];
|
|
162
|
+
readonly viewer?: Viewer;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Connects this page to the shell that frames it, once; later calls return the same connection.
|
|
167
|
+
* Resolves `undefined` when the page isn't framed by a Mostra shell. The client runtime calls it;
|
|
168
|
+
* artifact code doesn't need to.
|
|
169
|
+
*/
|
|
170
|
+
export declare function connectShell(): Promise<ShellConnection | undefined>;
|
|
171
|
+
/**
|
|
172
|
+
* Tells the shell the page has rendered (`mostra/ready`), after the next frame paints. The
|
|
173
|
+
* client runtime calls it right after mounting the page.
|
|
174
|
+
*/
|
|
175
|
+
export declare function reportReady(): void;
|
|
176
|
+
/**
|
|
177
|
+
* Opens `url` for the viewer in a new tab. Inside Mostra's shell the frame can't navigate the
|
|
178
|
+
* viewer's tab (D98 item 7), so the shell opens it (`ui/open-link`, MCP Apps); on a page of its
|
|
179
|
+
* own, the browser does. Only `http:`, `https:`, `mailto:` and `tel:` URLs open. Resolves `true`
|
|
180
|
+
* when the URL was handed to the browser.
|
|
181
|
+
*/
|
|
182
|
+
export declare function openLink(url: string): Promise<boolean>;
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/mount.d.ts
|
|
185
|
+
/**
|
|
186
|
+
* Mounts a single-page artifact: the generated entry calls it with the entry's default export.
|
|
187
|
+
* Artifact code doesn't need to.
|
|
188
|
+
*/
|
|
189
|
+
export declare function mountArtifact(options: {
|
|
190
|
+
readonly page: ComponentType;
|
|
191
|
+
}): Promise<void>;
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/runtime/errors.d.ts
|
|
194
|
+
/**
|
|
195
|
+
* Errors the client runtime throws: each carries the stable diagnostic code an artifact can switch
|
|
196
|
+
* on (INTERFACES §13), and the diagnostic itself when the gateway sent one.
|
|
197
|
+
*/
|
|
198
|
+
/** A diagnostic as the gateway sends it; only the fields the runtime reads. */
|
|
199
|
+
interface DiagnosticLike {
|
|
200
|
+
/** The stable diagnostic code, such as `MOSTRA_FIELD_TOO_LARGE`. */
|
|
201
|
+
readonly code: string;
|
|
202
|
+
/** What is wrong. */
|
|
203
|
+
readonly message: string;
|
|
204
|
+
/** What to do about it. */
|
|
205
|
+
readonly hint?: string;
|
|
206
|
+
/** The code's documentation page. */
|
|
207
|
+
readonly docs?: string;
|
|
208
|
+
}
|
|
209
|
+
/** An error with a Mostra diagnostic code, such as `MOSTRA_SUBMIT_CHANNEL_DENIED`. */
|
|
210
|
+
export declare class MostraError extends Error {
|
|
211
|
+
/** The stable diagnostic code. */
|
|
212
|
+
readonly code: string;
|
|
213
|
+
/** The HTTP status the gateway answered with, when the error came from it. */
|
|
214
|
+
readonly status: number | undefined;
|
|
215
|
+
/** The diagnostic, when the gateway sent one. */
|
|
216
|
+
readonly diagnostic: DiagnosticLike | undefined;
|
|
217
|
+
/** Extra data the refusal carried, such as the existing submission of `MOSTRA_SUBMIT_ALREADY`. */
|
|
218
|
+
readonly body: Readonly<Record<string, unknown>> | undefined;
|
|
219
|
+
constructor(code: string, message: string, options?: {
|
|
220
|
+
readonly status?: number;
|
|
221
|
+
readonly diagnostic?: DiagnosticLike;
|
|
222
|
+
readonly body?: Readonly<Record<string, unknown>>;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/server-hooks.d.ts
|
|
227
|
+
/** What `useAction` shows of its run (INTERFACES §2.3). */
|
|
228
|
+
interface ActionHandle<A extends unknown[], R> {
|
|
229
|
+
/**
|
|
230
|
+
* Runs the action: a durable run (D24) whose result this resolves with, or whose failure it
|
|
231
|
+
* rejects with as a `MostraError`. While this hook's run is in flight, another call joins it; with
|
|
232
|
+
* a `key`, every call anywhere joins the one run of that key, so it happens at most once.
|
|
233
|
+
*/
|
|
234
|
+
run(...args: A): Promise<R>;
|
|
235
|
+
/**
|
|
236
|
+
* `idle` before any run, then the run's state. `waiting` is reserved for a run waiting for its
|
|
237
|
+
* owner's instance (a hosted artifact, WP-28).
|
|
238
|
+
*/
|
|
239
|
+
readonly status: "idle" | RunStatus;
|
|
240
|
+
/** Whether a run is starting or in flight. */
|
|
241
|
+
readonly isPending: boolean;
|
|
242
|
+
/** The result, once the run succeeded. */
|
|
243
|
+
readonly data?: R;
|
|
244
|
+
/** Why the run failed, was cancelled or couldn't start. */
|
|
245
|
+
readonly error?: {
|
|
246
|
+
readonly code: string;
|
|
247
|
+
readonly message: string;
|
|
248
|
+
};
|
|
249
|
+
/** The run's latest progress (`progress()` in the action). */
|
|
250
|
+
readonly progress?: RunProgress;
|
|
251
|
+
/** The run's log lines (`log()` and the console in the action), capped at 1 MB. */
|
|
252
|
+
readonly log: readonly string[];
|
|
253
|
+
/** For an action that returns an async iterable: the items it yielded so far. */
|
|
254
|
+
readonly partial: readonly (R extends AsyncIterable<infer T> ? T : unknown)[];
|
|
255
|
+
/** Cancels the run: the action's `abortSignal` fires (AC-04). */
|
|
256
|
+
cancel(): void;
|
|
257
|
+
/** The run's `run_` ID, once it has one. */
|
|
258
|
+
readonly runId?: string;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Runs a `"use server"` function as durable action runs (D24, D54): each call is recorded before
|
|
262
|
+
* it runs, streams progress, log lines and partial results, survives the tab closing, and is
|
|
263
|
+
* found again after a reload. Without a `key`, a call while this hook's run is in flight joins it,
|
|
264
|
+
* so a double click runs once; with a `key`, the run of that key happens at most once across
|
|
265
|
+
* clicks, tabs, reloads and devices, and a page that opens later shows it.
|
|
266
|
+
*
|
|
267
|
+
* ```tsx
|
|
268
|
+
* const send = useAction(sendReply, { key: `send.${message.id}` });
|
|
269
|
+
* <button disabled={send.isPending || send.status === "succeeded"} onClick={() => send.run(message.id, reply)}>
|
|
270
|
+
* {send.status === "succeeded" ? "Sent" : (send.progress?.message ?? "Send")}
|
|
271
|
+
* </button>
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
export declare function useAction<A extends unknown[], R>(fn: (...args: A) => Promise<R>, options?: {
|
|
275
|
+
readonly key?: string;
|
|
276
|
+
}): ActionHandle<A, Awaited<R>>;
|
|
277
|
+
/**
|
|
278
|
+
* The live sources of the artifact, by key, with their value types. A generated declaration
|
|
279
|
+
* (`mostra-env.d.ts`, written by `mostra new`) fills it from `live.ts`:
|
|
280
|
+
*
|
|
281
|
+
* ```ts
|
|
282
|
+
* declare module "@mostra/react" {
|
|
283
|
+
* interface LiveSources extends LiveValuesOf<typeof import("./live")> {}
|
|
284
|
+
* }
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
interface LiveSources {}
|
|
288
|
+
/** The value types of a `live.ts` module's sources, by export name (D56). */
|
|
289
|
+
type LiveValuesOf<M> = { [K in keyof M as M[K] extends {
|
|
290
|
+
readonly "~mostra.live": unknown;
|
|
291
|
+
} ? K : never]: M[K] extends {
|
|
292
|
+
readonly __value?: infer T;
|
|
293
|
+
} ? T : never; };
|
|
294
|
+
/** A live source's key: one of `LiveSources`, or any string before the declaration exists. */
|
|
295
|
+
type LiveKey = [keyof LiveSources] extends [never] ? string : keyof LiveSources & string;
|
|
296
|
+
/** A live source's value type. */
|
|
297
|
+
type LiveValue<K extends string> = K extends keyof LiveSources ? LiveSources[K] : unknown;
|
|
298
|
+
/** What `useLive` shows (INTERFACES §2.3). */
|
|
299
|
+
interface LiveResult<T> {
|
|
300
|
+
/** The latest value; in append mode, the values kept, oldest first. */
|
|
301
|
+
readonly data: T | undefined;
|
|
302
|
+
/** Whether no value has arrived yet. */
|
|
303
|
+
readonly isLoading: boolean;
|
|
304
|
+
/** The producer's last failure; the last good value stays in `data` (FAILURE_MODEL §6). */
|
|
305
|
+
readonly error?: Error;
|
|
306
|
+
/** Asks the producer to load again now. */
|
|
307
|
+
refresh(): void;
|
|
308
|
+
/** The value's revision, which only grows. */
|
|
309
|
+
readonly revision?: string;
|
|
310
|
+
/** When the value was produced. */
|
|
311
|
+
readonly at?: string;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Reads a live source of `live.ts`, or one an agent or host pushes to (D56): the latest value, or
|
|
315
|
+
* in append mode the recent values for a high-rate chart. The value is shared by every component
|
|
316
|
+
* that shows it and updates without a reload; nothing of it is a durable event (D63).
|
|
317
|
+
*
|
|
318
|
+
* ```tsx
|
|
319
|
+
* const { data, isLoading, error } = useLive("inbox");
|
|
320
|
+
* const { data: points } = useLive("cpu", { mode: "append", window: 600 });
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
export declare function useLive<K extends LiveKey>(key: K, options: {
|
|
324
|
+
readonly mode: "append";
|
|
325
|
+
readonly window?: number;
|
|
326
|
+
}): LiveResult<LiveValue<K>[]>;
|
|
327
|
+
/** Reads a live source's latest value (D56). See the first signature. */
|
|
328
|
+
export declare function useLive<K extends LiveKey>(key: K, options?: {
|
|
329
|
+
readonly mode?: "latest";
|
|
330
|
+
readonly window?: number;
|
|
331
|
+
}): LiveResult<LiveValue<K>>;
|
|
332
|
+
//#endregion
|
|
333
|
+
//#region src/server-reference.d.ts
|
|
334
|
+
/**
|
|
335
|
+
* The client side of `"use server"` functions (D19 seam 2, D54). The builder compiles every export
|
|
336
|
+
* of a `"use server"` module in client code into a server reference: a function that carries the
|
|
337
|
+
* server function's ID and hands the call to the page's runtime, as React's own server references
|
|
338
|
+
* hand theirs to the callback `setServerCallback` installs. The runtime calls Mostra (a plain
|
|
339
|
+
* call: no record, the result streamed back); where there is no Mostra server, such as a static
|
|
340
|
+
* export, the call rejects with `MOSTRA_TARGET_UNAVAILABLE`.
|
|
341
|
+
*/
|
|
342
|
+
/** A `"use server"` function as client code sees it. */
|
|
343
|
+
interface ServerReference<A extends unknown[] = unknown[], R = unknown> {
|
|
344
|
+
/** Calls the server function with `args`; resolves with its result. */
|
|
345
|
+
(...args: A): Promise<R>;
|
|
346
|
+
/** The server function's ID: its module path, `#` and its export name (`actions.ts#sendReply`). */
|
|
347
|
+
readonly mostraServerFunctionId: string;
|
|
348
|
+
}
|
|
349
|
+
/** How the page's runtime calls a server function. */
|
|
350
|
+
type CallServer = (id: string, args: unknown[]) => Promise<unknown>;
|
|
351
|
+
/**
|
|
352
|
+
* Installs how server references call the server: the runtime does it when the page starts (a
|
|
353
|
+
* live page calls Mostra; a static export keeps the default, which rejects). Returns the function
|
|
354
|
+
* that restores the previous one.
|
|
355
|
+
*/
|
|
356
|
+
export declare function setServerCallback(callback: CallServer): () => void;
|
|
357
|
+
/**
|
|
358
|
+
* Creates the client stub of one server function. The builder emits these calls; artifact code
|
|
359
|
+
* imports the server module as usual and never calls this itself.
|
|
360
|
+
*/
|
|
361
|
+
export declare function createServerReference(id: string): ServerReference;
|
|
362
|
+
//#endregion
|
|
363
|
+
//#region src/storage.d.ts
|
|
364
|
+
/** What `get` and `set` resolve with. */
|
|
365
|
+
interface StorageEntry {
|
|
366
|
+
/** The key. */
|
|
367
|
+
readonly key: string;
|
|
368
|
+
/** Its value: any JSON value. */
|
|
369
|
+
readonly value: unknown;
|
|
370
|
+
/** Whether it is shared by every viewer (artifact state) or the viewer's own. */
|
|
371
|
+
readonly shared: boolean;
|
|
372
|
+
}
|
|
373
|
+
/** The storage API of Claude artifacts, backed by Mostra fields (D53). */
|
|
374
|
+
interface ClaudeCompatibleStorage {
|
|
375
|
+
/** The value of `key`, or `null` when it has none. */
|
|
376
|
+
get(key: string, shared?: boolean): Promise<StorageEntry | null>;
|
|
377
|
+
/** Stores `value` under `key`; resolves once Mostra saved it. */
|
|
378
|
+
set(key: string, value: unknown, shared?: boolean): Promise<StorageEntry>;
|
|
379
|
+
/** Removes `key`; resolves once Mostra saved that. */
|
|
380
|
+
delete(key: string, shared?: boolean): Promise<{
|
|
381
|
+
readonly key: string;
|
|
382
|
+
readonly deleted: true;
|
|
383
|
+
readonly shared: boolean;
|
|
384
|
+
}>;
|
|
385
|
+
/** The keys stored, those starting with `prefix` when it is given. */
|
|
386
|
+
list(prefix?: string, shared?: boolean): Promise<{
|
|
387
|
+
readonly keys: string[];
|
|
388
|
+
readonly prefix?: string;
|
|
389
|
+
readonly shared: boolean;
|
|
390
|
+
}>;
|
|
391
|
+
}
|
|
392
|
+
declare global {
|
|
393
|
+
interface Window {
|
|
394
|
+
/** Claude-compatible persistent storage, backed by Mostra fields (D53). */
|
|
395
|
+
storage: ClaudeCompatibleStorage;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
//#endregion
|
|
399
|
+
//#region src/index.d.ts
|
|
400
|
+
/**
|
|
401
|
+
* Declares an artifact's manifest (INTERFACES §2.2, D51). Mostra reads the object statically
|
|
402
|
+
* without running the page, so write every value as a literal: strings, numbers, booleans, arrays
|
|
403
|
+
* and objects. At run time it returns its argument unchanged.
|
|
404
|
+
*
|
|
405
|
+
* ```ts
|
|
406
|
+
* export const artifact = defineArtifact({ title: "Q3 plan", family: "proposal" });
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
export declare function defineArtifact<const M extends ArtifactManifest$1>(manifest: M): M;
|
|
410
|
+
//#endregion
|
|
411
|
+
export type { ActionHandle, ArtifactManifest, ArtifactMode, CallServer, ChoiceCandidate, ClaudeCompatibleStorage, DiagnosticLike, FieldMeta, FieldOptions, LiveKey, LiveResult, LiveSources, LiveValue, LiveValuesOf, ServerReference, ShellConnection, StandardSchema, StorageEntry, SubmissionSummary, SubmitBlocker, SubmitHandle, SubmitState };
|
|
412
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":["SubmissionSummary","SubmitBlocker","ArtifactManifest"],"sources":["../src/runtime/store.ts","../src/hooks.ts","../src/shell-bridge.ts","../src/mount.ts","../src/runtime/errors.ts","../src/server-hooks.ts","../src/server-reference.ts","../src/storage.ts","../src/index.ts"],"mappings":";;;;KAmGY;;;;KCjEA;;;;;UAsDK,eAAe;;WAErB;;IAEP,SACE;eACY;iBAA6B;;QAAwB;;aAE1D;eAAmB,OAAO;eAAY,QAAQ;;;;;UAK1C,aAAa;;WAEnB;;WAEA;;WAEA;;WAEA,WAAW,eAAe,OAAO,OAAO;;WAExC,QAAQ;;;UAIF,UAAU;;;;;;WAMhB;;WAEA;;WAEA;aAAsB,QAAQ;IAAG;IAAkB;;;WAEnD;aAAuB;aAA4C;;;;;;;;;;;;;wBAgD9D,SAAS,GACvB,YACA,SAAS,GAGT,UAAS,aAAa,QAAQ,OAC5B,IAAI,MAAM,MAAM,UAAU,MAAM,aAAa,UAAU;;UAoI1C;;;;;;EAMf,UAAU,QAAQA;;WAET;;WAEA,mBAAmBC;;WAEnB,OAAO;;WAEP;aAAwB;aAAqB;;;;wBAIxC,aAAa;;;;;;wBA0Db,iBAAiB;WACtB;WACA;;;wBAeK,UAAU;EAAiB,KAAK,oBAAoB;;;UAWnD;;WAEN;;WAEA;;;;;;;;wBASK,UACd,qBACA;WAAqB,sBAAsB;WAA4B;;WAC3D;EAAiB,OAAO,sBAAsB;;;wBA8D5C;WACL;WACA;WACA;;;wBAeK;WACL;WACA;WACA;WACA;WACA,MAAM;;;;;UC7dA;;WAEN;;WAEA,MAAM;;WAEN,QAAQ;;;;;WAKR;aACE;aACA;aACA,SAAS;;;;;;;;wBAkCN,gBAAgB,QAAQ;;;;;wBA0FxB;;;;;;;wBAaM,SAAS,cAAc;;;;;;;wBCvHvB,cAAc;WAAoB,MAAM;IAAkB;;;;;;;;UCnE/D;;WAEN;;WAEA;;WAEA;;WAEA;;;qBAIE,oBAAoB;;WAEtB;;WAEA;;WAEA,YAAY;;WAEZ,MAAM,SAAS;EAExB,YACE,cACA,iBACA;aACW;aACA,aAAa;aACb,OAAO,SAAS;;;;;;UCjBd,aAAa,qBAAqB;;;;;;EAMjD,OAAO,MAAM,IAAI,QAAQ;;;;;WAKhB,iBAAiB;;WAEjB;;WAEA,OAAO;;WAEP;aAAmB;aAAuB;;;WAE1C,WAAW;;WAEX;;WAEA,mBAAmB,UAAU,oBAAoB,KAAK;;EAE/D;;WAES;;;;;;;;;;;;;;;;wBA6BK,UAAU,qBAAqB,GAC7C,QAAQ,MAAM,MAAM,QAAQ,IAC5B;WAAqB;IACpB,aAAa,GAAG,QAAQ;;;;;;;;;;;UA6HV;;KAGL,aAAa,QACtB,WAAW,KAAK,EAAE;WAAsB;IAA4B,YAAY,EAAE;WACxE,gBAAgB;IAEvB;;KAKM,iBAAiB,8CAA8C;;KAG/D,UAAU,oBAAoB,gBAAgB,cAAc,YAAY;;UAGnE,WAAW;;WAEjB,MAAM;;WAEN;;WAEA,QAAQ;;EAEjB;;WAES;;WAEA;;;;;;;;;;;;wBAgBK,QAAQ,UAAU,SAChC,KAAK,GACL;WAAoB;WAAyB;IAC5C,WAAW,UAAU;;wBAER,QAAQ,UAAU,SAChC,KAAK,GACL;WAAqB;WAA0B;IAC9C,WAAW,UAAU;;;;;;;;;;;;UCnPP,gBAAgB,iCAAiC;;MAE5D,MAAM,IAAI,QAAQ;;WAEb;;;KAIC,cAAc,YAAY,oBAAoB;;;;;;wBAiB1C,kBAAkB,UAAU;;;;;wBAY5B,sBAAsB,aAAa;;;;UC/BlC;;WAEN;;WAEA;;WAEA;;;UAIM;;EAEf,IAAI,aAAa,mBAAmB,QAAQ;;EAE5C,IAAI,aAAa,gBAAgB,mBAAmB,QAAQ;;EAE5D,OACE,aACA,mBACC;aAAmB;aAAsB;aAAwB;;;EAEpE,KACE,iBACA,mBACC;aAAmB;aAAyB;aAA0B;;;QA0G5D;YACH;;IAER,SAAS;;;;;;;;;;;;;;wBC/EG,qBAAqB,UAAUC,oBAAkB,UAAU,IAAI"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { _ as serverFunctionId, a as useSignal, c as useSubmitControl, d as connectShell, i as useField, l as useViewer, m as reportReady, n as useArtifact, o as useStore, p as openLink, r as useChoice, s as useSubmit, x as MostraError } from "./hooks-Dh_l8KqJ.js";
|
|
3
|
+
import { a as createServerReference, o as setServerCallback, t as mountArtifact } from "./mount-Bpg0M5ln.js";
|
|
4
|
+
import { isFinalRunStatus } from "@mostra/protocol";
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
|
+
//#region src/server-hooks.ts
|
|
7
|
+
/**
|
|
8
|
+
* Server functions and live data in the page (INTERFACES §2.3, D54, D56): `useAction` turns calls
|
|
9
|
+
* of a `"use server"` function into durable action runs, and `useLive` reads a live source. Both
|
|
10
|
+
* share the page's runtime, its view and its event stream, so a run started in one tab or before a
|
|
11
|
+
* reload is followed the same way, and every component showing one source shares one value.
|
|
12
|
+
*/
|
|
13
|
+
const NO_LOG = [];
|
|
14
|
+
const IDLE = { starting: false };
|
|
15
|
+
const NO_STATES = /* @__PURE__ */ new Map();
|
|
16
|
+
/**
|
|
17
|
+
* Runs a `"use server"` function as durable action runs (D24, D54): each call is recorded before
|
|
18
|
+
* it runs, streams progress, log lines and partial results, survives the tab closing, and is
|
|
19
|
+
* found again after a reload. Without a `key`, a call while this hook's run is in flight joins it,
|
|
20
|
+
* so a double click runs once; with a `key`, the run of that key happens at most once across
|
|
21
|
+
* clicks, tabs, reloads and devices, and a page that opens later shows it.
|
|
22
|
+
*
|
|
23
|
+
* ```tsx
|
|
24
|
+
* const send = useAction(sendReply, { key: `send.${message.id}` });
|
|
25
|
+
* <button disabled={send.isPending || send.status === "succeeded"} onClick={() => send.run(message.id, reply)}>
|
|
26
|
+
* {send.status === "succeeded" ? "Sent" : (send.progress?.message ?? "Send")}
|
|
27
|
+
* </button>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function useAction(fn, options) {
|
|
31
|
+
const store = useStore();
|
|
32
|
+
const server = store.server;
|
|
33
|
+
const fnId = serverFunctionId(fn);
|
|
34
|
+
const key = options?.key;
|
|
35
|
+
const identity = `${fnId ?? ""}\u0000${key ?? ""}`;
|
|
36
|
+
const [states, setStates] = useState(NO_STATES);
|
|
37
|
+
const { runId, starting, startError } = states.get(identity) ?? IDLE;
|
|
38
|
+
const update = useCallback((of, patch) => {
|
|
39
|
+
setStates((previous) => {
|
|
40
|
+
const next = new Map(previous);
|
|
41
|
+
next.set(of, {
|
|
42
|
+
...previous.get(of) ?? IDLE,
|
|
43
|
+
...patch
|
|
44
|
+
});
|
|
45
|
+
return next;
|
|
46
|
+
});
|
|
47
|
+
}, []);
|
|
48
|
+
const inFlight = useRef(/* @__PURE__ */ new Map());
|
|
49
|
+
useSyncExternalStore(server.subscribe, server.version, server.version);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (key === void 0 || fnId === void 0 || store.mode !== "live") return;
|
|
52
|
+
let active = true;
|
|
53
|
+
server.attach({ keyed: [{
|
|
54
|
+
fnId,
|
|
55
|
+
key
|
|
56
|
+
}] }).then((runs) => {
|
|
57
|
+
const [found] = runs;
|
|
58
|
+
if (active && found) update(identity, { runId: found.runId });
|
|
59
|
+
}, () => {});
|
|
60
|
+
return () => {
|
|
61
|
+
active = false;
|
|
62
|
+
};
|
|
63
|
+
}, [
|
|
64
|
+
server,
|
|
65
|
+
store.mode,
|
|
66
|
+
fnId,
|
|
67
|
+
key,
|
|
68
|
+
identity,
|
|
69
|
+
update
|
|
70
|
+
]);
|
|
71
|
+
const view = runId === void 0 ? void 0 : server.run(runId);
|
|
72
|
+
const run = useCallback((...args) => {
|
|
73
|
+
if (fnId === void 0) return Promise.reject(/* @__PURE__ */ new TypeError("useAction needs a server function: import it from a module that starts with \"use server\"."));
|
|
74
|
+
const joined = inFlight.current.get(identity);
|
|
75
|
+
if (joined) return joined;
|
|
76
|
+
update(identity, {
|
|
77
|
+
starting: true,
|
|
78
|
+
startError: void 0
|
|
79
|
+
});
|
|
80
|
+
const started = (async () => {
|
|
81
|
+
let state;
|
|
82
|
+
try {
|
|
83
|
+
state = await server.startRun(fnId, args, key);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
update(identity, {
|
|
86
|
+
starting: false,
|
|
87
|
+
startError: error instanceof MostraError ? {
|
|
88
|
+
code: error.code,
|
|
89
|
+
message: error.message
|
|
90
|
+
} : {
|
|
91
|
+
code: "MOSTRA_ACTION_THREW",
|
|
92
|
+
message: error.message
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
update(identity, {
|
|
98
|
+
starting: false,
|
|
99
|
+
runId: state.runId
|
|
100
|
+
});
|
|
101
|
+
return server.settled(state.runId);
|
|
102
|
+
})();
|
|
103
|
+
inFlight.current.set(identity, started);
|
|
104
|
+
const settle = () => {
|
|
105
|
+
if (inFlight.current.get(identity) === started) inFlight.current.delete(identity);
|
|
106
|
+
};
|
|
107
|
+
started.then(settle, settle);
|
|
108
|
+
return started;
|
|
109
|
+
}, [
|
|
110
|
+
server,
|
|
111
|
+
fnId,
|
|
112
|
+
key,
|
|
113
|
+
identity,
|
|
114
|
+
update
|
|
115
|
+
]);
|
|
116
|
+
const cancel = useCallback(() => {
|
|
117
|
+
if (runId === void 0) return;
|
|
118
|
+
const current = server.run(runId);
|
|
119
|
+
if (current && !isFinalRunStatus(current.status)) server.cancel(runId).catch(() => {});
|
|
120
|
+
}, [server, runId]);
|
|
121
|
+
return useMemo(() => {
|
|
122
|
+
const status = view?.status ?? "idle";
|
|
123
|
+
const error = view?.error && (view.status === "failed" || view.status === "cancelled") ? {
|
|
124
|
+
code: view.error.code ?? "MOSTRA_ACTION_THREW",
|
|
125
|
+
message: view.error.message
|
|
126
|
+
} : view?.status === "cancelled" ? {
|
|
127
|
+
code: "MOSTRA_ACTION_CANCELLED",
|
|
128
|
+
message: "The run was cancelled."
|
|
129
|
+
} : startError;
|
|
130
|
+
return {
|
|
131
|
+
run,
|
|
132
|
+
cancel,
|
|
133
|
+
status,
|
|
134
|
+
isPending: starting || status === "queued" || status === "running" || status === "waiting",
|
|
135
|
+
...view?.status === "succeeded" && view.data !== void 0 ? { data: view.data } : {},
|
|
136
|
+
...error ? { error } : {},
|
|
137
|
+
...view?.progress ? { progress: view.progress } : {},
|
|
138
|
+
log: view?.log ?? NO_LOG,
|
|
139
|
+
partial: view?.partial ?? [],
|
|
140
|
+
...runId !== void 0 ? { runId } : {}
|
|
141
|
+
};
|
|
142
|
+
}, [
|
|
143
|
+
run,
|
|
144
|
+
cancel,
|
|
145
|
+
view,
|
|
146
|
+
starting,
|
|
147
|
+
startError,
|
|
148
|
+
runId
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
/** How long append mode keeps values without a `window`. */
|
|
152
|
+
const DEFAULT_WINDOW = 1024;
|
|
153
|
+
function useLive(key, options) {
|
|
154
|
+
const server = useStore().server;
|
|
155
|
+
const append = options?.mode === "append";
|
|
156
|
+
const window = append ? options?.window ?? DEFAULT_WINDOW : void 0;
|
|
157
|
+
useSyncExternalStore(server.subscribe, server.version, server.version);
|
|
158
|
+
useEffect(() => server.watchLive(key, window), [
|
|
159
|
+
server,
|
|
160
|
+
key,
|
|
161
|
+
window
|
|
162
|
+
]);
|
|
163
|
+
const view = server.liveView(key);
|
|
164
|
+
const refresh = useCallback(() => server.refreshLive(key), [server, key]);
|
|
165
|
+
return useMemo(() => {
|
|
166
|
+
const data = append ? view?.ring.length ? view.ring.slice(-(window ?? 0)) : void 0 : view?.data;
|
|
167
|
+
return {
|
|
168
|
+
data,
|
|
169
|
+
isLoading: data === void 0 && (view?.loading ?? true),
|
|
170
|
+
...view?.error ? { error: new MostraError(view.error.code ?? "MOSTRA_LIVE_SOURCE_FAILED", view.error.message) } : {},
|
|
171
|
+
refresh,
|
|
172
|
+
...view?.revision !== void 0 ? { revision: view.revision } : {},
|
|
173
|
+
...view?.at !== void 0 ? { at: view.at } : {}
|
|
174
|
+
};
|
|
175
|
+
}, [
|
|
176
|
+
view,
|
|
177
|
+
append,
|
|
178
|
+
window,
|
|
179
|
+
refresh
|
|
180
|
+
]);
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/index.ts
|
|
184
|
+
/**
|
|
185
|
+
* Declares an artifact's manifest (INTERFACES §2.2, D51). Mostra reads the object statically
|
|
186
|
+
* without running the page, so write every value as a literal: strings, numbers, booleans, arrays
|
|
187
|
+
* and objects. At run time it returns its argument unchanged.
|
|
188
|
+
*
|
|
189
|
+
* ```ts
|
|
190
|
+
* export const artifact = defineArtifact({ title: "Q3 plan", family: "proposal" });
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
function defineArtifact(manifest) {
|
|
194
|
+
return manifest;
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
export { MostraError, connectShell, createServerReference, defineArtifact, mountArtifact, openLink, reportReady, setServerCallback, useAction, useArtifact, useChoice, useField, useLive, useSignal, useSubmit, useSubmitControl, useViewer };
|
|
198
|
+
|
|
199
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/server-hooks.ts","../src/index.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Server functions and live data in the page (INTERFACES §2.3, D54, D56): `useAction` turns calls\n * of a `\"use server\"` function into durable action runs, and `useLive` reads a live source. Both\n * share the page's runtime, its view and its event stream, so a run started in one tab or before a\n * reload is followed the same way, and every component showing one source shares one value.\n */\nimport { isFinalRunStatus, type RunProgress, type RunStatus } from \"@mostra/protocol\";\nimport { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport { useStore } from \"./hooks.ts\";\nimport { MostraError } from \"./runtime/errors.ts\";\nimport { serverFunctionId } from \"./runtime/server-client.ts\";\n\n// ── useAction ─────────────────────────────────────────────────────────────────────────────\n\n/** What `useAction` shows of its run (INTERFACES §2.3). */\nexport interface ActionHandle<A extends unknown[], R> {\n /**\n * Runs the action: a durable run (D24) whose result this resolves with, or whose failure it\n * rejects with as a `MostraError`. While this hook's run is in flight, another call joins it; with\n * a `key`, every call anywhere joins the one run of that key, so it happens at most once.\n */\n run(...args: A): Promise<R>;\n /**\n * `idle` before any run, then the run's state. `waiting` is reserved for a run waiting for its\n * owner's instance (a hosted artifact, WP-28).\n */\n readonly status: \"idle\" | RunStatus;\n /** Whether a run is starting or in flight. */\n readonly isPending: boolean;\n /** The result, once the run succeeded. */\n readonly data?: R;\n /** Why the run failed, was cancelled or couldn't start. */\n readonly error?: { readonly code: string; readonly message: string };\n /** The run's latest progress (`progress()` in the action). */\n readonly progress?: RunProgress;\n /** The run's log lines (`log()` and the console in the action), capped at 1 MB. */\n readonly log: readonly string[];\n /** For an action that returns an async iterable: the items it yielded so far. */\n readonly partial: readonly (R extends AsyncIterable<infer T> ? T : unknown)[];\n /** Cancels the run: the action's `abortSignal` fires (AC-04). */\n cancel(): void;\n /** The run's `run_` ID, once it has one. */\n readonly runId?: string;\n}\n\nconst NO_LOG: readonly string[] = [];\n\n/** What `useAction` knows of one function and key. */\ninterface IdentityState {\n readonly runId?: string | undefined;\n readonly starting: boolean;\n readonly startError?: { code: string; message: string } | undefined;\n}\n\nconst IDLE: IdentityState = { starting: false };\nconst NO_STATES: ReadonlyMap<string, IdentityState> = new Map();\n\n/**\n * Runs a `\"use server\"` function as durable action runs (D24, D54): each call is recorded before\n * it runs, streams progress, log lines and partial results, survives the tab closing, and is\n * found again after a reload. Without a `key`, a call while this hook's run is in flight joins it,\n * so a double click runs once; with a `key`, the run of that key happens at most once across\n * clicks, tabs, reloads and devices, and a page that opens later shows it.\n *\n * ```tsx\n * const send = useAction(sendReply, { key: `send.${message.id}` });\n * <button disabled={send.isPending || send.status === \"succeeded\"} onClick={() => send.run(message.id, reply)}>\n * {send.status === \"succeeded\" ? \"Sent\" : (send.progress?.message ?? \"Send\")}\n * </button>\n * ```\n */\nexport function useAction<A extends unknown[], R>(\n fn: (...args: A) => Promise<R>,\n options?: { readonly key?: string },\n): ActionHandle<A, Awaited<R>> {\n const store = useStore();\n const server = store.server;\n const fnId = serverFunctionId(fn);\n const key = options?.key;\n // Everything the hook knows belongs to one function and key: a component that changes either\n // shows the new one's run, never the old one's, and a late answer for the old one lands under\n // the old identity, where the hook no longer looks.\n const identity = `${fnId ?? \"\"}\\u0000${key ?? \"\"}`;\n const [states, setStates] = useState<ReadonlyMap<string, IdentityState>>(NO_STATES);\n const current = states.get(identity) ?? IDLE;\n const { runId, starting, startError } = current;\n const update = useCallback((of: string, patch: Partial<IdentityState>): void => {\n setStates((previous) => {\n const next = new Map(previous);\n next.set(of, { ...(previous.get(of) ?? IDLE), ...patch });\n return next;\n });\n }, []);\n const inFlight = useRef(new Map<string, Promise<unknown>>());\n useSyncExternalStore(server.subscribe, server.version, server.version);\n\n // A keyed run started earlier, in another tab or before a reload, is shown again (AC-02).\n useEffect(() => {\n if (key === undefined || fnId === undefined || store.mode !== \"live\") return;\n let active = true;\n server.attach({ keyed: [{ fnId, key }] }).then(\n (runs) => {\n const [found] = runs;\n if (active && found) update(identity, { runId: found.runId });\n },\n () => {},\n );\n return () => {\n active = false;\n };\n }, [server, store.mode, fnId, key, identity, update]);\n\n const view = runId === undefined ? undefined : server.run(runId);\n\n const run = useCallback(\n (...args: A): Promise<Awaited<R>> => {\n if (fnId === undefined) {\n return Promise.reject(\n new TypeError(\n 'useAction needs a server function: import it from a module that starts with \"use server\".',\n ),\n );\n }\n // One run in flight per hook instance and function and key (D54): a second click joins it.\n const joined = inFlight.current.get(identity);\n if (joined) return joined as Promise<Awaited<R>>;\n update(identity, { starting: true, startError: undefined });\n const started = (async () => {\n let state: Awaited<ReturnType<typeof server.startRun>>;\n try {\n state = await server.startRun(fnId, args, key);\n } catch (error) {\n // The run never started (a refused link, a parked worker, the network): say why.\n update(identity, {\n starting: false,\n startError:\n error instanceof MostraError\n ? { code: error.code, message: error.message }\n : { code: \"MOSTRA_ACTION_THREW\", message: (error as Error).message },\n });\n throw error;\n }\n update(identity, { starting: false, runId: state.runId });\n return server.settled(state.runId);\n })();\n inFlight.current.set(identity, started);\n const settle = (): void => {\n if (inFlight.current.get(identity) === started) inFlight.current.delete(identity);\n };\n started.then(settle, settle);\n return started as Promise<Awaited<R>>;\n },\n [server, fnId, key, identity, update],\n );\n\n const cancel = useCallback(() => {\n if (runId === undefined) return;\n const current = server.run(runId);\n if (current && !isFinalRunStatus(current.status)) void server.cancel(runId).catch(() => {});\n }, [server, runId]);\n\n return useMemo(() => {\n const status: ActionHandle<A, Awaited<R>>[\"status\"] = view?.status ?? \"idle\";\n const error =\n view?.error && (view.status === \"failed\" || view.status === \"cancelled\")\n ? { code: view.error.code ?? \"MOSTRA_ACTION_THREW\", message: view.error.message }\n : view?.status === \"cancelled\"\n ? { code: \"MOSTRA_ACTION_CANCELLED\", message: \"The run was cancelled.\" }\n : startError;\n return {\n run,\n cancel,\n status,\n isPending: starting || status === \"queued\" || status === \"running\" || status === \"waiting\",\n ...(view?.status === \"succeeded\" && view.data !== undefined\n ? { data: view.data as Awaited<R> }\n : {}),\n ...(error ? { error } : {}),\n ...(view?.progress ? { progress: view.progress } : {}),\n log: view?.log ?? NO_LOG,\n partial: (view?.partial ?? []) as ActionHandle<A, Awaited<R>>[\"partial\"],\n ...(runId !== undefined ? { runId } : {}),\n };\n }, [run, cancel, view, starting, startError, runId]);\n}\n\n// ── useLive ───────────────────────────────────────────────────────────────────────────────\n\n/**\n * The live sources of the artifact, by key, with their value types. A generated declaration\n * (`mostra-env.d.ts`, written by `mostra new`) fills it from `live.ts`:\n *\n * ```ts\n * declare module \"@mostra/react\" {\n * interface LiveSources extends LiveValuesOf<typeof import(\"./live\")> {}\n * }\n * ```\n */\n// biome-ignore lint/suspicious/noEmptyInterface: augmented by each artifact's declaration.\nexport interface LiveSources {}\n\n/** The value types of a `live.ts` module's sources, by export name (D56). */\nexport type LiveValuesOf<M> = {\n [K in keyof M as M[K] extends { readonly \"~mostra.live\": unknown } ? K : never]: M[K] extends {\n readonly __value?: infer T;\n }\n ? T\n : never;\n};\n\n/** A live source's key: one of `LiveSources`, or any string before the declaration exists. */\nexport type LiveKey = [keyof LiveSources] extends [never] ? string : keyof LiveSources & string;\n\n/** A live source's value type. */\nexport type LiveValue<K extends string> = K extends keyof LiveSources ? LiveSources[K] : unknown;\n\n/** What `useLive` shows (INTERFACES §2.3). */\nexport interface LiveResult<T> {\n /** The latest value; in append mode, the values kept, oldest first. */\n readonly data: T | undefined;\n /** Whether no value has arrived yet. */\n readonly isLoading: boolean;\n /** The producer's last failure; the last good value stays in `data` (FAILURE_MODEL §6). */\n readonly error?: Error;\n /** Asks the producer to load again now. */\n refresh(): void;\n /** The value's revision, which only grows. */\n readonly revision?: string;\n /** When the value was produced. */\n readonly at?: string;\n}\n\n/** How long append mode keeps values without a `window`. */\nconst DEFAULT_WINDOW = 1024;\n\n/**\n * Reads a live source of `live.ts`, or one an agent or host pushes to (D56): the latest value, or\n * in append mode the recent values for a high-rate chart. The value is shared by every component\n * that shows it and updates without a reload; nothing of it is a durable event (D63).\n *\n * ```tsx\n * const { data, isLoading, error } = useLive(\"inbox\");\n * const { data: points } = useLive(\"cpu\", { mode: \"append\", window: 600 });\n * ```\n */\nexport function useLive<K extends LiveKey>(\n key: K,\n options: { readonly mode: \"append\"; readonly window?: number },\n): LiveResult<LiveValue<K>[]>;\n/** Reads a live source's latest value (D56). See the first signature. */\nexport function useLive<K extends LiveKey>(\n key: K,\n options?: { readonly mode?: \"latest\"; readonly window?: number },\n): LiveResult<LiveValue<K>>;\nexport function useLive<K extends LiveKey>(\n key: K,\n options?: { readonly mode?: \"latest\" | \"append\"; readonly window?: number },\n): LiveResult<unknown> {\n const store = useStore();\n const server = store.server;\n const append = options?.mode === \"append\";\n const window = append ? (options?.window ?? DEFAULT_WINDOW) : undefined;\n useSyncExternalStore(server.subscribe, server.version, server.version);\n useEffect(() => server.watchLive(key, window), [server, key, window]);\n const view = server.liveView(key);\n const refresh = useCallback(() => server.refreshLive(key), [server, key]);\n return useMemo(() => {\n const data = append\n ? view?.ring.length\n ? view.ring.slice(-(window ?? 0))\n : undefined\n : view?.data;\n return {\n data,\n isLoading: data === undefined && (view?.loading ?? true),\n ...(view?.error\n ? {\n error: new MostraError(\n view.error.code ?? \"MOSTRA_LIVE_SOURCE_FAILED\",\n view.error.message,\n ),\n }\n : {}),\n refresh,\n ...(view?.revision !== undefined ? { revision: view.revision } : {}),\n ...(view?.at !== undefined ? { at: view.at } : {}),\n };\n }, [view, append, window, refresh]);\n}\n","\"use client\";\n\n/**\n * `@mostra/react`: the client runtime artifact authors import (INTERFACES §2.3). Fields that behave\n * like `useState` with a stable ID and persist as the viewer works, the artifact channel's\n * submit, the shell's trusted Submit label, signals, review-set choices, the viewer and the\n * artifact, a Claude-compatible `window.storage`, and the manifest helper. `@mostra/react/form`\n * adapts react-hook-form; `@mostra/react/router` mounts `pages/` artifacts.\n *\n * ```tsx\n * import { defineArtifact, useField, useSubmitControl } from \"@mostra/react\";\n *\n * export const artifact = defineArtifact({ title: \"Sidebar options\", family: \"review\" });\n *\n * export default function Page() {\n * const [layout, setLayout] = useField(\"layout\", \"left\", { label: \"Layout\", required: true });\n * useSubmitControl({ label: \"Send my choice\" });\n * return <select value={layout} onChange={(e) => setLayout(e.target.value)}>…</select>;\n * }\n * ```\n *\n * @packageDocumentation\n */\nimport type { ArtifactManifest } from \"@mostra/protocol\";\n\nexport type { ArtifactManifest, SubmissionSummary, SubmitBlocker } from \"@mostra/protocol\";\nexport {\n type ArtifactMode,\n type ChoiceCandidate,\n type FieldMeta,\n type FieldOptions,\n type StandardSchema,\n type SubmitHandle,\n useArtifact,\n useChoice,\n useField,\n useSignal,\n useSubmit,\n useSubmitControl,\n useViewer,\n} from \"./hooks.ts\";\nexport { mountArtifact } from \"./mount.ts\";\nexport { type DiagnosticLike, MostraError } from \"./runtime/errors.ts\";\nexport type { SubmitState } from \"./runtime/store.ts\";\nexport {\n type ActionHandle,\n type LiveKey,\n type LiveResult,\n type LiveSources,\n type LiveValue,\n type LiveValuesOf,\n useAction,\n useLive,\n} from \"./server-hooks.ts\";\nexport {\n type CallServer,\n createServerReference,\n type ServerReference,\n setServerCallback,\n} from \"./server-reference.ts\";\nexport { connectShell, openLink, reportReady, type ShellConnection } from \"./shell-bridge.ts\";\nexport type { ClaudeCompatibleStorage, StorageEntry } from \"./storage.ts\";\n\n/**\n * Declares an artifact's manifest (INTERFACES §2.2, D51). Mostra reads the object statically\n * without running the page, so write every value as a literal: strings, numbers, booleans, arrays\n * and objects. At run time it returns its argument unchanged.\n *\n * ```ts\n * export const artifact = defineArtifact({ title: \"Q3 plan\", family: \"proposal\" });\n * ```\n */\nexport function defineArtifact<const M extends ArtifactManifest>(manifest: M): M {\n return manifest;\n}\n"],"mappings":"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"}
|