@sanity/workbench 0.1.0-alpha.12 → 0.1.0-alpha.13
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/dist/_chunks-es/index.js +6 -1
- package/dist/_chunks-es/index.js.map +1 -1
- package/dist/_chunks-es/studio.js +798 -0
- package/dist/_chunks-es/studio.js.map +1 -0
- package/dist/core.d.ts +132 -34
- package/dist/core.js +3 -729
- package/dist/core.js.map +1 -1
- package/dist/system.d.ts +456 -0
- package/dist/system.js +436 -0
- package/dist/system.js.map +1 -0
- package/package.json +14 -3
- package/src/_exports/system.ts +1 -0
- package/src/core/log/index.ts +33 -0
- package/src/core/user-applications/core-app.ts +19 -6
- package/src/core/user-applications/studios/schemas.ts +12 -1
- package/src/core/user-applications/studios/studio.ts +92 -77
- package/src/core/user-applications/studios/workspace.ts +5 -0
- package/src/core/user-applications/user-application.ts +17 -1
- package/src/system/auth.machine.ts +223 -0
- package/src/system/index.ts +6 -0
- package/src/system/inspect.ts +40 -0
- package/src/system/root.machine.ts +165 -0
- package/src/system/telemetry.machine.ts +179 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createSanityInstance, type SanityInstance } from "@sanity/sdk";
|
|
2
|
+
import { raise, sendTo, setup, type ActorOptions } from "xstate";
|
|
3
|
+
|
|
4
|
+
import { authLogic } from "./auth.machine";
|
|
5
|
+
import { inspect } from "./inspect";
|
|
6
|
+
import {
|
|
7
|
+
telemetryLogic,
|
|
8
|
+
type WorkbenchUserProperties,
|
|
9
|
+
} from "./telemetry.machine";
|
|
10
|
+
|
|
11
|
+
type OSInput = WorkbenchUserProperties;
|
|
12
|
+
type OSContext = {
|
|
13
|
+
instance: SanityInstance;
|
|
14
|
+
userProperties: WorkbenchUserProperties;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The base inputs for the OS machine.
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export interface OSBaseInput extends Pick<OSContext, "instance"> {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The sanity OS machine, responsible for managing the global state of the OS.
|
|
25
|
+
* @public
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { os, createOSOptions } from "@sanity/workbench/system";
|
|
29
|
+
* import { useActor } from "@xstate/react";
|
|
30
|
+
*
|
|
31
|
+
* const [state, send] = useActor(os, createOSOptions());
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export const os = setup({
|
|
35
|
+
types: {
|
|
36
|
+
input: {} as OSInput,
|
|
37
|
+
context: {} as OSContext,
|
|
38
|
+
events: {} as
|
|
39
|
+
| { type: "boot.auth.ready" }
|
|
40
|
+
| { type: "boot.auth.failed" }
|
|
41
|
+
| { type: "boot.telemetry.ready" },
|
|
42
|
+
// https://github.com/statelyai/xstate/issues/5515
|
|
43
|
+
children: {} as {
|
|
44
|
+
auth: "auth";
|
|
45
|
+
telemetry: "telemetry";
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
actors: {
|
|
49
|
+
auth: authLogic,
|
|
50
|
+
telemetry: telemetryLogic,
|
|
51
|
+
},
|
|
52
|
+
guards: {
|
|
53
|
+
hasTag: (_, params: { hasTag: boolean }) => params.hasTag,
|
|
54
|
+
},
|
|
55
|
+
actions: {
|
|
56
|
+
raiseAuthReady: raise({ type: "boot.auth.ready" }),
|
|
57
|
+
raiseAuthFailed: raise({ type: "boot.auth.failed" }),
|
|
58
|
+
raiseTelemetryReady: raise({
|
|
59
|
+
type: "boot.telemetry.ready",
|
|
60
|
+
}),
|
|
61
|
+
startTelemetry: sendTo("telemetry", {
|
|
62
|
+
type: "telemetry.start",
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
65
|
+
}).createMachine({
|
|
66
|
+
id: "os",
|
|
67
|
+
context: ({ input }) => ({
|
|
68
|
+
instance: createSanityInstance(),
|
|
69
|
+
userProperties: {
|
|
70
|
+
version: input.version,
|
|
71
|
+
organizationId: input.organizationId,
|
|
72
|
+
environment: input.environment,
|
|
73
|
+
userAgent: input.userAgent,
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
initial: "booting",
|
|
77
|
+
invoke: [
|
|
78
|
+
{
|
|
79
|
+
id: "auth",
|
|
80
|
+
systemId: "auth",
|
|
81
|
+
src: "auth",
|
|
82
|
+
input: ({ context }) => ({ instance: context.instance }),
|
|
83
|
+
onSnapshot: [
|
|
84
|
+
{
|
|
85
|
+
guard: {
|
|
86
|
+
type: "hasTag",
|
|
87
|
+
params: ({ event }) => ({
|
|
88
|
+
hasTag: event.snapshot.hasTag("authenticated"),
|
|
89
|
+
}),
|
|
90
|
+
},
|
|
91
|
+
actions: [{ type: "raiseAuthReady" }],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
guard: {
|
|
95
|
+
type: "hasTag",
|
|
96
|
+
params: ({ event }) => ({
|
|
97
|
+
hasTag: event.snapshot.hasTag("error"),
|
|
98
|
+
}),
|
|
99
|
+
},
|
|
100
|
+
actions: [{ type: "raiseAuthFailed" }],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "telemetry",
|
|
106
|
+
systemId: "telemetry",
|
|
107
|
+
src: "telemetry",
|
|
108
|
+
input: ({ context }) => ({
|
|
109
|
+
instance: context.instance,
|
|
110
|
+
...context.userProperties,
|
|
111
|
+
}),
|
|
112
|
+
onSnapshot: [
|
|
113
|
+
{
|
|
114
|
+
guard: {
|
|
115
|
+
type: "hasTag",
|
|
116
|
+
params: ({ event }) => ({
|
|
117
|
+
hasTag: event.snapshot.hasTag("telemetry-resolved"),
|
|
118
|
+
}),
|
|
119
|
+
},
|
|
120
|
+
actions: [{ type: "raiseTelemetryReady" }],
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
states: {
|
|
126
|
+
booting: {
|
|
127
|
+
initial: "auth",
|
|
128
|
+
states: {
|
|
129
|
+
auth: {
|
|
130
|
+
on: {
|
|
131
|
+
"boot.auth.ready": {
|
|
132
|
+
target: "telemetry",
|
|
133
|
+
actions: [{ type: "startTelemetry" }],
|
|
134
|
+
},
|
|
135
|
+
"boot.auth.failed": { target: "error" },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
telemetry: {
|
|
139
|
+
on: {
|
|
140
|
+
"boot.telemetry.ready": { target: "done" },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
error: {},
|
|
144
|
+
done: { type: "final" },
|
|
145
|
+
},
|
|
146
|
+
onDone: { target: "running" },
|
|
147
|
+
},
|
|
148
|
+
running: {
|
|
149
|
+
type: "parallel",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Creates a set of default options for the OS machine, including passing
|
|
156
|
+
* the sanity configuration for the internal instance.
|
|
157
|
+
* @public
|
|
158
|
+
*/
|
|
159
|
+
export function createOSOptions(input: OSInput) {
|
|
160
|
+
return {
|
|
161
|
+
id: "os",
|
|
162
|
+
input,
|
|
163
|
+
inspect,
|
|
164
|
+
} satisfies ActorOptions<typeof os>;
|
|
165
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { getClient, type SanityInstance } from "@sanity/sdk";
|
|
2
|
+
import {
|
|
3
|
+
type ConsentStatus,
|
|
4
|
+
createBatchedStore,
|
|
5
|
+
createSessionId,
|
|
6
|
+
type TelemetryEvent,
|
|
7
|
+
type TelemetryStore,
|
|
8
|
+
} from "@sanity/telemetry";
|
|
9
|
+
import { assign, fromPromise, setup } from "xstate";
|
|
10
|
+
|
|
11
|
+
import type { OSBaseInput } from "./root.machine";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export type WorkbenchUserProperties = {
|
|
17
|
+
version: string;
|
|
18
|
+
organizationId: string;
|
|
19
|
+
environment: string;
|
|
20
|
+
userAgent: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export interface TelemetryInput extends OSBaseInput, WorkbenchUserProperties {}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* TODO: this shouldn't be unique to the telemetry machine,
|
|
30
|
+
* remove this and set it globally with a single client actor.
|
|
31
|
+
*/
|
|
32
|
+
const TELEMETRY_API_VERSION = "2024-11-12";
|
|
33
|
+
/**
|
|
34
|
+
* 30 seconds, in milliseconds, is the default flush interval
|
|
35
|
+
* for the telemetry store.
|
|
36
|
+
*/
|
|
37
|
+
const FLUSH_INTERVAL_MS = 30_000;
|
|
38
|
+
|
|
39
|
+
type ConsentResult = { status: ConsentStatus };
|
|
40
|
+
|
|
41
|
+
const checkConsentLogic = fromPromise<
|
|
42
|
+
ConsentResult,
|
|
43
|
+
{ instance: SanityInstance }
|
|
44
|
+
>(async ({ input, signal }) => {
|
|
45
|
+
try {
|
|
46
|
+
const client = getClient(input.instance, {
|
|
47
|
+
apiVersion: TELEMETRY_API_VERSION,
|
|
48
|
+
});
|
|
49
|
+
return await client.request<ConsentResult>({
|
|
50
|
+
uri: "/intake/telemetry-status",
|
|
51
|
+
tag: "telemetry-consent",
|
|
52
|
+
signal,
|
|
53
|
+
});
|
|
54
|
+
} catch {
|
|
55
|
+
return { status: "undetermined" } satisfies ConsentResult;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
type TelemetryContext = {
|
|
60
|
+
instance: SanityInstance;
|
|
61
|
+
store: TelemetryStore<WorkbenchUserProperties> | null;
|
|
62
|
+
userProperties: WorkbenchUserProperties;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type TelemetryMachineEvent =
|
|
66
|
+
| { type: "telemetry.start" }
|
|
67
|
+
| { type: "telemetry.stop" };
|
|
68
|
+
|
|
69
|
+
export const telemetryLogic = setup({
|
|
70
|
+
types: {
|
|
71
|
+
input: {} as TelemetryInput,
|
|
72
|
+
context: {} as TelemetryContext,
|
|
73
|
+
events: {} as TelemetryMachineEvent,
|
|
74
|
+
},
|
|
75
|
+
actors: {
|
|
76
|
+
checkConsent: checkConsentLogic,
|
|
77
|
+
},
|
|
78
|
+
guards: {
|
|
79
|
+
isConsentGranted: (_, params: { status: ConsentStatus }) =>
|
|
80
|
+
params.status === "granted",
|
|
81
|
+
},
|
|
82
|
+
actions: {
|
|
83
|
+
createStore: assign({
|
|
84
|
+
store: ({ context }) => {
|
|
85
|
+
const sessionId = createSessionId();
|
|
86
|
+
const client = getClient(context.instance, {
|
|
87
|
+
apiVersion: TELEMETRY_API_VERSION,
|
|
88
|
+
});
|
|
89
|
+
const store = createBatchedStore<WorkbenchUserProperties>(sessionId, {
|
|
90
|
+
flushInterval: FLUSH_INTERVAL_MS,
|
|
91
|
+
resolveConsent: () =>
|
|
92
|
+
client.request<{ status: ConsentStatus }>({
|
|
93
|
+
uri: "/intake/telemetry-status",
|
|
94
|
+
tag: "telemetry-consent",
|
|
95
|
+
}),
|
|
96
|
+
sendEvents: (batch: TelemetryEvent[]) =>
|
|
97
|
+
client.request({
|
|
98
|
+
uri: "/intake/batch",
|
|
99
|
+
method: "POST",
|
|
100
|
+
body: { batch },
|
|
101
|
+
tag: "telemetry.batch",
|
|
102
|
+
}),
|
|
103
|
+
sendBeacon: (batch: TelemetryEvent[]) => {
|
|
104
|
+
if (typeof navigator === "undefined") {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return navigator.sendBeacon(
|
|
108
|
+
client.getUrl("/intake/batch"),
|
|
109
|
+
JSON.stringify({ batch }),
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
store.logger.updateUserProperties(context.userProperties);
|
|
114
|
+
return store;
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
teardownStore: ({ context }) => {
|
|
118
|
+
context.store?.end();
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
}).createMachine({
|
|
122
|
+
id: "telemetry",
|
|
123
|
+
initial: "idle",
|
|
124
|
+
context: ({ input }) => ({
|
|
125
|
+
instance: input.instance,
|
|
126
|
+
store: null,
|
|
127
|
+
userProperties: {
|
|
128
|
+
version: input.version,
|
|
129
|
+
organizationId: input.organizationId,
|
|
130
|
+
environment: input.environment,
|
|
131
|
+
userAgent: input.userAgent,
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
states: {
|
|
135
|
+
idle: {
|
|
136
|
+
on: {
|
|
137
|
+
"telemetry.start": {
|
|
138
|
+
target: "checkingConsent",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
checkingConsent: {
|
|
143
|
+
invoke: {
|
|
144
|
+
src: "checkConsent",
|
|
145
|
+
input: ({ context }) => ({
|
|
146
|
+
instance: context.instance,
|
|
147
|
+
}),
|
|
148
|
+
onDone: [
|
|
149
|
+
{
|
|
150
|
+
guard: {
|
|
151
|
+
type: "isConsentGranted",
|
|
152
|
+
params: ({ event }) => ({
|
|
153
|
+
status: event.output.status,
|
|
154
|
+
}),
|
|
155
|
+
},
|
|
156
|
+
actions: [{ type: "createStore" }],
|
|
157
|
+
target: "active",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
target: "denied",
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
active: {
|
|
166
|
+
tags: ["telemetry-resolved"],
|
|
167
|
+
exit: [{ type: "teardownStore" }],
|
|
168
|
+
on: {
|
|
169
|
+
"telemetry.stop": { target: "stopped" },
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
stopped: {
|
|
173
|
+
type: "final",
|
|
174
|
+
},
|
|
175
|
+
denied: {
|
|
176
|
+
tags: ["telemetry-resolved"],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
});
|