@powerhousedao/reactor-browser 6.2.2-dev.5 → 6.2.2-dev.50

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.
Files changed (66) hide show
  1. package/README.md +283 -0
  2. package/dist/attachment-service-CghxHYta.d.ts +970 -0
  3. package/dist/attachment-service-CghxHYta.d.ts.map +1 -0
  4. package/dist/client-BaHKLGy5.d.ts +14 -0
  5. package/dist/client-BaHKLGy5.d.ts.map +1 -0
  6. package/dist/{client-u13cr-Vg.js → client-Zz_l9QzI.js} +30 -3
  7. package/dist/client-Zz_l9QzI.js.map +1 -0
  8. package/dist/{client-_Gh2Uf0E.js → client-s_ISz_Zs.js} +30 -4
  9. package/dist/client-s_ISz_Zs.js.map +1 -0
  10. package/dist/{document-by-id-dLYFX-xz.js → document-by-id-DWEQI7wn.js} +48 -43
  11. package/dist/document-by-id-DWEQI7wn.js.map +1 -0
  12. package/dist/document-operations-D0ePGjbp.js +2778 -0
  13. package/dist/document-operations-D0ePGjbp.js.map +1 -0
  14. package/dist/global-CbyAMlZd.d.ts +400 -0
  15. package/dist/global-CbyAMlZd.d.ts.map +1 -0
  16. package/dist/index-CxxxP14A.d.ts +144 -0
  17. package/dist/index-CxxxP14A.d.ts.map +1 -0
  18. package/dist/index-DPq1VjpB.d.ts +345 -0
  19. package/dist/index-DPq1VjpB.d.ts.map +1 -0
  20. package/dist/index.d.ts +177 -695
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +256 -1627
  23. package/dist/index.js.map +1 -1
  24. package/dist/{inspector-proxy-CvQDDxmT.d.ts → inspector-proxy-CaCFdEG4.d.ts} +4 -2
  25. package/dist/inspector-proxy-CaCFdEG4.d.ts.map +1 -0
  26. package/dist/{make-ph-event-functions-BHoLPif5.js → make-ph-event-functions-DBq3iWYn.js} +1 -1
  27. package/dist/{make-ph-event-functions-BHoLPif5.js.map → make-ph-event-functions-DBq3iWYn.js.map} +1 -1
  28. package/dist/{index-DS4W07Y8.d.ts → pglite-CntadC_p-BtEalg1H.d.ts} +2 -142
  29. package/dist/pglite-CntadC_p-BtEalg1H.d.ts.map +1 -0
  30. package/dist/{relational-Dj06-YkI.js → relational-C_0GdGVN.js} +2 -2
  31. package/dist/{relational-Dj06-YkI.js.map → relational-C_0GdGVN.js.map} +1 -1
  32. package/dist/renown-BZabzj4l.js +68 -0
  33. package/dist/renown-BZabzj4l.js.map +1 -0
  34. package/dist/{renown-D-1c_Bvx.js → renown-C7a1ygsj.js} +471 -57
  35. package/dist/renown-C7a1ygsj.js.map +1 -0
  36. package/dist/src/analytics/index.d.ts +1 -1
  37. package/dist/src/analytics/index.js +2 -1
  38. package/dist/src/analytics/index.js.map +1 -1
  39. package/dist/src/graphql/client.d.ts +3 -2
  40. package/dist/src/graphql/client.js +1 -1
  41. package/dist/src/graphql-client/entry.d.ts +4 -0
  42. package/dist/src/graphql-client/entry.js +3 -0
  43. package/dist/src/relational/index.d.ts +1 -1
  44. package/dist/src/relational/index.js +1 -1
  45. package/dist/src/renown/index.d.ts +2 -2
  46. package/dist/src/renown/index.js +3 -2
  47. package/dist/src/rpc/index.d.ts +5 -3
  48. package/dist/src/rpc/index.d.ts.map +1 -1
  49. package/dist/src/rpc/index.js +37 -10
  50. package/dist/src/rpc/index.js.map +1 -1
  51. package/dist/store-DAJq6vSs.js +37 -0
  52. package/dist/store-DAJq6vSs.js.map +1 -0
  53. package/dist/{client-09xv0Dq6.d.ts → types-Bitg5Z5j.d.ts} +25 -13
  54. package/dist/types-Bitg5Z5j.d.ts.map +1 -0
  55. package/dist/{types-CPV0Cnsy.d.ts → types-wGaiXwjh.d.ts} +1 -1
  56. package/dist/{types-CPV0Cnsy.d.ts.map → types-wGaiXwjh.d.ts.map} +1 -1
  57. package/package.json +15 -9
  58. package/dist/client-09xv0Dq6.d.ts.map +0 -1
  59. package/dist/client-_Gh2Uf0E.js.map +0 -1
  60. package/dist/client-u13cr-Vg.js.map +0 -1
  61. package/dist/document-by-id-dLYFX-xz.js.map +0 -1
  62. package/dist/index-D3bV5rcl.d.ts +0 -216
  63. package/dist/index-D3bV5rcl.d.ts.map +0 -1
  64. package/dist/index-DS4W07Y8.d.ts.map +0 -1
  65. package/dist/inspector-proxy-CvQDDxmT.d.ts.map +0 -1
  66. package/dist/renown-D-1c_Bvx.js.map +0 -1
