@sanity/workflow-studio 0.8.0 → 0.9.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/CHANGELOG.md +34 -0
- package/README.md +47 -60
- package/dist/index.cjs +211 -134
- package/dist/index.d.cts +70 -52
- package/dist/index.d.ts +70 -52
- package/dist/index.js +213 -140
- package/package.json +8 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# @sanity/workflow-studio
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f1fbedd: **BREAKING:** Route every Studio reactive stream through the App SDK store, removing per-document and per-query Studio listeners. `makeStudioObserver` now accepts `(sdk, {client, engineResource})`; Studio hooks bootstrap the SDK in Studio mode so both token and cookie authentication remain supported. Reactive sessions no longer include Studio-only uncommitted form-buffer edits.
|
|
8
|
+
- d26cc1a: Add the engine-owned Actor resolution contract with client-backed resolution for server, CLI, and MCP runtimes; add native Studio and App SDK project-user integrations; and migrate the Studio plugin member picker to the shared directory.
|
|
9
|
+
- f02d026: **BREAKING:** Observer stores now expose `loading`, `ready`, `invalid`, and `error` as one reactive snapshot state instead of encoding loading and invalid outcomes in the observed value. Session and instance-list hooks surface read failures through `error` and clear them when the live stream recovers. Studio reports contextual RxJS failures while retrying, and the App SDK adapter translates state-source throws into the shared contract.
|
|
10
|
+
- 5cd8ffd: **BREAKING:** Effect handlers now receive each host's concrete Sanity client unchanged, preserving its complete runtime and type surface. Handler traffic is no longer automatically tagged as `workflow.effect` or pinned to the engine API version; handlers own that configuration.
|
|
11
|
+
- 5244335: **BREAKING:** Require Sanity Studio v6. The Studio validation harness now installs the workflow CLI alongside the plugin and builds that combined package set, guarding the shared Vite 8 toolchain.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [239d9f6]
|
|
16
|
+
- Updated dependencies [3af2ca0]
|
|
17
|
+
- Updated dependencies [7276702]
|
|
18
|
+
- Updated dependencies [58f8211]
|
|
19
|
+
- Updated dependencies [00c089d]
|
|
20
|
+
- Updated dependencies [71cd58e]
|
|
21
|
+
- Updated dependencies [cd03973]
|
|
22
|
+
- Updated dependencies [d26cc1a]
|
|
23
|
+
- Updated dependencies [9126299]
|
|
24
|
+
- Updated dependencies [f1fbedd]
|
|
25
|
+
- Updated dependencies [f02d026]
|
|
26
|
+
- Updated dependencies [72018af]
|
|
27
|
+
- Updated dependencies [5cd8ffd]
|
|
28
|
+
- Updated dependencies [b82bda4]
|
|
29
|
+
- Updated dependencies [55a54b3]
|
|
30
|
+
- Updated dependencies [b82bda4]
|
|
31
|
+
- Updated dependencies [4490442]
|
|
32
|
+
- Updated dependencies [1540dce]
|
|
33
|
+
- @sanity/workflow-engine@0.18.0
|
|
34
|
+
- @sanity/workflow-sdk@0.10.0
|
|
35
|
+
- @sanity/workflow-react@0.13.0
|
|
36
|
+
|
|
3
37
|
## 0.8.0
|
|
4
38
|
|
|
5
39
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @sanity/workflow-studio
|
|
2
2
|
|
|
3
3
|
Reactive workflow adapter for **Sanity Studio**. Everything must render inside
|
|
4
|
-
Studio source context (`
|
|
4
|
+
Studio source context (`useSource` / `useClient`).
|
|
5
5
|
|
|
6
6
|
- `useWorkflowEngine({workflowResource, tag, resourceClients?})` —
|
|
7
7
|
build the Studio-side engine once: the workspace client rebound to the
|
|
@@ -20,57 +20,58 @@ Studio source context (`useDocumentStore` / `useSource` / `useClient`).
|
|
|
20
20
|
vets it.)
|
|
21
21
|
- `useWorkflowSession` — drives an engine session for one instance and
|
|
22
22
|
returns
|
|
23
|
-
`{evaluation, ready, invalid, guards, tick, fireAction, editField, previewField, discardFieldPreview}`.
|
|
23
|
+
`{evaluation, ready, invalid, error, guards, tick, fireAction, editField, previewField, discardFieldPreview}`.
|
|
24
24
|
The observer validates every engine-owned stream, so a model-ahead or
|
|
25
25
|
corrupt doc surfaces as the explicit `invalid` state (never `ready` while
|
|
26
26
|
set) instead of an eternal spinner.
|
|
27
27
|
- `useWorkflowInstances` / `useDocumentWorkflows` — live instance lists
|
|
28
28
|
(filterable collection / per-document discovery), each returning
|
|
29
|
-
`{instances, loading, invalid}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- When
|
|
62
|
-
|
|
63
|
-
|
|
29
|
+
`{instances, loading, invalid, error}` through the App SDK query store. Hooks
|
|
30
|
+
under one Studio source share the auto-bootstrapped SDK instance; callers can
|
|
31
|
+
instead pass a shared `sdk` explicitly. SDK store errors surface reactively
|
|
32
|
+
through `error`.
|
|
33
|
+
- `useStudioProjectUsers` — the shared Studio project-user directory used by
|
|
34
|
+
actor resolution and member pickers. It returns the upstream membership and
|
|
35
|
+
profile records without an Editorial Workflows projection, plus the engine's
|
|
36
|
+
`ProjectUserDirectory` integration. Concurrent loads are coalesced and a
|
|
37
|
+
settled snapshot is reused for 30 seconds before mutable user data is
|
|
38
|
+
refreshed. `studioProjectUserDirectory` provides the same contract outside
|
|
39
|
+
React when a Studio client and project ID are already available.
|
|
40
|
+
|
|
41
|
+
## How observation routes
|
|
42
|
+
|
|
43
|
+
Every reactive stream uses the App SDK store, including documents in the
|
|
44
|
+
mounted workspace, instance documents in the engine dataset, per-resource
|
|
45
|
+
guard queries, and instance lists. Document and query observation therefore
|
|
46
|
+
share the SDK's resource-scoped live streams instead of opening one Studio
|
|
47
|
+
listener per document or query.
|
|
48
|
+
|
|
49
|
+
Dataset GDRs route through `{projectId, dataset}` resource handles. `canvas:`
|
|
50
|
+
and `media-library:` GDRs route through `{canvasId}` and `{mediaLibraryId}`.
|
|
51
|
+
`dashboard:` remains unsupported because the App SDK has no dashboard document
|
|
52
|
+
resource; a workflow with a dashboard subject must run engine-direct.
|
|
53
|
+
|
|
54
|
+
The SDK store does not expose Studio's private, uncommitted form buffer.
|
|
55
|
+
Reactive evaluation follows App SDK document state; Content Lake remains the
|
|
56
|
+
authoritative state read by commits. For an explicit uncommitted projection,
|
|
57
|
+
call the session's `previewField` callback and remove it with
|
|
58
|
+
`discardFieldPreview`; these previews never persist the instance or content
|
|
59
|
+
document.
|
|
60
|
+
|
|
61
|
+
- When no `sdk` is supplied, the hook **bootstraps an App SDK instance
|
|
62
|
+
automatically** in Studio mode. The SDK follows Studio's reactive token
|
|
63
|
+
source, including token rotation; an authenticated Studio whose token source
|
|
64
|
+
emits `null` uses cookie authentication. Callers may instead supply their own
|
|
65
|
+
instance: `useWorkflowSession({engine, instanceId, sdk})`.
|
|
64
66
|
- Guards (`observeGuards`) are observed wherever they live: a guard
|
|
65
67
|
co-locates with the subject it locks, so the stream spans the datasets the
|
|
66
68
|
instance's currently-observed GDRs name (engine dataset ∪ each
|
|
67
|
-
open-stage/workflow-scope subject dataset).
|
|
68
|
-
|
|
69
|
-
instance, the same path foreign content refs take.
|
|
69
|
+
open-stage/workflow-scope subject dataset). Every resource routes through
|
|
70
|
+
the App SDK query store.
|
|
70
71
|
- **Auth caveat for foreign refs:** the SDK instance's auth must cover the
|
|
71
72
|
foreign resource; a foreign _project_ needs a session/token valid there.
|
|
72
|
-
- `makeStudioObserver(
|
|
73
|
-
|
|
73
|
+
- `makeStudioObserver(sdk, {client, engineResource})` is exported for custom
|
|
74
|
+
wiring, matching `makeSdkObserver`. `client` is
|
|
74
75
|
required: a workspace-bound client that carries `ensureDocumentExists`
|
|
75
76
|
writes into the mounted dataset.
|
|
76
77
|
|
|
@@ -79,29 +80,15 @@ live cross-dataset end-to-end verification) are known follow-up work.
|
|
|
79
80
|
|
|
80
81
|
## Packaging
|
|
81
82
|
|
|
82
|
-
This adapter
|
|
83
|
-
its peer. Foreign-resource observation is the routine path (the engine's
|
|
84
|
-
state dataset is usually dedicated), and it routes through the SDK stores —
|
|
85
|
-
so `@sanity/workflow-sdk` is a hard dependency, consumed through its headless
|
|
83
|
+
This adapter composes `@sanity/workflow-sdk`'s store layer through its headless
|
|
86
84
|
`./observer` entry. What that means for installs:
|
|
87
85
|
|
|
88
|
-
- **`@sanity/sdk` is required** (peer) — it addresses every resource
|
|
89
|
-
the mounted workspace.
|
|
86
|
+
- **`@sanity/sdk` is required** (peer) — it addresses every observed resource.
|
|
90
87
|
- **`@sanity/sdk-react` is NOT required** — nothing here touches the App
|
|
91
88
|
SDK's React binding.
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
One stream rests on non-public Studio surface; expect Studio to be able to
|
|
96
|
-
move it in a minor, and this package to track it:
|
|
97
|
-
|
|
98
|
-
- **`documentStore.pair.editState`** (reached via `useDocumentStore`; Studio
|
|
99
|
-
marks both `@hidden` / `@beta`) — the only stream of the optimistic pair,
|
|
100
|
-
local uncommitted edits included. No public Studio API exposes it; until
|
|
101
|
-
one does, this dependency is deliberate and documented rather than hidden.
|
|
102
|
-
|
|
103
|
-
(The context hooks themselves — `useDocumentStore`, `useSource` — are
|
|
104
|
-
non-public Studio surface too, the standard plugin-context caveat.)
|
|
90
|
+
`useSource` remains a non-public Studio context hook, which is the standard
|
|
91
|
+
plugin-context caveat.
|
|
105
92
|
|
|
106
93
|
## Notes
|
|
107
94
|
|
package/dist/index.cjs
CHANGED
|
@@ -4,171 +4,103 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: !0
|
|
5
5
|
});
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var observer = require("@sanity/workflow-react/observer"), observer$1 = require("@sanity/workflow-sdk/observer"), sdk = require("@sanity/sdk"), workflowEngine = require("@sanity/workflow-engine"), react = require("react"), sanity = require("sanity"), workflowReact = require("@sanity/workflow-react");
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function observableStore({observable: observable, resolve: resolve, initial: initial, name: name}) {
|
|
12
|
-
let latest = initial;
|
|
9
|
+
function makeStudioObserver(sdk2, options) {
|
|
10
|
+
const {client: client, engineResource: engineResource} = options;
|
|
13
11
|
return {
|
|
14
|
-
|
|
15
|
-
let subscription, retry, stopped = !1;
|
|
16
|
-
const connect = () => {
|
|
17
|
-
subscription = observable.subscribe({
|
|
18
|
-
next: value => {
|
|
19
|
-
latest = resolve(value), onChange();
|
|
20
|
-
},
|
|
21
|
-
error: err => {
|
|
22
|
-
console.error(`[workflow-studio] ${name} stream errored — resubscribing in ${RESUBSCRIBE_DELAY_MS / 1e3}s:`, err),
|
|
23
|
-
stopped || (retry = setTimeout(connect, RESUBSCRIBE_DELAY_MS));
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
};
|
|
27
|
-
return connect(), () => {
|
|
28
|
-
stopped = !0, retry !== void 0 && clearTimeout(retry), subscription?.unsubscribe();
|
|
29
|
-
};
|
|
30
|
-
},
|
|
31
|
-
getSnapshot: () => latest
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function makeStudioObserver(documentStore, options) {
|
|
36
|
-
const {mounted: mounted, engineResource: engineResource, sdk: sdk2, client: client} = options, engineIsMounted = observer.sameDataset(engineResource, mounted), editStateStore = ({id: id, type: type, release: release, draftFallback: draftFallback}) => observableStore({
|
|
37
|
-
observable: documentStore.pair.editState(id, type, release),
|
|
38
|
-
resolve: editState => resolveEditState(editState, draftFallback),
|
|
39
|
-
initial: void 0,
|
|
40
|
-
name: `the edit state of "${id}"`
|
|
41
|
-
}), requireSdk = what => {
|
|
42
|
-
if (sdk2 !== void 0) return sdk2;
|
|
43
|
-
throw new Error(`@sanity/workflow-studio: ${what} — Studio's document store is bound to ${mounted.projectId}.${mounted.dataset} and cannot observe it. Pass an App SDK instance via the hook's \`sdk\` option (e.g. \`useWorkflowSession({engine, instanceId, sdk})\`) to observe foreign resources.`);
|
|
44
|
-
};
|
|
45
|
-
return {
|
|
46
|
-
observeInstance: instanceId => engineIsMounted ? observer.validatedInstanceStore(editStateStore({
|
|
47
|
-
id: instanceId,
|
|
48
|
-
type: workflowEngine.WORKFLOW_INSTANCE_TYPE,
|
|
49
|
-
release: void 0,
|
|
50
|
-
draftFallback: !0
|
|
51
|
-
})) : observer.validatedInstanceStore(observer$1.sdkInstanceDocStore({
|
|
52
|
-
sdk: requireSdk(`instance "${instanceId}" lives in the engine dataset ${engineResource.projectId}.${engineResource.dataset}`),
|
|
53
|
-
instanceId: instanceId,
|
|
12
|
+
...observer$1.makeSdkObserver(sdk2, {
|
|
54
13
|
engineResource: engineResource
|
|
55
|
-
})
|
|
56
|
-
observeDocs: (documents, perspective) => observer.combineDocStores(documents.map(ref => observer.classifyRef(ref, mounted) === "mounted-dataset" ? {
|
|
57
|
-
key: ref.globalDocumentId,
|
|
58
|
-
store: editStateStore({
|
|
59
|
-
id: ref.documentId,
|
|
60
|
-
type: ref.type,
|
|
61
|
-
release: workflowEngine.contentReleaseName({
|
|
62
|
-
ref: ref,
|
|
63
|
-
perspective: perspective
|
|
64
|
-
}),
|
|
65
|
-
draftFallback: workflowEngine.contentDraftFallback({
|
|
66
|
-
ref: ref,
|
|
67
|
-
perspective: perspective
|
|
68
|
-
})
|
|
69
|
-
})
|
|
70
|
-
} : {
|
|
71
|
-
key: ref.globalDocumentId,
|
|
72
|
-
store: observer$1.sdkContentDocStore({
|
|
73
|
-
sdk: requireSdk(`"${ref.globalDocumentId}" lives outside this workspace`),
|
|
74
|
-
ref: ref,
|
|
75
|
-
perspective: perspective
|
|
76
|
-
})
|
|
77
|
-
})),
|
|
78
|
-
observeGuards: (instanceId, resources) => observer.validatedGuardsStore(observer.combineGuardStores(resources.map(resource => {
|
|
79
|
-
if (observer.sameDataset(resource, mounted)) {
|
|
80
|
-
const {query: query, params: params} = workflowEngine.instanceGuardQuery(instanceId);
|
|
81
|
-
return observableStore({
|
|
82
|
-
observable: documentStore.listenQuery(query, params, {}),
|
|
83
|
-
resolve: docs => docs,
|
|
84
|
-
initial: void 0,
|
|
85
|
-
name: `the guard list for "${instanceId}"`
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
return observer$1.sdkGuardStore({
|
|
89
|
-
sdk: requireSdk(`guards for "${instanceId}" live in ${resource.projectId}.${resource.dataset}`),
|
|
90
|
-
instanceId: instanceId,
|
|
91
|
-
resource: resource
|
|
92
|
-
});
|
|
93
|
-
}))),
|
|
94
|
-
observeInstances: query => engineIsMounted ? observer.validatedInstancesStore(observableStore({
|
|
95
|
-
observable: documentStore.listenQuery(query.query, query.params, {}),
|
|
96
|
-
resolve: docs => docs,
|
|
97
|
-
initial: void 0,
|
|
98
|
-
name: "the instance list"
|
|
99
|
-
})) : observer.validatedInstancesStore(observer$1.sdkInstancesStore({
|
|
100
|
-
sdk: requireSdk(`the instance list lives in the engine dataset ${engineResource.projectId}.${engineResource.dataset}`),
|
|
101
|
-
query: query,
|
|
102
|
-
resource: engineResource
|
|
103
|
-
})),
|
|
14
|
+
}),
|
|
104
15
|
ensureDocumentExists: args => observer.ensureDocumentExists(client, args)
|
|
105
16
|
};
|
|
106
17
|
}
|
|
107
18
|
|
|
108
|
-
function resolveEditState(editState, draftFallback) {
|
|
109
|
-
if (!editState.ready) return;
|
|
110
|
-
const draft = draftFallback ? editState.draft : null;
|
|
111
|
-
return editState.version ?? draft ?? editState.published;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
19
|
const SESSION_CLIENT_OPTIONS = {
|
|
115
20
|
apiVersion: workflowEngine.ENGINE_API_VERSION
|
|
116
21
|
};
|
|
117
22
|
|
|
118
23
|
function useStudioObserver({engine: engine, sdk: suppliedSdk}) {
|
|
119
|
-
const
|
|
24
|
+
const source = sanity.useSource(), {projectId: projectId, dataset: dataset} = source, mounted = react.useMemo(() => ({
|
|
120
25
|
projectId: projectId,
|
|
121
26
|
dataset: dataset
|
|
122
|
-
}), [ projectId, dataset ]), engineResource = react.useMemo(() => observer.datasetResourceId(engine.workflowResource), [ engine ]), sdk2 = useEmbeddedSdk({
|
|
27
|
+
}), [ projectId, dataset ]), engineResource = react.useMemo(() => observer.datasetResourceId(engine.workflowResource), [ engine ]), studio = useStudioAuth(), sdk2 = useEmbeddedSdk({
|
|
123
28
|
supplied: suppliedSdk,
|
|
124
|
-
|
|
29
|
+
owner: source,
|
|
125
30
|
mounted: mounted,
|
|
126
|
-
|
|
31
|
+
studio: studio
|
|
127
32
|
});
|
|
128
|
-
return react.useMemo(() => makeStudioObserver(
|
|
129
|
-
mounted: mounted,
|
|
33
|
+
return react.useMemo(() => makeStudioObserver(sdk2, {
|
|
130
34
|
client: source.getClient(SESSION_CLIENT_OPTIONS),
|
|
131
|
-
engineResource: engineResource
|
|
132
|
-
|
|
133
|
-
sdk: sdk2
|
|
134
|
-
} : {}
|
|
135
|
-
}), [ documentStore, source, mounted, engineResource, sdk2 ]);
|
|
35
|
+
engineResource: engineResource
|
|
36
|
+
}), [ source, engineResource, sdk2 ]);
|
|
136
37
|
}
|
|
137
38
|
|
|
138
|
-
function
|
|
139
|
-
const
|
|
140
|
-
|
|
39
|
+
function useStudioAuth() {
|
|
40
|
+
const {authenticated: authenticated, auth: auth} = sanity.useSource();
|
|
41
|
+
return react.useMemo(() => {
|
|
42
|
+
const token = auth.token;
|
|
43
|
+
return token === void 0 ? {
|
|
44
|
+
authenticated: authenticated
|
|
45
|
+
} : {
|
|
46
|
+
authenticated: authenticated,
|
|
47
|
+
auth: {
|
|
48
|
+
token: token
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}, [ authenticated, auth.token ]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function useEmbeddedSdk({supplied: supplied, owner: owner, mounted: mounted, studio: studio}) {
|
|
55
|
+
const resolved = supplied === void 0 ? {
|
|
56
|
+
kind: "shared",
|
|
57
|
+
shared: sharedEmbeddedSdk({
|
|
58
|
+
owner: owner,
|
|
59
|
+
mounted: mounted,
|
|
60
|
+
studio: studio
|
|
61
|
+
})
|
|
62
|
+
} : {
|
|
63
|
+
kind: "supplied",
|
|
64
|
+
instance: supplied
|
|
65
|
+
};
|
|
66
|
+
return useSharedDispose(owner, resolved.kind === "shared" ? resolved.shared : void 0),
|
|
67
|
+
resolved.kind === "shared" ? resolved.shared.instance : resolved.instance;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const sharedSdks = /* @__PURE__ */ new WeakMap;
|
|
71
|
+
|
|
72
|
+
function sharedEmbeddedSdk({owner: owner, mounted: mounted, studio: studio}) {
|
|
73
|
+
const key = `${mounted.projectId}.${mounted.dataset}`;
|
|
74
|
+
let byResource = sharedSdks.get(owner);
|
|
75
|
+
byResource === void 0 && (byResource = /* @__PURE__ */ new Map, sharedSdks.set(owner, byResource));
|
|
76
|
+
const current = byResource.get(key);
|
|
77
|
+
if (current !== void 0 && !current.instance.isDisposed()) return current;
|
|
78
|
+
const shared = {
|
|
79
|
+
instance: bootstrapInstance(mounted, studio),
|
|
141
80
|
key: key,
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return useDeferredDispose(embedded), supplied ?? embedded;
|
|
81
|
+
users: 0
|
|
82
|
+
};
|
|
83
|
+
return byResource.set(key, shared), shared;
|
|
146
84
|
}
|
|
147
85
|
|
|
148
|
-
function bootstrapInstance(mounted,
|
|
149
|
-
if (sessionToken === void 0) throw new Error("@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 via the hook's `sdk` option, e.g. useWorkflowSession({engine, instanceId, sdk}).");
|
|
86
|
+
function bootstrapInstance(mounted, studio) {
|
|
150
87
|
return sdk.createSanityInstance({
|
|
151
88
|
projectId: mounted.projectId,
|
|
152
89
|
dataset: mounted.dataset,
|
|
153
|
-
|
|
154
|
-
token: sessionToken
|
|
155
|
-
}
|
|
90
|
+
studio: studio
|
|
156
91
|
});
|
|
157
92
|
}
|
|
158
93
|
|
|
159
|
-
function
|
|
160
|
-
const disposal = react.useRef(void 0);
|
|
94
|
+
function useSharedDispose(owner, shared) {
|
|
161
95
|
react.useEffect(() => {
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
instance
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}, 0)
|
|
169
|
-
};
|
|
96
|
+
if (shared !== void 0) return shared.users += 1, () => {
|
|
97
|
+
const byResource = sharedSdks.get(owner);
|
|
98
|
+
byResource?.get(shared.key) === shared && (shared.users -= 1, setTimeout(() => {
|
|
99
|
+
shared.users !== 0 || byResource.get(shared.key) !== shared || (shared.instance.isDisposed() || shared.instance.dispose(),
|
|
100
|
+
byResource.delete(shared.key), byResource.size === 0 && sharedSdks.delete(owner));
|
|
101
|
+
}, 0));
|
|
170
102
|
};
|
|
171
|
-
}, [
|
|
103
|
+
}, [ owner, shared ]);
|
|
172
104
|
}
|
|
173
105
|
|
|
174
106
|
function useWorkflowEngine({workflowResource: workflowResource, tag: tag, resourceClients: resourceClients, effectHandlers: effectHandlers, missingHandler: missingHandler}) {
|
|
@@ -252,14 +184,159 @@ function useDocumentWorkflows({engine: engine, document: document, sdk: sdk2}) {
|
|
|
252
184
|
});
|
|
253
185
|
}
|
|
254
186
|
|
|
187
|
+
const PROJECT_USERS_TTL_MS = 3e4;
|
|
188
|
+
|
|
189
|
+
function isRecord(value) {
|
|
190
|
+
return typeof value == "object" && value !== null && !Array.isArray(value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function isRole(value) {
|
|
194
|
+
return isRecord(value) && typeof value.name == "string";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function isMembership(value) {
|
|
198
|
+
return isRecord(value) && typeof value.id == "string" && (value.isRobot === void 0 || typeof value.isRobot == "boolean") && (value.roles === void 0 || Array.isArray(value.roles) && value.roles.every(isRole));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isUser(value) {
|
|
202
|
+
return isRecord(value) && typeof value.id == "string" && (value.email === void 0 || typeof value.email == "string") && (value.displayName === void 0 || typeof value.displayName == "string") && (value.imageUrl === void 0 || value.imageUrl === null || typeof value.imageUrl == "string");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function fetchProjectUsers(client, projectId) {
|
|
206
|
+
const project = await client.request({
|
|
207
|
+
uri: `/projects/${projectId}`
|
|
208
|
+
});
|
|
209
|
+
if (!isRecord(project) || !Array.isArray(project.members)) throw new Error("Studio project user response did not contain a members array");
|
|
210
|
+
const membershipRows = project.members;
|
|
211
|
+
if (!membershipRows.every(isMembership)) throw new Error("Studio project user response contained an invalid membership row");
|
|
212
|
+
const memberships = membershipRows.filter(member => !member.isRobot);
|
|
213
|
+
if (memberships.length === 0) return [];
|
|
214
|
+
const ids = memberships.map(member => encodeURIComponent(member.id)), response = await client.request({
|
|
215
|
+
uri: `/projects/${projectId}/users/${ids.join(",")}`
|
|
216
|
+
}), profiles = (Array.isArray(response) ? response : [ response ]).filter(isUser), profileById = new Map(profiles.map(profile => [ profile.id, profile ]));
|
|
217
|
+
return memberships.map(membership => ({
|
|
218
|
+
membership: membership,
|
|
219
|
+
profile: profileById.get(membership.id)
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const projectUsersByClient = /* @__PURE__ */ new WeakMap;
|
|
224
|
+
|
|
225
|
+
function clientProjectUsers(client) {
|
|
226
|
+
const cached = projectUsersByClient.get(client);
|
|
227
|
+
if (cached) return cached;
|
|
228
|
+
const created = /* @__PURE__ */ new Map;
|
|
229
|
+
return projectUsersByClient.set(client, created), created;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function loadProjectUsers(client, projectId) {
|
|
233
|
+
const projectUsers = clientProjectUsers(client), cached = projectUsers.get(projectId);
|
|
234
|
+
if (cached && cached.expiresAt > Date.now()) return cached.promise;
|
|
235
|
+
projectUsers.delete(projectId);
|
|
236
|
+
const pending = fetchProjectUsers(client, projectId), entry = {
|
|
237
|
+
promise: pending,
|
|
238
|
+
expiresAt: Number.POSITIVE_INFINITY
|
|
239
|
+
};
|
|
240
|
+
return projectUsers.set(projectId, entry), pending.then(() => {
|
|
241
|
+
entry.expiresAt = Date.now() + PROJECT_USERS_TTL_MS;
|
|
242
|
+
}, () => {
|
|
243
|
+
projectUsers.get(projectId) === entry && projectUsers.delete(projectId);
|
|
244
|
+
}), pending;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function studioProjectUserDirectory(client, projectId) {
|
|
248
|
+
const load = () => loadProjectUsers(client, projectId);
|
|
249
|
+
return {
|
|
250
|
+
findById: async id => {
|
|
251
|
+
try {
|
|
252
|
+
const user = (await load()).find(candidate => candidate.membership.id === id);
|
|
253
|
+
return user ? {
|
|
254
|
+
status: "resolved",
|
|
255
|
+
user: user
|
|
256
|
+
} : {
|
|
257
|
+
status: "missing"
|
|
258
|
+
};
|
|
259
|
+
} catch (cause) {
|
|
260
|
+
return {
|
|
261
|
+
status: "inaccessible",
|
|
262
|
+
cause: cause
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
findByEmail: async email => {
|
|
267
|
+
try {
|
|
268
|
+
const normalized = email.toLowerCase(), user = (await load()).find(candidate => candidate.profile?.email?.toLowerCase() === normalized);
|
|
269
|
+
return user ? {
|
|
270
|
+
status: "resolved",
|
|
271
|
+
user: user
|
|
272
|
+
} : {
|
|
273
|
+
status: "missing"
|
|
274
|
+
};
|
|
275
|
+
} catch (cause) {
|
|
276
|
+
return {
|
|
277
|
+
status: "inaccessible",
|
|
278
|
+
cause: cause
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function useStudioProjectUsers() {
|
|
286
|
+
const client = sanity.useClient({
|
|
287
|
+
apiVersion: workflowEngine.ENGINE_API_VERSION
|
|
288
|
+
}), projectId = client.config().projectId, directory = react.useMemo(() => projectId ? studioProjectUserDirectory(client, projectId) : void 0, [ client, projectId ]), [state, setState] = react.useState({
|
|
289
|
+
users: [],
|
|
290
|
+
loading: projectId !== void 0,
|
|
291
|
+
error: projectId === void 0 ? new Error("No projectId on the Studio client") : void 0
|
|
292
|
+
});
|
|
293
|
+
return react.useEffect(() => {
|
|
294
|
+
if (!projectId) {
|
|
295
|
+
setState({
|
|
296
|
+
users: [],
|
|
297
|
+
loading: !1,
|
|
298
|
+
error: new Error("No projectId on the Studio client")
|
|
299
|
+
});
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
let cancelled = !1;
|
|
303
|
+
return setState({
|
|
304
|
+
users: [],
|
|
305
|
+
loading: !0,
|
|
306
|
+
error: void 0
|
|
307
|
+
}), loadProjectUsers(client, projectId).then(users => {
|
|
308
|
+
cancelled || setState({
|
|
309
|
+
users: users,
|
|
310
|
+
loading: !1,
|
|
311
|
+
error: void 0
|
|
312
|
+
});
|
|
313
|
+
}, error => {
|
|
314
|
+
cancelled || setState({
|
|
315
|
+
users: [],
|
|
316
|
+
loading: !1,
|
|
317
|
+
error: error
|
|
318
|
+
});
|
|
319
|
+
}), () => {
|
|
320
|
+
cancelled = !0;
|
|
321
|
+
};
|
|
322
|
+
}, [ client, projectId ]), {
|
|
323
|
+
...state,
|
|
324
|
+
directory: directory
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
255
328
|
exports.makeStudioObserver = makeStudioObserver;
|
|
256
329
|
|
|
330
|
+
exports.studioProjectUserDirectory = studioProjectUserDirectory;
|
|
331
|
+
|
|
257
332
|
exports.studioResourceClients = studioResourceClients;
|
|
258
333
|
|
|
259
334
|
exports.useDocumentWorkflows = useDocumentWorkflows;
|
|
260
335
|
|
|
261
336
|
exports.useStudioObserver = useStudioObserver;
|
|
262
337
|
|
|
338
|
+
exports.useStudioProjectUsers = useStudioProjectUsers;
|
|
339
|
+
|
|
263
340
|
exports.useWorkflowEngine = useWorkflowEngine;
|
|
264
341
|
|
|
265
342
|
exports.useWorkflowInstances = useWorkflowInstances;
|