@sanity/workflow-studio 0.2.0 → 0.3.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/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # @sanity/workflow-studio
2
+
3
+ Reactive workflow adapter for **Sanity Studio**. `useWorkflowInstance` drives
4
+ an `@sanity/workflow-engine` session and returns
5
+ `{evaluation, ready, guards, tick, fireAction}`. Must render inside Studio
6
+ source context (`useDocumentStore` / `useSource`).
7
+
8
+ ## How observation routes (per watched ref)
9
+
10
+ Each ref in the watch-set is classified against the workspace's own
11
+ project + dataset and observed by the store that can actually address it:
12
+
13
+ | Ref | Observed via |
14
+ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15
+ | **Mounted workspace dataset** | `documentStore.pair.editState` — Studio's optimistic pair store, **including local uncommitted edits** (the one stream only Studio has). Note: `pair.editState` is `@internal` in Studio |
16
+ | **Engine state dataset** (instance doc + guards) | The workspace's `documentStore` when the engine dataset IS the workspace; otherwise the **App SDK store** (`sdkInstanceDocStore` / `sdkGuardStore` from `@sanity/workflow-sdk`) |
17
+ | **Any other dataset / project** | The **App SDK document store** (`sdkContentDocStore` from `@sanity/workflow-sdk`) — live, GDR-routed per-resource — through the bootstrapped or supplied SDK instance (see below) |
18
+ | **Non-dataset schemes** (`canvas:`, `media-library:`) | Routed like foreign resources; the SDK store rejects them loudly (it addresses dataset resources only) |
19
+
20
+ There is **no silent fallback**: a ref the configured stores can't address
21
+ throws, instead of being misread from the workspace dataset under the same
22
+ `_id`.
23
+
24
+ - **The engine's `workflowResource` is routinely a dedicated dataset** (e.g.
25
+ `workflows`), foreign to every content workspace. When it differs from the
26
+ workspace and no `sdk` is supplied, the hook **bootstraps an App SDK
27
+ instance automatically** from the Studio session's token; that instance
28
+ also observes foreign content refs. Cookie-authenticated studios have no
29
+ readable session token — the hook throws an actionable error; supply your
30
+ own instance: `useWorkflowInstance(engine, id, {sdk})`.
31
+ - When the engine dataset IS the workspace and a watch-set ref points at a
32
+ foreign resource, there is no bootstrap signal — pass `{sdk}` or the
33
+ observer throws the config-mismatch error for that ref.
34
+ - Guards (`observeGuards`) observe wherever the instance lives — only guards
35
+ in the instance's own datasource can deny the instance write, so the stream
36
+ is engine-dataset-scoped by contract.
37
+ - **Auth caveat for foreign refs:** the SDK instance's auth must cover the
38
+ foreign resource; a foreign _project_ needs a session/token valid there.
39
+ - `makeStudioObserver(documentStore, {mounted, engineResource, sdk?})` is
40
+ exported for custom wiring, matching `makeSdkObserver`.
41
+
42
+ Remaining gaps (ancestor-instance guards, full cascade write-set pre-flight,
43
+ live cross-dataset end-to-end verification) are known follow-up work.
44
+
45
+ ## Notes
46
+
47
+ - See `@sanity/workflow-react`'s README for the guard-verdict semantics
48
+ (what `mutation-guard-denied` does and doesn't cover).
package/dist/index.cjs CHANGED
@@ -1,28 +1,71 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: !0 });
3
- var workflowReact = require("@sanity/workflow-react"), react = require("react"), sanity = require("sanity"), workflowEngine = require("@sanity/workflow-engine");
4
- function makeStudioObserver(documentStore) {
5
- const docStore = (id, type, release) => {
6
- const observable = documentStore.pair.editState(id, type, release);
7
- let latest;
8
- return {
9
- subscribe(onChange) {
10
- const subscription = observable.subscribe((editState) => {
11
- latest = resolveEditState(editState), onChange();
12
- });
13
- return () => subscription.unsubscribe();
14
- },
15
- getSnapshot: () => latest
16
- };
3
+ var workflowEngine = require("@sanity/workflow-engine"), workflowReact = require("@sanity/workflow-react"), workflowSdk = require("@sanity/workflow-sdk"), sdk = require("@sanity/sdk"), react = require("react"), sanity = require("sanity");
4
+ function observableStore(observable, resolve, initial) {
5
+ let latest = initial;
6
+ return {
7
+ subscribe(onChange) {
8
+ const subscription = observable.subscribe((value) => {
9
+ latest = resolve(value), onChange();
10
+ });
11
+ return () => subscription.unsubscribe();
12
+ },
13
+ getSnapshot: () => latest
14
+ };
15
+ }
16
+ function makeStudioObserver(documentStore, options) {
17
+ const { mounted, engineResource, sdk: sdk2 } = options, engineIsMounted = workflowReact.sameDataset(engineResource, mounted), editStateStore = (id, type, release) => observableStore(
18
+ documentStore.pair.editState(id, type, release),
19
+ resolveEditState,
20
+ void 0
21
+ ), requireSdk = (what) => {
22
+ if (sdk2 !== void 0) return sdk2;
23
+ throw new Error(
24
+ `@sanity/workflow-studio: ${what} \u2014 Studio's document store is bound to ${mounted.projectId}.${mounted.dataset} and cannot observe it. Pass an App SDK instance (\`useWorkflowInstance(engine, id, {sdk})\`) to observe foreign resources.`
25
+ );
17
26
  };
18
27
  return {
19
- observeInstance: (instanceId) => docStore(instanceId, "workflow.instance", void 0),
28
+ observeInstance: (instanceId) => engineIsMounted ? editStateStore(instanceId, workflowEngine.WORKFLOW_INSTANCE_TYPE, void 0) : workflowSdk.sdkInstanceDocStore(
29
+ requireSdk(
30
+ `instance "${instanceId}" lives in the engine dataset ${engineResource.projectId}.${engineResource.dataset}`
31
+ ),
32
+ instanceId,
33
+ engineResource
34
+ ),
20
35
  observeDocs: (documents, perspective) => workflowReact.combineDocStores(
21
- documents.map((ref) => ({
36
+ documents.map((ref) => workflowReact.classifyRef(ref, mounted) === "mounted-dataset" ? {
22
37
  key: ref.globalDocumentId,
23
- store: docStore(ref.documentId, ref.type, workflowEngine.contentReleaseName({ ref, perspective }))
24
- }))
25
- )
38
+ store: editStateStore(
39
+ ref.documentId,
40
+ ref.type,
41
+ workflowEngine.contentReleaseName({ ref, perspective })
42
+ )
43
+ } : {
44
+ key: ref.globalDocumentId,
45
+ store: workflowSdk.sdkContentDocStore(
46
+ requireSdk(`"${ref.globalDocumentId}" lives outside this workspace`),
47
+ ref,
48
+ perspective
49
+ )
50
+ })
51
+ ),
52
+ observeGuards: (instanceId) => {
53
+ if (engineIsMounted) {
54
+ const { query, params } = workflowEngine.instanceGuardQuery(instanceId);
55
+ return observableStore(
56
+ documentStore.listenQuery(query, params, {}),
57
+ (docs) => docs,
58
+ workflowReact.NO_GUARDS
59
+ );
60
+ }
61
+ return workflowSdk.sdkGuardStore(
62
+ requireSdk(
63
+ `guards for "${instanceId}" live in the engine dataset ${engineResource.projectId}.${engineResource.dataset}`
64
+ ),
65
+ instanceId,
66
+ engineResource
67
+ );
68
+ }
26
69
  };
27
70
  }
28
71
  function resolveEditState(editState) {
@@ -30,8 +73,71 @@ function resolveEditState(editState) {
30
73
  return editState.version ?? editState.draft ?? editState.published;
31
74
  }
32
75
  function useWorkflowInstance(engine, instanceId, opts) {
33
- const documentStore = sanity.useDocumentStore(), observer = react.useMemo(() => makeStudioObserver(documentStore), [documentStore]);
34
- return workflowReact.useWorkflowSession(engine, instanceId, observer, opts);
76
+ const documentStore = sanity.useDocumentStore(), { projectId, dataset } = sanity.useSource(), mounted = react.useMemo(() => ({ projectId, dataset }), [projectId, dataset]), engineResource = react.useMemo(() => workflowReact.datasetResourceId(engine.workflowResource), [engine]), sdk2 = useEmbeddedSdk(
77
+ opts?.sdk,
78
+ // The engine dataset being foreign is the signal an SDK instance is
79
+ // needed; foreign content refs without one still fail loud per ref.
80
+ !workflowReact.sameDataset(engineResource, mounted),
81
+ mounted
82
+ ), observer = react.useMemo(
83
+ () => makeStudioObserver(documentStore, {
84
+ mounted,
85
+ engineResource,
86
+ ...sdk2 !== void 0 ? { sdk: sdk2 } : {}
87
+ }),
88
+ [documentStore, mounted, engineResource, sdk2]
89
+ );
90
+ return workflowReact.useWorkflowSession(engine, instanceId, observer, sessionOptions(opts));
91
+ }
92
+ function sessionOptions(opts = {}) {
93
+ const { sdk: _, ...session } = opts;
94
+ return session;
95
+ }
96
+ function useEmbeddedSdk(supplied, needed, mounted) {
97
+ const wanted = supplied === void 0 && needed, held = react.useRef(void 0), key = `${mounted.projectId}.${mounted.dataset}`;
98
+ wanted && (held.current?.key !== key || held.current.instance.isDisposed()) && (held.current = { key, instance: bootstrapInstance(mounted) });
99
+ const embedded = wanted ? held.current?.instance : void 0;
100
+ return useDeferredDispose(embedded), supplied ?? embedded;
101
+ }
102
+ function bootstrapInstance(mounted) {
103
+ return sdk.createSanityInstance({
104
+ projectId: mounted.projectId,
105
+ dataset: mounted.dataset,
106
+ auth: { token: studioSessionToken(mounted.projectId) }
107
+ });
108
+ }
109
+ function useDeferredDispose(instance) {
110
+ const disposal = react.useRef(void 0);
111
+ react.useEffect(() => {
112
+ if (instance !== void 0)
113
+ return disposal.current?.instance === instance && (clearTimeout(disposal.current.timeoutId), disposal.current = void 0), () => {
114
+ disposal.current = {
115
+ instance,
116
+ timeoutId: setTimeout(() => {
117
+ instance.isDisposed() || instance.dispose();
118
+ }, 0)
119
+ };
120
+ };
121
+ }, [instance]);
122
+ }
123
+ function studioSessionToken(projectId) {
124
+ const token = persistedToken(projectId);
125
+ if (typeof token != "string")
126
+ throw new Error(
127
+ "@sanity/workflow-studio: the engine's state dataset is not this workspace's dataset, and no Studio session token is readable to bootstrap an App SDK instance (cookie-authenticated studio?). Pass one explicitly: useWorkflowInstance(engine, id, {sdk})."
128
+ );
129
+ return token;
130
+ }
131
+ function persistedToken(projectId) {
132
+ const raw = window.localStorage.getItem(`__studio_auth_token_${projectId}`);
133
+ if (raw !== null)
134
+ try {
135
+ const parsed = JSON.parse(raw);
136
+ return typeof parsed != "object" || parsed === null ? void 0 : parsed.token;
137
+ } catch {
138
+ return;
139
+ }
35
140
  }
141
+ exports.makeStudioObserver = makeStudioObserver;
36
142
  exports.useWorkflowInstance = useWorkflowInstance;
37
143
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/studio-observer.ts","../src/use-workflow-instance.tsx"],"sourcesContent":["import {contentReleaseName} from '@sanity/workflow-engine'\nimport {\n combineDocStores,\n type DocStore,\n type ObservedDoc,\n type WorkflowObserver,\n} from '@sanity/workflow-react'\nimport type {DocumentStore, EditStateFor} from 'sanity'\n\n/**\n * A {@link WorkflowObserver} backed by Studio's optimistic document store\n * (`documentStore.pair.editState` — a replayed RxJS observable, so a fresh\n * subscriber gets the current value synchronously). Content docs resolve under\n * the perspective's release via the `editState` version arg; instance /\n * ancestor / `system.release` docs read raw. The fan-out/snapshot machinery\n * lives in {@link combineDocStores}.\n */\nexport function makeStudioObserver(documentStore: DocumentStore): WorkflowObserver {\n const docStore = (\n id: string,\n type: string,\n release: string | undefined,\n ): DocStore<ObservedDoc> => {\n const observable = documentStore.pair.editState(id, type, release)\n let latest: ObservedDoc\n return {\n subscribe(onChange) {\n const subscription = observable.subscribe((editState) => {\n latest = resolveEditState(editState)\n onChange()\n })\n return () => subscription.unsubscribe()\n },\n getSnapshot: () => latest,\n }\n }\n return {\n observeInstance: (instanceId) => docStore(instanceId, 'workflow.instance', undefined),\n observeDocs: (documents, perspective) =>\n combineDocStores(\n documents.map((ref) => ({\n key: ref.globalDocumentId,\n store: docStore(ref.documentId, ref.type, contentReleaseName({ref, perspective})),\n })),\n ),\n }\n}\n\n/**\n * Resolve an `editState` emission to a single observed value: `undefined` while\n * not ready (gates the session feed), otherwise the version / draft / published\n * doc in precedence order (`null` when the doc doesn't exist).\n */\nfunction resolveEditState(editState: EditStateFor): ObservedDoc {\n if (!editState.ready) return undefined\n return (editState.version ?? editState.draft ?? editState.published) as ObservedDoc\n}\n","import type {Engine, WorkflowAccessOverride} from '@sanity/workflow-engine'\nimport {useWorkflowSession, type WorkflowInstanceController} from '@sanity/workflow-react'\nimport {useMemo} from 'react'\nimport {useDocumentStore} from 'sanity'\n\nimport {makeStudioObserver} from './studio-observer.ts'\n\n/**\n * Drive a workflow instance reactively from the Studio document store: observes\n * the instance and its watch-set through `documentStore.pair.editState` and\n * feeds the engine's session. Returns `{evaluation, ready, tick, fireAction}`;\n * the consumer decides when to advance. Must render inside Studio source context\n * (so `useDocumentStore` resolves).\n */\nexport function useWorkflowInstance(\n engine: Engine,\n instanceId: string,\n opts?: {access?: WorkflowAccessOverride; grantsFromPath?: string},\n): WorkflowInstanceController {\n const documentStore = useDocumentStore()\n const observer = useMemo(() => makeStudioObserver(documentStore), [documentStore])\n return useWorkflowSession(engine, instanceId, observer, opts)\n}\n"],"names":["combineDocStores","contentReleaseName","useDocumentStore","useMemo","useWorkflowSession"],"mappings":";;;AAiBO,SAAS,mBAAmB,eAAgD;AACjF,QAAM,WAAW,CACf,IACA,MACA,YAC0B;AAC1B,UAAM,aAAa,cAAc,KAAK,UAAU,IAAI,MAAM,OAAO;AACjE,QAAI;AACJ,WAAO;AAAA,MACL,UAAU,UAAU;AAClB,cAAM,eAAe,WAAW,UAAU,CAAC,cAAc;AACvD,mBAAS,iBAAiB,SAAS,GACnC,SAAA;AAAA,QACF,CAAC;AACD,eAAO,MAAM,aAAa,YAAA;AAAA,MAC5B;AAAA,MACA,aAAa,MAAM;AAAA,IAAA;AAAA,EAEvB;AACA,SAAO;AAAA,IACL,iBAAiB,CAAC,eAAe,SAAS,YAAY,qBAAqB,MAAS;AAAA,IACpF,aAAa,CAAC,WAAW,gBACvBA,cAAAA;AAAAA,MACE,UAAU,IAAI,CAAC,SAAS;AAAA,QACtB,KAAK,IAAI;AAAA,QACT,OAAO,SAAS,IAAI,YAAY,IAAI,MAAMC,kCAAmB,EAAC,KAAK,aAAY,CAAC;AAAA,MAAA,EAChF;AAAA,IAAA;AAAA,EACJ;AAEN;AAOA,SAAS,iBAAiB,WAAsC;AAC9D,MAAK,UAAU;AACf,WAAQ,UAAU,WAAW,UAAU,SAAS,UAAU;AAC5D;AC1CO,SAAS,oBACd,QACA,YACA,MAC4B;AAC5B,QAAM,gBAAgBC,wBAAA,GAChB,WAAWC,MAAAA,QAAQ,MAAM,mBAAmB,aAAa,GAAG,CAAC,aAAa,CAAC;AACjF,SAAOC,cAAAA,mBAAmB,QAAQ,YAAY,UAAU,IAAI;AAC9D;;"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/studio-observer.ts","../src/use-workflow-instance.tsx"],"sourcesContent":["import type {SanityInstance} from '@sanity/sdk'\nimport {\n contentReleaseName,\n instanceGuardQuery,\n WORKFLOW_INSTANCE_TYPE,\n type MutationGuardDoc,\n} from '@sanity/workflow-engine'\nimport {\n classifyRef,\n combineDocStores,\n NO_GUARDS,\n sameDataset,\n type DatasetResourceId,\n type DocStore,\n type ObservedDoc,\n type WorkflowObserver,\n} from '@sanity/workflow-react'\nimport {sdkContentDocStore, sdkGuardStore, sdkInstanceDocStore} from '@sanity/workflow-sdk'\nimport type {DocumentStore, EditStateFor} from 'sanity'\n\n/** The slice of an RxJS observable this module needs — kept structural so the\n * file doesn't take an `rxjs` import just to name the type. */\ninterface ObservableLike<E> {\n subscribe: (next: (value: E) => void) => {unsubscribe: () => void}\n}\n\n/** Bridge a replayed RxJS observable into a {@link DocStore}: cache the latest\n * resolved value and notify `useSyncExternalStore` on each emission. Both the\n * per-doc `editState` stream and the guard `listenQuery` stream share it. */\nfunction observableStore<E, T>(\n observable: ObservableLike<E>,\n resolve: (value: E) => T,\n initial: T,\n): DocStore<T> {\n let latest = initial\n return {\n subscribe(onChange) {\n const subscription = observable.subscribe((value) => {\n latest = resolve(value)\n onChange()\n })\n return () => subscription.unsubscribe()\n },\n getSnapshot: () => latest,\n }\n}\n\nexport interface StudioObserverOptions {\n /** The workspace's project + dataset — the only resource Studio's\n * `documentStore` can address. */\n mounted: DatasetResourceId\n /** The engine's state dataset (`datasetResourceId(engine.workflowResource)`)\n * — where the instance doc and its verdict-relevant guards live. Routinely\n * a dedicated `workflows` dataset distinct from the workspace. */\n engineResource: DatasetResourceId\n /** Observe resources OUTSIDE the mounted dataset (foreign content refs, and\n * the engine dataset when it differs from the workspace) through the App\n * SDK store. Omit ⇒ any foreign observation throws an actionable\n * config-mismatch error instead of being silently misread. */\n sdk?: SanityInstance\n}\n\n/**\n * A {@link WorkflowObserver} that routes every stream to the store that can\n * actually address it:\n *\n * - Anything in the **mounted workspace dataset** → `documentStore`\n * (`pair.editState` for docs — including Studio's local uncommitted edits,\n * the one stream only Studio has — and `listenQuery` for guards).\n * - Anything else (foreign content refs; the engine's state dataset when it\n * isn't the workspace) → the App SDK store via {@link sdkInstanceDocStore} /\n * {@link sdkContentDocStore} / {@link sdkGuardStore} when\n * {@link StudioObserverOptions.sdk} is supplied; otherwise an actionable\n * error. Never a silent wrong-dataset read.\n */\nexport function makeStudioObserver(\n documentStore: DocumentStore,\n options: StudioObserverOptions,\n): WorkflowObserver {\n const {mounted, engineResource, sdk} = options\n const engineIsMounted = sameDataset(engineResource, mounted)\n\n const editStateStore = (\n id: string,\n type: string,\n release: string | undefined,\n ): DocStore<ObservedDoc> =>\n observableStore<EditStateFor, ObservedDoc>(\n documentStore.pair.editState(id, type, release),\n resolveEditState,\n undefined,\n )\n\n const requireSdk = (what: string): SanityInstance => {\n if (sdk !== undefined) return sdk\n throw new Error(\n `@sanity/workflow-studio: ${what} — Studio's document store is bound to ${mounted.projectId}.${mounted.dataset} and cannot observe it. Pass an App SDK instance (\\`useWorkflowInstance(engine, id, {sdk})\\`) to observe foreign resources.`,\n )\n }\n\n return {\n observeInstance: (instanceId) => {\n if (engineIsMounted) return editStateStore(instanceId, WORKFLOW_INSTANCE_TYPE, undefined)\n return sdkInstanceDocStore(\n requireSdk(\n `instance \"${instanceId}\" lives in the engine dataset ${engineResource.projectId}.${engineResource.dataset}`,\n ),\n instanceId,\n engineResource,\n )\n },\n observeDocs: (documents, perspective) =>\n combineDocStores(\n documents.map((ref) => {\n if (classifyRef(ref, mounted) === 'mounted-dataset') {\n return {\n key: ref.globalDocumentId,\n store: editStateStore(\n ref.documentId,\n ref.type,\n contentReleaseName({ref, perspective}),\n ),\n }\n }\n return {\n key: ref.globalDocumentId,\n store: sdkContentDocStore(\n requireSdk(`\"${ref.globalDocumentId}\" lives outside this workspace`),\n ref,\n perspective,\n ),\n }\n }),\n ),\n observeGuards: (instanceId) => {\n if (engineIsMounted) {\n const {query, params} = instanceGuardQuery(instanceId)\n return observableStore<MutationGuardDoc[], readonly MutationGuardDoc[]>(\n documentStore.listenQuery(query, params, {}),\n (docs) => docs,\n NO_GUARDS,\n )\n }\n return sdkGuardStore(\n requireSdk(\n `guards for \"${instanceId}\" live in the engine dataset ${engineResource.projectId}.${engineResource.dataset}`,\n ),\n instanceId,\n engineResource,\n )\n },\n }\n}\n\n/**\n * Resolve an `editState` emission to a single observed value: `undefined` while\n * not ready (gates the session feed), otherwise the version / draft / published\n * doc in precedence order (`null` when the doc doesn't exist).\n */\nfunction resolveEditState(editState: EditStateFor): ObservedDoc {\n if (!editState.ready) return undefined\n return (editState.version ?? editState.draft ?? editState.published) as ObservedDoc\n}\n","import {createSanityInstance, type SanityInstance} from '@sanity/sdk'\nimport type {Engine, WorkflowAccessOverride} from '@sanity/workflow-engine'\nimport {\n datasetResourceId,\n sameDataset,\n useWorkflowSession,\n type DatasetResourceId,\n type WorkflowInstanceController,\n} from '@sanity/workflow-react'\nimport {useEffect, useMemo, useRef} from 'react'\nimport {useDocumentStore, useSource} from 'sanity'\n\nimport {makeStudioObserver} from './studio-observer.ts'\n\n/**\n * Drive a workflow instance reactively from Sanity Studio. Watched docs in the\n * workspace's own dataset observe through `documentStore.pair.editState`\n * (including local uncommitted edits); everything else — the engine's state\n * dataset (routinely a dedicated `workflows` dataset) and foreign content\n * refs — observes through the App SDK store. When the engine dataset differs\n * from the workspace, an SDK instance is bootstrapped automatically from the\n * Studio session (pass `opts.sdk` to supply your own — required for\n * cookie-authenticated studios, where no session token is readable). Returns\n * `{evaluation, ready, guards, tick, fireAction}`; the consumer decides when\n * to advance. Must render inside Studio source context (so\n * `useDocumentStore`/`useSource` resolve).\n */\nexport function useWorkflowInstance(\n engine: Engine,\n instanceId: string,\n opts?: {access?: WorkflowAccessOverride; grantsFromPath?: string; sdk?: SanityInstance},\n): WorkflowInstanceController {\n const documentStore = useDocumentStore()\n const {projectId, dataset} = useSource()\n const mounted = useMemo(() => ({projectId, dataset}), [projectId, dataset])\n const engineResource = useMemo(() => datasetResourceId(engine.workflowResource), [engine])\n const sdk = useEmbeddedSdk(\n opts?.sdk,\n // The engine dataset being foreign is the signal an SDK instance is\n // needed; foreign content refs without one still fail loud per ref.\n !sameDataset(engineResource, mounted),\n mounted,\n )\n const observer = useMemo(\n () =>\n makeStudioObserver(documentStore, {\n mounted,\n engineResource,\n ...(sdk !== undefined ? {sdk} : {}),\n }),\n [documentStore, mounted, engineResource, sdk],\n )\n return useWorkflowSession(engine, instanceId, observer, sessionOptions(opts))\n}\n\n/** Only the session options cross into the engine — `sdk` is a store handle\n * for the observer, never an `engine.instance(...)` argument. */\nfunction sessionOptions(\n opts: {access?: WorkflowAccessOverride; grantsFromPath?: string; sdk?: SanityInstance} = {},\n): {access?: WorkflowAccessOverride; grantsFromPath?: string} {\n const {sdk: _, ...session} = opts\n return session\n}\n\n/**\n * The supplied SDK instance, or one bootstrapped from the Studio session's\n * token when foreign observation is needed. Lifecycle mirrors the SDK's own\n * `ResourceProvider`: created in render (the observer needs it synchronously),\n * recreated when a previous unmount disposed it, and released via\n * {@link useDeferredDispose} — so StrictMode's unmount/remount probe neither\n * leaks an instance nor hands the remount a dead one. Exported for tests only.\n */\nexport function useEmbeddedSdk(\n supplied: SanityInstance | undefined,\n needed: boolean,\n mounted: DatasetResourceId,\n): SanityInstance | undefined {\n const wanted = supplied === undefined && needed\n const held = useRef<{key: string; instance: SanityInstance} | undefined>(undefined)\n const key = `${mounted.projectId}.${mounted.dataset}`\n if (wanted && (held.current?.key !== key || held.current.instance.isDisposed())) {\n held.current = {key, instance: bootstrapInstance(mounted)}\n }\n const embedded = wanted ? held.current?.instance : undefined\n useDeferredDispose(embedded)\n return supplied ?? embedded\n}\n\n/** An SDK instance over the workspace's own project, authed with the Studio\n * session's token — the engine dataset is reached per-resource from here. */\nfunction bootstrapInstance(mounted: DatasetResourceId): SanityInstance {\n return createSanityInstance({\n projectId: mounted.projectId,\n dataset: mounted.dataset,\n auth: {token: studioSessionToken(mounted.projectId)},\n })\n}\n\n/**\n * Dispose `instance` after unmount via a zero-delay timer that an immediate\n * remount of the same instance cancels — the SDK `ResourceProvider`'s own\n * idiom for surviving StrictMode's unmount/remount probe.\n */\nfunction useDeferredDispose(instance: SanityInstance | undefined): void {\n const disposal = useRef<\n {instance: SanityInstance; timeoutId: ReturnType<typeof setTimeout>} | undefined\n >(undefined)\n useEffect(() => {\n if (instance === undefined) return undefined\n if (disposal.current?.instance === instance) {\n clearTimeout(disposal.current.timeoutId)\n disposal.current = undefined\n }\n return () => {\n disposal.current = {\n instance,\n timeoutId: setTimeout(() => {\n if (!instance.isDisposed()) instance.dispose()\n }, 0),\n }\n }\n }, [instance])\n}\n\n/** The Studio session's token, as persisted by Studio's own auth store.\n * Exported for tests only. */\nexport function studioSessionToken(projectId: string): string {\n const token = persistedToken(projectId)\n if (typeof token !== 'string') {\n throw new Error(\n `@sanity/workflow-studio: the engine's state dataset is not this workspace's dataset, and no Studio session token is readable to bootstrap an App SDK instance (cookie-authenticated studio?). Pass one explicitly: useWorkflowInstance(engine, id, {sdk}).`,\n )\n }\n return token\n}\n\n/** Best-effort read of Studio's persisted `{token}` localStorage entry — any\n * unreadable shape (absent key, malformed JSON, non-object value) resolves to\n * `undefined` so every failure mode funnels into the one actionable error. */\nfunction persistedToken(projectId: string): unknown {\n const raw = window.localStorage.getItem(`__studio_auth_token_${projectId}`)\n if (raw === null) return undefined\n try {\n const parsed: unknown = JSON.parse(raw)\n if (typeof parsed !== 'object' || parsed === null) return undefined\n return (parsed as {token?: unknown}).token\n } catch {\n return undefined\n }\n}\n"],"names":["sdk","sameDataset","WORKFLOW_INSTANCE_TYPE","sdkInstanceDocStore","combineDocStores","classifyRef","contentReleaseName","sdkContentDocStore","instanceGuardQuery","NO_GUARDS","sdkGuardStore","useDocumentStore","useSource","useMemo","datasetResourceId","useWorkflowSession","useRef","createSanityInstance","useEffect"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,13 +1,59 @@
1
+ import { DatasetResourceId } from "@sanity/workflow-react";
2
+ import type { DocumentStore } from "sanity";
1
3
  import type { Engine } from "@sanity/workflow-engine";
4
+ import { MutationGuardDoc } from "@sanity/workflow-react";
5
+ import { SanityInstance } from "@sanity/sdk";
2
6
  import type { WorkflowAccessOverride } from "@sanity/workflow-engine";
3
7
  import { WorkflowInstanceController } from "@sanity/workflow-react";
8
+ import { WorkflowObserver } from "@sanity/workflow-react";
4
9
 
5
10
  /**
6
- * Drive a workflow instance reactively from the Studio document store: observes
7
- * the instance and its watch-set through `documentStore.pair.editState` and
8
- * feeds the engine's session. Returns `{evaluation, ready, tick, fireAction}`;
9
- * the consumer decides when to advance. Must render inside Studio source context
10
- * (so `useDocumentStore` resolves).
11
+ * A {@link WorkflowObserver} that routes every stream to the store that can
12
+ * actually address it:
13
+ *
14
+ * - Anything in the **mounted workspace dataset** `documentStore`
15
+ * (`pair.editState` for docs — including Studio's local uncommitted edits,
16
+ * the one stream only Studio has — and `listenQuery` for guards).
17
+ * - Anything else (foreign content refs; the engine's state dataset when it
18
+ * isn't the workspace) → the App SDK store via {@link sdkInstanceDocStore} /
19
+ * {@link sdkContentDocStore} / {@link sdkGuardStore} when
20
+ * {@link StudioObserverOptions.sdk} is supplied; otherwise an actionable
21
+ * error. Never a silent wrong-dataset read.
22
+ */
23
+ export declare function makeStudioObserver(
24
+ documentStore: DocumentStore,
25
+ options: StudioObserverOptions,
26
+ ): WorkflowObserver;
27
+
28
+ export { MutationGuardDoc };
29
+
30
+ export declare interface StudioObserverOptions {
31
+ /** The workspace's project + dataset — the only resource Studio's
32
+ * `documentStore` can address. */
33
+ mounted: DatasetResourceId;
34
+ /** The engine's state dataset (`datasetResourceId(engine.workflowResource)`)
35
+ * — where the instance doc and its verdict-relevant guards live. Routinely
36
+ * a dedicated `workflows` dataset distinct from the workspace. */
37
+ engineResource: DatasetResourceId;
38
+ /** Observe resources OUTSIDE the mounted dataset (foreign content refs, and
39
+ * the engine dataset when it differs from the workspace) through the App
40
+ * SDK store. Omit ⇒ any foreign observation throws an actionable
41
+ * config-mismatch error instead of being silently misread. */
42
+ sdk?: SanityInstance;
43
+ }
44
+
45
+ /**
46
+ * Drive a workflow instance reactively from Sanity Studio. Watched docs in the
47
+ * workspace's own dataset observe through `documentStore.pair.editState`
48
+ * (including local uncommitted edits); everything else — the engine's state
49
+ * dataset (routinely a dedicated `workflows` dataset) and foreign content
50
+ * refs — observes through the App SDK store. When the engine dataset differs
51
+ * from the workspace, an SDK instance is bootstrapped automatically from the
52
+ * Studio session (pass `opts.sdk` to supply your own — required for
53
+ * cookie-authenticated studios, where no session token is readable). Returns
54
+ * `{evaluation, ready, guards, tick, fireAction}`; the consumer decides when
55
+ * to advance. Must render inside Studio source context (so
56
+ * `useDocumentStore`/`useSource` resolve).
11
57
  */
12
58
  export declare function useWorkflowInstance(
13
59
  engine: Engine,
@@ -15,6 +61,7 @@ export declare function useWorkflowInstance(
15
61
  opts?: {
16
62
  access?: WorkflowAccessOverride;
17
63
  grantsFromPath?: string;
64
+ sdk?: SanityInstance;
18
65
  },
19
66
  ): WorkflowInstanceController;
20
67
 
package/dist/index.d.ts CHANGED
@@ -1,13 +1,59 @@
1
+ import { DatasetResourceId } from "@sanity/workflow-react";
2
+ import type { DocumentStore } from "sanity";
1
3
  import type { Engine } from "@sanity/workflow-engine";
4
+ import { MutationGuardDoc } from "@sanity/workflow-react";
5
+ import { SanityInstance } from "@sanity/sdk";
2
6
  import type { WorkflowAccessOverride } from "@sanity/workflow-engine";
3
7
  import { WorkflowInstanceController } from "@sanity/workflow-react";
8
+ import { WorkflowObserver } from "@sanity/workflow-react";
4
9
 
5
10
  /**
6
- * Drive a workflow instance reactively from the Studio document store: observes
7
- * the instance and its watch-set through `documentStore.pair.editState` and
8
- * feeds the engine's session. Returns `{evaluation, ready, tick, fireAction}`;
9
- * the consumer decides when to advance. Must render inside Studio source context
10
- * (so `useDocumentStore` resolves).
11
+ * A {@link WorkflowObserver} that routes every stream to the store that can
12
+ * actually address it:
13
+ *
14
+ * - Anything in the **mounted workspace dataset** `documentStore`
15
+ * (`pair.editState` for docs — including Studio's local uncommitted edits,
16
+ * the one stream only Studio has — and `listenQuery` for guards).
17
+ * - Anything else (foreign content refs; the engine's state dataset when it
18
+ * isn't the workspace) → the App SDK store via {@link sdkInstanceDocStore} /
19
+ * {@link sdkContentDocStore} / {@link sdkGuardStore} when
20
+ * {@link StudioObserverOptions.sdk} is supplied; otherwise an actionable
21
+ * error. Never a silent wrong-dataset read.
22
+ */
23
+ export declare function makeStudioObserver(
24
+ documentStore: DocumentStore,
25
+ options: StudioObserverOptions,
26
+ ): WorkflowObserver;
27
+
28
+ export { MutationGuardDoc };
29
+
30
+ export declare interface StudioObserverOptions {
31
+ /** The workspace's project + dataset — the only resource Studio's
32
+ * `documentStore` can address. */
33
+ mounted: DatasetResourceId;
34
+ /** The engine's state dataset (`datasetResourceId(engine.workflowResource)`)
35
+ * — where the instance doc and its verdict-relevant guards live. Routinely
36
+ * a dedicated `workflows` dataset distinct from the workspace. */
37
+ engineResource: DatasetResourceId;
38
+ /** Observe resources OUTSIDE the mounted dataset (foreign content refs, and
39
+ * the engine dataset when it differs from the workspace) through the App
40
+ * SDK store. Omit ⇒ any foreign observation throws an actionable
41
+ * config-mismatch error instead of being silently misread. */
42
+ sdk?: SanityInstance;
43
+ }
44
+
45
+ /**
46
+ * Drive a workflow instance reactively from Sanity Studio. Watched docs in the
47
+ * workspace's own dataset observe through `documentStore.pair.editState`
48
+ * (including local uncommitted edits); everything else — the engine's state
49
+ * dataset (routinely a dedicated `workflows` dataset) and foreign content
50
+ * refs — observes through the App SDK store. When the engine dataset differs
51
+ * from the workspace, an SDK instance is bootstrapped automatically from the
52
+ * Studio session (pass `opts.sdk` to supply your own — required for
53
+ * cookie-authenticated studios, where no session token is readable). Returns
54
+ * `{evaluation, ready, guards, tick, fireAction}`; the consumer decides when
55
+ * to advance. Must render inside Studio source context (so
56
+ * `useDocumentStore`/`useSource` resolve).
11
57
  */
12
58
  export declare function useWorkflowInstance(
13
59
  engine: Engine,
@@ -15,6 +61,7 @@ export declare function useWorkflowInstance(
15
61
  opts?: {
16
62
  access?: WorkflowAccessOverride;
17
63
  grantsFromPath?: string;
64
+ sdk?: SanityInstance;
18
65
  },
19
66
  ): WorkflowInstanceController;
20
67
 
package/dist/index.js CHANGED
@@ -1,29 +1,74 @@
1
- import { combineDocStores, useWorkflowSession } from "@sanity/workflow-react";
2
- import { useMemo } from "react";
3
- import { useDocumentStore } from "sanity";
4
- import { contentReleaseName } from "@sanity/workflow-engine";
5
- function makeStudioObserver(documentStore) {
6
- const docStore = (id, type, release) => {
7
- const observable = documentStore.pair.editState(id, type, release);
8
- let latest;
9
- return {
10
- subscribe(onChange) {
11
- const subscription = observable.subscribe((editState) => {
12
- latest = resolveEditState(editState), onChange();
13
- });
14
- return () => subscription.unsubscribe();
15
- },
16
- getSnapshot: () => latest
17
- };
1
+ import { instanceGuardQuery, contentReleaseName, WORKFLOW_INSTANCE_TYPE } from "@sanity/workflow-engine";
2
+ import { sameDataset, combineDocStores, classifyRef, NO_GUARDS, datasetResourceId, useWorkflowSession } from "@sanity/workflow-react";
3
+ import { sdkGuardStore, sdkContentDocStore, sdkInstanceDocStore } from "@sanity/workflow-sdk";
4
+ import { createSanityInstance } from "@sanity/sdk";
5
+ import { useMemo, useRef, useEffect } from "react";
6
+ import { useDocumentStore, useSource } from "sanity";
7
+ function observableStore(observable, resolve, initial) {
8
+ let latest = initial;
9
+ return {
10
+ subscribe(onChange) {
11
+ const subscription = observable.subscribe((value) => {
12
+ latest = resolve(value), onChange();
13
+ });
14
+ return () => subscription.unsubscribe();
15
+ },
16
+ getSnapshot: () => latest
17
+ };
18
+ }
19
+ function makeStudioObserver(documentStore, options) {
20
+ const { mounted, engineResource, sdk } = options, engineIsMounted = sameDataset(engineResource, mounted), editStateStore = (id, type, release) => observableStore(
21
+ documentStore.pair.editState(id, type, release),
22
+ resolveEditState,
23
+ void 0
24
+ ), requireSdk = (what) => {
25
+ if (sdk !== void 0) return sdk;
26
+ throw new Error(
27
+ `@sanity/workflow-studio: ${what} \u2014 Studio's document store is bound to ${mounted.projectId}.${mounted.dataset} and cannot observe it. Pass an App SDK instance (\`useWorkflowInstance(engine, id, {sdk})\`) to observe foreign resources.`
28
+ );
18
29
  };
19
30
  return {
20
- observeInstance: (instanceId) => docStore(instanceId, "workflow.instance", void 0),
31
+ observeInstance: (instanceId) => engineIsMounted ? editStateStore(instanceId, WORKFLOW_INSTANCE_TYPE, void 0) : sdkInstanceDocStore(
32
+ requireSdk(
33
+ `instance "${instanceId}" lives in the engine dataset ${engineResource.projectId}.${engineResource.dataset}`
34
+ ),
35
+ instanceId,
36
+ engineResource
37
+ ),
21
38
  observeDocs: (documents, perspective) => combineDocStores(
22
- documents.map((ref) => ({
39
+ documents.map((ref) => classifyRef(ref, mounted) === "mounted-dataset" ? {
23
40
  key: ref.globalDocumentId,
24
- store: docStore(ref.documentId, ref.type, contentReleaseName({ ref, perspective }))
25
- }))
26
- )
41
+ store: editStateStore(
42
+ ref.documentId,
43
+ ref.type,
44
+ contentReleaseName({ ref, perspective })
45
+ )
46
+ } : {
47
+ key: ref.globalDocumentId,
48
+ store: sdkContentDocStore(
49
+ requireSdk(`"${ref.globalDocumentId}" lives outside this workspace`),
50
+ ref,
51
+ perspective
52
+ )
53
+ })
54
+ ),
55
+ observeGuards: (instanceId) => {
56
+ if (engineIsMounted) {
57
+ const { query, params } = instanceGuardQuery(instanceId);
58
+ return observableStore(
59
+ documentStore.listenQuery(query, params, {}),
60
+ (docs) => docs,
61
+ NO_GUARDS
62
+ );
63
+ }
64
+ return sdkGuardStore(
65
+ requireSdk(
66
+ `guards for "${instanceId}" live in the engine dataset ${engineResource.projectId}.${engineResource.dataset}`
67
+ ),
68
+ instanceId,
69
+ engineResource
70
+ );
71
+ }
27
72
  };
28
73
  }
29
74
  function resolveEditState(editState) {
@@ -31,10 +76,73 @@ function resolveEditState(editState) {
31
76
  return editState.version ?? editState.draft ?? editState.published;
32
77
  }
33
78
  function useWorkflowInstance(engine, instanceId, opts) {
34
- const documentStore = useDocumentStore(), observer = useMemo(() => makeStudioObserver(documentStore), [documentStore]);
35
- return useWorkflowSession(engine, instanceId, observer, opts);
79
+ const documentStore = useDocumentStore(), { projectId, dataset } = useSource(), mounted = useMemo(() => ({ projectId, dataset }), [projectId, dataset]), engineResource = useMemo(() => datasetResourceId(engine.workflowResource), [engine]), sdk = useEmbeddedSdk(
80
+ opts?.sdk,
81
+ // The engine dataset being foreign is the signal an SDK instance is
82
+ // needed; foreign content refs without one still fail loud per ref.
83
+ !sameDataset(engineResource, mounted),
84
+ mounted
85
+ ), observer = useMemo(
86
+ () => makeStudioObserver(documentStore, {
87
+ mounted,
88
+ engineResource,
89
+ ...sdk !== void 0 ? { sdk } : {}
90
+ }),
91
+ [documentStore, mounted, engineResource, sdk]
92
+ );
93
+ return useWorkflowSession(engine, instanceId, observer, sessionOptions(opts));
94
+ }
95
+ function sessionOptions(opts = {}) {
96
+ const { sdk: _, ...session } = opts;
97
+ return session;
98
+ }
99
+ function useEmbeddedSdk(supplied, needed, mounted) {
100
+ const wanted = supplied === void 0 && needed, held = useRef(void 0), key = `${mounted.projectId}.${mounted.dataset}`;
101
+ wanted && (held.current?.key !== key || held.current.instance.isDisposed()) && (held.current = { key, instance: bootstrapInstance(mounted) });
102
+ const embedded = wanted ? held.current?.instance : void 0;
103
+ return useDeferredDispose(embedded), supplied ?? embedded;
104
+ }
105
+ function bootstrapInstance(mounted) {
106
+ return createSanityInstance({
107
+ projectId: mounted.projectId,
108
+ dataset: mounted.dataset,
109
+ auth: { token: studioSessionToken(mounted.projectId) }
110
+ });
111
+ }
112
+ function useDeferredDispose(instance) {
113
+ const disposal = useRef(void 0);
114
+ useEffect(() => {
115
+ if (instance !== void 0)
116
+ return disposal.current?.instance === instance && (clearTimeout(disposal.current.timeoutId), disposal.current = void 0), () => {
117
+ disposal.current = {
118
+ instance,
119
+ timeoutId: setTimeout(() => {
120
+ instance.isDisposed() || instance.dispose();
121
+ }, 0)
122
+ };
123
+ };
124
+ }, [instance]);
125
+ }
126
+ function studioSessionToken(projectId) {
127
+ const token = persistedToken(projectId);
128
+ if (typeof token != "string")
129
+ throw new Error(
130
+ "@sanity/workflow-studio: the engine's state dataset is not this workspace's dataset, and no Studio session token is readable to bootstrap an App SDK instance (cookie-authenticated studio?). Pass one explicitly: useWorkflowInstance(engine, id, {sdk})."
131
+ );
132
+ return token;
133
+ }
134
+ function persistedToken(projectId) {
135
+ const raw = window.localStorage.getItem(`__studio_auth_token_${projectId}`);
136
+ if (raw !== null)
137
+ try {
138
+ const parsed = JSON.parse(raw);
139
+ return typeof parsed != "object" || parsed === null ? void 0 : parsed.token;
140
+ } catch {
141
+ return;
142
+ }
36
143
  }
37
144
  export {
145
+ makeStudioObserver,
38
146
  useWorkflowInstance
39
147
  };
40
148
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/studio-observer.ts","../src/use-workflow-instance.tsx"],"sourcesContent":["import {contentReleaseName} from '@sanity/workflow-engine'\nimport {\n combineDocStores,\n type DocStore,\n type ObservedDoc,\n type WorkflowObserver,\n} from '@sanity/workflow-react'\nimport type {DocumentStore, EditStateFor} from 'sanity'\n\n/**\n * A {@link WorkflowObserver} backed by Studio's optimistic document store\n * (`documentStore.pair.editState` — a replayed RxJS observable, so a fresh\n * subscriber gets the current value synchronously). Content docs resolve under\n * the perspective's release via the `editState` version arg; instance /\n * ancestor / `system.release` docs read raw. The fan-out/snapshot machinery\n * lives in {@link combineDocStores}.\n */\nexport function makeStudioObserver(documentStore: DocumentStore): WorkflowObserver {\n const docStore = (\n id: string,\n type: string,\n release: string | undefined,\n ): DocStore<ObservedDoc> => {\n const observable = documentStore.pair.editState(id, type, release)\n let latest: ObservedDoc\n return {\n subscribe(onChange) {\n const subscription = observable.subscribe((editState) => {\n latest = resolveEditState(editState)\n onChange()\n })\n return () => subscription.unsubscribe()\n },\n getSnapshot: () => latest,\n }\n }\n return {\n observeInstance: (instanceId) => docStore(instanceId, 'workflow.instance', undefined),\n observeDocs: (documents, perspective) =>\n combineDocStores(\n documents.map((ref) => ({\n key: ref.globalDocumentId,\n store: docStore(ref.documentId, ref.type, contentReleaseName({ref, perspective})),\n })),\n ),\n }\n}\n\n/**\n * Resolve an `editState` emission to a single observed value: `undefined` while\n * not ready (gates the session feed), otherwise the version / draft / published\n * doc in precedence order (`null` when the doc doesn't exist).\n */\nfunction resolveEditState(editState: EditStateFor): ObservedDoc {\n if (!editState.ready) return undefined\n return (editState.version ?? editState.draft ?? editState.published) as ObservedDoc\n}\n","import type {Engine, WorkflowAccessOverride} from '@sanity/workflow-engine'\nimport {useWorkflowSession, type WorkflowInstanceController} from '@sanity/workflow-react'\nimport {useMemo} from 'react'\nimport {useDocumentStore} from 'sanity'\n\nimport {makeStudioObserver} from './studio-observer.ts'\n\n/**\n * Drive a workflow instance reactively from the Studio document store: observes\n * the instance and its watch-set through `documentStore.pair.editState` and\n * feeds the engine's session. Returns `{evaluation, ready, tick, fireAction}`;\n * the consumer decides when to advance. Must render inside Studio source context\n * (so `useDocumentStore` resolves).\n */\nexport function useWorkflowInstance(\n engine: Engine,\n instanceId: string,\n opts?: {access?: WorkflowAccessOverride; grantsFromPath?: string},\n): WorkflowInstanceController {\n const documentStore = useDocumentStore()\n const observer = useMemo(() => makeStudioObserver(documentStore), [documentStore])\n return useWorkflowSession(engine, instanceId, observer, opts)\n}\n"],"names":[],"mappings":";;;;AAiBO,SAAS,mBAAmB,eAAgD;AACjF,QAAM,WAAW,CACf,IACA,MACA,YAC0B;AAC1B,UAAM,aAAa,cAAc,KAAK,UAAU,IAAI,MAAM,OAAO;AACjE,QAAI;AACJ,WAAO;AAAA,MACL,UAAU,UAAU;AAClB,cAAM,eAAe,WAAW,UAAU,CAAC,cAAc;AACvD,mBAAS,iBAAiB,SAAS,GACnC,SAAA;AAAA,QACF,CAAC;AACD,eAAO,MAAM,aAAa,YAAA;AAAA,MAC5B;AAAA,MACA,aAAa,MAAM;AAAA,IAAA;AAAA,EAEvB;AACA,SAAO;AAAA,IACL,iBAAiB,CAAC,eAAe,SAAS,YAAY,qBAAqB,MAAS;AAAA,IACpF,aAAa,CAAC,WAAW,gBACvB;AAAA,MACE,UAAU,IAAI,CAAC,SAAS;AAAA,QACtB,KAAK,IAAI;AAAA,QACT,OAAO,SAAS,IAAI,YAAY,IAAI,MAAM,mBAAmB,EAAC,KAAK,aAAY,CAAC;AAAA,MAAA,EAChF;AAAA,IAAA;AAAA,EACJ;AAEN;AAOA,SAAS,iBAAiB,WAAsC;AAC9D,MAAK,UAAU;AACf,WAAQ,UAAU,WAAW,UAAU,SAAS,UAAU;AAC5D;AC1CO,SAAS,oBACd,QACA,YACA,MAC4B;AAC5B,QAAM,gBAAgB,iBAAA,GAChB,WAAW,QAAQ,MAAM,mBAAmB,aAAa,GAAG,CAAC,aAAa,CAAC;AACjF,SAAO,mBAAmB,QAAQ,YAAY,UAAU,IAAI;AAC9D;"}
1
+ {"version":3,"file":"index.js","sources":["../src/studio-observer.ts","../src/use-workflow-instance.tsx"],"sourcesContent":["import type {SanityInstance} from '@sanity/sdk'\nimport {\n contentReleaseName,\n instanceGuardQuery,\n WORKFLOW_INSTANCE_TYPE,\n type MutationGuardDoc,\n} from '@sanity/workflow-engine'\nimport {\n classifyRef,\n combineDocStores,\n NO_GUARDS,\n sameDataset,\n type DatasetResourceId,\n type DocStore,\n type ObservedDoc,\n type WorkflowObserver,\n} from '@sanity/workflow-react'\nimport {sdkContentDocStore, sdkGuardStore, sdkInstanceDocStore} from '@sanity/workflow-sdk'\nimport type {DocumentStore, EditStateFor} from 'sanity'\n\n/** The slice of an RxJS observable this module needs — kept structural so the\n * file doesn't take an `rxjs` import just to name the type. */\ninterface ObservableLike<E> {\n subscribe: (next: (value: E) => void) => {unsubscribe: () => void}\n}\n\n/** Bridge a replayed RxJS observable into a {@link DocStore}: cache the latest\n * resolved value and notify `useSyncExternalStore` on each emission. Both the\n * per-doc `editState` stream and the guard `listenQuery` stream share it. */\nfunction observableStore<E, T>(\n observable: ObservableLike<E>,\n resolve: (value: E) => T,\n initial: T,\n): DocStore<T> {\n let latest = initial\n return {\n subscribe(onChange) {\n const subscription = observable.subscribe((value) => {\n latest = resolve(value)\n onChange()\n })\n return () => subscription.unsubscribe()\n },\n getSnapshot: () => latest,\n }\n}\n\nexport interface StudioObserverOptions {\n /** The workspace's project + dataset — the only resource Studio's\n * `documentStore` can address. */\n mounted: DatasetResourceId\n /** The engine's state dataset (`datasetResourceId(engine.workflowResource)`)\n * — where the instance doc and its verdict-relevant guards live. Routinely\n * a dedicated `workflows` dataset distinct from the workspace. */\n engineResource: DatasetResourceId\n /** Observe resources OUTSIDE the mounted dataset (foreign content refs, and\n * the engine dataset when it differs from the workspace) through the App\n * SDK store. Omit ⇒ any foreign observation throws an actionable\n * config-mismatch error instead of being silently misread. */\n sdk?: SanityInstance\n}\n\n/**\n * A {@link WorkflowObserver} that routes every stream to the store that can\n * actually address it:\n *\n * - Anything in the **mounted workspace dataset** → `documentStore`\n * (`pair.editState` for docs — including Studio's local uncommitted edits,\n * the one stream only Studio has — and `listenQuery` for guards).\n * - Anything else (foreign content refs; the engine's state dataset when it\n * isn't the workspace) → the App SDK store via {@link sdkInstanceDocStore} /\n * {@link sdkContentDocStore} / {@link sdkGuardStore} when\n * {@link StudioObserverOptions.sdk} is supplied; otherwise an actionable\n * error. Never a silent wrong-dataset read.\n */\nexport function makeStudioObserver(\n documentStore: DocumentStore,\n options: StudioObserverOptions,\n): WorkflowObserver {\n const {mounted, engineResource, sdk} = options\n const engineIsMounted = sameDataset(engineResource, mounted)\n\n const editStateStore = (\n id: string,\n type: string,\n release: string | undefined,\n ): DocStore<ObservedDoc> =>\n observableStore<EditStateFor, ObservedDoc>(\n documentStore.pair.editState(id, type, release),\n resolveEditState,\n undefined,\n )\n\n const requireSdk = (what: string): SanityInstance => {\n if (sdk !== undefined) return sdk\n throw new Error(\n `@sanity/workflow-studio: ${what} — Studio's document store is bound to ${mounted.projectId}.${mounted.dataset} and cannot observe it. Pass an App SDK instance (\\`useWorkflowInstance(engine, id, {sdk})\\`) to observe foreign resources.`,\n )\n }\n\n return {\n observeInstance: (instanceId) => {\n if (engineIsMounted) return editStateStore(instanceId, WORKFLOW_INSTANCE_TYPE, undefined)\n return sdkInstanceDocStore(\n requireSdk(\n `instance \"${instanceId}\" lives in the engine dataset ${engineResource.projectId}.${engineResource.dataset}`,\n ),\n instanceId,\n engineResource,\n )\n },\n observeDocs: (documents, perspective) =>\n combineDocStores(\n documents.map((ref) => {\n if (classifyRef(ref, mounted) === 'mounted-dataset') {\n return {\n key: ref.globalDocumentId,\n store: editStateStore(\n ref.documentId,\n ref.type,\n contentReleaseName({ref, perspective}),\n ),\n }\n }\n return {\n key: ref.globalDocumentId,\n store: sdkContentDocStore(\n requireSdk(`\"${ref.globalDocumentId}\" lives outside this workspace`),\n ref,\n perspective,\n ),\n }\n }),\n ),\n observeGuards: (instanceId) => {\n if (engineIsMounted) {\n const {query, params} = instanceGuardQuery(instanceId)\n return observableStore<MutationGuardDoc[], readonly MutationGuardDoc[]>(\n documentStore.listenQuery(query, params, {}),\n (docs) => docs,\n NO_GUARDS,\n )\n }\n return sdkGuardStore(\n requireSdk(\n `guards for \"${instanceId}\" live in the engine dataset ${engineResource.projectId}.${engineResource.dataset}`,\n ),\n instanceId,\n engineResource,\n )\n },\n }\n}\n\n/**\n * Resolve an `editState` emission to a single observed value: `undefined` while\n * not ready (gates the session feed), otherwise the version / draft / published\n * doc in precedence order (`null` when the doc doesn't exist).\n */\nfunction resolveEditState(editState: EditStateFor): ObservedDoc {\n if (!editState.ready) return undefined\n return (editState.version ?? editState.draft ?? editState.published) as ObservedDoc\n}\n","import {createSanityInstance, type SanityInstance} from '@sanity/sdk'\nimport type {Engine, WorkflowAccessOverride} from '@sanity/workflow-engine'\nimport {\n datasetResourceId,\n sameDataset,\n useWorkflowSession,\n type DatasetResourceId,\n type WorkflowInstanceController,\n} from '@sanity/workflow-react'\nimport {useEffect, useMemo, useRef} from 'react'\nimport {useDocumentStore, useSource} from 'sanity'\n\nimport {makeStudioObserver} from './studio-observer.ts'\n\n/**\n * Drive a workflow instance reactively from Sanity Studio. Watched docs in the\n * workspace's own dataset observe through `documentStore.pair.editState`\n * (including local uncommitted edits); everything else — the engine's state\n * dataset (routinely a dedicated `workflows` dataset) and foreign content\n * refs — observes through the App SDK store. When the engine dataset differs\n * from the workspace, an SDK instance is bootstrapped automatically from the\n * Studio session (pass `opts.sdk` to supply your own — required for\n * cookie-authenticated studios, where no session token is readable). Returns\n * `{evaluation, ready, guards, tick, fireAction}`; the consumer decides when\n * to advance. Must render inside Studio source context (so\n * `useDocumentStore`/`useSource` resolve).\n */\nexport function useWorkflowInstance(\n engine: Engine,\n instanceId: string,\n opts?: {access?: WorkflowAccessOverride; grantsFromPath?: string; sdk?: SanityInstance},\n): WorkflowInstanceController {\n const documentStore = useDocumentStore()\n const {projectId, dataset} = useSource()\n const mounted = useMemo(() => ({projectId, dataset}), [projectId, dataset])\n const engineResource = useMemo(() => datasetResourceId(engine.workflowResource), [engine])\n const sdk = useEmbeddedSdk(\n opts?.sdk,\n // The engine dataset being foreign is the signal an SDK instance is\n // needed; foreign content refs without one still fail loud per ref.\n !sameDataset(engineResource, mounted),\n mounted,\n )\n const observer = useMemo(\n () =>\n makeStudioObserver(documentStore, {\n mounted,\n engineResource,\n ...(sdk !== undefined ? {sdk} : {}),\n }),\n [documentStore, mounted, engineResource, sdk],\n )\n return useWorkflowSession(engine, instanceId, observer, sessionOptions(opts))\n}\n\n/** Only the session options cross into the engine — `sdk` is a store handle\n * for the observer, never an `engine.instance(...)` argument. */\nfunction sessionOptions(\n opts: {access?: WorkflowAccessOverride; grantsFromPath?: string; sdk?: SanityInstance} = {},\n): {access?: WorkflowAccessOverride; grantsFromPath?: string} {\n const {sdk: _, ...session} = opts\n return session\n}\n\n/**\n * The supplied SDK instance, or one bootstrapped from the Studio session's\n * token when foreign observation is needed. Lifecycle mirrors the SDK's own\n * `ResourceProvider`: created in render (the observer needs it synchronously),\n * recreated when a previous unmount disposed it, and released via\n * {@link useDeferredDispose} — so StrictMode's unmount/remount probe neither\n * leaks an instance nor hands the remount a dead one. Exported for tests only.\n */\nexport function useEmbeddedSdk(\n supplied: SanityInstance | undefined,\n needed: boolean,\n mounted: DatasetResourceId,\n): SanityInstance | undefined {\n const wanted = supplied === undefined && needed\n const held = useRef<{key: string; instance: SanityInstance} | undefined>(undefined)\n const key = `${mounted.projectId}.${mounted.dataset}`\n if (wanted && (held.current?.key !== key || held.current.instance.isDisposed())) {\n held.current = {key, instance: bootstrapInstance(mounted)}\n }\n const embedded = wanted ? held.current?.instance : undefined\n useDeferredDispose(embedded)\n return supplied ?? embedded\n}\n\n/** An SDK instance over the workspace's own project, authed with the Studio\n * session's token — the engine dataset is reached per-resource from here. */\nfunction bootstrapInstance(mounted: DatasetResourceId): SanityInstance {\n return createSanityInstance({\n projectId: mounted.projectId,\n dataset: mounted.dataset,\n auth: {token: studioSessionToken(mounted.projectId)},\n })\n}\n\n/**\n * Dispose `instance` after unmount via a zero-delay timer that an immediate\n * remount of the same instance cancels — the SDK `ResourceProvider`'s own\n * idiom for surviving StrictMode's unmount/remount probe.\n */\nfunction useDeferredDispose(instance: SanityInstance | undefined): void {\n const disposal = useRef<\n {instance: SanityInstance; timeoutId: ReturnType<typeof setTimeout>} | undefined\n >(undefined)\n useEffect(() => {\n if (instance === undefined) return undefined\n if (disposal.current?.instance === instance) {\n clearTimeout(disposal.current.timeoutId)\n disposal.current = undefined\n }\n return () => {\n disposal.current = {\n instance,\n timeoutId: setTimeout(() => {\n if (!instance.isDisposed()) instance.dispose()\n }, 0),\n }\n }\n }, [instance])\n}\n\n/** The Studio session's token, as persisted by Studio's own auth store.\n * Exported for tests only. */\nexport function studioSessionToken(projectId: string): string {\n const token = persistedToken(projectId)\n if (typeof token !== 'string') {\n throw new Error(\n `@sanity/workflow-studio: the engine's state dataset is not this workspace's dataset, and no Studio session token is readable to bootstrap an App SDK instance (cookie-authenticated studio?). Pass one explicitly: useWorkflowInstance(engine, id, {sdk}).`,\n )\n }\n return token\n}\n\n/** Best-effort read of Studio's persisted `{token}` localStorage entry — any\n * unreadable shape (absent key, malformed JSON, non-object value) resolves to\n * `undefined` so every failure mode funnels into the one actionable error. */\nfunction persistedToken(projectId: string): unknown {\n const raw = window.localStorage.getItem(`__studio_auth_token_${projectId}`)\n if (raw === null) return undefined\n try {\n const parsed: unknown = JSON.parse(raw)\n if (typeof parsed !== 'object' || parsed === null) return undefined\n return (parsed as {token?: unknown}).token\n } catch {\n return undefined\n }\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/workflow-studio",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "React adapter that drives the @sanity/workflow-engine reactive session from the Sanity Studio document store.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -40,17 +40,25 @@
40
40
  "access": "restricted"
41
41
  },
42
42
  "dependencies": {
43
- "@sanity/workflow-engine": "0.3.0",
44
- "@sanity/workflow-react": "0.2.0"
43
+ "@sanity/workflow-engine": "0.4.0",
44
+ "@sanity/workflow-react": "0.3.0",
45
+ "@sanity/workflow-sdk": "0.3.0"
45
46
  },
46
47
  "devDependencies": {
47
48
  "@sanity/pkg-utils": "^10.5.2",
49
+ "@sanity/sdk": "^2.12.0",
50
+ "@sanity/sdk-react": "^2.12.0",
51
+ "@testing-library/react": "^16.3.2",
48
52
  "@types/react": "^19.2.17",
53
+ "jsdom": "^29.1.1",
49
54
  "react": "^19.2.7",
55
+ "react-dom": "^19.2.7",
50
56
  "sanity": "^5.30.0",
51
57
  "vitest": "^4.1.8"
52
58
  },
53
59
  "peerDependencies": {
60
+ "@sanity/sdk": "^2.12.0",
61
+ "@sanity/sdk-react": "^2.12.0",
54
62
  "react": "^19.2.7",
55
63
  "sanity": "^5.30.0"
56
64
  },