@sanity/workbench 0.1.0-alpha.19 → 0.1.0-alpha.20

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.
@@ -0,0 +1,120 @@
1
+ import { type FederationInstance } from "@sanity/federation/runtime";
2
+ import { type ActorRefFrom, enqueueActions, setup } from "xstate";
3
+
4
+ import { serviceLogic } from "./service.machine";
5
+
6
+ /** The shared federation instance, supplied by the root machine. */
7
+ type ServicesInput = {
8
+ instance: FederationInstance;
9
+ };
10
+
11
+ type ServicesContext = {
12
+ instance: FederationInstance;
13
+ children: Map<string, ActorRefFrom<typeof serviceLogic>>;
14
+ };
15
+
16
+ /**
17
+ * The full set of background services the workbench should currently be
18
+ * running, re-sent whenever the application list — or any app's declared
19
+ * interfaces — changes. The supervisor reconciles its children against it.
20
+ */
21
+ type ServicesEvent = {
22
+ type: "services.sync";
23
+ services: ReadonlyArray<{
24
+ appId: string;
25
+ serviceName: string;
26
+ entry: string;
27
+ }>;
28
+ };
29
+
30
+ /** Stable child key for a service: `${appId}:${serviceName}`. */
31
+ function serviceKey(appId: string, serviceName: string): string {
32
+ return `${appId}:${serviceName}`;
33
+ }
34
+
35
+ /**
36
+ * Supervisor machine for background service workers.
37
+ *
38
+ * Uses the shared {@link FederationInstance} (supplied by the root machine and
39
+ * shared with `remotes`, so an app's remote is registered once for both its
40
+ * views and its workers) and spawns one {@link serviceLogic} child per
41
+ * `(app, service)`. Invoked at the root of the OS machine for inspector
42
+ * visibility — boot does not gate on it.
43
+ *
44
+ * On every `services.sync` it reconciles its children against the desired set:
45
+ * a newly-declared worker is spawned, and a worker whose declaration was
46
+ * removed (e.g. deleted from `sanity.cli.ts` during dev — FR-024) is stopped,
47
+ * which disposes its `runWorker` callback and terminates the Web Worker. A
48
+ * service `src` edit still triggers a Vite full page reload (workers have no
49
+ * HMR boundary), which reboots the workbench and respawns with the new code.
50
+ *
51
+ * @internal
52
+ */
53
+ export const servicesLogic = setup({
54
+ types: {
55
+ input: {} as ServicesInput,
56
+ context: {} as ServicesContext,
57
+ events: {} as ServicesEvent,
58
+ },
59
+ actors: {
60
+ service: serviceLogic,
61
+ },
62
+ actions: {
63
+ /**
64
+ * Reconcile the running workers against the desired set: spawn the ones not
65
+ * yet running, stop (and forget) the ones no longer declared.
66
+ */
67
+ syncServices: enqueueActions(({ context, event, enqueue }) => {
68
+ const desired = new Map(
69
+ event.services.map((service) => [
70
+ serviceKey(service.appId, service.serviceName),
71
+ service,
72
+ ]),
73
+ );
74
+
75
+ // Stop + forget workers whose declaration was removed. Stopping the child
76
+ // disposes its `runWorker` callback, which terminates the Web Worker.
77
+ for (const [key, ref] of context.children) {
78
+ if (!desired.has(key)) enqueue.stopChild(ref);
79
+ }
80
+
81
+ enqueue.assign({
82
+ children: ({ context: ctx, spawn }) => {
83
+ const next = new Map<string, ActorRefFrom<typeof serviceLogic>>();
84
+ // Keep the children still declared.
85
+ for (const [key, ref] of ctx.children) {
86
+ if (desired.has(key)) next.set(key, ref);
87
+ }
88
+ // Spawn the newly-declared ones.
89
+ for (const [key, service] of desired) {
90
+ if (next.has(key)) continue;
91
+ next.set(
92
+ key,
93
+ spawn("service", {
94
+ id: key,
95
+ systemId: `services.${key}`,
96
+ input: {
97
+ key,
98
+ appId: service.appId,
99
+ serviceName: service.serviceName,
100
+ entry: service.entry,
101
+ instance: ctx.instance,
102
+ },
103
+ }),
104
+ );
105
+ }
106
+ return next;
107
+ },
108
+ });
109
+ }),
110
+ },
111
+ }).createMachine({
112
+ id: "services",
113
+ context: ({ input }) => ({
114
+ instance: input.instance,
115
+ children: new Map(),
116
+ }),
117
+ on: {
118
+ "services.sync": { actions: "syncServices" },
119
+ },
120
+ });