@@ -0,0 +1,2778 @@
1
+ import { S as uploadOperations, _ as isDuplicateManifestError, a as setDocumentCache, b as queueActions, g as DOCUMENT_CHANGE_TYPE, i as addDocumentCacheEventHandler, n as useDocumentsByIds, p as DocumentCache, r as useDispatch, v as isDuplicateModuleError, x as queueOperations } from "./document-by-id-DWEQI7wn.js";
2
+ import { t as makePHEventFunctions } from "./make-ph-event-functions-DBq3iWYn.js";
3
+ import { a as PhDocumentFieldsFragmentDoc, n as DocumentChangeType, o as PropagationMode, r as DocumentChangesDocument, t as createClient$1 } from "./client-s_ISz_Zs.js";
4
+ import { f as addLoadingEventHandler, t as addRenownEventHandler } from "./renown-BZabzj4l.js";
5
+ import { logger } from "document-model";
6
+ import { UnsupportedDocumentModelVersionError, assertAuthPreservedOnDuplicate, baseLoadFromInput, baseLoadFromInputVersioned, baseSaveToFileHandle, createPresignedHeader, createZip, documentModelDocumentType, generateId, hashDocumentStateForScope, replayDocumentVersioned, setName } from "@powerhousedao/shared/document-model";
7
+ import { addFolder, copyNode, generateNodesCopy, handleTargetNameCollisions, isFileNode, isFolderNode, moveNode, updateNode } from "@powerhousedao/shared/document-drive";
8
+ import { allPass, conditional, constant, forEachObj, funnel, isDefined, isNot, isStrictEqual, isString, isTruthy, once } from "remeda";
9
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
10
+ import { GraphQLClient } from "graphql-request";
11
+ import { gql } from "graphql-tag";
12
+ import { Kind, print } from "graphql";
13
+ import { createClient } from "graphql-ws/client";
14
+ import slug from "slug";
15
+ import { Fragment, jsx } from "react/jsx-runtime";
16
+ //#region src/errors.ts
17
+ var UnsupportedDocumentTypeError = class extends Error {
18
+ constructor(documentType) {
19
+ super(`Document type ${documentType} is not supported`);
20
+ this.name = "UnsupportedDocumentTypeError";
21
+ }
22
+ static isError(error) {
23
+ return Error.isError(error) && error.name === "UnsupportedDocumentTypeError";
24
+ }
25
+ };
26
+ var DocumentNotFoundError = class extends Error {
27
+ constructor(documentId) {
28
+ super(`Document with id ${documentId} not found`);
29
+ }
30
+ };
31
+ var DocumentModelNotFoundError = class extends Error {
32
+ documentType;
33
+ name = "DocumentModelNotFoundError";
34
+ constructor(documentType) {
35
+ super(`Document model module for type ${documentType} not found`);
36
+ this.documentType = documentType;
37
+ }
38
+ static isError(error) {
39
+ return Error.isError(error) && error.name === "DocumentModelNotFoundError";
40
+ }
41
+ };
42
+ var DocumentTypeMismatchError = class extends Error {
43
+ constructor(documentId, expectedType, actualType) {
44
+ super(`Document ${documentId} is not of type ${expectedType}. Actual type: ${actualType}`);
45
+ }
46
+ };
47
+ var NoSelectedDocumentError = class extends Error {
48
+ constructor() {
49
+ super("There is no selected document. Did you mean to call 'useSelectedDocumentSafe' instead?");
50
+ }
51
+ };
52
+ //#endregion
53
+ //#region src/hooks/modals.ts
54
+ const modalEventFunctions = makePHEventFunctions("modal");
55
+ /** Returns the current modal */
56
+ const usePHModal = modalEventFunctions.useValue;
57
+ /** Sets the current modal */
58
+ const setPHModal = modalEventFunctions.setValue;
59
+ /** Adds an event handler for the modal */
60
+ const addModalEventHandler = modalEventFunctions.addEventHandler;
61
+ /** Shows a modal */
62
+ function showPHModal(modal) {
63
+ setPHModal(modal);
64
+ }
65
+ /** Closes the current modal */
66
+ function closePHModal() {
67
+ setPHModal(void 0);
68
+ }
69
+ /** Shows the create document modal */
70
+ function showCreateDocumentModal(documentType, documentModelVersion) {
71
+ setPHModal({
72
+ type: "createDocument",
73
+ documentType,
74
+ ...documentModelVersion !== void 0 && { documentModelVersion }
75
+ });
76
+ }
77
+ /** Shows the delete node modal */
78
+ function showDeleteNodeModal(nodeOrId) {
79
+ setPHModal({
80
+ type: "deleteItem",
81
+ id: typeof nodeOrId === "string" ? nodeOrId : nodeOrId.id
82
+ });
83
+ }
84
+ //#endregion
85
+ //#region src/utils/documents.ts
86
+ function isDocumentTypeSupported(documentType, supportedDocuments) {
87
+ if (!supportedDocuments?.length) return true;
88
+ return supportedDocuments.some((pattern) => {
89
+ if (pattern.endsWith("/*")) {
90
+ const prefix = pattern.slice(0, -2);
91
+ return documentType.startsWith(prefix + "/");
92
+ }
93
+ if (pattern.endsWith("*") && !pattern.endsWith("/*")) {
94
+ const prefix = pattern.slice(0, -1);
95
+ return documentType.startsWith(prefix);
96
+ }
97
+ return pattern === documentType;
98
+ });
99
+ }
100
+ //#endregion
101
+ //#region src/utils/user.ts
102
+ function getUserPermissions() {
103
+ const user = window.ph?.renown?.user;
104
+ const allowList = window.ph?.allowList;
105
+ if (!allowList) return {
106
+ isAllowedToCreateDocuments: true,
107
+ isAllowedToEditDocuments: true
108
+ };
109
+ return {
110
+ isAllowedToCreateDocuments: allowList.includes(user?.address ?? ""),
111
+ isAllowedToEditDocuments: allowList.includes(user?.address ?? "")
112
+ };
113
+ }
114
+ //#endregion
115
+ //#region src/actions/document.ts
116
+ const NON_DOMAIN_SCOPES = new Set(["auth", "document"]);
117
+ async function isDocumentInLocation(document, driveId, parentFolder) {
118
+ const reactorClient = window.ph?.reactorClient;
119
+ if (!reactorClient) return { isDuplicate: false };
120
+ let drive;
121
+ try {
122
+ drive = await reactorClient.get(driveId);
123
+ } catch {
124
+ return { isDuplicate: false };
125
+ }
126
+ const nodeById = drive.state.global.nodes.find((node) => node.id === document.header.id);
127
+ if (nodeById && nodeById.parentFolder === (parentFolder ?? null)) return {
128
+ isDuplicate: true,
129
+ duplicateType: "id",
130
+ nodeId: nodeById.id
131
+ };
132
+ const nodeByNameAndType = drive.state.global.nodes.find((node) => isFileNode(node) && node.name === document.header.name && node.documentType === document.header.documentType && node.parentFolder === (parentFolder ?? null));
133
+ if (nodeByNameAndType) return {
134
+ isDuplicate: true,
135
+ duplicateType: "name",
136
+ nodeId: nodeByNameAndType.id
137
+ };
138
+ return { isDuplicate: false };
139
+ }
140
+ function getDocumentTypeIcon(document) {
141
+ switch (document.header.documentType) {
142
+ case "powerhouse/document-model": return "document-model";
143
+ case "powerhouse/app": return "app";
144
+ case "powerhouse/document-editor": return "editor";
145
+ case "powerhouse/subgraph": return "subgraph";
146
+ case "powerhouse/package": return "package";
147
+ case "powerhouse/processor": {
148
+ const processorType = document.state.global?.type;
149
+ if (processorType === "analytics") return "analytics-processor";
150
+ if (processorType === "relational") return "relational-processor";
151
+ if (processorType === "codegen") return "codegen-processor";
152
+ return;
153
+ }
154
+ default: return;
155
+ }
156
+ }
157
+ async function downloadFile(document, fileName) {
158
+ try {
159
+ const data = await createZip(document);
160
+ const blob = new Blob([new Uint8Array(data)], { type: "application/zip" });
161
+ const link = window.document.createElement("a");
162
+ link.style.display = "none";
163
+ link.href = URL.createObjectURL(blob);
164
+ link.download = fileName;
165
+ window.document.body.appendChild(link);
166
+ link.click();
167
+ window.document.body.removeChild(link);
168
+ } catch (e) {
169
+ logger.error(e instanceof Error ? e.message : String(e));
170
+ }
171
+ }
172
+ async function getDocumentExtension(document) {
173
+ const documentType = document.header.documentType;
174
+ let rawExtension;
175
+ if (documentType === documentModelDocumentType) rawExtension = document.state.global?.extension;
176
+ else {
177
+ const reactorClient = window.ph?.reactorClientModule?.client;
178
+ if (reactorClient) {
179
+ const { results: documentModelModules } = await reactorClient.getDocumentModelModules();
180
+ rawExtension = documentModelModules.find((m) => m.documentModel.global.id === documentType)?.utils.fileExtension;
181
+ }
182
+ }
183
+ return (rawExtension ?? "").replace(/^\.+|\.+$/g, "");
184
+ }
185
+ /**
186
+ * Fetches all operations for a document using cursor-based pagination.
187
+ * The reactor client handles multi-scope cursors transparently via
188
+ * composite cursors, so all scopes are fetched in a single paginated stream.
189
+ */
190
+ async function fetchDocumentOperations(reactorClient, document, pageSize = 100) {
191
+ const scopes = Object.keys(document.state);
192
+ const operations = {};
193
+ for (const scope of scopes) operations[scope] = [];
194
+ let cursor = "";
195
+ do {
196
+ const page = await reactorClient.getOperations(document.header.id, { scopes }, void 0, {
197
+ cursor,
198
+ limit: pageSize
199
+ });
200
+ for (const op of page.results) {
201
+ const scope = op.action.scope ?? "global";
202
+ if (operations[scope]) operations[scope].push(op);
203
+ }
204
+ cursor = page.nextCursor ?? "";
205
+ } while (cursor);
206
+ return operations;
207
+ }
208
+ function extractInitialState(documentScopeOps) {
209
+ const upgradeOp = documentScopeOps.find((op) => op.action.type === "UPGRADE_DOCUMENT");
210
+ if (!upgradeOp) throw new Error("No UPGRADE_DOCUMENT operation found — document is invalid");
211
+ const input = upgradeOp.action.input;
212
+ const initialState = input.initialState ?? input.state;
213
+ if (!initialState) throw new Error("UPGRADE_DOCUMENT operation has no initialState — document is invalid");
214
+ return initialState;
215
+ }
216
+ function filterDomainOperations(operations) {
217
+ return Object.fromEntries(Object.entries(operations).filter(([scope]) => !NON_DOMAIN_SCOPES.has(scope)));
218
+ }
219
+ function countOperations(operations) {
220
+ let total = 0;
221
+ for (const ops of Object.values(operations)) total += ops?.length ?? 0;
222
+ return total;
223
+ }
224
+ /**
225
+ * Split an imported document's domain operations at its mid-history upgrade
226
+ * boundaries, so the caller can re-dispatch each upgrade between segments.
227
+ *
228
+ * The seed's CREATE_DOCUMENT and 0 -> N upgrade are replayed by the creation
229
+ * path and must not be repeated. A later upgrade must be: it is where the
230
+ * reducer version changes, and dropping it replays everything after it through
231
+ * the module the document was created with.
232
+ */
233
+ function splitAtUpgradeBoundaries(operations) {
234
+ const upgrades = (operations.document ?? []).filter((op) => op.action.type === "UPGRADE_DOCUMENT").filter((op) => op.action.input.fromVersion > 0).sort((a, b) => a.index - b.index);
235
+ const domain = filterDomainOperations(operations);
236
+ if (upgrades.length === 0) return [{ operations: domain }];
237
+ const segments = [];
238
+ const consumed = /* @__PURE__ */ new Map();
239
+ const take = (until) => {
240
+ const slice = {};
241
+ for (const [scope, ops] of Object.entries(domain)) {
242
+ if (!ops) continue;
243
+ const from = consumed.get(scope) ?? 0;
244
+ const stop = Math.max(from, until(scope, ops));
245
+ const chunk = ops.slice(from, stop);
246
+ if (chunk.length > 0) slice[scope] = chunk;
247
+ consumed.set(scope, stop);
248
+ }
249
+ return slice;
250
+ };
251
+ for (const upgrade of upgrades) {
252
+ const input = upgrade.action.input;
253
+ const slice = take((scope, ops) => boundary(upgrade, scope, ops));
254
+ segments.push({
255
+ operations: slice,
256
+ upgradeTo: input.toVersion
257
+ });
258
+ }
259
+ const tail = take((_scope, ops) => ops.length);
260
+ if (countOperations(tail) > 0) segments.push({ operations: tail });
261
+ return segments;
262
+ }
263
+ /**
264
+ * How many of a scope's operations precede an upgrade. Must stay identical to
265
+ * the boundary computation in `replayDocumentVersioned` — snapshots compare
266
+ * against operation indices, not array positions, and the timestamp fallback
267
+ * excludes the upgrade's own instant.
268
+ */
269
+ function boundary(upgrade, scope, ops) {
270
+ const snapshot = upgrade.action.input.revision;
271
+ if (snapshot !== void 0) {
272
+ const revision = snapshot[scope] ?? 0;
273
+ let count = 0;
274
+ for (let i = 0; i < ops.length; i++) if (ops[i].index < revision) count = i + 1;
275
+ return count;
276
+ }
277
+ for (let i = 0; i < ops.length; i++) if (ops[i].timestampUtcMs >= upgrade.timestampUtcMs) return i;
278
+ return ops.length;
279
+ }
280
+ async function exportFile(document, suggestedName) {
281
+ const reactorClient = window.ph?.reactorClient;
282
+ if (!reactorClient) throw new Error("ReactorClient not initialized");
283
+ const operations = await fetchDocumentOperations(reactorClient, document);
284
+ const initialState = extractInitialState(operations["document"] ?? []);
285
+ const documentWithOps = {
286
+ ...document,
287
+ operations,
288
+ initialState
289
+ };
290
+ const extension = await getDocumentExtension(documentWithOps);
291
+ const baseName = suggestedName || documentWithOps.header.name || "Untitled";
292
+ const name = extension ? `${baseName}.${extension}.phd` : `${baseName}.phd`;
293
+ if (!window.showSaveFilePicker) return await downloadFile(documentWithOps, name);
294
+ try {
295
+ const fileHandle = await window.showSaveFilePicker({ suggestedName: name });
296
+ await baseSaveToFileHandle(documentWithOps, fileHandle);
297
+ return fileHandle;
298
+ } catch (e) {
299
+ if (!(e instanceof DOMException && e.name === "AbortError")) throw e;
300
+ }
301
+ }
302
+ /**
303
+ * Loads a document file and replays it with version-aware reducers from the registry.
304
+ * Falls back to single-version legacy replay when no upgrade manifest is registered for
305
+ * the document type.
306
+ */
307
+ async function loadFile(path) {
308
+ const baseDocument = await baseLoadFromInput(path, (state) => state, { checkHashes: true });
309
+ const reactorClient = window.ph?.reactorClientModule?.client;
310
+ if (!reactorClient) throw new Error("ReactorClient not initialized");
311
+ const documentType = baseDocument.header.documentType;
312
+ const { results: documentModelModules } = await reactorClient.getDocumentModelModules();
313
+ const modulesForType = documentModelModules.filter((module) => module.documentModel.global.id === documentType);
314
+ if (modulesForType.length === 0) throw new DocumentModelNotFoundError(documentType);
315
+ const reducers = {};
316
+ for (const module of modulesForType) reducers[module.version ?? 1] = module.reducer;
317
+ const registry = window.ph?.reactorClientModule?.reactorModule?.documentModelRegistry;
318
+ let upgradeManifest;
319
+ if (registry) try {
320
+ upgradeManifest = registry.getUpgradeManifest(documentType);
321
+ } catch {}
322
+ return baseLoadFromInputVersioned(path, {
323
+ reducers,
324
+ upgradeManifest
325
+ });
326
+ }
327
+ async function addDocument(driveId, name, documentType, parentFolder, document, id, preferredEditor, documentModelVersion) {
328
+ const { isAllowedToCreateDocuments } = getUserPermissions();
329
+ if (!isAllowedToCreateDocuments) throw new Error("User is not allowed to create documents");
330
+ const reactorClient = window.ph?.reactorClientModule?.client;
331
+ if (!reactorClient) throw new Error("ReactorClient not initialized");
332
+ let documentModelModule;
333
+ if (documentModelVersion !== void 0) {
334
+ const { results: documentModelModules } = await reactorClient.getDocumentModelModules();
335
+ const module = documentModelModules.find((m) => m.documentModel.global.id === documentType && (m.version ?? 1) === documentModelVersion);
336
+ if (!module) throw new Error(`Document model not found for type: ${documentType} with version: ${documentModelVersion}`);
337
+ documentModelModule = module;
338
+ } else documentModelModule = await reactorClient.getDocumentModelModule(documentType);
339
+ const newDocument = document ?? documentModelModule.utils.createDocument();
340
+ if (!document) newDocument.state.document.version = documentModelModule.version ?? 1;
341
+ newDocument.header.name = name;
342
+ if (preferredEditor) newDocument.header.meta = {
343
+ ...newDocument.header.meta,
344
+ preferredEditor
345
+ };
346
+ let newDoc;
347
+ try {
348
+ newDoc = await reactorClient.drives.addFile(driveId, newDocument, parentFolder);
349
+ } catch (e) {
350
+ logger.error("Error adding document: @error", e);
351
+ throw new Error("There was an error adding document", { cause: e });
352
+ }
353
+ return {
354
+ id: newDoc.header.id,
355
+ name: newDoc.header.name,
356
+ documentType,
357
+ parentFolder: parentFolder ?? null,
358
+ kind: "file"
359
+ };
360
+ }
361
+ async function addFile(file, driveId, name, parentFolder) {
362
+ logger.verbose(`addFile(drive: ${driveId}, name: ${name}, folder: ${parentFolder})`);
363
+ const { isAllowedToCreateDocuments } = getUserPermissions();
364
+ if (!isAllowedToCreateDocuments) throw new Error("User is not allowed to create files");
365
+ const document = await loadFile(file);
366
+ let duplicateId = false;
367
+ const reactorClient = window.ph?.reactorClient;
368
+ if (!reactorClient) throw new Error("ReactorClient not initialized");
369
+ try {
370
+ await reactorClient.get(document.header.id);
371
+ duplicateId = true;
372
+ } catch {}
373
+ const documentId = duplicateId ? generateId() : document.header.id;
374
+ const header = createPresignedHeader(documentId, document.header.documentType);
375
+ header.lastModifiedAtUtcIso = document.header.createdAtUtcIso;
376
+ header.meta = document.header.meta;
377
+ header.name = name || document.header.name;
378
+ const initialDocument = {
379
+ ...document,
380
+ header,
381
+ state: document.initialState,
382
+ operations: Object.keys(document.operations).reduce((acc, key) => {
383
+ acc[key] = [];
384
+ return acc;
385
+ }, {})
386
+ };
387
+ await addDocument(driveId, name || document.header.name, document.header.documentType, parentFolder, initialDocument, documentId, document.header.meta?.preferredEditor);
388
+ for (const segment of splitAtUpgradeBoundaries(document.operations)) {
389
+ await uploadOperations(documentId, segment.operations, queueOperations);
390
+ if (segment.upgradeTo !== void 0) await upgradeDocument(documentId, segment.upgradeTo);
391
+ }
392
+ }
393
+ async function addFileWithProgress(file, driveId, name, parentFolder, onProgress, documentTypes, resolveConflict) {
394
+ logger.verbose(`addFileWithProgress(drive: ${driveId}, name: ${name}, folder: ${parentFolder})`);
395
+ const reactor = window.ph?.reactorClientModule?.client;
396
+ if (!reactor) return;
397
+ const { isAllowedToCreateDocuments } = getUserPermissions();
398
+ if (!isAllowedToCreateDocuments) throw new Error("User is not allowed to create files");
399
+ try {
400
+ onProgress?.({
401
+ stage: "loading",
402
+ progress: 0
403
+ });
404
+ let document;
405
+ try {
406
+ document = await loadFile(file);
407
+ } catch (loadError) {
408
+ if (UnsupportedDocumentModelVersionError.isError(loadError)) {
409
+ showPHModal({
410
+ type: "documentVersionUnsupported",
411
+ documentType: loadError.documentType,
412
+ requiredVersion: loadError.requiredVersion,
413
+ availableVersions: loadError.availableVersions
414
+ });
415
+ onProgress?.({
416
+ stage: "failed",
417
+ progress: 100,
418
+ error: loadError.message
419
+ });
420
+ return;
421
+ }
422
+ const discoveryService = window.ph?.packageDiscoveryService;
423
+ if (discoveryService && DocumentModelNotFoundError.isError(loadError)) {
424
+ retryAfterDiscovery(discoveryService, loadError.documentType, file, driveId, name, parentFolder, onProgress, documentTypes, resolveConflict);
425
+ return;
426
+ }
427
+ throw loadError;
428
+ }
429
+ const duplicateCheck = await isDocumentInLocation(document, driveId, parentFolder);
430
+ if (duplicateCheck.isDuplicate && !resolveConflict) {
431
+ onProgress?.({
432
+ stage: "conflict",
433
+ progress: 0,
434
+ duplicateType: duplicateCheck.duplicateType
435
+ });
436
+ return;
437
+ }
438
+ const documentType = getDocumentTypeIcon(document);
439
+ if (documentType) onProgress?.({
440
+ stage: "loading",
441
+ progress: 10,
442
+ documentType
443
+ });
444
+ else onProgress?.({
445
+ stage: "loading",
446
+ progress: 10
447
+ });
448
+ if (!isDocumentTypeSupported(document.header.documentType, documentTypes)) {
449
+ onProgress?.({
450
+ stage: "unsupported-document-type",
451
+ progress: 100,
452
+ error: `Document type ${document.header.documentType} is not supported`
453
+ });
454
+ throw new UnsupportedDocumentTypeError(document.header.documentType);
455
+ }
456
+ await reactor.getDocumentModelModule(document.header.documentType);
457
+ onProgress?.({
458
+ stage: "initializing",
459
+ progress: 10
460
+ });
461
+ let duplicateId = false;
462
+ try {
463
+ await reactor.get(document.header.id);
464
+ duplicateId = true;
465
+ } catch {}
466
+ const documentId = duplicateId ? generateId() : document.header.id;
467
+ const header = createPresignedHeader(documentId, document.header.documentType);
468
+ header.lastModifiedAtUtcIso = document.header.createdAtUtcIso;
469
+ header.meta = document.header.meta;
470
+ header.name = name || document.header.name;
471
+ const initialDocument = {
472
+ ...document,
473
+ header,
474
+ state: document.initialState,
475
+ operations: Object.keys(document.operations).reduce((acc, key) => {
476
+ acc[key] = [];
477
+ return acc;
478
+ }, {})
479
+ };
480
+ const fileNode = await addDocument(driveId, name || document.header.name, document.header.documentType, parentFolder, initialDocument, documentId, document.header.meta?.preferredEditor);
481
+ if (!fileNode) throw new Error("There was an error adding file");
482
+ onProgress?.({
483
+ stage: "initializing",
484
+ progress: 20
485
+ });
486
+ await reactor.get(documentId);
487
+ console.log("Document created, starting upload of operations");
488
+ const segments = splitAtUpgradeBoundaries(document.operations);
489
+ const totalOperations = segments.reduce((total, segment) => total + countOperations(segment.operations), 0);
490
+ let uploadedBefore = 0;
491
+ for (const segment of segments) {
492
+ await uploadOperations(documentId, segment.operations, queueOperations, { onProgress: (uploadProgress) => {
493
+ if (uploadProgress.uploadedOperations === void 0) return;
494
+ const uploaded = uploadedBefore + uploadProgress.uploadedOperations;
495
+ const uploadPercent = totalOperations > 0 ? uploaded / totalOperations : 0;
496
+ onProgress?.({
497
+ stage: "uploading",
498
+ progress: 20 + Math.round(uploadPercent * 80),
499
+ totalOperations,
500
+ uploadedOperations: uploaded
501
+ });
502
+ } });
503
+ uploadedBefore += countOperations(segment.operations);
504
+ if (segment.upgradeTo !== void 0) await reactor.upgradeDocument(documentId, segment.upgradeTo);
505
+ }
506
+ onProgress?.({
507
+ stage: "complete",
508
+ progress: 100,
509
+ fileNode
510
+ });
511
+ return fileNode;
512
+ } catch (error) {
513
+ if (!UnsupportedDocumentTypeError.isError(error)) {
514
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
515
+ onProgress?.({
516
+ stage: "failed",
517
+ progress: 100,
518
+ error: errorMessage
519
+ });
520
+ }
521
+ throw error;
522
+ }
523
+ }
524
+ async function retryAfterDiscovery(discoveryService, documentType, file, driveId, name, parentFolder, onProgress, documentTypes, resolveConflict) {
525
+ if (!discoveryService) return;
526
+ try {
527
+ await discoveryService.load(documentType);
528
+ } catch {
529
+ onProgress?.({
530
+ stage: "unsupported-document-type",
531
+ progress: 100,
532
+ error: `Document type ${documentType} is not supported`
533
+ });
534
+ return;
535
+ }
536
+ await addFileWithProgress(file, driveId, name, parentFolder, onProgress, documentTypes, resolveConflict);
537
+ }
538
+ async function addFolder$1(driveId, name, parentFolder) {
539
+ const { isAllowedToCreateDocuments } = getUserPermissions();
540
+ if (!isAllowedToCreateDocuments) throw new Error("User is not allowed to create folders");
541
+ const reactorClient = window.ph?.reactorClient;
542
+ if (!reactorClient) throw new Error("ReactorClient not initialized");
543
+ await reactorClient.get(driveId);
544
+ const folderId = generateId();
545
+ const node = (await reactorClient.execute(driveId, "main", [addFolder({
546
+ id: folderId,
547
+ name,
548
+ parentFolder
549
+ })])).state.global.nodes.find((node) => node.id === folderId);
550
+ if (!node || !isFolderNode(node)) throw new Error("There was an error adding folder");
551
+ return node;
552
+ }
553
+ async function deleteNode(driveId, nodeId) {
554
+ const { isAllowedToCreateDocuments } = getUserPermissions();
555
+ if (!isAllowedToCreateDocuments) throw new Error("User is not allowed to delete documents");
556
+ const reactorClient = window.ph?.reactorClientModule?.client;
557
+ if (!reactorClient) throw new Error("ReactorClient not initialized");
558
+ await reactorClient.drives.removeNode(driveId, nodeId);
559
+ }
560
+ async function renameNode(driveId, nodeId, name) {
561
+ const { isAllowedToCreateDocuments } = getUserPermissions();
562
+ if (!isAllowedToCreateDocuments) throw new Error("User is not allowed to rename documents");
563
+ const reactorClient = window.ph?.reactorClient;
564
+ if (!reactorClient) throw new Error("ReactorClient not initialized");
565
+ if ((await reactorClient.execute(nodeId, "main", [setName({ name })])).header.name !== name) throw new Error("There was an error renaming the node");
566
+ const node = (await reactorClient.execute(driveId, "main", [updateNode({
567
+ id: nodeId,
568
+ name
569
+ })])).state.global.nodes.find((n) => n.id === nodeId);
570
+ if (!node) throw new Error("There was an error renaming node in the drive");
571
+ return node;
572
+ }
573
+ async function renameDriveNode(driveId, nodeId, name) {
574
+ const { isAllowedToCreateDocuments } = getUserPermissions();
575
+ if (!isAllowedToCreateDocuments) throw new Error("User is not allowed to rename documents");
576
+ const reactorClient = window.ph?.reactorClient;
577
+ if (!reactorClient) throw new Error("ReactorClient not initialized");
578
+ if ((await reactorClient.execute(nodeId, "main", [setName({ name })])).header.name !== name) throw new Error("There was an error renaming the node");
579
+ await reactorClient.execute(driveId, "main", [updateNode({
580
+ id: nodeId,
581
+ name
582
+ })]);
583
+ return (await reactorClient.get(driveId)).state.global.nodes.find((n) => n.id === nodeId);
584
+ }
585
+ async function moveNodeById(args) {
586
+ const { driveId, srcId, targetId } = args;
587
+ if (!driveId || !srcId) return;
588
+ const { isAllowedToCreateDocuments } = getUserPermissions();
589
+ if (!isAllowedToCreateDocuments) throw new Error("User is not allowed to move documents");
590
+ const reactorClient = window.ph?.reactorClient;
591
+ if (!reactorClient) throw new Error("ReactorClient not initialized");
592
+ const targetParentFolder = conditional(targetId, [isNot(isDefined), constant(void 0)], [isStrictEqual(driveId), constant(void 0)], constant(targetId));
593
+ if (isStrictEqual(targetParentFolder, srcId)) return;
594
+ return await reactorClient.execute(driveId, "main", [moveNode({
595
+ srcFolder: srcId,
596
+ targetParentFolder
597
+ })]);
598
+ }
599
+ async function moveNode$1(driveId, src, target) {
600
+ const { isAllowedToCreateDocuments } = getUserPermissions();
601
+ if (!isAllowedToCreateDocuments) throw new Error("User is not allowed to move documents");
602
+ const reactorClient = window.ph?.reactorClient;
603
+ if (!reactorClient) throw new Error("ReactorClient not initialized");
604
+ return await reactorClient.execute(driveId, "main", [moveNode({
605
+ srcFolder: src.id,
606
+ targetParentFolder: target?.id
607
+ })]);
608
+ }
609
+ /**
610
+ * Duplicates a document under a new id using version-aware replay.
611
+ * Falls back gracefully when no upgrade manifest is registered for the document type.
612
+ */
613
+ async function _duplicateDocument(reactor, document, newId = generateId()) {
614
+ const documentType = document.header.documentType;
615
+ const { results: allModules } = await reactor.getDocumentModelModules();
616
+ const modulesForType = allModules.filter((m) => m.documentModel.global.id === documentType);
617
+ const reducers = {};
618
+ for (const m of modulesForType) reducers[m.version ?? 1] = m.reducer;
619
+ if (Object.keys(reducers).length === 0) throw new Error(`Document model module not found for type: ${documentType}`);
620
+ const registry = window.ph?.reactorClientModule?.reactorModule?.documentModelRegistry;
621
+ let upgradeManifest;
622
+ if (registry) try {
623
+ upgradeManifest = registry.getUpgradeManifest(documentType);
624
+ } catch {}
625
+ const config = {
626
+ reducers,
627
+ upgradeManifest
628
+ };
629
+ const header = createPresignedHeader(newId, documentType);
630
+ const duplicated = replayDocumentVersioned(document.initialState, document.operations, config, header);
631
+ assertAuthPreservedOnDuplicate(document.header.id, document.state.auth, duplicated.state.auth);
632
+ return duplicated;
633
+ }
634
+ async function copyNode$1(driveId, src, target) {
635
+ const reactor = window.ph?.reactorClientModule?.client;
636
+ if (!reactor) return;
637
+ const { isAllowedToCreateDocuments } = getUserPermissions();
638
+ if (!isAllowedToCreateDocuments) throw new Error("User is not allowed to copy documents");
639
+ const drive = await reactor.get(driveId);
640
+ const copyNodesInput = generateNodesCopy({
641
+ srcId: src.id,
642
+ targetParentFolder: target?.id,
643
+ targetName: src.name
644
+ }, () => generateId(), drive.state.global.nodes);
645
+ const resolvedNamesMap = /* @__PURE__ */ new Map();
646
+ for (const copyNodeInput of copyNodesInput) {
647
+ const node = drive.state.global.nodes.find((n) => n.id === copyNodeInput.srcId);
648
+ if (node) {
649
+ const resolvedName = handleTargetNameCollisions({
650
+ nodes: drive.state.global.nodes,
651
+ srcName: copyNodeInput.targetName || node.name,
652
+ srcKind: isFileNode(node) ? "file" : "folder",
653
+ targetParentFolder: copyNodeInput.targetParentFolder || null
654
+ });
655
+ resolvedNamesMap.set(copyNodeInput.targetId, resolvedName);
656
+ }
657
+ }
658
+ const fileNodesToCopy = copyNodesInput.filter((copyNodeInput) => {
659
+ const node = drive.state.global.nodes.find((node) => node.id === copyNodeInput.srcId);
660
+ return node !== void 0 && isFileNode(node);
661
+ });
662
+ for (const fileNodeToCopy of fileNodesToCopy) try {
663
+ const duplicatedDocument = await _duplicateDocument(reactor, await reactor.get(fileNodeToCopy.srcId), fileNodeToCopy.targetId);
664
+ const resolvedName = resolvedNamesMap.get(fileNodeToCopy.targetId);
665
+ if (resolvedName) duplicatedDocument.header.name = resolvedName;
666
+ await reactor.drives.addFile(driveId, duplicatedDocument, target?.id);
667
+ } catch (e) {
668
+ logger.error(`Error copying document ${fileNodeToCopy.srcId}: ${String(e)}`);
669
+ }
670
+ return await queueActions(drive, copyNodesInput.map((copyNodeInput) => copyNode(copyNodeInput)));
671
+ }
672
+ /**
673
+ * Upgrades a document to a newer document model version. Defaults to the
674
+ * latest registered version for the document's type.
675
+ */
676
+ async function upgradeDocument(documentId, toVersion) {
677
+ const reactorClient = window.ph?.reactorClientModule?.client;
678
+ if (!reactorClient) throw new Error("ReactorClient not initialized");
679
+ return reactorClient.upgradeDocument(documentId, toVersion);
680
+ }
681
+ //#endregion
682
+ //#region src/remote-controller/utils.ts
683
+ /** Convert SCREAMING_SNAKE_CASE to camelCase (e.g. "SET_MODEL_NAME" → "setModelName"). */
684
+ function screamingSnakeToCamel(s) {
685
+ return s.toLowerCase().replace(/_([a-z])/g, (_, c) => c.toUpperCase());
686
+ }
687
+ /** Error thrown when a push conflict is detected with the "reject" strategy. */
688
+ var ConflictError = class extends Error {
689
+ constructor(conflict) {
690
+ super("Push conflict: remote has new operations since last pull");
691
+ this.conflict = conflict;
692
+ this.name = "ConflictError";
693
+ }
694
+ };
695
+ /** Convert a remote operation to the local Operation type. */
696
+ function remoteOperationToLocal(remote) {
697
+ return {
698
+ id: remote.id ?? "",
699
+ index: remote.index,
700
+ skip: remote.skip,
701
+ timestampUtcMs: remote.timestampUtcMs,
702
+ hash: remote.hash,
703
+ error: remote.error ?? void 0,
704
+ deniedReason: remote.deniedReason ?? void 0,
705
+ action: {
706
+ id: remote.action.id,
707
+ type: remote.action.type,
708
+ timestampUtcMs: remote.action.timestampUtcMs,
709
+ input: remote.action.input,
710
+ scope: remote.action.scope,
711
+ context: remote.action.context?.signer ? { signer: {
712
+ user: remote.action.context.signer.user ?? {
713
+ address: "",
714
+ networkId: "",
715
+ chainId: 0
716
+ },
717
+ app: remote.action.context.signer.app ?? {
718
+ name: "",
719
+ key: ""
720
+ },
721
+ signatures: remote.action.context.signer.signatures.map((s) => deserializeSignature(s))
722
+ } } : void 0
723
+ }
724
+ };
725
+ }
726
+ /**
727
+ * Deserialize a signature string back to a 5-element tuple.
728
+ * The server serializes tuples via `tuple.join(", ")`.
729
+ */
730
+ function deserializeSignature(s) {
731
+ const parts = s.split(", ");
732
+ return [
733
+ parts[0] ?? "",
734
+ parts[1] ?? "",
735
+ parts[2] ?? "",
736
+ parts[3] ?? "",
737
+ parts[4] ?? ""
738
+ ];
739
+ }
740
+ /** Convert remote operations to local DocumentOperations format. */
741
+ function convertRemoteOperations(operationsByScope) {
742
+ const operations = {};
743
+ for (const [scope, remoteOps] of Object.entries(operationsByScope)) operations[scope] = remoteOps.map((op) => remoteOperationToLocal(op));
744
+ return operations;
745
+ }
746
+ /** Reconstruct a PHDocument from remote document data and operations. */
747
+ function buildPulledDocument(remoteDoc, operations, initialDoc, branch) {
748
+ return {
749
+ header: {
750
+ ...initialDoc.header,
751
+ id: remoteDoc.id,
752
+ name: remoteDoc.name,
753
+ slug: remoteDoc.slug ?? "",
754
+ documentType: remoteDoc.documentType,
755
+ createdAtUtcIso: typeof remoteDoc.createdAtUtcIso === "string" ? remoteDoc.createdAtUtcIso : remoteDoc.createdAtUtcIso.toISOString(),
756
+ lastModifiedAtUtcIso: typeof remoteDoc.lastModifiedAtUtcIso === "string" ? remoteDoc.lastModifiedAtUtcIso : remoteDoc.lastModifiedAtUtcIso.toISOString(),
757
+ revision: Object.fromEntries(remoteDoc.revisionsList.map((r) => [r.scope, r.revision])),
758
+ branch
759
+ },
760
+ state: remoteDoc.state,
761
+ initialState: initialDoc.initialState,
762
+ operations,
763
+ clipboard: []
764
+ };
765
+ }
766
+ /** Extract revision map from a remote document's revisionsList. */
767
+ function extractRevisionMap(revisionsList) {
768
+ return Object.fromEntries(revisionsList.map((r) => [r.scope, r.revision]));
769
+ }
770
+ /**
771
+ * Check if any scope in currentRevision is ahead of knownRevision.
772
+ * When `scopes` is provided, only those scopes are checked.
773
+ */
774
+ function hasRevisionConflict(currentRevision, knownRevision, scopes) {
775
+ for (const scope in currentRevision) {
776
+ if (scopes && !scopes.has(scope)) continue;
777
+ if ((currentRevision[scope] ?? 0) > (knownRevision[scope] ?? 0)) return true;
778
+ }
779
+ return false;
780
+ }
781
+ //#endregion
782
+ //#region src/graphql-client/adapter.ts
783
+ /**
784
+ * Converts a `GetDocument` result into a `PHDocument`.
785
+ *
786
+ * Unlike the legacy `src/graphql/adapters.ts` this preserves the per-scope
787
+ * revision map, which the write path needs to compute the `prevOpIndex` of a
788
+ * signed action. `sig` is filled with the empty shape used by
789
+ * `createPresignedHeader`: the creation signature is not fetchable over
790
+ * GraphQL and is not part of the operation signing payload.
791
+ *
792
+ * The read path never fetches operation history, so `operations` is a map of
793
+ * the document's known scopes to empty arrays.
794
+ *
795
+ * `branch` is the branch the document was read from. The GraphQL type does not
796
+ * carry it, so it has to be passed back in by the caller; it defaults to the
797
+ * server's own default branch.
798
+ */
799
+ function phDocumentFromGetDocument(document, branch) {
800
+ const state = document.state;
801
+ return {
802
+ header: phDocumentHeaderFromGetDocument(document, branch),
803
+ state,
804
+ initialState: state,
805
+ operations: emptyOperationsForScopes(document.revisionsList),
806
+ clipboard: []
807
+ };
808
+ }
809
+ /**
810
+ * Converts a mutation result into a `PHDocument`, carrying the operations the
811
+ * mutation produced.
812
+ *
813
+ * `dispatchActions` reads per-action failures out of `operations[scope]`, so
814
+ * unlike the read path this one must not return empty operation lists.
815
+ */
816
+ function phDocumentFromMutation(document, operations, branch) {
817
+ const phDocument = phDocumentFromGetDocument(document, branch);
818
+ const operationsByScope = { ...phDocument.operations };
819
+ for (const remote of operations) {
820
+ const operation = remoteOperationToLocal(remote);
821
+ const scope = operation.action.scope;
822
+ const scopeOperations = operationsByScope[scope] ?? [];
823
+ scopeOperations.push(operation);
824
+ operationsByScope[scope] = scopeOperations;
825
+ }
826
+ phDocument.operations = operationsByScope;
827
+ return phDocument;
828
+ }
829
+ /** Turns a `revisionsList` selection into the header's per-scope revision map. */
830
+ function revisionMapFromRevisionsList(revisionsList) {
831
+ return Object.fromEntries(revisionsList.map((r) => [r.scope, r.revision]));
832
+ }
833
+ /** The branch the server reads and writes when none is asked for. */
834
+ const defaultBranch = "main";
835
+ function phDocumentHeaderFromGetDocument(document, branch) {
836
+ return {
837
+ id: document.id,
838
+ sig: {
839
+ publicKey: {},
840
+ nonce: ""
841
+ },
842
+ documentType: document.documentType,
843
+ createdAtUtcIso: isoStringFromDateTime(document.createdAtUtcIso),
844
+ slug: document.slug ?? "",
845
+ name: document.name,
846
+ branch: branch ?? defaultBranch,
847
+ revision: revisionMapFromRevisionsList(document.revisionsList),
848
+ lastModifiedAtUtcIso: isoStringFromDateTime(document.lastModifiedAtUtcIso)
849
+ };
850
+ }
851
+ function emptyOperationsForScopes(revisionsList) {
852
+ return Object.fromEntries(revisionsList.map((r) => [r.scope, []]));
853
+ }
854
+ /** The `DateTime` scalar deserializes as either an ISO string or a `Date`. */
855
+ function isoStringFromDateTime(value) {
856
+ return value instanceof Date ? value.toISOString() : value;
857
+ }
858
+ //#endregion
859
+ //#region src/graphql-client/auth.ts
860
+ /** Lifetime requested for an ambient Renown token, in seconds. */
861
+ const ambientTokenExpiresInSeconds = 600;
862
+ /**
863
+ * The default token provider: the token of the currently logged-in Renown user.
864
+ *
865
+ * It is deliberately resolved from `window.ph` on every call rather than
866
+ * captured at construction time, so a client built before login starts sending
867
+ * the token as soon as there is one.
868
+ *
869
+ * The token is requested WITHOUT an `aud` claim - the Switchboard verifier
870
+ * rejects tokens that carry an audience.
871
+ */
872
+ async function ambientRenownTokenProvider() {
873
+ if (typeof window === "undefined") return;
874
+ const renown = window.ph?.renown;
875
+ if (!renown?.user) return;
876
+ return renown.getBearerToken({ expiresIn: ambientTokenExpiresInSeconds });
877
+ }
878
+ /**
879
+ * Builds the SDK middleware that authenticates every request.
880
+ *
881
+ * The token is resolved per request and never cached, so a login, a logout or
882
+ * an expiry between two calls is picked up by the next one. When no token is
883
+ * available the request goes out unauthenticated.
884
+ */
885
+ function makeAuthMiddleware(tokenProvider) {
886
+ return async (action) => {
887
+ const token = await tokenProvider();
888
+ if (!token) return action();
889
+ return action({ authorization: `Bearer ${token}` });
890
+ };
891
+ }
892
+ //#endregion
893
+ //#region src/graphql-client/operations.ts
894
+ /**
895
+ * The operation selection of `GetDocumentOperations`, lifted into a fragment so
896
+ * the write path can reuse `remoteOperationToLocal` on the items it selects.
897
+ */
898
+ const ReactorOperationFieldsFragmentDoc = gql`
899
+ fragment ReactorOperationFields on ReactorOperation {
900
+ index
901
+ timestampUtcMs
902
+ hash
903
+ skip
904
+ error
905
+ deniedReason
906
+ id
907
+ action {
908
+ id
909
+ type
910
+ timestampUtcMs
911
+ input
912
+ scope
913
+ context {
914
+ signer {
915
+ user {
916
+ address
917
+ networkId
918
+ chainId
919
+ }
920
+ app {
921
+ name
922
+ key
923
+ }
924
+ signatures
925
+ }
926
+ }
927
+ }
928
+ }
929
+ `;
930
+ /**
931
+ * `mutateDocument` plus the operations the mutation produced.
932
+ *
933
+ * The generated `MutateDocument` only selects the document fields, but
934
+ * `dispatchActions` reads per-action failures out of `result.operations[scope]`
935
+ * (`src/actions/dispatch.ts`), so the reducer-level errors would be lost.
936
+ *
937
+ * The operations filter keeps the extra selection to the operations this call
938
+ * could have appended rather than the whole history. All three of its fields
939
+ * matter:
940
+ * - `sinceRevision` is the lowest head revision across the targeted scopes, so
941
+ * it is only a safe window when the selection is scope-limited as well;
942
+ * - `scopes` limits the response to the scopes the pushed actions target,
943
+ * without it the server walks every scope of the document
944
+ * (`packages/reactor/src/core/reactor.ts` `getOperations`);
945
+ * - `branch` must match the branch the mutation writes to, otherwise the
946
+ * operations come from `main` (the server's default) while the actions were
947
+ * applied elsewhere.
948
+ */
949
+ const MutateDocumentWithOperationsDocument = gql`
950
+ mutation MutateDocumentWithOperations(
951
+ $documentIdentifier: String!
952
+ $actions: [JSONObject!]!
953
+ $view: ViewFilterInput
954
+ $sinceRevision: Int
955
+ $scopes: [String!]
956
+ $branch: String
957
+ ) {
958
+ mutateDocument(
959
+ documentIdentifier: $documentIdentifier
960
+ actions: $actions
961
+ view: $view
962
+ ) {
963
+ ...PHDocumentFields
964
+ operations(
965
+ filter: {
966
+ sinceRevision: $sinceRevision
967
+ scopes: $scopes
968
+ branch: $branch
969
+ }
970
+ ) {
971
+ items {
972
+ ...ReactorOperationFields
973
+ }
974
+ }
975
+ }
976
+ }
977
+ ${PhDocumentFieldsFragmentDoc}
978
+ ${ReactorOperationFieldsFragmentDoc}
979
+ `;
980
+ //#endregion
981
+ //#region src/graphql-client/signing.ts
982
+ /**
983
+ * Stamps an action with the head of the scope it targets.
984
+ *
985
+ * `prevOpHash` is the hash of the scope state the action is meant to apply to;
986
+ * for a document at head that is the hash of the last operation in the scope,
987
+ * which the read path cannot fetch, so it is recomputed from the state.
988
+ * `prevOpIndex` is the index of that last operation: revisions count
989
+ * operations and indices are zero based, so an empty scope stamps `-1`, which
990
+ * matches what the remote controller records.
991
+ */
992
+ function stampAction(action, document) {
993
+ const revision = document.header.revision[action.scope] ?? 0;
994
+ return {
995
+ ...action,
996
+ context: {
997
+ ...action.context,
998
+ prevOpHash: hashDocumentStateForScope(document, action.scope),
999
+ prevOpIndex: revision - 1
1000
+ }
1001
+ };
1002
+ }
1003
+ /**
1004
+ * Signs a stamped action, preserving any signatures it already carries.
1005
+ *
1006
+ * The signer reads `context.prevOpHash` off the action, so this must run after
1007
+ * {@link stampAction}.
1008
+ */
1009
+ async function signStampedAction(action, signer) {
1010
+ const actionSigner = action.context?.signer;
1011
+ const user = actionSigner?.user ?? signer.user;
1012
+ const app = actionSigner?.app ?? signer.app;
1013
+ if (!user || !app) throw new Error("cannot sign an action: the signer has no user or app identity");
1014
+ const signature = await signer.signAction(action);
1015
+ return {
1016
+ ...action,
1017
+ context: {
1018
+ ...action.context,
1019
+ signer: {
1020
+ user,
1021
+ app,
1022
+ signatures: [...actionSigner?.signatures ?? [], signature]
1023
+ }
1024
+ }
1025
+ };
1026
+ }
1027
+ //#endregion
1028
+ //#region src/graphql-client/subgraph.ts
1029
+ /** What {@link describeGraphQLDocument} could not read off a document. */
1030
+ const unnamedOperation = "AnonymousOperation";
1031
+ const defaultOperationType = "query";
1032
+ /**
1033
+ * Derives a subgraph's endpoint from the Switchboard's GraphQL endpoint.
1034
+ *
1035
+ * Subgraphs are served next to the supergraph, at `<graphql base>/<name>`, so
1036
+ * `http://localhost:4001/graphql` plus `statements` is
1037
+ * `http://localhost:4001/graphql/statements`.
1038
+ */
1039
+ function subgraphUrlFromGraphqlUrl(url, name) {
1040
+ const trimmed = name.trim();
1041
+ if (trimmed === "") throw new Error("a subgraph name is required");
1042
+ return `${url.replace(/\/+$/, "")}/${trimmed}`;
1043
+ }
1044
+ /**
1045
+ * Reads the operation name and type off a document, for the middleware.
1046
+ *
1047
+ * Nothing depends on getting this right - it is reported to the middleware, not
1048
+ * sent to the server - so an unreadable document falls back to an anonymous
1049
+ * query rather than throwing.
1050
+ */
1051
+ function describeGraphQLDocument(document) {
1052
+ if (typeof document !== "string") {
1053
+ const operation = document.definitions.find((definition) => definition.kind === Kind.OPERATION_DEFINITION);
1054
+ if (!operation || operation.kind !== Kind.OPERATION_DEFINITION) return {
1055
+ operationName: unnamedOperation,
1056
+ operationType: defaultOperationType
1057
+ };
1058
+ return {
1059
+ operationName: operation.name?.value ?? unnamedOperation,
1060
+ operationType: operation.operation
1061
+ };
1062
+ }
1063
+ const match = /\b(query|mutation|subscription)\s+([_A-Za-z][_0-9A-Za-z]*)/.exec(document);
1064
+ if (!match) return {
1065
+ operationName: unnamedOperation,
1066
+ operationType: /\bmutation\b/.test(document) ? "mutation" : defaultOperationType
1067
+ };
1068
+ return {
1069
+ operationName: match[2],
1070
+ operationType: match[1]
1071
+ };
1072
+ }
1073
+ /**
1074
+ * Binds typed subgraph SDKs to one Switchboard's transport and auth.
1075
+ *
1076
+ * Every capability a Switchboard exposes lives behind the same base URL, so an
1077
+ * app should not have to build a second client - with a second copy of the auth
1078
+ * configuration - to reach a project's own subgraph. The registry derives the
1079
+ * endpoint, reuses one transport per subgraph and hands the code generator's
1080
+ * `getSdk` the very middleware the reactor operations already go through.
1081
+ *
1082
+ * Transport and typing only: subgraph results are not cached and produce no
1083
+ * change events.
1084
+ */
1085
+ var SubgraphSdkRegistry = class {
1086
+ url;
1087
+ middleware;
1088
+ bindings = /* @__PURE__ */ new Map();
1089
+ constructor(url, middleware) {
1090
+ this.url = url;
1091
+ this.middleware = middleware;
1092
+ }
1093
+ /**
1094
+ * Returns the subgraph's SDK, building it on first use.
1095
+ *
1096
+ * The transport is kept per subgraph name, so repeated calls do not rebuild
1097
+ * it. The SDK itself is only reused when the same factory is passed again -
1098
+ * two different generated SDKs over one subgraph each get their own, rather
1099
+ * than the second call being handed the first one's methods.
1100
+ */
1101
+ get(name, getSdk) {
1102
+ const binding = this.binding(name);
1103
+ if (binding.factory === getSdk) return binding.sdk;
1104
+ const sdk = getSdk(binding.transport, this.middleware);
1105
+ binding.factory = getSdk;
1106
+ binding.sdk = sdk;
1107
+ return sdk;
1108
+ }
1109
+ /** Returns the subgraph's endpoint, for diagnostics and tests. */
1110
+ urlFor(name) {
1111
+ return subgraphUrlFromGraphqlUrl(this.url, name);
1112
+ }
1113
+ binding(name) {
1114
+ const existing = this.bindings.get(name);
1115
+ if (existing) return existing;
1116
+ const binding = { transport: new GraphQLClient(this.urlFor(name)) };
1117
+ this.bindings.set(name, binding);
1118
+ return binding;
1119
+ }
1120
+ };
1121
+ //#endregion
1122
+ //#region src/graphql-client/subscriptions.ts
1123
+ /**
1124
+ * Opens one `documentChanges` subscription and feeds every event to `onEvent`.
1125
+ *
1126
+ * The subscription is a firehose: no `search` argument, so the server sends
1127
+ * every change the connection is allowed to see and the caller filters. This is
1128
+ * the only module that knows about `graphql-ws`.
1129
+ *
1130
+ * Returns the stop function, which cancels the subscription and closes the
1131
+ * socket. It is safe to call more than once.
1132
+ */
1133
+ function startDocumentChangesSubscription(options) {
1134
+ const client = createClient({
1135
+ url: options.wsUrl,
1136
+ connectionParams: options.connectionParams
1137
+ });
1138
+ const unsubscribe = client.subscribe({
1139
+ operationName: "DocumentChanges",
1140
+ query: print(DocumentChangesDocument)
1141
+ }, {
1142
+ next: (result) => {
1143
+ const event = result.data?.documentChanges;
1144
+ if (event) options.onEvent(event);
1145
+ },
1146
+ error: (error) => options.onError(error),
1147
+ complete: () => void 0
1148
+ });
1149
+ let stopped = false;
1150
+ return () => {
1151
+ if (stopped) return;
1152
+ stopped = true;
1153
+ unsubscribe();
1154
+ client.dispose();
1155
+ };
1156
+ }
1157
+ /**
1158
+ * Derives the websocket endpoint from the GraphQL http endpoint.
1159
+ *
1160
+ * `http://host/graphql` becomes `ws://host/graphql/subscriptions`, which is
1161
+ * where the Switchboard mounts its websocket server (`packages/reactor-api`
1162
+ * `startServer`). A URL that already speaks `ws`/`wss` only gets the path.
1163
+ */
1164
+ function subscriptionsUrlFromGraphqlUrl(url) {
1165
+ const trimmed = url.replace(/\/+$/, "");
1166
+ return `${trimmed.startsWith("https://") ? `wss://${trimmed.slice(8)}` : trimmed.startsWith("http://") ? `ws://${trimmed.slice(7)}` : trimmed}/subscriptions`;
1167
+ }
1168
+ /**
1169
+ * Turns a bearer token provider into `connectionParams`.
1170
+ *
1171
+ * The header key is the lowercase `authorization` the Switchboard's websocket
1172
+ * context factory reads. Without a token the payload is empty: an open
1173
+ * Switchboard serves anonymous subscribers.
1174
+ */
1175
+ function makeAuthConnectionParams(tokenProvider) {
1176
+ return async () => {
1177
+ const token = await tokenProvider();
1178
+ if (!token) return {};
1179
+ return { authorization: `Bearer ${token}` };
1180
+ };
1181
+ }
1182
+ //#endregion
1183
+ //#region src/graphql-client/graphql-reactor-client.ts
1184
+ /** Paging defaults, matching the reactor's own client. */
1185
+ const defaultPaging = {
1186
+ cursor: "0",
1187
+ limit: 100
1188
+ };
1189
+ /**
1190
+ * The marker {@link isGraphQLReactorClient} looks for.
1191
+ *
1192
+ * A page can end up with two copies of this module - an app that bundles a
1193
+ * package carrying its own copy, or a dev server that hot-replaced it - and
1194
+ * then `instanceof` compares an instance against the other copy's class object
1195
+ * and answers `false`. A branded property survives both.
1196
+ */
1197
+ const graphQLReactorClientBrand = "__powerhouseGraphQLReactorClient";
1198
+ /**
1199
+ * A light implementation of {@link IReactorBrowserClient} that talks plain
1200
+ * GraphQL to a Switchboard, with no reactor in the bundle.
1201
+ *
1202
+ * It fills the same `window.ph` slots the full in-browser reactor client fills,
1203
+ * so the reactor-browser hooks cannot tell the two apart.
1204
+ */
1205
+ var GraphQLReactorClient = class {
1206
+ /** See {@link isGraphQLReactorClient}. */
1207
+ [graphQLReactorClientBrand] = true;
1208
+ sdk;
1209
+ subgraphs;
1210
+ listeners = [];
1211
+ tokenProvider;
1212
+ subscriptionsUrl;
1213
+ stopRealtime;
1214
+ realtimeStarted = false;
1215
+ realtimeGeneration = 0;
1216
+ realtimeErrorLogged = false;
1217
+ constructor(options) {
1218
+ this.tokenProvider = options.tokenProvider ?? ambientRenownTokenProvider;
1219
+ this.subscriptionsUrl = options.realtime === false ? void 0 : options.subscriptionsUrl ?? subscriptionsUrlFromGraphqlUrl(options.url);
1220
+ const middleware = makeAuthMiddleware(this.tokenProvider);
1221
+ this.sdk = options.graphqlClient ?? createClient$1(options.url, middleware);
1222
+ this.subgraphs = new SubgraphSdkRegistry(options.url, middleware);
1223
+ }
1224
+ async get(identifier, view, signal) {
1225
+ const viewInput = viewFilterInputFromViewFilter(view);
1226
+ const document = (await this.sdk.GetDocument({
1227
+ identifier,
1228
+ view: viewInput
1229
+ }, void 0, signal)).document;
1230
+ if (!document) throw new Error(`Document not found: ${identifier}`);
1231
+ return phDocumentFromGetDocument(document.document, view?.branch);
1232
+ }
1233
+ async getOperations(documentIdentifier, view, filter, paging, signal) {
1234
+ const viewInput = viewFilterInputFromViewFilter(view);
1235
+ const operationsFilter = {
1236
+ documentId: documentIdentifier,
1237
+ branch: viewInput?.branch,
1238
+ scopes: viewInput?.scopes,
1239
+ actionTypes: filter?.actionTypes,
1240
+ timestampFrom: filter?.timestampFrom,
1241
+ timestampTo: filter?.timestampTo,
1242
+ sinceRevision: filter?.sinceRevision
1243
+ };
1244
+ const page = (await this.sdk.GetDocumentOperations({
1245
+ filter: operationsFilter,
1246
+ paging: pagingInputFromPaging(paging)
1247
+ }, void 0, signal)).documentOperations;
1248
+ const nextCursor = page.hasNextPage ? page.cursor ?? void 0 : void 0;
1249
+ const effectivePaging = paging ?? defaultPaging;
1250
+ return {
1251
+ results: page.items.map((item) => remoteOperationToLocal(item)),
1252
+ options: effectivePaging,
1253
+ nextCursor,
1254
+ totalCount: page.totalCount,
1255
+ next: nextCursor ? () => this.getOperations(documentIdentifier, view, filter, {
1256
+ cursor: nextCursor,
1257
+ limit: effectivePaging.limit
1258
+ }, signal) : void 0
1259
+ };
1260
+ }
1261
+ async create(document, parentIdentifier, signal) {
1262
+ const created = phDocumentFromGetDocument((await this.sdk.CreateDocument({
1263
+ document,
1264
+ parentIdentifier
1265
+ }, void 0, signal)).createDocument);
1266
+ this.emitChange({
1267
+ type: DOCUMENT_CHANGE_TYPE.Created,
1268
+ documents: [created]
1269
+ });
1270
+ return created;
1271
+ }
1272
+ /**
1273
+ * Pushes actions to the Switchboard and returns the updated document.
1274
+ *
1275
+ * The document is fetched first: the response is the baseline for the
1276
+ * `sinceRevision` filter that narrows the returned operations to the ones
1277
+ * this call appended, and it carries the state hash a signed action is
1278
+ * stamped with.
1279
+ *
1280
+ * The emitted `Updated` event carries the document's id, never the
1281
+ * identifier this was called with: subscribers - including `DocumentCache` -
1282
+ * work in id space.
1283
+ */
1284
+ async execute(documentIdentifier, branch, actions, signal) {
1285
+ const document = await this.get(documentIdentifier, { branch }, signal);
1286
+ const variables = {
1287
+ documentIdentifier,
1288
+ actions: await prepareActionsForPush(actions, document),
1289
+ view: { branch },
1290
+ sinceRevision: sinceRevisionForActions(document, actions),
1291
+ scopes: scopesForActions(actions),
1292
+ branch
1293
+ };
1294
+ const mutated = (await this.sdk.RunDocument({
1295
+ operationName: "MutateDocumentWithOperations",
1296
+ operationType: "mutation",
1297
+ document: MutateDocumentWithOperationsDocument,
1298
+ variables,
1299
+ signal
1300
+ })).mutateDocument;
1301
+ const updated = phDocumentFromMutation(mutated, mutated.operations?.items ?? [], branch);
1302
+ this.emitChange({
1303
+ type: DOCUMENT_CHANGE_TYPE.Updated,
1304
+ documents: [updated]
1305
+ });
1306
+ return updated;
1307
+ }
1308
+ /**
1309
+ * Deletes a document and announces it as a `Deleted` event.
1310
+ *
1311
+ * Pass a document id. The server resolves a slug just as happily, but
1312
+ * `deleteDocument` returns a boolean - there is no document left to read an
1313
+ * id off - so the identifier is announced verbatim on `context.childId`. A
1314
+ * subscriber keyed by id, `DocumentCache` included, therefore only reacts to
1315
+ * a delete by id.
1316
+ */
1317
+ async deleteDocument(identifier, propagate, signal) {
1318
+ await this.sdk.DeleteDocument({
1319
+ identifier,
1320
+ propagate: propagationModeInput(propagate)
1321
+ }, void 0, signal);
1322
+ this.emitChange({
1323
+ type: DOCUMENT_CHANGE_TYPE.Deleted,
1324
+ documents: [],
1325
+ context: { childId: identifier }
1326
+ });
1327
+ }
1328
+ /**
1329
+ * Registers a change subscriber and returns its unsubscribe function.
1330
+ *
1331
+ * Two things produce the events delivered here. Every successful `create`,
1332
+ * `execute` and `deleteDocument` emits one, which is what makes a
1333
+ * `DocumentCache` built on this client invalidate after a dispatch. And,
1334
+ * unless realtime is switched off, the first call to this method opens a
1335
+ * websocket to the Switchboard and every change it reports - from another
1336
+ * user, another tab or a server-side processor - is emitted too. A write of
1337
+ * this client's own is therefore announced twice, once locally and once by
1338
+ * the server; the cache refetches twice, which is harmless.
1339
+ *
1340
+ * A websocket that cannot be opened or is refused is logged once and then
1341
+ * ignored: realtime is an enhancement, never a dependency. The failed socket
1342
+ * is closed, so the next subscriber to arrive tries again.
1343
+ *
1344
+ * `view` is accepted for interface compatibility and ignored: the emitted
1345
+ * documents are whatever the write returned, so there is nothing to re-scope.
1346
+ *
1347
+ * Events live in id space: `search.ids` is matched against the resolved
1348
+ * document id, never against the identifier the write was issued with. The
1349
+ * one exception is `deleteDocument`, which has no document to resolve an id
1350
+ * from - see its own note. A `DocumentCache` on this client must therefore
1351
+ * be read by id: a slug-keyed entry is never invalidated.
1352
+ */
1353
+ subscribe(search, callback, view) {
1354
+ const listener = {
1355
+ search,
1356
+ callback
1357
+ };
1358
+ this.listeners.push(listener);
1359
+ this.startRealtime();
1360
+ return () => {
1361
+ const index = this.listeners.indexOf(listener);
1362
+ if (index !== -1) this.listeners.splice(index, 1);
1363
+ };
1364
+ }
1365
+ /**
1366
+ * Closes the realtime socket, if one was opened.
1367
+ *
1368
+ * Registered subscribers are left in place: they still receive the events
1369
+ * this client produces itself.
1370
+ *
1371
+ * Disposing is NOT final - a later `subscribe` opens a new socket. React
1372
+ * remounts a tree by running an effect's cleanup and then the effect again,
1373
+ * on the same client, and `StrictMode` does exactly that on every mount; a
1374
+ * terminal flag here would leave those pages without realtime for good, with
1375
+ * nothing logged.
1376
+ */
1377
+ dispose() {
1378
+ this.teardownRealtime();
1379
+ }
1380
+ /**
1381
+ * Binds a project's generated SDK to this client's transport and auth.
1382
+ *
1383
+ * Subgraphs are served next to the reactor's own supergraph, so the endpoint
1384
+ * is derived from the client's `url` by appending the name, and the SDK is
1385
+ * handed the same auth middleware every reactor call goes through. An app
1386
+ * therefore configures one URL and one token provider, and reaches every
1387
+ * capability of its Switchboard through this one client.
1388
+ *
1389
+ * `getSdk` is the function a GraphQL code generator emits for the subgraph's
1390
+ * schema - see the README in this folder for the codegen recipe.
1391
+ *
1392
+ * Transport and typing only. Subgraph results are not cached, produce no
1393
+ * change events and never reach `useDocument`.
1394
+ */
1395
+ subgraph(name, getSdk) {
1396
+ return this.subgraphs.get(name, getSdk);
1397
+ }
1398
+ /**
1399
+ * Runs a one-off GraphQL document against the Switchboard's own endpoint,
1400
+ * through the same transport and auth as everything else.
1401
+ *
1402
+ * The escape hatch for an operation not worth generating an SDK for. Pass a
1403
+ * document a code generator typed and the result type follows; pass a plain
1404
+ * `gql` document or a string and name the result type at the call site.
1405
+ */
1406
+ async request(document, variables, options) {
1407
+ const described = describeGraphQLDocument(document);
1408
+ return this.sdk.RunDocument({
1409
+ operationName: options?.operationName ?? described.operationName,
1410
+ operationType: options?.operationType ?? described.operationType,
1411
+ document,
1412
+ variables,
1413
+ signal: options?.signal
1414
+ });
1415
+ }
1416
+ /**
1417
+ * Opens the realtime socket on the first subscriber.
1418
+ *
1419
+ * The subscription is a firehose - no `search` argument - and this client
1420
+ * applies each subscriber's own filter on the way out, exactly as it does for
1421
+ * its own emissions.
1422
+ */
1423
+ startRealtime() {
1424
+ if (!this.subscriptionsUrl || this.realtimeStarted) return;
1425
+ this.realtimeStarted = true;
1426
+ const generation = this.realtimeGeneration;
1427
+ const stop = startDocumentChangesSubscription({
1428
+ wsUrl: this.subscriptionsUrl,
1429
+ connectionParams: makeAuthConnectionParams(this.tokenProvider),
1430
+ onEvent: (event) => this.emitServerEvent(event),
1431
+ onError: (error) => this.handleRealtimeFailure(generation, error)
1432
+ });
1433
+ if (this.realtimeGeneration !== generation) {
1434
+ stop();
1435
+ return;
1436
+ }
1437
+ this.stopRealtime = stop;
1438
+ }
1439
+ /**
1440
+ * Gives up on a failed socket so that a later subscriber can try again.
1441
+ *
1442
+ * `graphql-ws` retries on its own and only reports here once it has given up,
1443
+ * or once the server refused the subscription outright - which is what an
1444
+ * auth-enabled Switchboard does to an anonymous subscriber. Keeping the dead
1445
+ * stop function would make every later `subscribe` a no-op, so realtime would
1446
+ * stay off for the life of the page even after the user signs in.
1447
+ *
1448
+ * The generation stamp discards a report from a socket that has already been
1449
+ * replaced or disposed.
1450
+ */
1451
+ handleRealtimeFailure(generation, error) {
1452
+ if (generation !== this.realtimeGeneration) return;
1453
+ this.teardownRealtime();
1454
+ this.logRealtimeError(error);
1455
+ }
1456
+ /** Closes the socket and lets a later subscriber open a new one. */
1457
+ teardownRealtime() {
1458
+ this.realtimeGeneration += 1;
1459
+ this.realtimeStarted = false;
1460
+ this.stopRealtime?.();
1461
+ this.stopRealtime = void 0;
1462
+ }
1463
+ /** Maps a server event onto the client-side event and emits it. */
1464
+ emitServerEvent(event) {
1465
+ this.emitChange({
1466
+ type: documentChangeTypes[event.type],
1467
+ documents: event.documents.map((document) => phDocumentFromGetDocument(document)),
1468
+ context: event.context ? {
1469
+ parentId: event.context.parentId ?? void 0,
1470
+ childId: event.context.childId ?? void 0
1471
+ } : void 0
1472
+ });
1473
+ }
1474
+ /**
1475
+ * Reports a broken realtime socket once per client, however many sockets it
1476
+ * goes through.
1477
+ *
1478
+ * `graphql-ws` retries on its own, and a Switchboard without a websocket
1479
+ * endpoint would otherwise fill the console for as long as the page is open.
1480
+ */
1481
+ logRealtimeError(error) {
1482
+ if (this.realtimeErrorLogged) return;
1483
+ this.realtimeErrorLogged = true;
1484
+ logger.warn("GraphQLReactorClient: realtime document changes are unavailable, falling back to local change events", error);
1485
+ }
1486
+ /**
1487
+ * Delivers an event to every matching subscriber.
1488
+ *
1489
+ * A subscriber that throws is logged and skipped so one bad listener cannot
1490
+ * stop the others from being notified.
1491
+ */
1492
+ emitChange(event) {
1493
+ for (const listener of [...this.listeners]) {
1494
+ if (!eventMatchesSearch(event, listener.search)) continue;
1495
+ try {
1496
+ listener.callback(event);
1497
+ } catch (error) {
1498
+ logger.error("GraphQLReactorClient: a document change subscriber threw", error);
1499
+ }
1500
+ }
1501
+ }
1502
+ };
1503
+ /**
1504
+ * Whether a value is a {@link GraphQLReactorClient}, including one built by
1505
+ * another copy of this module.
1506
+ *
1507
+ * Use this rather than `instanceof`: the brand it tests for is shared by every
1508
+ * copy of the class, so a client built by a hot-replaced module or by a package
1509
+ * that bundled its own copy is still recognised.
1510
+ */
1511
+ function isGraphQLReactorClient(client) {
1512
+ return client?.[graphQLReactorClientBrand] === true;
1513
+ }
1514
+ /** The schema's change types, mapped onto the reactor's. */
1515
+ const documentChangeTypes = {
1516
+ [DocumentChangeType.Created]: DOCUMENT_CHANGE_TYPE.Created,
1517
+ [DocumentChangeType.Deleted]: DOCUMENT_CHANGE_TYPE.Deleted,
1518
+ [DocumentChangeType.Updated]: DOCUMENT_CHANGE_TYPE.Updated,
1519
+ [DocumentChangeType.ParentAdded]: DOCUMENT_CHANGE_TYPE.ParentAdded,
1520
+ [DocumentChangeType.ParentRemoved]: DOCUMENT_CHANGE_TYPE.ParentRemoved,
1521
+ [DocumentChangeType.ChildAdded]: DOCUMENT_CHANGE_TYPE.ChildAdded,
1522
+ [DocumentChangeType.ChildRemoved]: DOCUMENT_CHANGE_TYPE.ChildRemoved
1523
+ };
1524
+ /**
1525
+ * Decides whether an event reaches a subscriber.
1526
+ *
1527
+ * Every populated field of the search filter is an AND condition, matching the
1528
+ * reactor's own subscription manager - which also treats an empty array as a
1529
+ * filter that nothing satisfies. `parentId` is a drive concept and is ignored.
1530
+ *
1531
+ * A `Deleted` event carries no documents, so it is matched on its
1532
+ * `context.childId` for identifier filters and delivered unconditionally to
1533
+ * type and slug filters: there is no document left to check them against.
1534
+ */
1535
+ function eventMatchesSearch(event, search) {
1536
+ const deleted = event.type === DOCUMENT_CHANGE_TYPE.Deleted;
1537
+ const { ids, slugs, type } = search;
1538
+ if (ids) {
1539
+ if (!(deleted ? [event.context?.childId] : event.documents.map((document) => document.header.id)).some((id) => id !== void 0 && ids.includes(id))) return false;
1540
+ }
1541
+ if (slugs && !deleted) {
1542
+ if (!event.documents.map((document) => document.header.slug).some((slug) => slugs.includes(slug))) return false;
1543
+ }
1544
+ if (type && !deleted) {
1545
+ if (!event.documents.map((document) => document.header.documentType).includes(type)) return false;
1546
+ }
1547
+ return true;
1548
+ }
1549
+ /**
1550
+ * Maps a reactor `ViewFilter` onto the GraphQL `ViewFilterInput`.
1551
+ *
1552
+ * Point-in-time reads are rejected: the Switchboard read API has no revision
1553
+ * argument, so silently returning the head state would be wrong.
1554
+ */
1555
+ function viewFilterInputFromViewFilter(view) {
1556
+ if (!view) return;
1557
+ if (view.revision !== void 0) throw new Error("point-in-time views are not supported by GraphQLReactorClient");
1558
+ if (view.branch === void 0 && view.scopes === void 0) return;
1559
+ return {
1560
+ branch: view.branch,
1561
+ scopes: view.scopes
1562
+ };
1563
+ }
1564
+ function pagingInputFromPaging(paging) {
1565
+ if (!paging) return;
1566
+ return {
1567
+ cursor: paging.cursor,
1568
+ limit: paging.limit
1569
+ };
1570
+ }
1571
+ /**
1572
+ * The reactor and the GraphQL schema disagree on the enum: the reactor's
1573
+ * `None` is the schema's `ORPHAN`. This is the inverse of the server's
1574
+ * `toReactorPropagationMode`.
1575
+ */
1576
+ const propagationModeInputs = {
1577
+ cascade: PropagationMode.Cascade,
1578
+ none: PropagationMode.Orphan
1579
+ };
1580
+ function propagationModeInput(propagate) {
1581
+ return propagate === void 0 ? void 0 : propagationModeInputs[propagate];
1582
+ }
1583
+ /**
1584
+ * Stamps and signs the actions about to be pushed.
1585
+ *
1586
+ * Signing needs the state the action applies to, so only a single action can be
1587
+ * signed per call: the second action of a batch applies to a state this client
1588
+ * cannot compute without running the reducer. Batches are pushed unsigned.
1589
+ */
1590
+ async function prepareActionsForPush(actions, document) {
1591
+ const signer = resolveAmbientSigner();
1592
+ if (!signer || actions.length === 0) return actions;
1593
+ if (actions.length > 1) {
1594
+ logger.warn("GraphQLReactorClient: pushing a multi-action batch unsigned, only single actions can be signed");
1595
+ return actions;
1596
+ }
1597
+ return [await signStampedAction(stampAction(actions[0], document), signer)];
1598
+ }
1599
+ /** Resolves the signer of the logged-in user, if there is one. */
1600
+ function resolveAmbientSigner() {
1601
+ if (typeof window === "undefined") return;
1602
+ const renown = window.ph?.renown;
1603
+ return renown?.user ? renown.signer : void 0;
1604
+ }
1605
+ /**
1606
+ * The revision the pushed operations start from: the lowest head revision
1607
+ * across the scopes the actions target.
1608
+ *
1609
+ * The baseline is only meaningful together with {@link scopesForActions}: a
1610
+ * batch that touches a scope at revision 1 and one at revision 5000 would
1611
+ * otherwise re-download the whole history of every scope of the document.
1612
+ */
1613
+ function sinceRevisionForActions(document, actions) {
1614
+ const revisions = actions.map((action) => document.header.revision[action.scope] ?? 0);
1615
+ return revisions.length > 0 ? Math.min(...revisions) : 0;
1616
+ }
1617
+ /** The distinct scopes the pushed actions target, in first-seen order. */
1618
+ function scopesForActions(actions) {
1619
+ if (actions.length === 0) return;
1620
+ return [...new Set(actions.map((action) => action.scope))];
1621
+ }
1622
+ //#endregion
1623
+ //#region src/hooks/config/editor.ts
1624
+ const isExternalControlsEnabledEventFunctions = makePHEventFunctions("isExternalControlsEnabled");
1625
+ /** Sets whether external controls are enabled for a given editor. */
1626
+ const setIsExternalControlsEnabled = isExternalControlsEnabledEventFunctions.setValue;
1627
+ /** Gets whether external controls are enabled for a given editor. */
1628
+ const useIsExternalControlsEnabled = isExternalControlsEnabledEventFunctions.useValue;
1629
+ /** Adds an event handler for when the external controls enabled state changes. */
1630
+ const addIsExternalControlsEnabledEventHandler = isExternalControlsEnabledEventFunctions.addEventHandler;
1631
+ const isDragAndDropEnabledEventFunctions = makePHEventFunctions("isDragAndDropEnabled");
1632
+ /** Sets whether drag and drop is enabled for a given app. */
1633
+ const setIsDragAndDropEnabled = isDragAndDropEnabledEventFunctions.setValue;
1634
+ /** Gets whether drag and drop is enabled for a given app. */
1635
+ const useIsDragAndDropEnabled = isDragAndDropEnabledEventFunctions.useValue;
1636
+ /** Adds an event handler for when the drag and drop enabled state changes. */
1637
+ const addIsDragAndDropEnabledEventHandler = isDragAndDropEnabledEventFunctions.addEventHandler;
1638
+ const allowedDocumentTypesEventFunctions = makePHEventFunctions("allowedDocumentTypes");
1639
+ /** Sets the allowed document types for a given app. */
1640
+ const setAllowedDocumentTypes = allowedDocumentTypesEventFunctions.setValue;
1641
+ /** Defines the document types a drive supports.
1642
+ *
1643
+ * Defaults to all of the document types registered in the reactor.
1644
+ */
1645
+ function useAllowedDocumentTypes() {
1646
+ return allowedDocumentTypesEventFunctions.useValue();
1647
+ }
1648
+ /** Adds an event handler for when the allowed document types for a given app changes. */
1649
+ const addAllowedDocumentTypesEventHandler = allowedDocumentTypesEventFunctions.addEventHandler;
1650
+ const phAppConfigSetters = {
1651
+ allowedDocumentTypes: setAllowedDocumentTypes,
1652
+ isDragAndDropEnabled: setIsDragAndDropEnabled
1653
+ };
1654
+ const phDocumentEditorConfigSetters = { isExternalControlsEnabled: setIsExternalControlsEnabled };
1655
+ const phAppConfigHooks = {
1656
+ allowedDocumentTypes: useAllowedDocumentTypes,
1657
+ isDragAndDropEnabled: useIsDragAndDropEnabled
1658
+ };
1659
+ const phDocumentEditorConfigHooks = { isExternalControlsEnabled: useIsExternalControlsEnabled };
1660
+ //#endregion
1661
+ //#region src/hooks/config/connect.ts
1662
+ const { useValue: useRouterBasename, setValue: setRouterBasename, addEventHandler: addRouterBasenameEventHandler } = makePHEventFunctions("routerBasename");
1663
+ const { useValue: useVersion, setValue: setVersion, addEventHandler: addVersionEventHandler } = makePHEventFunctions("version");
1664
+ const { useValue: useRequiresHardRefresh, setValue: setRequiresHardRefresh, addEventHandler: addRequiresHardRefreshEventHandler } = makePHEventFunctions("requiresHardRefresh");
1665
+ const { useValue: useWarnOutdatedApp, setValue: setWarnOutdatedApp, addEventHandler: addWarnOutdatedAppEventHandler } = makePHEventFunctions("warnOutdatedApp");
1666
+ const { useValue: useStudioMode, setValue: setStudioMode, addEventHandler: addStudioModeEventHandler } = makePHEventFunctions("studioMode");
1667
+ const { useValue: useBasePath, setValue: setBasePath, addEventHandler: addBasePathEventHandler } = makePHEventFunctions("basePath");
1668
+ const { useValue: useVersionCheckInterval, setValue: setVersionCheckInterval, addEventHandler: addVersionCheckIntervalEventHandler } = makePHEventFunctions("versionCheckInterval");
1669
+ const { useValue: useCliVersion, setValue: setCliVersion, addEventHandler: addCliVersionEventHandler } = makePHEventFunctions("cliVersion");
1670
+ const { useValue: useFileUploadOperationsChunkSize, setValue: setFileUploadOperationsChunkSize, addEventHandler: addFileUploadOperationsChunkSizeEventHandler } = makePHEventFunctions("fileUploadOperationsChunkSize");
1671
+ const { useValue: useIsDocumentModelSelectionSettingsEnabled, setValue: setIsDocumentModelSelectionSettingsEnabled, addEventHandler: addIsDocumentModelSelectionSettingsEnabledEventHandler } = makePHEventFunctions("isDocumentModelSelectionSettingsEnabled");
1672
+ const { useValue: useGaTrackingId, setValue: setGaTrackingId, addEventHandler: addGaTrackingIdEventHandler } = makePHEventFunctions("gaTrackingId");
1673
+ const { useValue: useDefaultDrivesUrl, setValue: setDefaultDrivesUrl, addEventHandler: addDefaultDrivesUrlEventHandler } = makePHEventFunctions("defaultDrivesUrl");
1674
+ const { useValue: useDrivesPreserveStrategy, setValue: setDrivesPreserveStrategy, addEventHandler: addDrivesPreserveStrategyEventHandler } = makePHEventFunctions("drivesPreserveStrategy");
1675
+ const { useValue: useIsLocalDrivesEnabled, setValue: setIsLocalDrivesEnabled, addEventHandler: addIsLocalDrivesEnabledEventHandler } = makePHEventFunctions("isLocalDrivesEnabled");
1676
+ const { useValue: useIsAddDriveEnabled, setValue: setIsAddDriveEnabled, addEventHandler: addIsAddDriveEnabledEventHandler } = makePHEventFunctions("isAddDriveEnabled");
1677
+ const { useValue: useIsPublicDrivesEnabled, setValue: setIsPublicDrivesEnabled, addEventHandler: addIsPublicDrivesEnabledEventHandler } = makePHEventFunctions("isPublicDrivesEnabled");
1678
+ const { useValue: useIsAddPublicDrivesEnabled, setValue: setIsAddPublicDrivesEnabled, addEventHandler: addIsAddPublicDrivesEnabledEventHandler } = makePHEventFunctions("isAddPublicDrivesEnabled");
1679
+ const { useValue: useIsDeletePublicDrivesEnabled, setValue: setIsDeletePublicDrivesEnabled, addEventHandler: addIsDeletePublicDrivesEnabledEventHandler } = makePHEventFunctions("isDeletePublicDrivesEnabled");
1680
+ const { useValue: useIsCloudDrivesEnabled, setValue: setIsCloudDrivesEnabled, addEventHandler: addIsCloudDrivesEnabledEventHandler } = makePHEventFunctions("isCloudDrivesEnabled");
1681
+ const { useValue: useIsAddCloudDrivesEnabled, setValue: setIsAddCloudDrivesEnabled, addEventHandler: addIsAddCloudDrivesEnabledEventHandler } = makePHEventFunctions("isAddCloudDrivesEnabled");
1682
+ const { useValue: useIsDeleteCloudDrivesEnabled, setValue: setIsDeleteCloudDrivesEnabled, addEventHandler: addIsDeleteCloudDrivesEnabledEventHandler } = makePHEventFunctions("isDeleteCloudDrivesEnabled");
1683
+ const { useValue: useLocalDrivesEnabled, setValue: setLocalDrivesEnabled, addEventHandler: addLocalDrivesEnabledEventHandler } = makePHEventFunctions("isLocalDrivesEnabled");
1684
+ const { useValue: useIsAddLocalDrivesEnabled, setValue: setIsAddLocalDrivesEnabled, addEventHandler: addIsAddLocalDrivesEnabledEventHandler } = makePHEventFunctions("isAddLocalDrivesEnabled");
1685
+ const { useValue: useIsDeleteLocalDrivesEnabled, setValue: setIsDeleteLocalDrivesEnabled, addEventHandler: addIsDeleteLocalDrivesEnabledEventHandler } = makePHEventFunctions("isDeleteLocalDrivesEnabled");
1686
+ const { useValue: useIsEditorDebugModeEnabled, setValue: setIsEditorDebugModeEnabled, addEventHandler: addIsEditorDebugModeEnabledEventHandler } = makePHEventFunctions("isEditorDebugModeEnabled");
1687
+ const { useValue: useIsEditorReadModeEnabled, setValue: setIsEditorReadModeEnabled, addEventHandler: addIsEditorReadModeEnabledEventHandler } = makePHEventFunctions("isEditorReadModeEnabled");
1688
+ const { useValue: useIsAnalyticsDatabaseWorkerEnabled, setValue: setIsAnalyticsDatabaseWorkerEnabled, addEventHandler: addIsAnalyticsDatabaseWorkerEnabledEventHandler } = makePHEventFunctions("isAnalyticsDatabaseWorkerEnabled");
1689
+ const { useValue: useIsDiffAnalyticsEnabled, setValue: setIsDiffAnalyticsEnabled, addEventHandler: addIsDiffAnalyticsEnabledEventHandler } = makePHEventFunctions("isDiffAnalyticsEnabled");
1690
+ const { useValue: useIsDriveAnalyticsEnabled, setValue: setIsDriveAnalyticsEnabled, addEventHandler: addIsDriveAnalyticsEnabledEventHandler } = makePHEventFunctions("isDriveAnalyticsEnabled");
1691
+ const { useValue: useRenownUrl, setValue: setRenownUrl, addEventHandler: addRenownUrlEventHandler } = makePHEventFunctions("renownUrl");
1692
+ const { useValue: useRenownNetworkId, setValue: setRenownNetworkId, addEventHandler: addRenownNetworkIdEventHandler } = makePHEventFunctions("renownNetworkId");
1693
+ const { useValue: useRenownChainId, setValue: setRenownChainId, addEventHandler: addRenownChainIdEventHandler } = makePHEventFunctions("renownChainId");
1694
+ const { useValue: useSwitchboardUrl, setValue: setSwitchboardUrl, addEventHandler: addSwitchboardUrlEventHandler } = makePHEventFunctions("switchboardUrl");
1695
+ const { useValue: useRenownAdapters, setValue: setRenownAdapters, addEventHandler: addRenownAdaptersEventHandler } = makePHEventFunctions("renownAdapters");
1696
+ const { useValue: useSentryRelease, setValue: setSentryRelease, addEventHandler: addSentryReleaseEventHandler } = makePHEventFunctions("sentryRelease");
1697
+ const { useValue: useSentryDsn, setValue: setSentryDsn, addEventHandler: addSentryDsnEventHandler } = makePHEventFunctions("sentryDsn");
1698
+ const { useValue: useSentryEnv, setValue: setSentryEnv, addEventHandler: addSentryEnvEventHandler } = makePHEventFunctions("sentryEnv");
1699
+ const { useValue: useIsSentryTracingEnabled, setValue: setIsSentryTracingEnabled, addEventHandler: addIsSentryTracingEnabledEventHandler } = makePHEventFunctions("isSentryTracingEnabled");
1700
+ const { useValue: useIsExternalProcessorsEnabled, setValue: setIsExternalProcessorsEnabled, addEventHandler: addIsExternalProcessorsEnabledEventHandler } = makePHEventFunctions("isExternalProcessorsEnabled");
1701
+ const { useValue: useIsExternalPackagesEnabled, setValue: setIsExternalPackagesEnabled, addEventHandler: addIsExternalPackagesEnabledEventHandler } = makePHEventFunctions("isExternalPackagesEnabled");
1702
+ const enabledEditorsEventFunctions = makePHEventFunctions("enabledEditors");
1703
+ /** Sets the enabled editors for Connect. */
1704
+ const setEnabledEditors = enabledEditorsEventFunctions.setValue;
1705
+ /** Gets the enabled editors for Connect. */
1706
+ const useEnabledEditors = enabledEditorsEventFunctions.useValue;
1707
+ /** Adds an event handler for when the enabled editors for Connect changes. */
1708
+ const addEnabledEditorsEventHandler = enabledEditorsEventFunctions.addEventHandler;
1709
+ const disabledEditorsEventFunctions = makePHEventFunctions("disabledEditors");
1710
+ /** Sets the disabled editors for Connect. */
1711
+ const setDisabledEditors = disabledEditorsEventFunctions.setValue;
1712
+ /** Gets the disabled editors for Connect. */
1713
+ const useDisabledEditors = disabledEditorsEventFunctions.useValue;
1714
+ /** Adds an event handler for when the disabled editors for Connect changes. */
1715
+ const addDisabledEditorsEventHandler = disabledEditorsEventFunctions.addEventHandler;
1716
+ const isRelationalProcessorsEnabled = makePHEventFunctions("isRelationalProcessorsEnabled");
1717
+ /** Sets the isRelationalProcessorsEnabled for Connect. */
1718
+ const setIsRelationalProcessorsEnabled = isRelationalProcessorsEnabled.setValue;
1719
+ /** Gets the isRelationalProcessorsEnabled for Connect. */
1720
+ const useIsRelationalProcessorsEnabled = isRelationalProcessorsEnabled.useValue;
1721
+ /** Adds an event handler for when the isRelationalProcessorsEnabled for Connect changes. */
1722
+ const addIsRelationalProcessorsEnabledEventHandler = isRelationalProcessorsEnabled.addEventHandler;
1723
+ const isExternalRelationalProcessorsEnabled = makePHEventFunctions("isExternalRelationalProcessorsEnabled");
1724
+ /** Sets the isExternalRelationalProcessorsEnabled for Connect. */
1725
+ const setIsExternalRelationalProcessorsEnabled = isExternalRelationalProcessorsEnabled.setValue;
1726
+ /** Gets the isExternalRelationalProcessorsEnabled for Connect. */
1727
+ const useIsExternalRelationalProcessorsEnabled = isExternalRelationalProcessorsEnabled.useValue;
1728
+ /** Adds an event handler for when the isExternalRelationalProcessorsEnabled for Connect changes. */
1729
+ const addIsExternalRelationalProcessorsEnabledEventHandler = isExternalRelationalProcessorsEnabled.addEventHandler;
1730
+ const isAnalyticsEnabledEventFunctions = makePHEventFunctions("isAnalyticsEnabled");
1731
+ /** Sets the isAnalyticsEnabled for Connect. */
1732
+ const setIsAnalyticsEnabled = isAnalyticsEnabledEventFunctions.setValue;
1733
+ /** Gets the isAnalyticsEnabled for Connect. */
1734
+ const useIsAnalyticsEnabled = isAnalyticsEnabledEventFunctions.useValue;
1735
+ /** Adds an event handler for when the isAnalyticsEnabled for Connect changes. */
1736
+ const addIsAnalyticsEnabledEventHandler = isAnalyticsEnabledEventFunctions.addEventHandler;
1737
+ const isAnalyticsExternalProcessorsEnabled = makePHEventFunctions("isAnalyticsExternalProcessorsEnabled");
1738
+ /** Sets the isAnalyticsExternalProcessorsEnabled for Connect. */
1739
+ const setIsAnalyticsExternalProcessorsEnabled = isAnalyticsExternalProcessorsEnabled.setValue;
1740
+ /** Gets the isAnalyticsExternalProcessorsEnabled for Connect. */
1741
+ const useIsAnalyticsExternalProcessorsEnabled = isAnalyticsExternalProcessorsEnabled.useValue;
1742
+ /** Adds an event handler for when the isAnalyticsExternalProcessorsEnabled for Connect changes. */
1743
+ const addIsAnalyticsExternalProcessorsEnabledEventHandler = isAnalyticsExternalProcessorsEnabled.addEventHandler;
1744
+ const analyticsDatabaseNameEventFunctions = makePHEventFunctions("analyticsDatabaseName");
1745
+ /** Sets the analytics database name for Connect. */
1746
+ const setAnalyticsDatabaseName = analyticsDatabaseNameEventFunctions.setValue;
1747
+ /** Gets the analytics database name for Connect. */
1748
+ const useAnalyticsDatabaseName = analyticsDatabaseNameEventFunctions.useValue;
1749
+ /** Adds an event handler for when the analytics database name for Connect changes. */
1750
+ const addAnalyticsDatabaseNameEventHandler = analyticsDatabaseNameEventFunctions.addEventHandler;
1751
+ const logLevelEventFunctions = makePHEventFunctions("logLevel");
1752
+ /** Sets the log level for Connect. */
1753
+ const setLogLevel = logLevelEventFunctions.setValue;
1754
+ /** Gets the log level for Connect. */
1755
+ const useLogLevel = logLevelEventFunctions.useValue;
1756
+ /** Adds an event handler for when the log level for Connect changes. */
1757
+ const addLogLevelEventHandler = logLevelEventFunctions.addEventHandler;
1758
+ const allowListEventFunctions = makePHEventFunctions("allowList");
1759
+ /** Sets the allow list for Connect. */
1760
+ const setAllowList = allowListEventFunctions.setValue;
1761
+ /** Gets the allow list for Connect. */
1762
+ const useAllowList = allowListEventFunctions.useValue;
1763
+ /** Adds an event handler for when the allow list for Connect changes. */
1764
+ const addAllowListEventHandler = allowListEventFunctions.addEventHandler;
1765
+ const nonUserConfigSetters = {
1766
+ routerBasename: setRouterBasename,
1767
+ version: setVersion,
1768
+ requiresHardRefresh: setRequiresHardRefresh,
1769
+ warnOutdatedApp: setWarnOutdatedApp,
1770
+ studioMode: setStudioMode,
1771
+ basePath: setBasePath,
1772
+ versionCheckInterval: setVersionCheckInterval,
1773
+ cliVersion: setCliVersion,
1774
+ fileUploadOperationsChunkSize: setFileUploadOperationsChunkSize,
1775
+ isDocumentModelSelectionSettingsEnabled: setIsDocumentModelSelectionSettingsEnabled,
1776
+ gaTrackingId: setGaTrackingId,
1777
+ defaultDrivesUrl: setDefaultDrivesUrl,
1778
+ drivesPreserveStrategy: setDrivesPreserveStrategy,
1779
+ isLocalDrivesEnabled: setIsLocalDrivesEnabled,
1780
+ isAddDriveEnabled: setIsAddDriveEnabled,
1781
+ isPublicDrivesEnabled: setIsPublicDrivesEnabled,
1782
+ isAddPublicDrivesEnabled: setIsAddPublicDrivesEnabled,
1783
+ isDeletePublicDrivesEnabled: setIsDeletePublicDrivesEnabled,
1784
+ isCloudDrivesEnabled: setIsCloudDrivesEnabled,
1785
+ isAddCloudDrivesEnabled: setIsAddCloudDrivesEnabled,
1786
+ isDeleteCloudDrivesEnabled: setIsDeleteCloudDrivesEnabled,
1787
+ isAddLocalDrivesEnabled: setIsAddLocalDrivesEnabled,
1788
+ isDeleteLocalDrivesEnabled: setIsDeleteLocalDrivesEnabled,
1789
+ isEditorDebugModeEnabled: setIsEditorDebugModeEnabled,
1790
+ isEditorReadModeEnabled: setIsEditorReadModeEnabled,
1791
+ isRelationalProcessorsEnabled: setIsRelationalProcessorsEnabled,
1792
+ isExternalRelationalProcessorsEnabled: setIsExternalRelationalProcessorsEnabled,
1793
+ isAnalyticsEnabled: setIsAnalyticsEnabled,
1794
+ analyticsDatabaseName: setAnalyticsDatabaseName,
1795
+ isAnalyticsExternalProcessorsEnabled: setIsAnalyticsExternalProcessorsEnabled,
1796
+ isAnalyticsDatabaseWorkerEnabled: setIsAnalyticsDatabaseWorkerEnabled,
1797
+ isDiffAnalyticsEnabled: setIsDiffAnalyticsEnabled,
1798
+ isDriveAnalyticsEnabled: setIsDriveAnalyticsEnabled,
1799
+ renownUrl: setRenownUrl,
1800
+ renownNetworkId: setRenownNetworkId,
1801
+ renownChainId: setRenownChainId,
1802
+ switchboardUrl: setSwitchboardUrl,
1803
+ renownAdapters: setRenownAdapters,
1804
+ sentryRelease: setSentryRelease,
1805
+ sentryDsn: setSentryDsn,
1806
+ sentryEnv: setSentryEnv,
1807
+ isSentryTracingEnabled: setIsSentryTracingEnabled,
1808
+ isExternalProcessorsEnabled: setIsExternalProcessorsEnabled,
1809
+ isExternalPackagesEnabled: setIsExternalPackagesEnabled,
1810
+ allowList: setAllowList,
1811
+ logLevel: setLogLevel,
1812
+ disabledEditors: setDisabledEditors,
1813
+ enabledEditors: setEnabledEditors
1814
+ };
1815
+ const phGlobalConfigSetters = {
1816
+ ...phAppConfigSetters,
1817
+ ...phDocumentEditorConfigSetters,
1818
+ ...nonUserConfigSetters
1819
+ };
1820
+ const nonUserConfigHooks = {
1821
+ routerBasename: useRouterBasename,
1822
+ version: useVersion,
1823
+ requiresHardRefresh: useRequiresHardRefresh,
1824
+ warnOutdatedApp: useWarnOutdatedApp,
1825
+ studioMode: useStudioMode,
1826
+ basePath: useBasePath,
1827
+ versionCheckInterval: useVersionCheckInterval,
1828
+ cliVersion: useCliVersion,
1829
+ fileUploadOperationsChunkSize: useFileUploadOperationsChunkSize,
1830
+ isDocumentModelSelectionSettingsEnabled: useIsDocumentModelSelectionSettingsEnabled,
1831
+ gaTrackingId: useGaTrackingId,
1832
+ defaultDrivesUrl: useDefaultDrivesUrl,
1833
+ drivesPreserveStrategy: useDrivesPreserveStrategy,
1834
+ isAddDriveEnabled: useIsAddDriveEnabled,
1835
+ isPublicDrivesEnabled: useIsPublicDrivesEnabled,
1836
+ isAddPublicDrivesEnabled: useIsAddPublicDrivesEnabled,
1837
+ isDeletePublicDrivesEnabled: useIsDeletePublicDrivesEnabled,
1838
+ isCloudDrivesEnabled: useIsCloudDrivesEnabled,
1839
+ isAddCloudDrivesEnabled: useIsAddCloudDrivesEnabled,
1840
+ isDeleteCloudDrivesEnabled: useIsDeleteCloudDrivesEnabled,
1841
+ isLocalDrivesEnabled: useIsLocalDrivesEnabled,
1842
+ isAddLocalDrivesEnabled: useIsAddLocalDrivesEnabled,
1843
+ isDeleteLocalDrivesEnabled: useIsDeleteLocalDrivesEnabled,
1844
+ isEditorDebugModeEnabled: useIsEditorDebugModeEnabled,
1845
+ isEditorReadModeEnabled: useIsEditorReadModeEnabled,
1846
+ isAnalyticsDatabaseWorkerEnabled: useIsAnalyticsDatabaseWorkerEnabled,
1847
+ isDiffAnalyticsEnabled: useIsDiffAnalyticsEnabled,
1848
+ isDriveAnalyticsEnabled: useIsDriveAnalyticsEnabled,
1849
+ renownUrl: useRenownUrl,
1850
+ renownNetworkId: useRenownNetworkId,
1851
+ renownChainId: useRenownChainId,
1852
+ switchboardUrl: useSwitchboardUrl,
1853
+ renownAdapters: useRenownAdapters,
1854
+ sentryRelease: useSentryRelease,
1855
+ sentryDsn: useSentryDsn,
1856
+ sentryEnv: useSentryEnv,
1857
+ isSentryTracingEnabled: useIsSentryTracingEnabled,
1858
+ isExternalProcessorsEnabled: useIsExternalProcessorsEnabled,
1859
+ isExternalPackagesEnabled: useIsExternalPackagesEnabled,
1860
+ allowList: useAllowList,
1861
+ isAnalyticsEnabled: useIsAnalyticsEnabled,
1862
+ isAnalyticsExternalProcessorsEnabled: useIsAnalyticsExternalProcessorsEnabled,
1863
+ isRelationalProcessorsEnabled: useIsRelationalProcessorsEnabled,
1864
+ isExternalRelationalProcessorsEnabled: useIsExternalRelationalProcessorsEnabled,
1865
+ analyticsDatabaseName: useAnalyticsDatabaseName,
1866
+ logLevel: useLogLevel,
1867
+ disabledEditors: useDisabledEditors,
1868
+ enabledEditors: useEnabledEditors
1869
+ };
1870
+ const phGlobalConfigHooks = {
1871
+ ...phAppConfigHooks,
1872
+ ...phDocumentEditorConfigHooks,
1873
+ ...nonUserConfigHooks
1874
+ };
1875
+ //#endregion
1876
+ //#region src/hooks/attachment-service.ts
1877
+ const attachmentServiceEventFunctions = makePHEventFunctions("attachmentService");
1878
+ /** Returns the attachment service from window.ph */
1879
+ const useAttachmentService = attachmentServiceEventFunctions.useValue;
1880
+ /** Sets the attachment service on window.ph */
1881
+ const setAttachmentService = attachmentServiceEventFunctions.setValue;
1882
+ /** Registers the attachmentService window event handler */
1883
+ const addAttachmentServiceEventHandler = attachmentServiceEventFunctions.addEventHandler;
1884
+ //#endregion
1885
+ //#region src/hooks/features.ts
1886
+ const featuresEventFunctions = makePHEventFunctions("features");
1887
+ const useFeatures = featuresEventFunctions.useValue;
1888
+ const setFeatures = featuresEventFunctions.setValue;
1889
+ const addFeaturesEventHandler = featuresEventFunctions.addEventHandler;
1890
+ //#endregion
1891
+ //#region src/utils/url.ts
1892
+ function resolveUrlPathname(path) {
1893
+ return new URL(path.replace(/^\/+/, ""), window.location.origin + (window.ph?.basePath ?? "/")).pathname;
1894
+ }
1895
+ /** Returns the current path without the base path */
1896
+ function getPathWithoutBase(path) {
1897
+ const basePath = window.ph?.basePath ?? "/";
1898
+ return path.replace(basePath, basePath.endsWith("/") ? "/" : "");
1899
+ }
1900
+ /** Makes a URL component for a drive. */
1901
+ function makeDriveUrlComponent(drive) {
1902
+ if (!drive) return "";
1903
+ return `/d/${slug(drive.header.slug)}`;
1904
+ }
1905
+ /** Makes a URL component for a node. */
1906
+ function makeNodeSlug(node) {
1907
+ if (!node) return "";
1908
+ const nodeName = node.name;
1909
+ if (!nodeName) return slug(node.id);
1910
+ return slug(`${nodeName}-${node.id}`);
1911
+ }
1912
+ /** Extracts the node slug from a path.
1913
+ *
1914
+ * The path is expected to be in the format `/d/<drive-slug>/<node-slug>`.
1915
+ */
1916
+ function extractNodeSlugFromPath(path) {
1917
+ const currentPath = getPathWithoutBase(path);
1918
+ return /^\/d\/[^/]+\/([^/]+)$/.exec(currentPath)?.[1];
1919
+ }
1920
+ /** Finds a UUID in a string, used for extracting node ids from node slugs in the URL. */
1921
+ function findUuid(input) {
1922
+ if (!input) return void 0;
1923
+ return /\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/.exec(input)?.[0];
1924
+ }
1925
+ function extractNodeIdFromSlug(nodeSlug) {
1926
+ return findUuid(nodeSlug);
1927
+ }
1928
+ function extractNodeIdFromPath(path) {
1929
+ return extractNodeIdFromSlug(extractNodeSlugFromPath(path));
1930
+ }
1931
+ /** Extracts the drive slug from a path.
1932
+ * Used for extracting drive ids from drive slugs in the URL.
1933
+ * Expects the path to be in the format `/d/<drive-slug>`.
1934
+ */
1935
+ function extractDriveSlugFromPath(path) {
1936
+ const currentPath = getPathWithoutBase(path);
1937
+ return /^\/d\/([^/]+)/.exec(currentPath)?.[1] ?? "";
1938
+ }
1939
+ function extractDriveIdFromSlug(driveSlug) {
1940
+ return findUuid(driveSlug);
1941
+ }
1942
+ function extractDriveIdFromPath(path) {
1943
+ return extractDriveIdFromSlug(extractDriveSlugFromPath(path));
1944
+ }
1945
+ /**
1946
+ * Creates a URL string with the given pathname while preserving existing query parameters.
1947
+ */
1948
+ function createUrlWithPreservedParams(pathname) {
1949
+ const search = window.location.search;
1950
+ return search ? `${pathname}${search}` : pathname;
1951
+ }
1952
+ //#endregion
1953
+ //#region src/hooks/drives.ts
1954
+ const drivesEventFunctions = makePHEventFunctions("drives");
1955
+ /** Returns all of the drives in the reactor */
1956
+ const useDrives = drivesEventFunctions.useValue;
1957
+ /** Sets the drives in the reactor */
1958
+ const setDrives = drivesEventFunctions.setValue;
1959
+ /** Adds an event handler for the drives */
1960
+ const addDrivesEventHandler = drivesEventFunctions.addEventHandler;
1961
+ //#endregion
1962
+ //#region src/hooks/set-selected-node.ts
1963
+ const selectedNodeIdEventFunctions = makePHEventFunctions("selectedNodeId");
1964
+ const useSelectedNodeId = selectedNodeIdEventFunctions.useValue;
1965
+ const setSelectedNodeId = selectedNodeIdEventFunctions.setValue;
1966
+ const addSelectedNodeIdEventHandler = selectedNodeIdEventFunctions.addEventHandler;
1967
+ /** Sets the selected node (file or folder). */
1968
+ function setSelectedNode(nodeOrNodeSlug) {
1969
+ const nodeSlug = typeof nodeOrNodeSlug === "string" ? nodeOrNodeSlug : makeNodeSlug(nodeOrNodeSlug);
1970
+ setSelectedNodeId(extractNodeIdFromSlug(nodeSlug));
1971
+ const driveSlugFromPath = extractDriveSlugFromPath(window.location.pathname);
1972
+ if (!driveSlugFromPath) return;
1973
+ if (!nodeSlug) {
1974
+ const pathname = resolveUrlPathname(`/d/${driveSlugFromPath}`);
1975
+ if (pathname === window.location.pathname) return;
1976
+ window.history.pushState(null, "", createUrlWithPreservedParams(pathname));
1977
+ return;
1978
+ }
1979
+ const pathname = resolveUrlPathname(`/d/${driveSlugFromPath}/${nodeSlug}`);
1980
+ if (pathname === window.location.pathname) return;
1981
+ window.history.pushState(null, "", createUrlWithPreservedParams(pathname));
1982
+ }
1983
+ function addResetSelectedNodeEventHandler() {
1984
+ window.addEventListener("ph:selectedDriveIdUpdated", () => {
1985
+ setSelectedNodeId(void 0);
1986
+ });
1987
+ }
1988
+ function addSetSelectedNodeOnPopStateEventHandler() {
1989
+ window.addEventListener("popstate", () => {
1990
+ const pathname = window.location.pathname;
1991
+ const nodeSlug = extractNodeSlugFromPath(pathname);
1992
+ if (extractNodeIdFromSlug(nodeSlug) !== window.ph?.selectedNodeId) setSelectedNode(nodeSlug);
1993
+ });
1994
+ }
1995
+ //#endregion
1996
+ //#region src/hooks/selected-drive.ts
1997
+ const selectedDriveIdEventFunctions = makePHEventFunctions("selectedDriveId");
1998
+ /** Returns the selected drive id */
1999
+ const useSelectedDriveId = selectedDriveIdEventFunctions.useValue;
2000
+ /** Sets the selected drive id */
2001
+ const setSelectedDriveId = selectedDriveIdEventFunctions.setValue;
2002
+ /** Adds an event handler for the selected drive id */
2003
+ const addSelectedDriveIdEventHandler = selectedDriveIdEventFunctions.addEventHandler;
2004
+ /** Returns the selected drive */
2005
+ function useSelectedDrive() {
2006
+ const drive = useSelectedDriveSafe();
2007
+ if (!drive[0]) throw new Error("There is no drive selected. Did you mean to call 'useSelectedDriveSafe'?");
2008
+ return drive;
2009
+ }
2010
+ /** Returns the selected drive, or undefined if no drive is selected */
2011
+ function useSelectedDriveSafe() {
2012
+ const selectedDriveId = useSelectedDriveId();
2013
+ const selectedDrive = useDrives()?.find((drive) => drive.header.id === selectedDriveId);
2014
+ const [drive, dispatch] = useDispatch(selectedDrive);
2015
+ if (!selectedDrive) return [void 0, void 0];
2016
+ return [drive, dispatch];
2017
+ }
2018
+ function setSelectedDrive(driveOrDriveSlug) {
2019
+ const driveSlug = typeof driveOrDriveSlug === "string" ? driveOrDriveSlug : driveOrDriveSlug?.header.slug;
2020
+ const drives = window.ph?.drives;
2021
+ const driveId = (typeof driveOrDriveSlug === "object" && driveOrDriveSlug !== null ? driveOrDriveSlug : drives?.find((d) => d.header.slug === driveSlug))?.header.id;
2022
+ if (!driveId && driveSlug && extractDriveSlugFromPath(window.location.pathname) === driveSlug) {
2023
+ if (window.ph?.selectedDriveId) setSelectedDriveId(void 0);
2024
+ deferDriveSelection(driveSlug);
2025
+ return;
2026
+ }
2027
+ cancelPendingDriveSelection();
2028
+ setSelectedDriveId(driveId);
2029
+ if (!driveId) {
2030
+ const pathname = resolveUrlPathname("/");
2031
+ if (pathname === window.location.pathname) return;
2032
+ window.history.pushState(null, "", createUrlWithPreservedParams(pathname));
2033
+ return;
2034
+ }
2035
+ const pathname = resolveUrlPathname(`/d/${driveSlug}`);
2036
+ if (pathname === window.location.pathname) return;
2037
+ window.history.pushState(null, "", createUrlWithPreservedParams(pathname));
2038
+ }
2039
+ const DEFERRED_DRIVE_TICK_MS = 2e3;
2040
+ const DEFERRED_DRIVE_MAX_WAIT_MS = 15e3;
2041
+ function isInitialSyncInFlight() {
2042
+ const remotes = window.ph?.reactorClientModule?.reactorModule?.syncModule?.syncManager?.list();
2043
+ if (!remotes?.length) return false;
2044
+ return remotes.some((remote) => {
2045
+ try {
2046
+ const snapshot = remote.channel.getConnectionState();
2047
+ return snapshot.receivingPages || !snapshot.lastSuccessUtcMs && snapshot.state !== "error";
2048
+ } catch {
2049
+ return false;
2050
+ }
2051
+ });
2052
+ }
2053
+ let pendingHandler;
2054
+ let pendingTimeout;
2055
+ function cancelPendingDriveSelection() {
2056
+ if (pendingHandler) {
2057
+ window.removeEventListener("ph:drivesUpdated", pendingHandler);
2058
+ pendingHandler = void 0;
2059
+ }
2060
+ if (pendingTimeout) {
2061
+ clearTimeout(pendingTimeout);
2062
+ pendingTimeout = void 0;
2063
+ }
2064
+ }
2065
+ function deferDriveSelection(driveSlug) {
2066
+ cancelPendingDriveSelection();
2067
+ const nodeSlug = extractNodeSlugFromPath(window.location.pathname);
2068
+ const handler = () => {
2069
+ if (!window.ph?.drives?.find((d) => d.header.slug === driveSlug)) return;
2070
+ cancelPendingDriveSelection();
2071
+ setSelectedDrive(driveSlug);
2072
+ setSelectedNode(nodeSlug);
2073
+ };
2074
+ pendingHandler = handler;
2075
+ window.addEventListener("ph:drivesUpdated", handler);
2076
+ const deadline = Date.now() + DEFERRED_DRIVE_MAX_WAIT_MS;
2077
+ const scheduleTick = () => {
2078
+ pendingTimeout = setTimeout(() => {
2079
+ if (Date.now() < deadline && isInitialSyncInFlight()) {
2080
+ scheduleTick();
2081
+ return;
2082
+ }
2083
+ cancelPendingDriveSelection();
2084
+ const pathname = resolveUrlPathname("/");
2085
+ if (pathname === window.location.pathname) return;
2086
+ window.history.pushState(null, "", createUrlWithPreservedParams(pathname));
2087
+ }, DEFERRED_DRIVE_TICK_MS);
2088
+ };
2089
+ scheduleTick();
2090
+ }
2091
+ function addSetSelectedDriveOnPopStateEventHandler() {
2092
+ window.addEventListener("popstate", () => {
2093
+ const pathname = window.location.pathname;
2094
+ const driveId = extractDriveIdFromPath(pathname);
2095
+ if (driveId !== window.ph?.selectedDriveId) setSelectedDrive(driveId);
2096
+ });
2097
+ }
2098
+ //#endregion
2099
+ //#region src/hooks/use-drop-target.ts
2100
+ function useDropTarget() {
2101
+ const [isDropTarget, setIsDropTarget] = useState(false);
2102
+ const isDragAndDropEnabled = useIsDragAndDropEnabled();
2103
+ const targetSetter = funnel(() => setIsDropTarget(true), {
2104
+ triggerAt: "start",
2105
+ minQuietPeriodMs: 100
2106
+ });
2107
+ const targetUnsetter = funnel(() => setIsDropTarget(false), {
2108
+ triggerAt: "start",
2109
+ minQuietPeriodMs: 100
2110
+ });
2111
+ function setTarget() {
2112
+ if (!isDragAndDropEnabled) return;
2113
+ targetUnsetter.cancel();
2114
+ targetSetter.call();
2115
+ }
2116
+ function unsetTarget() {
2117
+ if (!isDragAndDropEnabled) return;
2118
+ targetSetter.cancel();
2119
+ targetUnsetter.call();
2120
+ }
2121
+ return {
2122
+ isDropTarget,
2123
+ setTarget,
2124
+ unsetTarget
2125
+ };
2126
+ }
2127
+ //#endregion
2128
+ //#region src/hooks/node-drag-and-drop.ts
2129
+ const draggingNodeEventFunctions = makePHEventFunctions("draggingNode");
2130
+ const useDraggingNode = draggingNodeEventFunctions.useValue;
2131
+ const setDraggingNode = draggingNodeEventFunctions.setValue;
2132
+ const addDraggingNodeEventHandler = draggingNodeEventFunctions.addEventHandler;
2133
+ const draggingNodeSetter = funnel((node) => setDraggingNode(node), {
2134
+ reducer: (_, newNode) => newNode,
2135
+ triggerAt: "start",
2136
+ minQuietPeriodMs: 100
2137
+ });
2138
+ const draggingNodeUnsetter = funnel(() => setDraggingNode(void 0), {
2139
+ triggerAt: "start",
2140
+ minQuietPeriodMs: 100
2141
+ });
2142
+ function setDragging(node) {
2143
+ draggingNodeUnsetter.cancel();
2144
+ draggingNodeSetter.call(node);
2145
+ }
2146
+ function unsetDragging() {
2147
+ draggingNodeSetter.cancel();
2148
+ draggingNodeUnsetter.call();
2149
+ }
2150
+ const isNodeDrag = (params) => conditional(params, [({ srcId }) => isNot(isTruthy)(srcId), constant(false)], [({ driveId }) => isNot(isTruthy)(driveId), constant(false)], [({ driveId, srcId }) => isStrictEqual(driveId, srcId), constant(false)], constant(true));
2151
+ function useDragNode(args) {
2152
+ const { srcId, parentId } = args;
2153
+ const driveId = useSelectedDriveId();
2154
+ const draggingNodeId = useDraggingNode()?.srcId;
2155
+ const params = {
2156
+ driveId,
2157
+ srcId,
2158
+ parentId
2159
+ };
2160
+ const draggable = isNodeDrag(params);
2161
+ const isDragging = allPass({
2162
+ srcId,
2163
+ draggingNodeId
2164
+ }, [
2165
+ () => draggable,
2166
+ ({ srcId }) => isString(srcId),
2167
+ ({ draggingNodeId }) => isString(draggingNodeId),
2168
+ ({ srcId, draggingNodeId }) => isStrictEqual(srcId, draggingNodeId)
2169
+ ]);
2170
+ const onDragStart = () => {
2171
+ if (!draggable) return;
2172
+ setDragging(params);
2173
+ };
2174
+ const onDragEnd = () => {
2175
+ if (!draggable) return;
2176
+ unsetDragging();
2177
+ };
2178
+ return {
2179
+ isDragging,
2180
+ draggable,
2181
+ onDragStart,
2182
+ onDragEnd
2183
+ };
2184
+ }
2185
+ const isNodeDrop = (params) => conditional(params, [({ srcId }) => isNot(isTruthy)(srcId), constant(false)], [({ driveId }) => isNot(isDefined)(driveId), constant(false)], [({ srcId, targetId }) => isStrictEqual(srcId, targetId), constant(false)], [({ driveId, parentId, targetId }) => isNot(isTruthy)(parentId) && isStrictEqual(targetId, driveId), constant(false)], [({ targetId, parentId }) => isStrictEqual(targetId, parentId), constant(false)], constant(true));
2186
+ function useDropNode(targetId) {
2187
+ const driveId = useSelectedDriveId();
2188
+ const { srcId, parentId } = useDraggingNode() ?? {};
2189
+ const { isDropTarget, setTarget, unsetTarget } = useDropTarget();
2190
+ const params = {
2191
+ driveId,
2192
+ parentId,
2193
+ targetId,
2194
+ srcId
2195
+ };
2196
+ function handleNodeDrop(event, cb) {
2197
+ if (!isNodeDrop(params)) return;
2198
+ event.preventDefault();
2199
+ event.stopPropagation();
2200
+ cb?.();
2201
+ }
2202
+ const onDragEnter = (event) => handleNodeDrop(event);
2203
+ const onDragOver = (event) => handleNodeDrop(event, once(setTarget));
2204
+ const onDragLeave = (event) => handleNodeDrop(event, once(unsetTarget));
2205
+ const onDrop = (event) => handleNodeDrop(event, once(() => {
2206
+ unsetDragging();
2207
+ unsetTarget();
2208
+ moveNodeById(params).catch(console.error);
2209
+ }));
2210
+ return {
2211
+ isDropTarget,
2212
+ onDragEnter,
2213
+ onDragOver,
2214
+ onDragLeave,
2215
+ onDrop
2216
+ };
2217
+ }
2218
+ //#endregion
2219
+ //#region src/hooks/graphql-reactor-client.ts
2220
+ const graphQLReactorClientEventFunctions = makePHEventFunctions("reactorGraphQLClient");
2221
+ const useGraphQLReactorClient = graphQLReactorClientEventFunctions.useValue;
2222
+ const setGraphQLReactorClient = graphQLReactorClientEventFunctions.setValue;
2223
+ const addGraphQLReactorClientEventHandler = graphQLReactorClientEventFunctions.addEventHandler;
2224
+ //#endregion
2225
+ //#region src/hooks/package-discovery.ts
2226
+ const packageDiscoveryFunctions = makePHEventFunctions("packageDiscoveryService");
2227
+ const usePackageDiscoveryService = packageDiscoveryFunctions.useValue;
2228
+ const setPackageDiscoveryService = packageDiscoveryFunctions.setValue;
2229
+ const addPackageDiscoveryServiceEventHandler = packageDiscoveryFunctions.addEventHandler;
2230
+ //#endregion
2231
+ //#region src/hooks/reactor.ts
2232
+ const reactorClientModuleEventFunctions = makePHEventFunctions("reactorClientModule");
2233
+ const reactorClientEventFunctions = makePHEventFunctions("reactorClient");
2234
+ /** Returns the reactor client module (in-process or worker-backed) */
2235
+ const useReactorClientModule = reactorClientModuleEventFunctions.useValue;
2236
+ /** Sets the reactor client module */
2237
+ const setReactorClientModule = reactorClientModuleEventFunctions.setValue;
2238
+ /** Adds an event handler for the reactor client module */
2239
+ const addReactorClientModuleEventHandler = reactorClientModuleEventFunctions.addEventHandler;
2240
+ /** Returns the reactor client */
2241
+ const useReactorClient = reactorClientEventFunctions.useValue;
2242
+ /** Sets the reactor client */
2243
+ const setReactorClient = reactorClientEventFunctions.setValue;
2244
+ /** Adds an event handler for the reactor client */
2245
+ const addReactorClientEventHandler = reactorClientEventFunctions.addEventHandler;
2246
+ const useSync = () => useReactorClientModule()?.reactorModule?.syncModule?.syncManager;
2247
+ const useSyncList = () => {
2248
+ return useSync()?.list() ?? [];
2249
+ };
2250
+ const useModelRegistry = () => useReactorClientModule()?.reactorModule?.documentModelRegistry;
2251
+ const useDatabase = () => {
2252
+ const module = useReactorClientModule();
2253
+ return module?.kind === "browser" ? module.reactorModule?.database : void 0;
2254
+ };
2255
+ const usePGlite = () => {
2256
+ const module = useReactorClientModule();
2257
+ return module?.kind === "browser" ? module.reactorModule?.pg : void 0;
2258
+ };
2259
+ //#endregion
2260
+ //#region src/hooks/revision-history.ts
2261
+ const revisionHistoryEventFunctions = makePHEventFunctions("revisionHistoryVisible");
2262
+ /** Returns whether revision history is visible */
2263
+ const useRevisionHistoryVisible = revisionHistoryEventFunctions.useValue;
2264
+ /** Sets revision history visibility */
2265
+ const setRevisionHistoryVisible = revisionHistoryEventFunctions.setValue;
2266
+ /** Adds event handler for revision history visibility */
2267
+ const addRevisionHistoryVisibleEventHandler = revisionHistoryEventFunctions.addEventHandler;
2268
+ /** Shows the revision history */
2269
+ function showRevisionHistory() {
2270
+ setRevisionHistoryVisible(true);
2271
+ }
2272
+ /** Hides the revision history */
2273
+ function hideRevisionHistory() {
2274
+ setRevisionHistoryVisible(false);
2275
+ }
2276
+ //#endregion
2277
+ //#region src/utils/nodes.ts
2278
+ /** Sorts nodes by name. */
2279
+ function sortNodesByName(nodes) {
2280
+ return nodes.toSorted((a, b) => a.name.localeCompare(b.name));
2281
+ }
2282
+ /** Returns whether a node is a file. */
2283
+ function isFileNodeKind(node) {
2284
+ if (!node) return false;
2285
+ return node.kind.toUpperCase() === "FILE";
2286
+ }
2287
+ /** Returns whether a node is a folder. */
2288
+ function isFolderNodeKind(node) {
2289
+ if (!node) return false;
2290
+ return node.kind.toUpperCase() === "FOLDER";
2291
+ }
2292
+ //#endregion
2293
+ //#region src/hooks/items-in-selected-drive.ts
2294
+ /** Returns the nodes in the selected drive. */
2295
+ function useNodesInSelectedDrive() {
2296
+ const [selectedDrive] = useSelectedDriveSafe();
2297
+ return selectedDrive?.state.global.nodes;
2298
+ }
2299
+ /** Returns the file nodes in the selected drive. */
2300
+ function useFileNodesInSelectedDrive() {
2301
+ return useNodesInSelectedDrive()?.filter((n) => isFileNodeKind(n));
2302
+ }
2303
+ /** Returns the folder nodes in the selected drive. */
2304
+ function useFolderNodesInSelectedDrive() {
2305
+ return useNodesInSelectedDrive()?.filter((n) => isFolderNodeKind(n));
2306
+ }
2307
+ /** Returns the documents in the selected drive. */
2308
+ function useDocumentsInSelectedDrive() {
2309
+ const fileNodeIds = useFileNodesInSelectedDrive()?.map((node) => node.id);
2310
+ return useDocumentsByIds(fileNodeIds);
2311
+ }
2312
+ /** Returns the document types supported by the selected drive, as defined by the document model documents present in the drive */
2313
+ function useDocumentTypesInSelectedDrive() {
2314
+ return (useDocumentsInSelectedDrive()?.filter(isDocumentModelDocument))?.map((doc) => doc.state.global.id);
2315
+ }
2316
+ function isDocumentModelDocument(document) {
2317
+ return document.header.documentType === "powerhouse/document-model";
2318
+ }
2319
+ //#endregion
2320
+ //#region src/hooks/selected-node.ts
2321
+ /** Returns the selected node. */
2322
+ function useSelectedNode() {
2323
+ const selectedNodeId = useSelectedNodeId();
2324
+ return useNodesInSelectedDrive()?.find((n) => n.id === selectedNodeId);
2325
+ }
2326
+ //#endregion
2327
+ //#region src/hooks/selected-timeline-item.ts
2328
+ const selectedTimelineItemEventFunctions = makePHEventFunctions("selectedTimelineItem");
2329
+ /** Returns the selected timeline item */
2330
+ const useSelectedTimelineItem = selectedTimelineItemEventFunctions.useValue;
2331
+ /** Sets the selected timeline item */
2332
+ const setSelectedTimelineItem = selectedTimelineItemEventFunctions.setValue;
2333
+ /** Adds event handler for selected timeline item */
2334
+ const addSelectedTimelineItemEventHandler = selectedTimelineItemEventFunctions.addEventHandler;
2335
+ //#endregion
2336
+ //#region src/hooks/timeline-revision.ts
2337
+ const selectedTimelineRevisionEventFunctions = makePHEventFunctions("selectedTimelineRevision");
2338
+ /** Returns the selected timeline revision. */
2339
+ const useSelectedTimelineRevision = selectedTimelineRevisionEventFunctions.useValue;
2340
+ /** Sets the selected timeline revision. */
2341
+ const setSelectedTimelineRevision = selectedTimelineRevisionEventFunctions.setValue;
2342
+ /** Adds an event handler for the selected timeline revision. */
2343
+ const addSelectedTimelineRevisionEventHandler = selectedTimelineRevisionEventFunctions.addEventHandler;
2344
+ //#endregion
2345
+ //#region src/hooks/toast.ts
2346
+ const toastEventFunctions = makePHEventFunctions("toast");
2347
+ /** Returns the toast function */
2348
+ const usePHToast = toastEventFunctions.useValue;
2349
+ /** Sets the toast function */
2350
+ const setPHToast = toastEventFunctions.setValue;
2351
+ /** Adds an event handler for the toast */
2352
+ const addToastEventHandler = toastEventFunctions.addEventHandler;
2353
+ //#endregion
2354
+ //#region src/hooks/vetra-packages.ts
2355
+ const vetraPackageManagerFunctions = makePHEventFunctions("vetraPackageManager");
2356
+ const useVetraPackageManager = vetraPackageManagerFunctions.useValue;
2357
+ /** Returns all of the Vetra packages loaded by the Connect instance */
2358
+ const useVetraPackages = () => {
2359
+ const packageManager = useVetraPackageManager();
2360
+ return useSyncExternalStore((cb) => packageManager ? packageManager.subscribe(cb) : () => {}, () => packageManager?.packages ?? []);
2361
+ };
2362
+ /** Adds the Vetra package manager event handler */
2363
+ const addVetraPackageManagerEventHandler = vetraPackageManagerFunctions.addEventHandler;
2364
+ /** Sets the Vetra package manager and registers its packages */
2365
+ function setVetraPackageManager(packageManager) {
2366
+ vetraPackageManagerFunctions.setValue(packageManager);
2367
+ updateReactorClientDocumentModels(packageManager.packages);
2368
+ updateReactorClientUpgradeManifests(packageManager.packages);
2369
+ packageManager.subscribe(({ packages }) => {
2370
+ updateReactorClientDocumentModels(packages);
2371
+ updateReactorClientUpgradeManifests(packages);
2372
+ });
2373
+ }
2374
+ function updateReactorClientDocumentModels(packages) {
2375
+ const documentModelModules = packages.flatMap((pkg) => pkg.documentModels).filter((module, index, modules) => modules.findIndex((m) => m.documentModel.global.id === module.documentModel.global.id && (m.version ?? 1) === (module.version ?? 1)) === index);
2376
+ const registry = window.ph?.reactorClientModule?.reactorModule?.documentModelRegistry;
2377
+ if (!registry || documentModelModules.length === 0) return;
2378
+ const results = registry.registerModules(...documentModelModules);
2379
+ const duplicateTypes = /* @__PURE__ */ new Set();
2380
+ for (const result of results) if (result.status === "error") if (isDuplicateModuleError(result.error)) duplicateTypes.add(result.item.documentModel.global.id);
2381
+ else console.error("Failed to register document model module:", result.error);
2382
+ if (duplicateTypes.size > 0) {
2383
+ registry.unregisterModules(...duplicateTypes);
2384
+ registry.registerModules(...documentModelModules.filter((module) => duplicateTypes.has(module.documentModel.global.id)));
2385
+ }
2386
+ }
2387
+ function updateReactorClientUpgradeManifests(packages) {
2388
+ const upgradeManifests = packages.flatMap((pkg) => pkg.upgradeManifests).filter((u) => u !== void 0);
2389
+ const registry = window.ph?.reactorClientModule?.reactorModule?.documentModelRegistry;
2390
+ if (!registry || upgradeManifests.length === 0) return;
2391
+ const results = registry.registerUpgradeManifests(...upgradeManifests);
2392
+ const duplicates = [];
2393
+ for (const result of results) if (result.status === "error") if (isDuplicateManifestError(result.error)) duplicates.push(result);
2394
+ else console.error("Failed to register upgrade manifest:", result.error);
2395
+ if (duplicates.length > 0) {
2396
+ const duplicateTypes = duplicates.map((r) => r.item.documentType).filter((t) => !!t);
2397
+ registry.unregisterUpgradeManifests(...duplicateTypes);
2398
+ registry.registerUpgradeManifests(...duplicates.map((r) => r.item));
2399
+ }
2400
+ }
2401
+ //#endregion
2402
+ //#region src/hooks/add-ph-event-handlers.ts
2403
+ const commonGlobalEventHandlerFunctions = {
2404
+ loading: addLoadingEventHandler,
2405
+ drives: addDrivesEventHandler,
2406
+ selectedDriveId: () => {
2407
+ addSelectedDriveIdEventHandler();
2408
+ addSetSelectedDriveOnPopStateEventHandler();
2409
+ addResetSelectedNodeEventHandler();
2410
+ },
2411
+ selectedNodeId: () => {
2412
+ addSelectedNodeIdEventHandler();
2413
+ addSetSelectedNodeOnPopStateEventHandler();
2414
+ },
2415
+ documentCache: addDocumentCacheEventHandler,
2416
+ reactorGraphQLClient: addGraphQLReactorClientEventHandler,
2417
+ draggingNode: addDraggingNodeEventHandler
2418
+ };
2419
+ const phGlobalEventHandlerRegisterFunctions = {
2420
+ ...commonGlobalEventHandlerFunctions,
2421
+ reactorClientModule: addReactorClientModuleEventHandler,
2422
+ reactorClient: addReactorClientEventHandler,
2423
+ attachmentService: addAttachmentServiceEventHandler,
2424
+ features: addFeaturesEventHandler,
2425
+ modal: addModalEventHandler,
2426
+ renown: addRenownEventHandler,
2427
+ vetraPackageManager: addVetraPackageManagerEventHandler,
2428
+ packageDiscoveryService: addPackageDiscoveryServiceEventHandler,
2429
+ selectedTimelineRevision: addSelectedTimelineRevisionEventHandler,
2430
+ revisionHistoryVisible: addRevisionHistoryVisibleEventHandler,
2431
+ selectedTimelineItem: addSelectedTimelineItemEventHandler,
2432
+ routerBasename: addRouterBasenameEventHandler,
2433
+ version: addVersionEventHandler,
2434
+ logLevel: addLogLevelEventHandler,
2435
+ requiresHardRefresh: addRequiresHardRefreshEventHandler,
2436
+ warnOutdatedApp: addWarnOutdatedAppEventHandler,
2437
+ studioMode: addStudioModeEventHandler,
2438
+ basePath: addBasePathEventHandler,
2439
+ versionCheckInterval: addVersionCheckIntervalEventHandler,
2440
+ cliVersion: addCliVersionEventHandler,
2441
+ fileUploadOperationsChunkSize: addFileUploadOperationsChunkSizeEventHandler,
2442
+ isDocumentModelSelectionSettingsEnabled: addIsDocumentModelSelectionSettingsEnabledEventHandler,
2443
+ gaTrackingId: addGaTrackingIdEventHandler,
2444
+ allowList: addAllowListEventHandler,
2445
+ defaultDrivesUrl: addDefaultDrivesUrlEventHandler,
2446
+ drivesPreserveStrategy: addDrivesPreserveStrategyEventHandler,
2447
+ allowedDocumentTypes: addAllowedDocumentTypesEventHandler,
2448
+ enabledEditors: addEnabledEditorsEventHandler,
2449
+ disabledEditors: addDisabledEditorsEventHandler,
2450
+ isAddDriveEnabled: addIsAddDriveEnabledEventHandler,
2451
+ isLocalDrivesEnabled: addIsLocalDrivesEnabledEventHandler,
2452
+ isPublicDrivesEnabled: addIsPublicDrivesEnabledEventHandler,
2453
+ isAddPublicDrivesEnabled: addIsAddPublicDrivesEnabledEventHandler,
2454
+ isDeletePublicDrivesEnabled: addIsDeletePublicDrivesEnabledEventHandler,
2455
+ isCloudDrivesEnabled: addIsCloudDrivesEnabledEventHandler,
2456
+ isAddCloudDrivesEnabled: addIsAddCloudDrivesEnabledEventHandler,
2457
+ isDeleteCloudDrivesEnabled: addIsDeleteCloudDrivesEnabledEventHandler,
2458
+ isAddLocalDrivesEnabled: addIsAddLocalDrivesEnabledEventHandler,
2459
+ isDeleteLocalDrivesEnabled: addIsDeleteLocalDrivesEnabledEventHandler,
2460
+ isDragAndDropEnabled: addIsDragAndDropEnabledEventHandler,
2461
+ isExternalControlsEnabled: addIsExternalControlsEnabledEventHandler,
2462
+ isEditorDebugModeEnabled: addIsEditorDebugModeEnabledEventHandler,
2463
+ isEditorReadModeEnabled: addIsEditorReadModeEnabledEventHandler,
2464
+ isRelationalProcessorsEnabled: addIsRelationalProcessorsEnabledEventHandler,
2465
+ isExternalRelationalProcessorsEnabled: addIsExternalRelationalProcessorsEnabledEventHandler,
2466
+ isAnalyticsEnabled: addIsAnalyticsEnabledEventHandler,
2467
+ isAnalyticsExternalProcessorsEnabled: addIsAnalyticsExternalProcessorsEnabledEventHandler,
2468
+ analyticsDatabaseName: addAnalyticsDatabaseNameEventHandler,
2469
+ isAnalyticsDatabaseWorkerEnabled: addIsAnalyticsDatabaseWorkerEnabledEventHandler,
2470
+ isDiffAnalyticsEnabled: addIsDiffAnalyticsEnabledEventHandler,
2471
+ isDriveAnalyticsEnabled: addIsDriveAnalyticsEnabledEventHandler,
2472
+ renownUrl: addRenownUrlEventHandler,
2473
+ renownNetworkId: addRenownNetworkIdEventHandler,
2474
+ renownChainId: addRenownChainIdEventHandler,
2475
+ switchboardUrl: addSwitchboardUrlEventHandler,
2476
+ renownAdapters: addRenownAdaptersEventHandler,
2477
+ sentryRelease: addSentryReleaseEventHandler,
2478
+ sentryDsn: addSentryDsnEventHandler,
2479
+ sentryEnv: addSentryEnvEventHandler,
2480
+ isSentryTracingEnabled: addIsSentryTracingEnabledEventHandler,
2481
+ isExternalProcessorsEnabled: addIsExternalProcessorsEnabledEventHandler,
2482
+ isExternalPackagesEnabled: addIsExternalPackagesEnabledEventHandler,
2483
+ toast: addToastEventHandler
2484
+ };
2485
+ function addPHEventHandlers() {
2486
+ callEventHandlerRegisterFunctions(phGlobalEventHandlerRegisterFunctions);
2487
+ }
2488
+ function callEventHandlerRegisterFunctions(registerFunctions) {
2489
+ forEachObj(registerFunctions, (fn) => fn());
2490
+ }
2491
+ //#endregion
2492
+ //#region src/graphql-client/static-package-manager.ts
2493
+ /**
2494
+ * A fixed set of packages presented through the `IPackageManager` interface,
2495
+ * so the package-derived hooks - `useDocumentModelModules`,
2496
+ * `useDocumentModelModuleById`, `useEditorModules`, `useVetraPackages` - work
2497
+ * below `GraphQLReactorProvider` exactly as they do below Connect.
2498
+ *
2499
+ * Connect's package manager installs, updates and removes packages at runtime.
2500
+ * A light app instead declares its packages once, as code it imports itself
2501
+ * (every generated package root exports `manifest`, `documentModels`, `editors`
2502
+ * and `upgradeManifests` by name), so every mutating member throws and
2503
+ * `subscribe` never emits. For hand-picked modules without package artifacts,
2504
+ * {@link packageFromDocumentModels} wraps them in one synthetic package.
2505
+ */
2506
+ var StaticPackageManager = class {
2507
+ registryUrl = null;
2508
+ packages;
2509
+ constructor(packages) {
2510
+ this.packages = [...packages];
2511
+ }
2512
+ /**
2513
+ * Resolves a module by document type across all packages. Mirrors the
2514
+ * registry's semantics (`IDocumentModelRegistry.getModule`): the LATEST
2515
+ * version wins, with `version ?? 1` as each module's default.
2516
+ */
2517
+ load(documentType) {
2518
+ let latestModule;
2519
+ let latestVersion = -1;
2520
+ for (const pkg of this.packages) for (const module of pkg.documentModels) {
2521
+ if (module.documentModel.global.id !== documentType) continue;
2522
+ const moduleVersion = module.version ?? 1;
2523
+ if (moduleVersion > latestVersion) {
2524
+ latestVersion = moduleVersion;
2525
+ latestModule = module;
2526
+ }
2527
+ }
2528
+ return latestModule ? Promise.resolve(latestModule) : Promise.reject(/* @__PURE__ */ new Error(`Unknown document type: ${documentType}`));
2529
+ }
2530
+ subscribe(_handler) {
2531
+ return () => {};
2532
+ }
2533
+ getPackageSource(_packageName) {
2534
+ return null;
2535
+ }
2536
+ getPackageVersion(_packageName) {}
2537
+ getRegistryPackages() {
2538
+ return [];
2539
+ }
2540
+ addPackage(_packageName) {
2541
+ throw staticPackageManagerError("addPackage");
2542
+ }
2543
+ addPackages(_packageNames) {
2544
+ throw staticPackageManagerError("addPackages");
2545
+ }
2546
+ removePackage(_name) {
2547
+ throw staticPackageManagerError("removePackage");
2548
+ }
2549
+ updateLocalPackage(_pkg, _version) {
2550
+ throw staticPackageManagerError("updateLocalPackage");
2551
+ }
2552
+ addLocalPackage(_name, _loadedPackage, _version) {
2553
+ throw staticPackageManagerError("addLocalPackage");
2554
+ }
2555
+ };
2556
+ function staticPackageManagerError(member) {
2557
+ return /* @__PURE__ */ new Error(`${member} is not supported: StaticPackageManager holds a fixed set of packages`);
2558
+ }
2559
+ /**
2560
+ * Wraps hand-picked modules in one synthetic `DocumentModelLib`, for apps that
2561
+ * assemble their model list from mixed sources instead of passing whole
2562
+ * packages. The manifest is fabricated - loose modules carry none - so prefer
2563
+ * the `packages` prop with the real package exports when you have them: a real
2564
+ * package also carries `editors`, which makes the editor hooks work.
2565
+ */
2566
+ function packageFromDocumentModels(documentModels) {
2567
+ return {
2568
+ manifest: {
2569
+ name: "graphql-reactor-provider",
2570
+ description: "Document models passed to GraphQLReactorProvider"
2571
+ },
2572
+ documentModels,
2573
+ editors: []
2574
+ };
2575
+ }
2576
+ //#endregion
2577
+ //#region src/graphql-client/graphql-reactor-provider.tsx
2578
+ /**
2579
+ * Registers the `window.ph` event handlers once per page.
2580
+ *
2581
+ * The `set*` helpers only dispatch CustomEvents - nothing writes `window.ph`
2582
+ * until `addPHEventHandlers` has registered the listeners that do. Registration
2583
+ * is guarded by a marker on `window` rather than a module-level flag so that a
2584
+ * hot module replacement, or two copies of this module, still register once.
2585
+ */
2586
+ function ensurePHEventHandlers() {
2587
+ if (typeof window === "undefined" || window.__phEventHandlersRegistered) return;
2588
+ addPHEventHandlers();
2589
+ window.__phEventHandlersRegistered = true;
2590
+ }
2591
+ /**
2592
+ * Mounts a {@link GraphQLReactorClient} into the `window.ph` slots the
2593
+ * reactor-browser hooks read, so `useDocument`, `useDispatch` and
2594
+ * `useReactorClient` work below it against a Switchboard, with no reactor in
2595
+ * the bundle.
2596
+ *
2597
+ * It fills the document slots, plus `window.ph.vetraPackageManager` when
2598
+ * `documentModels` is given (a fixed {@link StaticPackageManager}, which makes
2599
+ * the document-model hooks work). `window.ph.reactorClientModule` always stays
2600
+ * empty, so full-reactor surfaces (drives, jobs, editor auto-discovery) find
2601
+ * nothing - an app below this provider renders components it imports itself.
2602
+ *
2603
+ * The client is built on the client only: on the server the slots stay empty
2604
+ * and the hooks report their normal loading states. The props are read once,
2605
+ * when the client is built - changing `url` afterwards does not rebuild it.
2606
+ *
2607
+ * The effect's cleanup disposes the cache and the client's socket, and the
2608
+ * effect rebuilds both if it runs again on the same client - which is what a
2609
+ * `StrictMode` mount does.
2610
+ */
2611
+ function GraphQLReactorProvider({ url, tokenProvider, subscriptionsUrl, realtime, attachmentService, documentModels, children }) {
2612
+ const [client] = useState(() => typeof window === "undefined" ? void 0 : new GraphQLReactorClient({
2613
+ url,
2614
+ tokenProvider,
2615
+ subscriptionsUrl,
2616
+ realtime
2617
+ }));
2618
+ useEffect(() => {
2619
+ if (!client) return;
2620
+ ensurePHEventHandlers();
2621
+ setReactorClient(client);
2622
+ const documentCache = new DocumentCache(client);
2623
+ setDocumentCache(documentCache);
2624
+ return () => {
2625
+ documentCache.dispose();
2626
+ client.dispose();
2627
+ };
2628
+ }, [client]);
2629
+ useEffect(() => {
2630
+ if (!attachmentService) return;
2631
+ ensurePHEventHandlers();
2632
+ setAttachmentService(attachmentService);
2633
+ }, [attachmentService]);
2634
+ useEffect(() => {
2635
+ if (!documentModels || documentModels.length === 0) return;
2636
+ ensurePHEventHandlers();
2637
+ setVetraPackageManager(new StaticPackageManager([packageFromDocumentModels(documentModels)]));
2638
+ }, [documentModels]);
2639
+ return /* @__PURE__ */ jsx(Fragment, { children });
2640
+ }
2641
+ /**
2642
+ * Returns the {@link GraphQLReactorClient} mounted by
2643
+ * {@link GraphQLReactorProvider}, or `undefined` when the slot holds something
2644
+ * else - the full in-browser reactor client, or nothing yet.
2645
+ *
2646
+ * `useReactorClient` is typed as the narrow `IReactorBrowserClient` subset both
2647
+ * implementations share. This is how a component reaches the light client's own
2648
+ * surface, i.e. `subgraph` and `request`.
2649
+ *
2650
+ * The narrowing is by brand, not by `instanceof`: a page can carry two copies
2651
+ * of this module - a bundled package with its own copy, or a hot-replaced one -
2652
+ * and `instanceof` would then answer `false` for a perfectly good client and
2653
+ * leave the component silently without one.
2654
+ */
2655
+ function useSwitchboardClient() {
2656
+ const client = useReactorClient();
2657
+ return isGraphQLReactorClient(client) ? client : void 0;
2658
+ }
2659
+ //#endregion
2660
+ //#region src/hooks/document-model-modules.ts
2661
+ function useDocumentModelModules() {
2662
+ return useVetraPackages().flatMap((pkg) => pkg.documentModels).filter((module, index, modules) => modules.findIndex((m) => m.documentModel.global.id === module.documentModel.global.id && m.version === module.version) === index);
2663
+ }
2664
+ /**
2665
+ * Resolves a document model module by document type, mirroring the registry's
2666
+ * semantics (`IDocumentModelRegistry.getModule`): with `version` omitted the
2667
+ * LATEST version of the type wins (`version ?? 1` as each module's default),
2668
+ * with `version` given only an exact match is returned. The reactor resolves
2669
+ * modules the same way when a document is created, so metadata read through
2670
+ * this hook and the version a creation actually uses cannot disagree.
2671
+ */
2672
+ function useDocumentModelModuleById(id, version) {
2673
+ const documentModelModules = useDocumentModelModules();
2674
+ if (!id || !documentModelModules) return void 0;
2675
+ let latestModule;
2676
+ let latestVersion = -1;
2677
+ for (const module of documentModelModules) {
2678
+ if (module.documentModel.global.id !== id) continue;
2679
+ const moduleVersion = module.version ?? 1;
2680
+ if (version !== void 0) {
2681
+ if (moduleVersion === version) return module;
2682
+ continue;
2683
+ }
2684
+ if (moduleVersion > latestVersion) {
2685
+ latestVersion = moduleVersion;
2686
+ latestModule = module;
2687
+ }
2688
+ }
2689
+ return latestModule;
2690
+ }
2691
+ //#endregion
2692
+ //#region src/hooks/document-operations.ts
2693
+ /**
2694
+ * Hook to fetch document operations via the reactor client.
2695
+ * Operations are no longer auto-populated on documents and must be fetched explicitly.
2696
+ *
2697
+ * @param documentId - The document ID to fetch operations for
2698
+ * @returns Object containing globalOperations, localOperations, isLoading, and error
2699
+ */
2700
+ function useDocumentOperations(documentId) {
2701
+ const reactorClient = useReactorClient();
2702
+ const hasFetchedRef = useRef(false);
2703
+ const [state, setState] = useState(() => ({
2704
+ globalOperations: [],
2705
+ localOperations: [],
2706
+ isLoading: !!documentId,
2707
+ error: void 0
2708
+ }));
2709
+ const fetchOperations = useCallback(async (retryCount = 0) => {
2710
+ const MAX_RETRIES = 5;
2711
+ const RETRY_DELAY_MS = 500;
2712
+ if (!documentId || !reactorClient) {
2713
+ setState({
2714
+ globalOperations: [],
2715
+ localOperations: [],
2716
+ isLoading: false,
2717
+ error: void 0
2718
+ });
2719
+ return;
2720
+ }
2721
+ setState((prev) => ({
2722
+ ...prev,
2723
+ isLoading: true,
2724
+ error: void 0
2725
+ }));
2726
+ let globalOps = [];
2727
+ let localOps = [];
2728
+ let fetchError;
2729
+ try {
2730
+ globalOps = (await reactorClient.getOperations(documentId, { scopes: ["global"] })).results;
2731
+ } catch (err) {
2732
+ fetchError = err instanceof Error ? err : new Error(String(err));
2733
+ }
2734
+ if (!fetchError) try {
2735
+ localOps = (await reactorClient.getOperations(documentId, { scopes: ["local"] })).results;
2736
+ } catch (err) {
2737
+ fetchError = err instanceof Error ? err : new Error(String(err));
2738
+ }
2739
+ if (!fetchError && globalOps.length === 0 && localOps.length === 0 && retryCount < MAX_RETRIES) {
2740
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
2741
+ return fetchOperations(retryCount + 1);
2742
+ }
2743
+ setState({
2744
+ globalOperations: globalOps,
2745
+ localOperations: localOps,
2746
+ isLoading: false,
2747
+ error: fetchError
2748
+ });
2749
+ hasFetchedRef.current = true;
2750
+ }, [documentId, reactorClient]);
2751
+ useEffect(() => {
2752
+ if (documentId && reactorClient) fetchOperations();
2753
+ else if (!documentId) {
2754
+ setState({
2755
+ globalOperations: [],
2756
+ localOperations: [],
2757
+ isLoading: false,
2758
+ error: void 0
2759
+ });
2760
+ hasFetchedRef.current = false;
2761
+ }
2762
+ }, [
2763
+ documentId,
2764
+ reactorClient,
2765
+ fetchOperations
2766
+ ]);
2767
+ const refetch = useCallback(() => {
2768
+ fetchOperations(0);
2769
+ }, [fetchOperations]);
2770
+ return {
2771
+ ...state,
2772
+ refetch
2773
+ };
2774
+ }
2775
+ //#endregion
2776
+ export { addGraphQLReactorClientEventHandler as $, makeAuthMiddleware as $i, setIsDeleteCloudDrivesEnabled as $n, useIsDriveAnalyticsEnabled as $r, addIsAnalyticsEnabledEventHandler as $t, useNodesInSelectedDrive as A, DocumentModelNotFoundError as Aa, phAppConfigSetters as Ai, addVersionCheckIntervalEventHandler as An, useAllowList as Ar, makeNodeSlug as At, addReactorClientModuleEventHandler as B, isGraphQLReactorClient as Bi, setDisabledEditors as Bn, useIsAddCloudDrivesEnabled as Br, addBasePathEventHandler as Bt, setSelectedTimelineItem as C, addModalEventHandler as Ca, useVersionCheckInterval as Ci, addRequiresHardRefreshEventHandler as Cn, setSentryEnv as Cr, extractDriveSlugFromPath as Ct, useDocumentsInSelectedDrive as D, showDeleteNodeModal as Da, addIsExternalControlsEnabledEventHandler as Di, addSentryReleaseEventHandler as Dn, setVersion as Dr, findUuid as Dt, useDocumentTypesInSelectedDrive as E, showCreateDocumentModal as Ea, addIsDragAndDropEnabledEventHandler as Ei, addSentryEnvEventHandler as En, setSwitchboardUrl as Er, extractNodeSlugFromPath as Et, hideRevisionHistory as F, setIsExternalControlsEnabled as Fi, setAllowList as Fn, useDisabledEditors as Fr, addAttachmentServiceEventHandler as Ft, usePGlite as G, SubgraphSdkRegistry as Gi, setIsAddCloudDrivesEnabled as Gn, useIsAnalyticsEnabled as Gr, addEnabledEditorsEventHandler as Gt, setReactorClientModule as H, makeAuthConnectionParams as Hi, setEnabledEditors as Hn, useIsAddLocalDrivesEnabled as Hr, addDefaultDrivesUrlEventHandler as Ht, setRevisionHistoryVisible as I, useAllowedDocumentTypes as Ii, setAnalyticsDatabaseName as In, useDrivesPreserveStrategy as Ir, setAttachmentService as It, useSync as J, signStampedAction as Ji, setIsAddPublicDrivesEnabled as Jn, useIsDeleteCloudDrivesEnabled as Jr, addIsAddCloudDrivesEnabledEventHandler as Jt, useReactorClient as K, describeGraphQLDocument as Ki, setIsAddDriveEnabled as Kn, useIsAnalyticsExternalProcessorsEnabled as Kr, addFileUploadOperationsChunkSizeEventHandler as Kt, showRevisionHistory as L, useIsDragAndDropEnabled as Li, setBasePath as Ln, useEnabledEditors as Lr, useAttachmentService as Lt, isFolderNodeKind as M, DocumentTypeMismatchError as Ma, phDocumentEditorConfigSetters as Mi, addWarnOutdatedAppEventHandler as Mn, useBasePath as Mr, addFeaturesEventHandler as Mt, sortNodesByName as N, NoSelectedDocumentError as Na, setAllowedDocumentTypes as Ni, phGlobalConfigHooks as Nn, useCliVersion as Nr, setFeatures as Nt, useFileNodesInSelectedDrive as O, showPHModal as Oa, isExternalControlsEnabledEventFunctions as Oi, addStudioModeEventHandler as On, setVersionCheckInterval as Or, getPathWithoutBase as Ot, addRevisionHistoryVisibleEventHandler as P, UnsupportedDocumentTypeError as Pa, setIsDragAndDropEnabled as Pi, phGlobalConfigSetters as Pn, useDefaultDrivesUrl as Pr, useFeatures as Pt, usePackageDiscoveryService as Q, ambientRenownTokenProvider as Qi, setIsCloudDrivesEnabled as Qn, useIsDocumentModelSelectionSettingsEnabled as Qr, addIsAnalyticsDatabaseWorkerEnabledEventHandler as Qt, useRevisionHistoryVisible as R, useIsExternalControlsEnabled as Ri, setCliVersion as Rn, useFileUploadOperationsChunkSize as Rr, addAllowListEventHandler as Rt, addSelectedTimelineItemEventHandler as S, isDocumentTypeSupported as Sa, useVersion as Si, addRenownUrlEventHandler as Sn, setSentryDsn as Sr, extractDriveIdFromSlug as St, useSelectedNode as T, setPHModal as Ta, addAllowedDocumentTypesEventHandler as Ti, addSentryDsnEventHandler as Tn, setStudioMode as Tr, extractNodeIdFromSlug as Tt, useDatabase as U, startDocumentChangesSubscription as Ui, setFileUploadOperationsChunkSize as Un, useIsAddPublicDrivesEnabled as Ur, addDisabledEditorsEventHandler as Ut, setReactorClient as V, viewFilterInputFromViewFilter as Vi, setDrivesPreserveStrategy as Vn, useIsAddDriveEnabled as Vr, addCliVersionEventHandler as Vt, useModelRegistry as W, subscriptionsUrlFromGraphqlUrl as Wi, setGaTrackingId as Wn, useIsAnalyticsDatabaseWorkerEnabled as Wr, addDrivesPreserveStrategyEventHandler as Wt, addPackageDiscoveryServiceEventHandler as X, MutateDocumentWithOperationsDocument as Xi, setIsAnalyticsEnabled as Xn, useIsDeletePublicDrivesEnabled as Xr, addIsAddLocalDrivesEnabledEventHandler as Xt, useSyncList as Y, stampAction as Yi, setIsAnalyticsDatabaseWorkerEnabled as Yn, useIsDeleteLocalDrivesEnabled as Yr, addIsAddDriveEnabledEventHandler as Yt, setPackageDiscoveryService as Z, ReactorOperationFieldsFragmentDoc as Zi, setIsAnalyticsExternalProcessorsEnabled as Zn, useIsDiffAnalyticsEnabled as Zr, addIsAddPublicDrivesEnabledEventHandler as Zt, setPHToast as _, moveNode$1 as _a, useSentryDsn as _i, addLocalDrivesEnabledEventHandler as _n, setRenownChainId as _r, addDrivesEventHandler as _t, ensurePHEventHandlers as a, convertRemoteOperations as aa, useIsLocalDrivesEnabled as ai, addIsDiffAnalyticsEnabledEventHandler as an, setIsEditorDebugModeEnabled as ar, useDropTarget as at, setSelectedTimelineRevision as b, upgradeDocument as ba, useStudioMode as bi, addRenownChainIdEventHandler as bn, setRequiresHardRefresh as br, createUrlWithPreservedParams as bt, packageFromDocumentModels as c, screamingSnakeToCamel as ca, useIsSentryTracingEnabled as ci, addIsEditorDebugModeEnabledEventHandler as cn, setIsExternalProcessorsEnabled as cr, setSelectedDrive as ct, commonGlobalEventHandlerFunctions as d, addFileWithProgress as da, useRenownAdapters as di, addIsExternalProcessorsEnabledEventHandler as dn, setIsPublicDrivesEnabled as dr, useSelectedDriveId as dt, phDocumentFromGetDocument as ea, useIsEditorDebugModeEnabled as ei, addIsAnalyticsExternalProcessorsEnabledEventHandler as en, setIsDeleteLocalDrivesEnabled as er, setGraphQLReactorClient as et, addVetraPackageManagerEventHandler as f, addFolder$1 as fa, useRenownChainId as fi, addIsExternalRelationalProcessorsEnabledEventHandler as fn, setIsRelationalProcessorsEnabled as fr, useSelectedDriveSafe as ft, addToastEventHandler as g, loadFile as ga, useRouterBasename as gi, addIsSentryTracingEnabledEventHandler as gn, setRenownAdapters as gr, setSelectedNode as gt, useVetraPackages as h, exportFile as ha, useRequiresHardRefresh as hi, addIsRelationalProcessorsEnabledEventHandler as hn, setLogLevel as hr, addSetSelectedNodeOnPopStateEventHandler as ht, GraphQLReactorProvider as i, buildPulledDocument as ia, useIsExternalRelationalProcessorsEnabled as ii, addIsDeletePublicDrivesEnabledEventHandler as in, setIsDriveAnalyticsEnabled as ir, useDropNode as it, isFileNodeKind as j, DocumentNotFoundError as ja, phDocumentEditorConfigHooks as ji, addVersionEventHandler as jn, useAnalyticsDatabaseName as jr, resolveUrlPathname as jt, useFolderNodesInSelectedDrive as k, usePHModal as ka, phAppConfigHooks as ki, addSwitchboardUrlEventHandler as kn, setWarnOutdatedApp as kr, makeDriveUrlComponent as kt, addPHEventHandlers as l, addDocument as la, useLocalDrivesEnabled as li, addIsEditorReadModeEnabledEventHandler as ln, setIsExternalRelationalProcessorsEnabled as lr, setSelectedDriveId as lt, useVetraPackageManager as m, deleteNode as ma, useRenownUrl as mi, addIsPublicDrivesEnabledEventHandler as mn, setLocalDrivesEnabled as mr, addSelectedNodeIdEventHandler as mt, useDocumentModelModuleById as n, revisionMapFromRevisionsList as na, useIsExternalPackagesEnabled as ni, addIsDeleteCloudDrivesEnabledEventHandler as nn, setIsDiffAnalyticsEnabled as nr, addDraggingNodeEventHandler as nt, useSwitchboardClient as o, extractRevisionMap as oa, useIsPublicDrivesEnabled as oi, addIsDocumentModelSelectionSettingsEnabledEventHandler as on, setIsEditorReadModeEnabled as or, addSelectedDriveIdEventHandler as ot, setVetraPackageManager as p, copyNode$1 as pa, useRenownNetworkId as pi, addIsLocalDrivesEnabledEventHandler as pn, setIsSentryTracingEnabled as pr, addResetSelectedNodeEventHandler as pt, useReactorClientModule as q, subgraphUrlFromGraphqlUrl as qi, setIsAddLocalDrivesEnabled as qn, useIsCloudDrivesEnabled as qr, addGaTrackingIdEventHandler as qt, useDocumentModelModules as r, ConflictError as ra, useIsExternalProcessorsEnabled as ri, addIsDeleteLocalDrivesEnabledEventHandler as rn, setIsDocumentModelSelectionSettingsEnabled as rr, useDragNode as rt, StaticPackageManager as s, hasRevisionConflict as sa, useIsRelationalProcessorsEnabled as si, addIsDriveAnalyticsEnabledEventHandler as sn, setIsExternalPackagesEnabled as sr, addSetSelectedDriveOnPopStateEventHandler as st, useDocumentOperations as t, phDocumentFromMutation as ta, useIsEditorReadModeEnabled as ti, addIsCloudDrivesEnabledEventHandler as tn, setIsDeletePublicDrivesEnabled as tr, useGraphQLReactorClient as tt, callEventHandlerRegisterFunctions as u, addFile as ua, useLogLevel as ui, addIsExternalPackagesEnabledEventHandler as un, setIsLocalDrivesEnabled as ur, useSelectedDrive as ut, usePHToast as v, renameDriveNode as va, useSentryEnv as vi, addLogLevelEventHandler as vn, setRenownNetworkId as vr, setDrives as vt, useSelectedTimelineItem as w, closePHModal as wa, useWarnOutdatedApp as wi, addRouterBasenameEventHandler as wn, setSentryRelease as wr, extractNodeIdFromPath as wt, useSelectedTimelineRevision as x, getUserPermissions as xa, useSwitchboardUrl as xi, addRenownNetworkIdEventHandler as xn, setRouterBasename as xr, extractDriveIdFromPath as xt, addSelectedTimelineRevisionEventHandler as y, renameNode as ya, useSentryRelease as yi, addRenownAdaptersEventHandler as yn, setRenownUrl as yr, useDrives as yt, addReactorClientEventHandler as z, GraphQLReactorClient as zi, setDefaultDrivesUrl as zn, useGaTrackingId as zr, addAnalyticsDatabaseNameEventHandler as zt };
2777
+
2778
+ //# sourceMappingURL=document-operations-D0ePGjbp.js.map