@sanity/workflow-studio 0.10.0 → 0.21.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 +48 -0
- package/README.md +7 -4
- package/dist/index.cjs +67 -60
- package/dist/index.d.cts +12 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.js +68 -59
- package/package.json +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @sanity/workflow-studio
|
|
2
2
|
|
|
3
|
+
## 0.21.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- bcc30fe: **BREAKING:** Every surface emits and matches the account-global user id (`sanityUserId`) — the one identity namespace the engine stores. Studio member records carry `sanityUserId` alongside the project-scoped id; the plugin's member/picker views, `useAssignmentIdentity()`, display resolution, and the new `useSelfActor()` join through it once, so pickers, "for me" matching, and self-authored actor stamps all speak global ids. `@sanity/workflow-sdk` gains `useProjectMembers(projectId)`, loading the configured project's human members as assignment rows whose `id` is the global user id (display from project profiles). `@sanity/workflow-components`' `ProjectMember.id` is documented as the account-global id, and the new `projectMemberRow` builder is the one place member-row semantics live (global id preferred, membership-id fallback) — both integrations delegate to it. "For me" and assignment matching compare global ids: instances stored with project-scoped assignees stop matching until their next engine commit self-heals them.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [d9394e5]
|
|
12
|
+
- Updated dependencies [a5b327d]
|
|
13
|
+
- Updated dependencies [a5b327d]
|
|
14
|
+
- Updated dependencies [92e28bd]
|
|
15
|
+
- Updated dependencies [f4bc057]
|
|
16
|
+
- Updated dependencies [bcc30fe]
|
|
17
|
+
- Updated dependencies [e7392af]
|
|
18
|
+
- Updated dependencies [fa9c796]
|
|
19
|
+
- Updated dependencies [bcc30fe]
|
|
20
|
+
- @sanity/workflow-engine@0.21.0
|
|
21
|
+
- @sanity/workflow-sdk@0.21.0
|
|
22
|
+
- @sanity/workflow-react@0.21.0
|
|
23
|
+
|
|
24
|
+
## 0.20.0
|
|
25
|
+
|
|
26
|
+
### Minor Changes
|
|
27
|
+
|
|
28
|
+
- 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.
|
|
29
|
+
- 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.
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- bce09fc: Restore automatic effect request tags while preserving concrete handler client APIs, backed by native request-prefix support in the test client.
|
|
34
|
+
- ab5e454: Keep one indexed project-member directory per Studio client, batch profile requests, and back off failed loads.
|
|
35
|
+
- Updated dependencies [ab5e454]
|
|
36
|
+
- Updated dependencies [bce09fc]
|
|
37
|
+
- Updated dependencies [efb4cd9]
|
|
38
|
+
- Updated dependencies [46b0285]
|
|
39
|
+
- Updated dependencies [ab5e454]
|
|
40
|
+
- Updated dependencies [8a76c67]
|
|
41
|
+
- Updated dependencies [e3122cc]
|
|
42
|
+
- Updated dependencies [e3122cc]
|
|
43
|
+
- Updated dependencies [7e8f459]
|
|
44
|
+
- Updated dependencies [98e488e]
|
|
45
|
+
- Updated dependencies [7c4dd86]
|
|
46
|
+
- Updated dependencies [e860898]
|
|
47
|
+
- @sanity/workflow-sdk@0.20.0
|
|
48
|
+
- @sanity/workflow-engine@0.20.0
|
|
49
|
+
- @sanity/workflow-react@0.20.0
|
|
50
|
+
|
|
3
51
|
## 0.10.0
|
|
4
52
|
|
|
5
53
|
### 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,43 @@ 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.flatMap(user => {
|
|
242
|
+
const entries = [ [ user.membership.id, user ] ], globalId = user.profile?.sanityUserId;
|
|
243
|
+
return globalId !== void 0 && globalId !== user.membership.id && entries.push([ globalId, user ]),
|
|
244
|
+
entries;
|
|
245
|
+
})), usersByEmail = new Map(users.flatMap(user => user.profile?.email ? [ [ user.profile.email.toLowerCase(), user ] ] : [])),
|
|
246
|
+
retryAt = 1 / 0, setSnapshot({
|
|
247
|
+
users: users,
|
|
248
|
+
loading: !1,
|
|
249
|
+
error: void 0
|
|
250
|
+
}), pending = void 0, users), error => {
|
|
251
|
+
throw retryAt = Date.now() + PROJECT_USERS_RETRY_MS, setSnapshot({
|
|
252
|
+
users: [],
|
|
253
|
+
loading: !1,
|
|
254
|
+
error: error
|
|
255
|
+
}), pending = void 0, error;
|
|
256
|
+
}), pending)),
|
|
257
|
+
subscribe: listener => (listeners.add(listener), () => listeners.delete(listener)),
|
|
258
|
+
userByEmail: email => usersByEmail.get(email.toLowerCase()),
|
|
259
|
+
userById: id => usersById.get(id)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
225
263
|
function clientProjectUsers(client) {
|
|
226
264
|
const cached = projectUsersByClient.get(client);
|
|
227
265
|
if (cached) return cached;
|
|
@@ -229,27 +267,20 @@ function clientProjectUsers(client) {
|
|
|
229
267
|
return projectUsersByClient.set(client, created), created;
|
|
230
268
|
}
|
|
231
269
|
|
|
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;
|
|
270
|
+
function projectUsersStore(client, projectId) {
|
|
271
|
+
const projectUsers2 = clientProjectUsers(client), cached = projectUsers2.get(projectId);
|
|
272
|
+
if (cached) return cached;
|
|
273
|
+
const created = createProjectUsersStore(client, projectId);
|
|
274
|
+
return projectUsers2.set(projectId, created), created;
|
|
245
275
|
}
|
|
246
276
|
|
|
247
277
|
function studioProjectUserDirectory(client, projectId) {
|
|
248
|
-
const
|
|
278
|
+
const store = projectUsersStore(client, projectId);
|
|
249
279
|
return {
|
|
250
280
|
findById: async id => {
|
|
251
281
|
try {
|
|
252
|
-
|
|
282
|
+
await store.load();
|
|
283
|
+
const user = store.userById(id);
|
|
253
284
|
return user ? {
|
|
254
285
|
status: "resolved",
|
|
255
286
|
user: user
|
|
@@ -265,7 +296,8 @@ function studioProjectUserDirectory(client, projectId) {
|
|
|
265
296
|
},
|
|
266
297
|
findByEmail: async email => {
|
|
267
298
|
try {
|
|
268
|
-
|
|
299
|
+
await store.load();
|
|
300
|
+
const user = store.userByEmail(email);
|
|
269
301
|
return user ? {
|
|
270
302
|
status: "resolved",
|
|
271
303
|
user: user
|
|
@@ -282,44 +314,19 @@ function studioProjectUserDirectory(client, projectId) {
|
|
|
282
314
|
};
|
|
283
315
|
}
|
|
284
316
|
|
|
317
|
+
const missingProjectSnapshot = {
|
|
318
|
+
users: [],
|
|
319
|
+
loading: !1,
|
|
320
|
+
error: new Error("No projectId on the Studio client")
|
|
321
|
+
}, noopSubscribe = () => () => {};
|
|
322
|
+
|
|
285
323
|
function useStudioProjectUsers() {
|
|
286
324
|
const client = sanity.useClient({
|
|
287
325
|
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
|
-
});
|
|
326
|
+
}), 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
327
|
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 ]), {
|
|
328
|
+
store && store.load().catch(() => {});
|
|
329
|
+
}, [ store ]), {
|
|
323
330
|
...state,
|
|
324
331
|
directory: directory
|
|
325
332
|
};
|
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,43 @@ 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.flatMap(user => {
|
|
250
|
+
const entries = [ [ user.membership.id, user ] ], globalId = user.profile?.sanityUserId;
|
|
251
|
+
return globalId !== void 0 && globalId !== user.membership.id && entries.push([ globalId, user ]),
|
|
252
|
+
entries;
|
|
253
|
+
})), usersByEmail = new Map(users.flatMap(user => user.profile?.email ? [ [ user.profile.email.toLowerCase(), user ] ] : [])),
|
|
254
|
+
retryAt = 1 / 0, setSnapshot({
|
|
255
|
+
users: users,
|
|
256
|
+
loading: !1,
|
|
257
|
+
error: void 0
|
|
258
|
+
}), pending = void 0, users), error => {
|
|
259
|
+
throw retryAt = Date.now() + PROJECT_USERS_RETRY_MS, setSnapshot({
|
|
260
|
+
users: [],
|
|
261
|
+
loading: !1,
|
|
262
|
+
error: error
|
|
263
|
+
}), pending = void 0, error;
|
|
264
|
+
}), pending)),
|
|
265
|
+
subscribe: listener => (listeners.add(listener), () => listeners.delete(listener)),
|
|
266
|
+
userByEmail: email => usersByEmail.get(email.toLowerCase()),
|
|
267
|
+
userById: id => usersById.get(id)
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
231
271
|
function clientProjectUsers(client) {
|
|
232
272
|
const cached = projectUsersByClient.get(client);
|
|
233
273
|
if (cached) return cached;
|
|
@@ -235,27 +275,20 @@ function clientProjectUsers(client) {
|
|
|
235
275
|
return projectUsersByClient.set(client, created), created;
|
|
236
276
|
}
|
|
237
277
|
|
|
238
|
-
function
|
|
278
|
+
function projectUsersStore(client, projectId) {
|
|
239
279
|
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;
|
|
280
|
+
if (cached) return cached;
|
|
281
|
+
const created = createProjectUsersStore(client, projectId);
|
|
282
|
+
return projectUsers.set(projectId, created), created;
|
|
251
283
|
}
|
|
252
284
|
|
|
253
285
|
function studioProjectUserDirectory(client, projectId) {
|
|
254
|
-
const
|
|
286
|
+
const store = projectUsersStore(client, projectId);
|
|
255
287
|
return {
|
|
256
288
|
findById: async id => {
|
|
257
289
|
try {
|
|
258
|
-
|
|
290
|
+
await store.load();
|
|
291
|
+
const user = store.userById(id);
|
|
259
292
|
return user ? {
|
|
260
293
|
status: "resolved",
|
|
261
294
|
user: user
|
|
@@ -271,7 +304,8 @@ function studioProjectUserDirectory(client, projectId) {
|
|
|
271
304
|
},
|
|
272
305
|
findByEmail: async email => {
|
|
273
306
|
try {
|
|
274
|
-
|
|
307
|
+
await store.load();
|
|
308
|
+
const user = store.userByEmail(email);
|
|
275
309
|
return user ? {
|
|
276
310
|
status: "resolved",
|
|
277
311
|
user: user
|
|
@@ -288,44 +322,19 @@ function studioProjectUserDirectory(client, projectId) {
|
|
|
288
322
|
};
|
|
289
323
|
}
|
|
290
324
|
|
|
325
|
+
const missingProjectSnapshot = {
|
|
326
|
+
users: [],
|
|
327
|
+
loading: !1,
|
|
328
|
+
error: new Error("No projectId on the Studio client")
|
|
329
|
+
}, noopSubscribe = () => () => {};
|
|
330
|
+
|
|
291
331
|
function useStudioProjectUsers() {
|
|
292
332
|
const client = useClient({
|
|
293
333
|
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
|
-
});
|
|
334
|
+
}), 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
335
|
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 ]), {
|
|
336
|
+
store && store.load().catch(() => {});
|
|
337
|
+
}, [ store ]), {
|
|
329
338
|
...state,
|
|
330
339
|
directory: directory
|
|
331
340
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/workflow-studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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-sdk": "0.21.0",
|
|
54
|
+
"@sanity/workflow-react": "0.21.0",
|
|
55
|
+
"@sanity/workflow-engine": "0.21.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.21.0",
|
|
62
|
+
"@sanity/workflow-react": "0.21.0",
|
|
63
|
+
"@sanity/workflow-sdk": "0.21.0"
|
|
63
64
|
},
|
|
64
65
|
"engines": {
|
|
65
66
|
"node": ">=20"
|