@sanity/workflow-studio 0.10.0 → 0.20.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 +27 -0
- package/README.md +7 -4
- package/dist/index.cjs +64 -60
- package/dist/index.d.cts +12 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.js +65 -59
- package/package.json +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @sanity/workflow-studio
|
|
2
2
|
|
|
3
|
+
## 0.20.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e860898: **BREAKING:** Published Editorial Workflows packages now ship as one fixed release stack and require exact-version peers for every shared runtime package. Install the matching stack so the engine, reactive core, adapters, tools, and UI cannot silently load private or version-skewed copies.
|
|
8
|
+
- e3122cc: `useWorkflowSession` gains `guardScope` — the co-mounted session id-set passed through to the observer so guard observation consolidates into one shared live query per resource.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- bce09fc: Restore automatic effect request tags while preserving concrete handler client APIs, backed by native request-prefix support in the test client.
|
|
13
|
+
- ab5e454: Keep one indexed project-member directory per Studio client, batch profile requests, and back off failed loads.
|
|
14
|
+
- Updated dependencies [ab5e454]
|
|
15
|
+
- Updated dependencies [bce09fc]
|
|
16
|
+
- Updated dependencies [efb4cd9]
|
|
17
|
+
- Updated dependencies [46b0285]
|
|
18
|
+
- Updated dependencies [ab5e454]
|
|
19
|
+
- Updated dependencies [8a76c67]
|
|
20
|
+
- Updated dependencies [e3122cc]
|
|
21
|
+
- Updated dependencies [e3122cc]
|
|
22
|
+
- Updated dependencies [7e8f459]
|
|
23
|
+
- Updated dependencies [98e488e]
|
|
24
|
+
- Updated dependencies [7c4dd86]
|
|
25
|
+
- Updated dependencies [e860898]
|
|
26
|
+
- @sanity/workflow-sdk@0.20.0
|
|
27
|
+
- @sanity/workflow-engine@0.20.0
|
|
28
|
+
- @sanity/workflow-react@0.20.0
|
|
29
|
+
|
|
3
30
|
## 0.10.0
|
|
4
31
|
|
|
5
32
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -34,10 +34,11 @@ Studio source context (`useSource` / `useClient`).
|
|
|
34
34
|
- `useStudioProjectUsers` — the shared Studio project-user directory used by
|
|
35
35
|
actor resolution and member pickers. It returns the upstream membership and
|
|
36
36
|
profile records without an Editorial Workflows projection, plus the engine's
|
|
37
|
-
`ProjectUserDirectory` integration. Concurrent loads are coalesced
|
|
38
|
-
settled
|
|
39
|
-
|
|
40
|
-
React when a Studio client and project ID
|
|
37
|
+
`ProjectUserDirectory` integration. Concurrent loads are coalesced, the
|
|
38
|
+
settled successful directory is reused for the client/project lifetime, and
|
|
39
|
+
failed loads retry after a 30-second backoff. `studioProjectUserDirectory`
|
|
40
|
+
provides the same contract outside React when a Studio client and project ID
|
|
41
|
+
are already available.
|
|
41
42
|
|
|
42
43
|
## How observation routes
|
|
43
44
|
|
|
@@ -85,6 +86,8 @@ This adapter composes `@sanity/workflow-sdk`'s store layer through its headless
|
|
|
85
86
|
`./observer` entry. What that means for installs:
|
|
86
87
|
|
|
87
88
|
- **`@sanity/sdk` is required** (peer) — it addresses every observed resource.
|
|
89
|
+
- **`@sanity/workflow-engine` is required** (peer) — the host and adapter share one engine runtime.
|
|
90
|
+
- **`@sanity/workflow-react` and `@sanity/workflow-sdk` are required** (exact-version peers) — install the matching Editorial Workflows release stack so the reactive context and SDK store are single-sourced.
|
|
88
91
|
- **`@sanity/sdk-react` is NOT required** — nothing here touches the App
|
|
89
92
|
SDK's React binding.
|
|
90
93
|
|
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: !0
|
|
5
5
|
});
|
|
6
6
|
|
|
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");
|
|
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"), projectUsers = require("@sanity/workflow-sdk/project-users");
|
|
8
8
|
|
|
9
9
|
function makeStudioObserver(sdk2, options) {
|
|
10
10
|
const {client: client, engineResource: engineResource} = options;
|
|
@@ -142,7 +142,7 @@ function studioResourceClients(client) {
|
|
|
142
142
|
}) : void 0;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
function useWorkflowSession({engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, sdk: sdk2}) {
|
|
145
|
+
function useWorkflowSession({engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, guardScope: guardScope, sdk: sdk2}) {
|
|
146
146
|
const observer2 = useStudioObserver({
|
|
147
147
|
engine: engine,
|
|
148
148
|
sdk: sdk2
|
|
@@ -152,6 +152,9 @@ function useWorkflowSession({engine: engine, instanceId: instanceId, grantsFromP
|
|
|
152
152
|
instanceId: instanceId,
|
|
153
153
|
observer: observer2,
|
|
154
154
|
host: "studio",
|
|
155
|
+
...guardScope !== void 0 ? {
|
|
156
|
+
guardScope: guardScope
|
|
157
|
+
} : {},
|
|
155
158
|
opts: {
|
|
156
159
|
grantsFromPath: grantsFromPath
|
|
157
160
|
}
|
|
@@ -184,7 +187,7 @@ function useDocumentWorkflows({engine: engine, document: document, sdk: sdk2}) {
|
|
|
184
187
|
});
|
|
185
188
|
}
|
|
186
189
|
|
|
187
|
-
const
|
|
190
|
+
const PROJECT_USERS_RETRY_MS = 3e4;
|
|
188
191
|
|
|
189
192
|
function isRecord(value) {
|
|
190
193
|
return typeof value == "object" && value !== null && !Array.isArray(value);
|
|
@@ -198,10 +201,6 @@ function isMembership(value) {
|
|
|
198
201
|
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
202
|
}
|
|
200
203
|
|
|
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
204
|
async function fetchProjectUsers(client, projectId) {
|
|
206
205
|
const project = await client.request({
|
|
207
206
|
uri: `/projects/${projectId}`
|
|
@@ -211,9 +210,11 @@ async function fetchProjectUsers(client, projectId) {
|
|
|
211
210
|
if (!membershipRows.every(isMembership)) throw new Error("Studio project user response contained an invalid membership row");
|
|
212
211
|
const memberships = membershipRows.filter(member => !member.isRobot);
|
|
213
212
|
if (memberships.length === 0) return [];
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
const profiles = await projectUsers.loadProjectUserProfiles({
|
|
214
|
+
client: client,
|
|
215
|
+
projectId: projectId,
|
|
216
|
+
ids: memberships.map(member => member.id)
|
|
217
|
+
}), profileById = new Map(profiles.map(profile => [ profile.id, profile ]));
|
|
217
218
|
return memberships.map(membership => ({
|
|
218
219
|
membership: membership,
|
|
219
220
|
profile: profileById.get(membership.id)
|
|
@@ -222,6 +223,40 @@ async function fetchProjectUsers(client, projectId) {
|
|
|
222
223
|
|
|
223
224
|
const projectUsersByClient = /* @__PURE__ */ new WeakMap;
|
|
224
225
|
|
|
226
|
+
function createProjectUsersStore(client, projectId) {
|
|
227
|
+
let snapshot = {
|
|
228
|
+
users: [],
|
|
229
|
+
loading: !1,
|
|
230
|
+
error: void 0
|
|
231
|
+
}, pending, retryAt = 0, usersById = /* @__PURE__ */ new Map, usersByEmail = /* @__PURE__ */ new Map;
|
|
232
|
+
const listeners = /* @__PURE__ */ new Set, setSnapshot = next => {
|
|
233
|
+
snapshot = next, listeners.forEach(listener => listener());
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
getSnapshot: () => snapshot,
|
|
237
|
+
load: () => pending || (snapshot.users.length > 0 || snapshot.error === void 0 && retryAt === 1 / 0 ? Promise.resolve(snapshot.users) : snapshot.error !== void 0 && Date.now() < retryAt ? Promise.reject(snapshot.error) : (setSnapshot({
|
|
238
|
+
...snapshot,
|
|
239
|
+
loading: !0,
|
|
240
|
+
error: void 0
|
|
241
|
+
}), pending = fetchProjectUsers(client, projectId).then(users => (usersById = new Map(users.map(user => [ user.membership.id, user ])),
|
|
242
|
+
usersByEmail = new Map(users.flatMap(user => user.profile?.email ? [ [ user.profile.email.toLowerCase(), user ] ] : [])),
|
|
243
|
+
retryAt = 1 / 0, setSnapshot({
|
|
244
|
+
users: users,
|
|
245
|
+
loading: !1,
|
|
246
|
+
error: void 0
|
|
247
|
+
}), pending = void 0, users), error => {
|
|
248
|
+
throw retryAt = Date.now() + PROJECT_USERS_RETRY_MS, setSnapshot({
|
|
249
|
+
users: [],
|
|
250
|
+
loading: !1,
|
|
251
|
+
error: error
|
|
252
|
+
}), pending = void 0, error;
|
|
253
|
+
}), pending)),
|
|
254
|
+
subscribe: listener => (listeners.add(listener), () => listeners.delete(listener)),
|
|
255
|
+
userByEmail: email => usersByEmail.get(email.toLowerCase()),
|
|
256
|
+
userById: id => usersById.get(id)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
225
260
|
function clientProjectUsers(client) {
|
|
226
261
|
const cached = projectUsersByClient.get(client);
|
|
227
262
|
if (cached) return cached;
|
|
@@ -229,27 +264,20 @@ function clientProjectUsers(client) {
|
|
|
229
264
|
return projectUsersByClient.set(client, created), created;
|
|
230
265
|
}
|
|
231
266
|
|
|
232
|
-
function
|
|
233
|
-
const
|
|
234
|
-
if (cached
|
|
235
|
-
|
|
236
|
-
|
|
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;
|
|
267
|
+
function projectUsersStore(client, projectId) {
|
|
268
|
+
const projectUsers2 = clientProjectUsers(client), cached = projectUsers2.get(projectId);
|
|
269
|
+
if (cached) return cached;
|
|
270
|
+
const created = createProjectUsersStore(client, projectId);
|
|
271
|
+
return projectUsers2.set(projectId, created), created;
|
|
245
272
|
}
|
|
246
273
|
|
|
247
274
|
function studioProjectUserDirectory(client, projectId) {
|
|
248
|
-
const
|
|
275
|
+
const store = projectUsersStore(client, projectId);
|
|
249
276
|
return {
|
|
250
277
|
findById: async id => {
|
|
251
278
|
try {
|
|
252
|
-
|
|
279
|
+
await store.load();
|
|
280
|
+
const user = store.userById(id);
|
|
253
281
|
return user ? {
|
|
254
282
|
status: "resolved",
|
|
255
283
|
user: user
|
|
@@ -265,7 +293,8 @@ function studioProjectUserDirectory(client, projectId) {
|
|
|
265
293
|
},
|
|
266
294
|
findByEmail: async email => {
|
|
267
295
|
try {
|
|
268
|
-
|
|
296
|
+
await store.load();
|
|
297
|
+
const user = store.userByEmail(email);
|
|
269
298
|
return user ? {
|
|
270
299
|
status: "resolved",
|
|
271
300
|
user: user
|
|
@@ -282,44 +311,19 @@ function studioProjectUserDirectory(client, projectId) {
|
|
|
282
311
|
};
|
|
283
312
|
}
|
|
284
313
|
|
|
314
|
+
const missingProjectSnapshot = {
|
|
315
|
+
users: [],
|
|
316
|
+
loading: !1,
|
|
317
|
+
error: new Error("No projectId on the Studio client")
|
|
318
|
+
}, noopSubscribe = () => () => {};
|
|
319
|
+
|
|
285
320
|
function useStudioProjectUsers() {
|
|
286
321
|
const client = sanity.useClient({
|
|
287
322
|
apiVersion: workflowEngine.ENGINE_API_VERSION
|
|
288
|
-
}), projectId = client.config().projectId, directory = react.useMemo(() => projectId ? studioProjectUserDirectory(client, projectId) : void 0, [ client, projectId ]), [
|
|
289
|
-
users: [],
|
|
290
|
-
loading: projectId !== void 0,
|
|
291
|
-
error: projectId === void 0 ? new Error("No projectId on the Studio client") : void 0
|
|
292
|
-
});
|
|
323
|
+
}), projectId = client.config().projectId, directory = react.useMemo(() => projectId ? studioProjectUserDirectory(client, projectId) : void 0, [ client, projectId ]), store = react.useMemo(() => projectId ? projectUsersStore(client, projectId) : void 0, [ client, projectId ]), state = react.useSyncExternalStore(store?.subscribe ?? noopSubscribe, store?.getSnapshot ?? (() => missingProjectSnapshot), store?.getSnapshot ?? (() => missingProjectSnapshot));
|
|
293
324
|
return react.useEffect(() => {
|
|
294
|
-
|
|
295
|
-
|
|
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 ]), {
|
|
325
|
+
store && store.load().catch(() => {});
|
|
326
|
+
}, [ store ]), {
|
|
323
327
|
...state,
|
|
324
328
|
directory: directory
|
|
325
329
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -7,10 +7,10 @@ import type { InstancesQueryFilter } from "@sanity/workflow-engine";
|
|
|
7
7
|
import { MissingHandlerPolicy } from "@sanity/workflow-engine";
|
|
8
8
|
import { MutationGuardDoc } from "@sanity/workflow-react";
|
|
9
9
|
import { ProjectUserDirectory } from "@sanity/workflow-engine";
|
|
10
|
+
import { ProjectUserProfile } from "@sanity/workflow-sdk/project-users";
|
|
10
11
|
import { ResourceClientResolver } from "@sanity/workflow-engine";
|
|
11
12
|
import { SanityClient } from "sanity";
|
|
12
13
|
import { SanityInstance } from "@sanity/sdk";
|
|
13
|
-
import { User } from "sanity";
|
|
14
14
|
import { WorkflowInstanceList } from "@sanity/workflow-react";
|
|
15
15
|
import { WorkflowObserver } from "@sanity/workflow-react/observer";
|
|
16
16
|
import { WorkflowResource } from "@sanity/workflow-engine";
|
|
@@ -81,12 +81,13 @@ declare interface StudioUserClient {
|
|
|
81
81
|
readonly config: () => {
|
|
82
82
|
readonly projectId?: string;
|
|
83
83
|
};
|
|
84
|
-
readonly request: (options: {
|
|
84
|
+
readonly request: (options: {
|
|
85
|
+
readonly uri: string;
|
|
86
|
+
readonly tag?: string;
|
|
87
|
+
}) => Promise<unknown>;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
export declare interface StudioUserProfile extends Omit<User, "imageUrl"> {
|
|
89
|
-
readonly imageUrl?: string | null;
|
|
90
|
+
export declare interface StudioUserProfile extends ProjectUserProfile {
|
|
90
91
|
readonly [key: string]: unknown;
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -173,11 +174,17 @@ export declare function useWorkflowSession({
|
|
|
173
174
|
engine,
|
|
174
175
|
instanceId,
|
|
175
176
|
grantsFromPath,
|
|
177
|
+
guardScope,
|
|
176
178
|
sdk,
|
|
177
179
|
}: {
|
|
178
180
|
engine: Engine;
|
|
179
181
|
instanceId: string;
|
|
180
182
|
grantsFromPath?: string;
|
|
183
|
+
/** The id-set of every co-mounted session, handed identically to each one
|
|
184
|
+
* so guard observation consolidates into one shared live query per
|
|
185
|
+
* resource. Must include `instanceId`. Omitted, guards are observed per
|
|
186
|
+
* instance. */
|
|
187
|
+
guardScope?: readonly string[];
|
|
181
188
|
sdk?: SanityInstance;
|
|
182
189
|
}): WorkflowSession;
|
|
183
190
|
|
package/dist/index.d.ts
CHANGED
|
@@ -7,10 +7,10 @@ import type { InstancesQueryFilter } from "@sanity/workflow-engine";
|
|
|
7
7
|
import { MissingHandlerPolicy } from "@sanity/workflow-engine";
|
|
8
8
|
import { MutationGuardDoc } from "@sanity/workflow-react";
|
|
9
9
|
import { ProjectUserDirectory } from "@sanity/workflow-engine";
|
|
10
|
+
import { ProjectUserProfile } from "@sanity/workflow-sdk/project-users";
|
|
10
11
|
import { ResourceClientResolver } from "@sanity/workflow-engine";
|
|
11
12
|
import { SanityClient } from "sanity";
|
|
12
13
|
import { SanityInstance } from "@sanity/sdk";
|
|
13
|
-
import { User } from "sanity";
|
|
14
14
|
import { WorkflowInstanceList } from "@sanity/workflow-react";
|
|
15
15
|
import { WorkflowObserver } from "@sanity/workflow-react/observer";
|
|
16
16
|
import { WorkflowResource } from "@sanity/workflow-engine";
|
|
@@ -81,12 +81,13 @@ declare interface StudioUserClient {
|
|
|
81
81
|
readonly config: () => {
|
|
82
82
|
readonly projectId?: string;
|
|
83
83
|
};
|
|
84
|
-
readonly request: (options: {
|
|
84
|
+
readonly request: (options: {
|
|
85
|
+
readonly uri: string;
|
|
86
|
+
readonly tag?: string;
|
|
87
|
+
}) => Promise<unknown>;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
export declare interface StudioUserProfile extends Omit<User, "imageUrl"> {
|
|
89
|
-
readonly imageUrl?: string | null;
|
|
90
|
+
export declare interface StudioUserProfile extends ProjectUserProfile {
|
|
90
91
|
readonly [key: string]: unknown;
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -173,11 +174,17 @@ export declare function useWorkflowSession({
|
|
|
173
174
|
engine,
|
|
174
175
|
instanceId,
|
|
175
176
|
grantsFromPath,
|
|
177
|
+
guardScope,
|
|
176
178
|
sdk,
|
|
177
179
|
}: {
|
|
178
180
|
engine: Engine;
|
|
179
181
|
instanceId: string;
|
|
180
182
|
grantsFromPath?: string;
|
|
183
|
+
/** The id-set of every co-mounted session, handed identically to each one
|
|
184
|
+
* so guard observation consolidates into one shared live query per
|
|
185
|
+
* resource. Must include `instanceId`. Omitted, guards are observed per
|
|
186
|
+
* instance. */
|
|
187
|
+
guardScope?: readonly string[];
|
|
181
188
|
sdk?: SanityInstance;
|
|
182
189
|
}): WorkflowSession;
|
|
183
190
|
|
package/dist/index.js
CHANGED
|
@@ -6,12 +6,14 @@ import { createSanityInstance } from "@sanity/sdk";
|
|
|
6
6
|
|
|
7
7
|
import { ENGINE_API_VERSION, createEngine, EXECUTION_KINDS } from "@sanity/workflow-engine";
|
|
8
8
|
|
|
9
|
-
import { useMemo, useEffect,
|
|
9
|
+
import { useMemo, useEffect, useSyncExternalStore } from "react";
|
|
10
10
|
|
|
11
11
|
import { useSource, useClient } from "sanity";
|
|
12
12
|
|
|
13
13
|
import { useWorkflowTelemetry, useWorkflowSession as useWorkflowSession$1, useDocumentWorkflows as useDocumentWorkflows$1, useWorkflowInstances as useWorkflowInstances$1 } from "@sanity/workflow-react";
|
|
14
14
|
|
|
15
|
+
import { loadProjectUserProfiles } from "@sanity/workflow-sdk/project-users";
|
|
16
|
+
|
|
15
17
|
function makeStudioObserver(sdk, options) {
|
|
16
18
|
const {client: client, engineResource: engineResource} = options;
|
|
17
19
|
return {
|
|
@@ -148,7 +150,7 @@ function studioResourceClients(client) {
|
|
|
148
150
|
}) : void 0;
|
|
149
151
|
}
|
|
150
152
|
|
|
151
|
-
function useWorkflowSession({engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, sdk: sdk}) {
|
|
153
|
+
function useWorkflowSession({engine: engine, instanceId: instanceId, grantsFromPath: grantsFromPath, guardScope: guardScope, sdk: sdk}) {
|
|
152
154
|
const observer = useStudioObserver({
|
|
153
155
|
engine: engine,
|
|
154
156
|
sdk: sdk
|
|
@@ -158,6 +160,9 @@ function useWorkflowSession({engine: engine, instanceId: instanceId, grantsFromP
|
|
|
158
160
|
instanceId: instanceId,
|
|
159
161
|
observer: observer,
|
|
160
162
|
host: "studio",
|
|
163
|
+
...guardScope !== void 0 ? {
|
|
164
|
+
guardScope: guardScope
|
|
165
|
+
} : {},
|
|
161
166
|
opts: {
|
|
162
167
|
grantsFromPath: grantsFromPath
|
|
163
168
|
}
|
|
@@ -190,7 +195,7 @@ function useDocumentWorkflows({engine: engine, document: document, sdk: sdk}) {
|
|
|
190
195
|
});
|
|
191
196
|
}
|
|
192
197
|
|
|
193
|
-
const
|
|
198
|
+
const PROJECT_USERS_RETRY_MS = 3e4;
|
|
194
199
|
|
|
195
200
|
function isRecord(value) {
|
|
196
201
|
return typeof value == "object" && value !== null && !Array.isArray(value);
|
|
@@ -204,10 +209,6 @@ function isMembership(value) {
|
|
|
204
209
|
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));
|
|
205
210
|
}
|
|
206
211
|
|
|
207
|
-
function isUser(value) {
|
|
208
|
-
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");
|
|
209
|
-
}
|
|
210
|
-
|
|
211
212
|
async function fetchProjectUsers(client, projectId) {
|
|
212
213
|
const project = await client.request({
|
|
213
214
|
uri: `/projects/${projectId}`
|
|
@@ -217,9 +218,11 @@ async function fetchProjectUsers(client, projectId) {
|
|
|
217
218
|
if (!membershipRows.every(isMembership)) throw new Error("Studio project user response contained an invalid membership row");
|
|
218
219
|
const memberships = membershipRows.filter(member => !member.isRobot);
|
|
219
220
|
if (memberships.length === 0) return [];
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
const profiles = await loadProjectUserProfiles({
|
|
222
|
+
client: client,
|
|
223
|
+
projectId: projectId,
|
|
224
|
+
ids: memberships.map(member => member.id)
|
|
225
|
+
}), profileById = new Map(profiles.map(profile => [ profile.id, profile ]));
|
|
223
226
|
return memberships.map(membership => ({
|
|
224
227
|
membership: membership,
|
|
225
228
|
profile: profileById.get(membership.id)
|
|
@@ -228,6 +231,40 @@ async function fetchProjectUsers(client, projectId) {
|
|
|
228
231
|
|
|
229
232
|
const projectUsersByClient = /* @__PURE__ */ new WeakMap;
|
|
230
233
|
|
|
234
|
+
function createProjectUsersStore(client, projectId) {
|
|
235
|
+
let snapshot = {
|
|
236
|
+
users: [],
|
|
237
|
+
loading: !1,
|
|
238
|
+
error: void 0
|
|
239
|
+
}, pending, retryAt = 0, usersById = /* @__PURE__ */ new Map, usersByEmail = /* @__PURE__ */ new Map;
|
|
240
|
+
const listeners = /* @__PURE__ */ new Set, setSnapshot = next => {
|
|
241
|
+
snapshot = next, listeners.forEach(listener => listener());
|
|
242
|
+
};
|
|
243
|
+
return {
|
|
244
|
+
getSnapshot: () => snapshot,
|
|
245
|
+
load: () => pending || (snapshot.users.length > 0 || snapshot.error === void 0 && retryAt === 1 / 0 ? Promise.resolve(snapshot.users) : snapshot.error !== void 0 && Date.now() < retryAt ? Promise.reject(snapshot.error) : (setSnapshot({
|
|
246
|
+
...snapshot,
|
|
247
|
+
loading: !0,
|
|
248
|
+
error: void 0
|
|
249
|
+
}), pending = fetchProjectUsers(client, projectId).then(users => (usersById = new Map(users.map(user => [ user.membership.id, user ])),
|
|
250
|
+
usersByEmail = new Map(users.flatMap(user => user.profile?.email ? [ [ user.profile.email.toLowerCase(), user ] ] : [])),
|
|
251
|
+
retryAt = 1 / 0, setSnapshot({
|
|
252
|
+
users: users,
|
|
253
|
+
loading: !1,
|
|
254
|
+
error: void 0
|
|
255
|
+
}), pending = void 0, users), error => {
|
|
256
|
+
throw retryAt = Date.now() + PROJECT_USERS_RETRY_MS, setSnapshot({
|
|
257
|
+
users: [],
|
|
258
|
+
loading: !1,
|
|
259
|
+
error: error
|
|
260
|
+
}), pending = void 0, error;
|
|
261
|
+
}), pending)),
|
|
262
|
+
subscribe: listener => (listeners.add(listener), () => listeners.delete(listener)),
|
|
263
|
+
userByEmail: email => usersByEmail.get(email.toLowerCase()),
|
|
264
|
+
userById: id => usersById.get(id)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
231
268
|
function clientProjectUsers(client) {
|
|
232
269
|
const cached = projectUsersByClient.get(client);
|
|
233
270
|
if (cached) return cached;
|
|
@@ -235,27 +272,20 @@ function clientProjectUsers(client) {
|
|
|
235
272
|
return projectUsersByClient.set(client, created), created;
|
|
236
273
|
}
|
|
237
274
|
|
|
238
|
-
function
|
|
275
|
+
function projectUsersStore(client, projectId) {
|
|
239
276
|
const projectUsers = clientProjectUsers(client), cached = projectUsers.get(projectId);
|
|
240
|
-
if (cached
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
promise: pending,
|
|
244
|
-
expiresAt: Number.POSITIVE_INFINITY
|
|
245
|
-
};
|
|
246
|
-
return projectUsers.set(projectId, entry), pending.then(() => {
|
|
247
|
-
entry.expiresAt = Date.now() + PROJECT_USERS_TTL_MS;
|
|
248
|
-
}, () => {
|
|
249
|
-
projectUsers.get(projectId) === entry && projectUsers.delete(projectId);
|
|
250
|
-
}), pending;
|
|
277
|
+
if (cached) return cached;
|
|
278
|
+
const created = createProjectUsersStore(client, projectId);
|
|
279
|
+
return projectUsers.set(projectId, created), created;
|
|
251
280
|
}
|
|
252
281
|
|
|
253
282
|
function studioProjectUserDirectory(client, projectId) {
|
|
254
|
-
const
|
|
283
|
+
const store = projectUsersStore(client, projectId);
|
|
255
284
|
return {
|
|
256
285
|
findById: async id => {
|
|
257
286
|
try {
|
|
258
|
-
|
|
287
|
+
await store.load();
|
|
288
|
+
const user = store.userById(id);
|
|
259
289
|
return user ? {
|
|
260
290
|
status: "resolved",
|
|
261
291
|
user: user
|
|
@@ -271,7 +301,8 @@ function studioProjectUserDirectory(client, projectId) {
|
|
|
271
301
|
},
|
|
272
302
|
findByEmail: async email => {
|
|
273
303
|
try {
|
|
274
|
-
|
|
304
|
+
await store.load();
|
|
305
|
+
const user = store.userByEmail(email);
|
|
275
306
|
return user ? {
|
|
276
307
|
status: "resolved",
|
|
277
308
|
user: user
|
|
@@ -288,44 +319,19 @@ function studioProjectUserDirectory(client, projectId) {
|
|
|
288
319
|
};
|
|
289
320
|
}
|
|
290
321
|
|
|
322
|
+
const missingProjectSnapshot = {
|
|
323
|
+
users: [],
|
|
324
|
+
loading: !1,
|
|
325
|
+
error: new Error("No projectId on the Studio client")
|
|
326
|
+
}, noopSubscribe = () => () => {};
|
|
327
|
+
|
|
291
328
|
function useStudioProjectUsers() {
|
|
292
329
|
const client = useClient({
|
|
293
330
|
apiVersion: ENGINE_API_VERSION
|
|
294
|
-
}), projectId = client.config().projectId, directory = useMemo(() => projectId ? studioProjectUserDirectory(client, projectId) : void 0, [ client, projectId ]), [
|
|
295
|
-
users: [],
|
|
296
|
-
loading: projectId !== void 0,
|
|
297
|
-
error: projectId === void 0 ? new Error("No projectId on the Studio client") : void 0
|
|
298
|
-
});
|
|
331
|
+
}), projectId = client.config().projectId, directory = useMemo(() => projectId ? studioProjectUserDirectory(client, projectId) : void 0, [ client, projectId ]), store = useMemo(() => projectId ? projectUsersStore(client, projectId) : void 0, [ client, projectId ]), state = useSyncExternalStore(store?.subscribe ?? noopSubscribe, store?.getSnapshot ?? (() => missingProjectSnapshot), store?.getSnapshot ?? (() => missingProjectSnapshot));
|
|
299
332
|
return useEffect(() => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
users: [],
|
|
303
|
-
loading: !1,
|
|
304
|
-
error: new Error("No projectId on the Studio client")
|
|
305
|
-
});
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
let cancelled = !1;
|
|
309
|
-
return setState({
|
|
310
|
-
users: [],
|
|
311
|
-
loading: !0,
|
|
312
|
-
error: void 0
|
|
313
|
-
}), loadProjectUsers(client, projectId).then(users => {
|
|
314
|
-
cancelled || setState({
|
|
315
|
-
users: users,
|
|
316
|
-
loading: !1,
|
|
317
|
-
error: void 0
|
|
318
|
-
});
|
|
319
|
-
}, error => {
|
|
320
|
-
cancelled || setState({
|
|
321
|
-
users: [],
|
|
322
|
-
loading: !1,
|
|
323
|
-
error: error
|
|
324
|
-
});
|
|
325
|
-
}), () => {
|
|
326
|
-
cancelled = !0;
|
|
327
|
-
};
|
|
328
|
-
}, [ client, projectId ]), {
|
|
333
|
+
store && store.load().catch(() => {});
|
|
334
|
+
}, [ store ]), {
|
|
329
335
|
...state,
|
|
330
336
|
directory: directory
|
|
331
337
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/workflow-studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "React adapter that drives the @sanity/workflow-engine reactive session in Sanity Studio through the App SDK store.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -40,11 +40,6 @@
|
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
|
-
"dependencies": {
|
|
44
|
-
"@sanity/workflow-engine": "0.19.0",
|
|
45
|
-
"@sanity/workflow-react": "0.14.0",
|
|
46
|
-
"@sanity/workflow-sdk": "0.11.0"
|
|
47
|
-
},
|
|
48
43
|
"devDependencies": {
|
|
49
44
|
"@sanity/pkg-utils": "^10.5.2",
|
|
50
45
|
"@sanity/sdk": "^2.12.0",
|
|
@@ -54,12 +49,18 @@
|
|
|
54
49
|
"react": "^19.2.7",
|
|
55
50
|
"react-dom": "^19.2.7",
|
|
56
51
|
"sanity": "^6",
|
|
57
|
-
"vitest": "^4.1.8"
|
|
52
|
+
"vitest": "^4.1.8",
|
|
53
|
+
"@sanity/workflow-engine": "0.20.0",
|
|
54
|
+
"@sanity/workflow-sdk": "0.20.0",
|
|
55
|
+
"@sanity/workflow-react": "0.20.0"
|
|
58
56
|
},
|
|
59
57
|
"peerDependencies": {
|
|
60
58
|
"@sanity/sdk": "^2.12.0",
|
|
61
59
|
"react": "^19.2.7",
|
|
62
|
-
"sanity": "^6"
|
|
60
|
+
"sanity": "^6",
|
|
61
|
+
"@sanity/workflow-engine": "0.20.0",
|
|
62
|
+
"@sanity/workflow-react": "0.20.0",
|
|
63
|
+
"@sanity/workflow-sdk": "0.20.0"
|
|
63
64
|
},
|
|
64
65
|
"engines": {
|
|
65
66
|
"node": ">=20"
|