@blastlauncher/runtime 1.0.5 → 3.0.0-alpha.1

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.
Files changed (110) hide show
  1. package/README.md +4 -10
  2. package/dist/api/action-panel.d.ts +134 -0
  3. package/dist/api/action-panel.js +36 -0
  4. package/dist/api/action.d.ts +386 -0
  5. package/dist/api/action.js +260 -0
  6. package/dist/api/ai-browser.d.ts +423 -0
  7. package/dist/api/ai-browser.js +113 -0
  8. package/dist/api/ai-decide.d.ts +41 -0
  9. package/dist/api/ai-decide.js +62 -0
  10. package/dist/api/applications.d.ts +13 -0
  11. package/dist/api/applications.js +48 -0
  12. package/dist/api/cache.d.ts +27 -0
  13. package/dist/api/cache.js +126 -0
  14. package/dist/api/capability.d.ts +14 -0
  15. package/dist/api/capability.js +30 -0
  16. package/dist/api/clipboard.d.ts +57 -0
  17. package/dist/api/clipboard.js +83 -0
  18. package/dist/api/detail.d.ts +33 -0
  19. package/dist/api/detail.js +11 -0
  20. package/dist/api/diagnostics.d.ts +16 -0
  21. package/dist/api/diagnostics.js +26 -0
  22. package/dist/api/dropdown.d.ts +17 -0
  23. package/dist/api/dropdown.js +48 -0
  24. package/dist/api/enums.generated.d.ts +750 -0
  25. package/dist/api/enums.generated.js +719 -0
  26. package/dist/api/environment.d.ts +44 -0
  27. package/dist/api/environment.js +61 -0
  28. package/dist/api/feedback.d.ts +94 -0
  29. package/dist/api/feedback.js +228 -0
  30. package/dist/api/form.d.ts +162 -0
  31. package/dist/api/form.js +345 -0
  32. package/dist/api/grid.d.ts +293 -0
  33. package/dist/api/grid.js +36 -0
  34. package/dist/api/implemented.d.ts +53 -0
  35. package/dist/api/implemented.js +43 -0
  36. package/dist/api/index.d.ts +612 -0
  37. package/dist/api/index.js +82 -0
  38. package/dist/api/keyboard.d.ts +182 -0
  39. package/dist/api/keyboard.js +107 -0
  40. package/dist/api/launch.d.ts +40 -0
  41. package/dist/api/launch.js +27 -0
  42. package/dist/api/legacy.d.ts +9 -0
  43. package/dist/api/legacy.js +25 -0
  44. package/dist/api/list.d.ts +291 -0
  45. package/dist/api/list.js +28 -0
  46. package/dist/api/menu-bar.d.ts +54 -0
  47. package/dist/api/menu-bar.js +30 -0
  48. package/dist/api/metadata.d.ts +62 -0
  49. package/dist/api/metadata.js +33 -0
  50. package/dist/api/navigation.d.ts +36 -0
  51. package/dist/api/navigation.js +99 -0
  52. package/dist/api/oauth.d.ts +84 -0
  53. package/dist/api/oauth.js +125 -0
  54. package/dist/api/open.d.ts +18 -0
  55. package/dist/api/open.js +36 -0
  56. package/dist/api/preferences.d.ts +43 -0
  57. package/dist/api/preferences.js +41 -0
  58. package/dist/api/selection.d.ts +10 -0
  59. package/dist/api/selection.js +31 -0
  60. package/dist/api/slot.d.ts +7 -0
  61. package/dist/api/slot.js +11 -0
  62. package/dist/api/storage.d.ts +24 -0
  63. package/dist/api/storage.js +45 -0
  64. package/dist/api/types.d.ts +46 -0
  65. package/dist/api/types.js +5 -0
  66. package/dist/api/unsupported.d.ts +18 -0
  67. package/dist/api/unsupported.generated.d.ts +1255 -0
  68. package/dist/api/unsupported.generated.js +56 -0
  69. package/dist/api/unsupported.js +65 -0
  70. package/dist/api/window-management.d.ts +97 -0
  71. package/dist/api/window-management.js +164 -0
  72. package/dist/api/window.d.ts +16 -0
  73. package/dist/api/window.js +32 -0
  74. package/dist/main.d.ts +1 -0
  75. package/dist/main.js +339 -0
  76. package/dist/provider.d.ts +8 -0
  77. package/dist/provider.js +107 -0
  78. package/dist/reconciler/index.d.ts +9 -0
  79. package/dist/reconciler/index.js +186 -0
  80. package/dist/reconciler/serialize.d.ts +3 -0
  81. package/dist/reconciler/serialize.js +53 -0
  82. package/dist/reconciler/types.d.ts +8 -0
  83. package/dist/reconciler/types.js +1 -0
  84. package/package.json +38 -23
  85. package/.eslintrc.js +0 -6
  86. package/CHANGELOG.md +0 -19
  87. package/dist/App.d.ts +0 -5
  88. package/dist/App.js +0 -10
  89. package/dist/components/CommandList/index.d.ts +0 -1
  90. package/dist/components/CommandList/index.js +0 -20
  91. package/dist/components/CommandList/utils.d.ts +0 -1
  92. package/dist/components/CommandList/utils.js +0 -24
  93. package/dist/index.d.ts +0 -1
  94. package/dist/index.js +0 -11
  95. package/dist/utils/connectDevtools.d.ts +0 -1
  96. package/dist/utils/connectDevtools.js +0 -11
  97. package/dist/utils/run.d.ts +0 -4
  98. package/dist/utils/run.js +0 -12
  99. package/dist/utils/window.d.ts +0 -0
  100. package/dist/utils/window.js +0 -2
  101. package/src/@types/raycast.d.ts +0 -4
  102. package/src/@types/react-devtools-core.d.ts +0 -19
  103. package/src/App.tsx +0 -18
  104. package/src/components/CommandList/index.tsx +0 -27
  105. package/src/components/CommandList/utils.ts +0 -26
  106. package/src/index.ts +0 -13
  107. package/src/utils/connectDevtools.ts +0 -8
  108. package/src/utils/run.ts +0 -7
  109. package/src/utils/window.js +0 -1
  110. package/tsconfig.json +0 -10
package/dist/main.js ADDED
@@ -0,0 +1,339 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { createLineDecoder, encodeMessage, parseHostMessage, PROTOCOL_VERSION, } from "@blastlauncher/protocol";
10
+ import { createModelProvider } from "./provider.js";
11
+ import { Console } from "node:console";
12
+ import { registerHooks } from "node:module";
13
+ import { Writable } from "node:stream";
14
+ import { pathToFileURL } from "node:url";
15
+ import { format } from "node:util";
16
+ import { createElement, Suspense } from "react";
17
+ import { setCapabilityRequester } from "./api/capability.js";
18
+ import { setRuntimeLogSink } from "./api/diagnostics.js";
19
+ import { setEnvironment } from "./api/environment.js";
20
+ import { setFeedbackMount } from "./api/feedback.js";
21
+ import { decodeFormValues, flushFormPersistence, setFormDraftValues } from "./api/form.js";
22
+ import { LaunchType } from "./api/launch.js";
23
+ import { NavigationProvider } from "./api/navigation.js";
24
+ import { setPreferenceValues } from "./api/preferences.js";
25
+ import { setRenderMount } from "./api/legacy.js";
26
+ import { UnsupportedApiError } from "./api/unsupported.js";
27
+ import { createSceneRoot } from "./reconciler/index.js";
28
+ let provider;
29
+ let providerLoad;
30
+ let stopping = false;
31
+ let initialized = false;
32
+ let loaded = false;
33
+ let nextRequest = 1;
34
+ const pending = new Map();
35
+ // Protocol output owns the real stdout stream. Extension code that writes to
36
+ // `process.stdout` (or a library doing it for them) must not corrupt the NDJSON
37
+ // framing, so those writes become log messages instead. The protocol keeps its
38
+ // own reference to the original writer.
39
+ process.stdout.on("error", (error) => {
40
+ if (error.code === "EPIPE")
41
+ process.exit(0);
42
+ throw error;
43
+ });
44
+ const writeToStdout = process.stdout.write.bind(process.stdout);
45
+ process.stdout.write = ((chunk, encoding, callback) => {
46
+ const done = typeof encoding === "function" ? encoding : callback;
47
+ log("info", [String(chunk).replace(/\n$/, "")]);
48
+ if (typeof done === "function")
49
+ queueMicrotask(done);
50
+ return true;
51
+ });
52
+ function send(message) {
53
+ if (!stopping)
54
+ writeToStdout(encodeMessage(message));
55
+ }
56
+ function errorMessage(error) {
57
+ return error instanceof Error ? error.message : String(error);
58
+ }
59
+ function finish(code) {
60
+ if (stopping)
61
+ return;
62
+ stopping = true;
63
+ process.stdin.pause();
64
+ setCapabilityRequester(undefined);
65
+ pending.clear();
66
+ // Explicit exit also stops extension timers; wait for protocol output to drain.
67
+ process.stdout.end(() => process.exit(code));
68
+ }
69
+ function fatal(error, code = "RUNTIME_ERROR") {
70
+ send({
71
+ type: "error",
72
+ code: error instanceof UnsupportedApiError ? error.code : code,
73
+ message: errorMessage(error),
74
+ fatal: true,
75
+ });
76
+ finish(1);
77
+ }
78
+ function log(level, args) {
79
+ send({ type: "log", level, message: format(...args) });
80
+ }
81
+ function logStream(level) {
82
+ return new Writable({
83
+ write(chunk, _encoding, callback) {
84
+ log(level, [String(chunk).replace(/\n$/, "")]);
85
+ callback();
86
+ },
87
+ });
88
+ }
89
+ // Console's remaining methods (trace, table, time, assert, …) also stay off stdout.
90
+ Object.assign(console, new Console({ stdout: logStream("info"), stderr: logStream("error") }));
91
+ console.timeStamp = () => { };
92
+ for (const [method, level] of [
93
+ ["log", "info"],
94
+ ["info", "info"],
95
+ ["debug", "debug"],
96
+ ["warn", "warn"],
97
+ ["error", "error"],
98
+ ]) {
99
+ console[method] = (...args) => log(level, args);
100
+ }
101
+ process.on("uncaughtException", (error) => fatal(error));
102
+ process.on("unhandledRejection", (error) => fatal(error));
103
+ process.stdout.on("error", () => process.exit(1));
104
+ process.stdin.on("error", fatal);
105
+ const modules = new Map([
106
+ ["@raycast/api", import.meta.resolve("@blastlauncher/runtime/api")],
107
+ ...["react", "react/jsx-runtime", "react/jsx-dev-runtime"].map((name) => [
108
+ name,
109
+ import.meta.resolve(name),
110
+ ]),
111
+ ]);
112
+ registerHooks({
113
+ resolve(specifier, context, nextResolve) {
114
+ const url = modules.get(specifier);
115
+ return url ? { url, shortCircuit: true } : nextResolve(specifier, context);
116
+ },
117
+ });
118
+ const root = createSceneRoot({
119
+ publish: (scene) => send({ type: "scene", ...scene }),
120
+ onWarning: (message) => log("warn", [message]),
121
+ onError: fatal,
122
+ });
123
+ setCapabilityRequester((name, params, options = {}) => {
124
+ if (options.signal?.aborted)
125
+ return Promise.reject(new DOMException("The operation was aborted", "AbortError"));
126
+ const id = String(nextRequest++);
127
+ return new Promise((resolve, reject) => {
128
+ const abort = () => {
129
+ reject(new DOMException("The operation was aborted", "AbortError"));
130
+ send({ type: "capability.cancel", id });
131
+ };
132
+ pending.set(id, {
133
+ resolve,
134
+ reject,
135
+ progress: (value) => {
136
+ if (!options.signal?.aborted)
137
+ options.onProgress?.(value);
138
+ },
139
+ cleanup: () => options.signal?.removeEventListener("abort", abort),
140
+ });
141
+ options.signal?.addEventListener("abort", abort, { once: true });
142
+ send({ type: "capability.request", id, name, params });
143
+ });
144
+ });
145
+ // `captureException` and `captureMemorySnapshot` report diagnostics through the
146
+ // protocol's log channel; the runtime owns no other reporter.
147
+ setRuntimeLogSink((level, message) => log(level, [message]));
148
+ function loadingFallback(mode) {
149
+ return mode === "menu-bar"
150
+ ? createElement("MenuBarExtra", { isLoading: true })
151
+ : createElement("List", { isLoading: true });
152
+ }
153
+ function withSuspense(element, mode) {
154
+ return createElement(Suspense, { fallback: loadingFallback(mode) }, element);
155
+ }
156
+ /**
157
+ * Launch props for this command: the manifest arguments the host resolved and,
158
+ * when another command started it, the `launchCommand` context. Blast only
159
+ * launches user initiated commands (background launch is out of scope).
160
+ */
161
+ function launchProps(command) {
162
+ return {
163
+ arguments: command.arguments ?? {},
164
+ launchType: LaunchType.UserInitiated,
165
+ ...(command.draftValues === undefined
166
+ ? {}
167
+ : { draftValues: decodeFormValues(command.draftValues) }),
168
+ ...(command.fallbackText === undefined ? {} : { fallbackText: command.fallbackText }),
169
+ ...(command.launchContext === undefined ? {} : { launchContext: command.launchContext }),
170
+ };
171
+ }
172
+ async function load(message) {
173
+ setEnvironment(message.environment, message.command);
174
+ setPreferenceValues(message.preferences);
175
+ const props = launchProps(message.command);
176
+ setFormDraftValues(props.draftValues);
177
+ // Legacy commands call `render(element)` while the module is being imported;
178
+ // that element becomes the command root instead of a default export.
179
+ let rendered = false;
180
+ setRenderMount((element) => {
181
+ if (rendered)
182
+ return;
183
+ rendered = true;
184
+ root.render(message.command.mode === "menu-bar"
185
+ ? withSuspense(element, message.command.mode)
186
+ : createElement(NavigationProvider, {
187
+ children: withSuspense(element, message.command.mode),
188
+ }));
189
+ loaded = true;
190
+ });
191
+ const command = await import(__rewriteRelativeImportExtension(pathToFileURL(message.command.entry).href));
192
+ setRenderMount(undefined);
193
+ if (stopping)
194
+ return;
195
+ if (rendered)
196
+ return;
197
+ if (message.command.mode === "model-provider") {
198
+ provider = createModelProvider(command, send);
199
+ return;
200
+ }
201
+ if (message.command.mode === "no-view") {
202
+ setFeedbackMount(() => {
203
+ if (!loaded) {
204
+ loaded = true;
205
+ root.render(createElement(NavigationProvider, { children: null }));
206
+ }
207
+ });
208
+ // A no-view command runs until it settles, with a scene only for feedback.
209
+ // A rejection is a fatal error through the caller's `.catch`.
210
+ await command.default(props);
211
+ if (stopping)
212
+ return;
213
+ finish(0);
214
+ return;
215
+ }
216
+ // Call inside React so ordinary view commands retain hooks and navigation.
217
+ // A thenable is a command body, never a suspending React component (0004).
218
+ let running = false;
219
+ function Command() {
220
+ if (running)
221
+ return null;
222
+ const result = command.default(props);
223
+ if (result !== null &&
224
+ (typeof result === "object" || typeof result === "function") &&
225
+ "then" in result &&
226
+ typeof result.then === "function") {
227
+ running = true;
228
+ void Promise.resolve(result).then(() => finish(0), fatal);
229
+ return null;
230
+ }
231
+ return result;
232
+ }
233
+ const wrap = message.command.mode === "view" &&
234
+ typeof command.default === "function" &&
235
+ !command.default.prototype?.isReactComponent;
236
+ const element = createElement(wrap ? Command : command.default, props);
237
+ const suspended = withSuspense(element, message.command.mode);
238
+ root.render(message.command.mode === "menu-bar"
239
+ ? suspended
240
+ : createElement(NavigationProvider, { children: suspended }));
241
+ loaded = true;
242
+ }
243
+ function shutdown() {
244
+ if (stopping)
245
+ return;
246
+ try {
247
+ root.unmount();
248
+ void flushFormPersistence().then(() => finish(0), fatal);
249
+ }
250
+ catch (error) {
251
+ fatal(error);
252
+ }
253
+ }
254
+ function handle(value) {
255
+ if (stopping)
256
+ return;
257
+ const parsed = parseHostMessage(value);
258
+ if (!parsed.ok)
259
+ return fatal(parsed.error, "PROTOCOL_ERROR");
260
+ const message = parsed.message;
261
+ if (!initialized) {
262
+ if (message.type !== "init" || message.protocol !== PROTOCOL_VERSION) {
263
+ return fatal(`Expected init with protocol ${PROTOCOL_VERSION}`, "PROTOCOL_ERROR");
264
+ }
265
+ initialized = true;
266
+ send({ type: "ready", protocol: PROTOCOL_VERSION });
267
+ providerLoad = load(message);
268
+ void providerLoad.catch(fatal);
269
+ return;
270
+ }
271
+ switch (message.type) {
272
+ case "provider.getModels":
273
+ case "provider.stream":
274
+ case "provider.abort":
275
+ void providerLoad
276
+ ?.then(() => {
277
+ if (!provider)
278
+ return fatal("Provider request in command session", "PROTOCOL_ERROR");
279
+ provider.handle(message);
280
+ })
281
+ .catch(fatal);
282
+ return;
283
+ case "init":
284
+ return fatal("Duplicate init", "PROTOCOL_ERROR");
285
+ case "shutdown":
286
+ return shutdown();
287
+ case "capability.progress": {
288
+ const request = pending.get(message.id);
289
+ if (!request)
290
+ return fatal(`Unknown capability id: ${message.id}`, "PROTOCOL_ERROR");
291
+ request.progress?.(message.value);
292
+ return;
293
+ }
294
+ case "capability.result": {
295
+ const request = pending.get(message.id);
296
+ if (!request)
297
+ return fatal(`Unknown capability id: ${message.id}`, "PROTOCOL_ERROR");
298
+ pending.delete(message.id);
299
+ request.cleanup();
300
+ if (message.ok)
301
+ request.resolve(message.value);
302
+ else
303
+ request.reject(Object.assign(new Error(message.error.message), { code: message.error.code }));
304
+ return;
305
+ }
306
+ case "event": {
307
+ if (!loaded)
308
+ return fatal("Event before command loaded", "PROTOCOL_ERROR");
309
+ const failed = (error) => {
310
+ if (error instanceof UnsupportedApiError)
311
+ fatal(error);
312
+ else
313
+ send({ type: "error", code: "EVENT_ERROR", message: errorMessage(error), fatal: false });
314
+ };
315
+ try {
316
+ // Do not await: a handler can be waiting for a capability.result on stdin.
317
+ void Promise.resolve(root.dispatch(message.node, message.handler, message.args)).catch(failed);
318
+ }
319
+ catch (error) {
320
+ failed(error);
321
+ }
322
+ try {
323
+ root.flush();
324
+ }
325
+ catch (error) {
326
+ fatal(error);
327
+ }
328
+ }
329
+ }
330
+ }
331
+ const decoder = createLineDecoder({
332
+ onLine: handle,
333
+ onError: (error) => fatal(error, "PROTOCOL_ERROR"),
334
+ });
335
+ process.stdin.on("data", (chunk) => decoder.push(chunk));
336
+ process.stdin.on("end", () => {
337
+ decoder.end();
338
+ shutdown();
339
+ });
@@ -0,0 +1,8 @@
1
+ import type { AI } from "./api/index.js";
2
+ import { type ProviderRequest, type ProviderResponse } from "@blastlauncher/protocol";
3
+ export declare function createModelProvider(exports: {
4
+ getModels?: AI.GetModels;
5
+ streamCompletion?: AI.StreamCompletion;
6
+ }, send: (message: ProviderResponse) => void): {
7
+ handle(message: ProviderRequest): void;
8
+ };
@@ -0,0 +1,107 @@
1
+ import { decodeModelRequest, parseRuntimeMessage, } from "@blastlauncher/protocol";
2
+ export function createModelProvider(exports, send) {
3
+ if (typeof exports.getModels !== "function" || typeof exports.streamCompletion !== "function")
4
+ throw new Error("Model provider requires named getModels and streamCompletion exports");
5
+ // Providers may attach private metadata; only the public model crosses the wire.
6
+ const modelFields = [
7
+ "id",
8
+ "title",
9
+ "isLocal",
10
+ "icon",
11
+ "description",
12
+ "capabilities",
13
+ "contextWindow",
14
+ "sizeInBytes",
15
+ ];
16
+ const active = new Map();
17
+ function emit(message) {
18
+ const parsed = parseRuntimeMessage(JSON.parse(JSON.stringify(message)));
19
+ if (!parsed.ok)
20
+ throw new Error(parsed.error);
21
+ send(parsed.message);
22
+ }
23
+ async function run(message) {
24
+ const state = { cancelled: false };
25
+ if (active.has(message.id))
26
+ throw new Error("Duplicate provider request id");
27
+ active.set(message.id, state);
28
+ try {
29
+ if (message.type === "provider.getModels") {
30
+ const models = await exports.getModels();
31
+ if (!state.cancelled)
32
+ emit({
33
+ type: "provider.models",
34
+ id: message.id,
35
+ models: models.map((model) => Object.fromEntries(modelFields
36
+ .filter((key) => Object.hasOwn(model, key))
37
+ .map((key) => [key, model[key]]))),
38
+ });
39
+ }
40
+ else {
41
+ const result = await exports.streamCompletion(message.model, decodeModelRequest(message.request));
42
+ const stream = "fullStream" in result
43
+ ? result.fullStream
44
+ : "textStream" in result
45
+ ? result.textStream
46
+ : result;
47
+ const iterator = stream[Symbol.asyncIterator]();
48
+ state.iterator = iterator;
49
+ if (state.cancelled) {
50
+ void Promise.resolve()
51
+ .then(() => iterator.return?.())
52
+ .catch(() => undefined);
53
+ return;
54
+ }
55
+ while (!state.cancelled) {
56
+ const next = await iterator.next();
57
+ if (state.cancelled || next.done)
58
+ break;
59
+ emit({
60
+ type: "provider.part",
61
+ id: message.id,
62
+ part: typeof next.value === "string"
63
+ ? { type: "text-delta", textDelta: next.value }
64
+ : next.value,
65
+ });
66
+ }
67
+ if (!state.cancelled)
68
+ emit({ type: "provider.end", id: message.id });
69
+ }
70
+ }
71
+ catch (error) {
72
+ if (!state.cancelled)
73
+ send({
74
+ type: "provider.error",
75
+ id: message.id,
76
+ error: {
77
+ code: "PROVIDER_ERROR",
78
+ message: error instanceof Error ? error.message : String(error),
79
+ },
80
+ });
81
+ }
82
+ finally {
83
+ active.delete(message.id);
84
+ }
85
+ }
86
+ return {
87
+ handle(message) {
88
+ if (message.type === "provider.abort") {
89
+ const state = active.get(message.id);
90
+ if (state) {
91
+ state.cancelled = true;
92
+ // A blocked producer need not cooperate; stop publishing immediately.
93
+ void Promise.resolve()
94
+ .then(() => state.iterator?.return?.())
95
+ .catch(() => undefined);
96
+ active.delete(message.id);
97
+ }
98
+ return;
99
+ }
100
+ void run(message).catch((error) => send({
101
+ type: "provider.error",
102
+ id: message.id,
103
+ error: { code: "PROVIDER_ERROR", message: String(error) },
104
+ }));
105
+ },
106
+ };
107
+ }
@@ -0,0 +1,9 @@
1
+ import { type ReactNode } from "react";
2
+ import type { SceneRootOptions } from "./types.js";
3
+ export type { JsonValue, SceneNode, SceneRootOptions, SceneSnapshot } from "./types.js";
4
+ export declare function createSceneRoot({ publish, onWarning, onError }: SceneRootOptions): {
5
+ render: (element: ReactNode) => void;
6
+ unmount(): void;
7
+ dispatch(nodeId: string, handler: string, args: unknown[]): unknown;
8
+ flush: () => void;
9
+ };
@@ -0,0 +1,186 @@
1
+ import { createContext } from "react";
2
+ import Reconciler from "react-reconciler";
3
+ import { ConcurrentRoot, DiscreteEventPriority, NoEventPriority, } from "react-reconciler/constants.js";
4
+ import { serializeProps } from "./serialize.js";
5
+ function remove(parent, child) {
6
+ const index = parent.children.indexOf(child);
7
+ if (index !== -1)
8
+ parent.children.splice(index, 1);
9
+ }
10
+ function insert(parent, child, before) {
11
+ remove(parent, child);
12
+ const index = before ? parent.children.indexOf(before) : -1;
13
+ parent.children.splice(index === -1 ? parent.children.length : index, 0, child);
14
+ }
15
+ function instance(container, type, props) {
16
+ return { id: String(container.nextId++), type, props, children: [], hidden: false };
17
+ }
18
+ const noop = () => { };
19
+ let priority = NoEventPriority;
20
+ const config = {
21
+ rendererVersion: "0.0.0",
22
+ rendererPackageName: "@blastlauncher/runtime",
23
+ extraDevToolsConfig: null,
24
+ bindToConsole: (_method, args) => () => console.log(...args),
25
+ supportsMutation: true,
26
+ supportsPersistence: false,
27
+ supportsHydration: false,
28
+ isPrimaryRenderer: true,
29
+ getRootHostContext: () => ({}),
30
+ getChildHostContext: (context) => context,
31
+ getPublicInstance: (node) => node,
32
+ createInstance: (type, props, container) => instance(container, type, props),
33
+ createTextInstance: (text, container) => instance(container, "#text", { text }),
34
+ appendInitialChild: insert,
35
+ appendChild: insert,
36
+ appendChildToContainer: insert,
37
+ insertBefore: insert,
38
+ insertInContainerBefore: insert,
39
+ removeChild: remove,
40
+ removeChildFromContainer: remove,
41
+ finalizeInitialChildren: () => false,
42
+ shouldSetTextContent: () => false,
43
+ prepareForCommit: () => null,
44
+ resetAfterCommit: (container) => container.commit(),
45
+ commitUpdate: (node, _type, _old, props) => {
46
+ node.props = props;
47
+ },
48
+ commitTextUpdate: (node, _old, text) => {
49
+ node.props = { text };
50
+ },
51
+ clearContainer: (container) => {
52
+ container.children = [];
53
+ },
54
+ hideInstance: (node) => {
55
+ node.hidden = true;
56
+ },
57
+ hideTextInstance: (node) => {
58
+ node.hidden = true;
59
+ },
60
+ unhideInstance: (node) => {
61
+ node.hidden = false;
62
+ },
63
+ unhideTextInstance: (node) => {
64
+ node.hidden = false;
65
+ },
66
+ scheduleTimeout: setTimeout,
67
+ cancelTimeout: clearTimeout,
68
+ noTimeout: -1,
69
+ supportsMicrotasks: true,
70
+ scheduleMicrotask: queueMicrotask,
71
+ preparePortalMount: noop,
72
+ getInstanceFromNode: () => null,
73
+ beforeActiveInstanceBlur: noop,
74
+ afterActiveInstanceBlur: noop,
75
+ prepareScopeUpdate: noop,
76
+ getInstanceFromScope: () => null,
77
+ detachDeletedInstance: noop,
78
+ NotPendingTransition: null,
79
+ HostTransitionContext: createContext(null),
80
+ setCurrentUpdatePriority: (next) => {
81
+ priority = next;
82
+ },
83
+ getCurrentUpdatePriority: () => priority,
84
+ // All ordinary updates use the sync lane so flush() needs no timers.
85
+ resolveUpdatePriority: () => priority || DiscreteEventPriority,
86
+ resetFormInstance: noop,
87
+ requestPostPaintCallback: (callback) => callback(performance.now()),
88
+ shouldAttemptEagerTransition: () => false,
89
+ trackSchedulerEvent: noop,
90
+ resolveEventType: () => null,
91
+ resolveEventTimeStamp: () => performance.now(),
92
+ maySuspendCommit: () => false,
93
+ maySuspendCommitOnUpdate: () => false,
94
+ maySuspendCommitInSyncRender: () => false,
95
+ preloadInstance: () => true,
96
+ startSuspendingCommit: () => null,
97
+ suspendInstance: noop,
98
+ suspendOnActiveViewTransition: noop,
99
+ waitForCommitToBeReady: () => null,
100
+ getSuspendedCommitReason: () => null,
101
+ };
102
+ const renderer = Reconciler(config);
103
+ export function createSceneRoot({ publish, onWarning = noop, onError }) {
104
+ let revision = 0;
105
+ let handlers = new Map();
106
+ const errors = [];
107
+ // Errors raised while serializing or publishing a commit. Throwing inside
108
+ // React's commit phase corrupts the reconciler for every later root in the
109
+ // process, so they are held here and reported once React has finished.
110
+ const commitErrors = [];
111
+ function drainCommitErrors() {
112
+ for (const error of commitErrors.splice(0))
113
+ reportError(error);
114
+ }
115
+ const container = {
116
+ children: [],
117
+ nextId: 1,
118
+ commit() {
119
+ try {
120
+ commitSnapshot();
121
+ }
122
+ catch (error) {
123
+ commitErrors.push(error);
124
+ // Async updates are not followed by flush(); report them on a microtask.
125
+ queueMicrotask(drainCommitErrors);
126
+ }
127
+ },
128
+ };
129
+ function commitSnapshot() {
130
+ const nextHandlers = new Map();
131
+ function snapshot(node) {
132
+ const callbacks = new Map();
133
+ const props = serializeProps(node.type, node.props, callbacks, onWarning);
134
+ nextHandlers.set(node.id, callbacks);
135
+ return {
136
+ id: node.id,
137
+ type: node.type,
138
+ props,
139
+ // Covered navigation views remain mounted, but cannot receive client events.
140
+ children: node.type === "Navigation.Entry" && node.props.active === false
141
+ ? []
142
+ : node.children.filter((child) => !child.hidden).map(snapshot),
143
+ };
144
+ }
145
+ const children = container.children.filter((child) => !child.hidden).map(snapshot);
146
+ // A fragment with multiple top-level nodes needs a serializable parent.
147
+ const root = children.length > 1 ? { id: "0", type: "#root", props: {}, children } : (children[0] ?? null);
148
+ handlers = nextHandlers;
149
+ publish({ revision: ++revision, root });
150
+ }
151
+ function reportError(error) {
152
+ if (onError)
153
+ onError(error);
154
+ else
155
+ errors.push(error);
156
+ }
157
+ const root = renderer.createContainer(container, ConcurrentRoot, null, false, null, "", reportError, renderer.defaultOnCaughtError, reportError, noop, null);
158
+ function flush() {
159
+ renderer.flushSyncWork();
160
+ while (renderer.flushPassiveEffects())
161
+ renderer.flushSyncWork();
162
+ drainCommitErrors();
163
+ if (errors.length)
164
+ throw errors.shift();
165
+ }
166
+ function render(element) {
167
+ renderer.updateContainerSync(element, root, null, null);
168
+ flush();
169
+ }
170
+ return {
171
+ render,
172
+ unmount() {
173
+ render(null);
174
+ },
175
+ dispatch(nodeId, handler, args) {
176
+ const callbacks = handlers.get(nodeId);
177
+ if (!callbacks)
178
+ throw new Error(`Unknown scene node: ${nodeId}`);
179
+ const callback = callbacks.get(handler);
180
+ if (!callback)
181
+ throw new Error(`Unknown handler ${handler} on scene node ${nodeId}`);
182
+ return callback(...args);
183
+ },
184
+ flush,
185
+ };
186
+ }
@@ -0,0 +1,3 @@
1
+ import type { JsonValue } from "./types.js";
2
+ export type Handler = (...args: unknown[]) => unknown;
3
+ export declare function serializeProps(type: string, props: Record<string, unknown>, handlers: Map<string, Handler>, warn: (message: string) => void): Record<string, JsonValue>;