@powerhousedao/reactor-local 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3935 @@
1
+ import { fileURLToPath } from 'url';
2
+ import path2 from 'path';
3
+ import { startAPI, setAdditionalContextFields, registerInternalListener, addSubgraph, createSchema } from '@powerhousedao/reactor-api';
4
+ import * as DocumentDrive from 'document-model-libs/document-drive';
5
+ import { isFileNode, utils as utils$1, actions, documentModel } from 'document-model-libs/document-drive';
6
+ import { utils } from 'document-model/document';
7
+ import { ClientError, gql, GraphQLClient } from 'graphql-request';
8
+ import { createNanoEvents } from 'nanoevents';
9
+ import { v4 } from 'uuid';
10
+ import { pascalCase } from 'change-case';
11
+ import { GraphQLError, buildSchema, GraphQLObjectType, GraphQLNonNull, GraphQLUnionType, GraphQLList, GraphQLScalarType } from 'graphql';
12
+ import stringify from 'json-stringify-deterministic';
13
+ import { readdirSync, existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
14
+ import fs from 'fs/promises';
15
+ import sanitize from 'sanitize-filename';
16
+ import * as DocumentModelsLibs from 'document-model-libs/document-models';
17
+ import { module } from 'document-model/document-model';
18
+ import dotenv from 'dotenv';
19
+ import { drizzle } from 'drizzle-orm/connect';
20
+ import * as searchListener from '@powerhousedao/general-document-indexer';
21
+
22
+ // ../../node_modules/.pnpm/tsup@8.3.0_@swc+core@1.5.29_postcss@8.4.47_typescript@5.6.3/node_modules/tsup/assets/esm_shims.js
23
+ var getFilename = () => fileURLToPath(import.meta.url);
24
+ var getDirname = () => path2.dirname(getFilename());
25
+ var __dirname = /* @__PURE__ */ getDirname();
26
+
27
+ // ../document-drive/src/cache/memory.ts
28
+ var InMemoryCache = class {
29
+ cache = /* @__PURE__ */ new Map();
30
+ async setDocument(drive, id, document) {
31
+ const global2 = document.operations.global.map((e) => {
32
+ delete e.resultingState;
33
+ return e;
34
+ });
35
+ const local = document.operations.local.map((e) => {
36
+ delete e.resultingState;
37
+ return e;
38
+ });
39
+ const doc = { ...document, operations: { global: global2, local } };
40
+ if (!this.cache.has(drive)) {
41
+ this.cache.set(drive, /* @__PURE__ */ new Map());
42
+ }
43
+ this.cache.get(drive)?.set(id, doc);
44
+ return true;
45
+ }
46
+ async deleteDocument(drive, id) {
47
+ return this.cache.get(drive)?.delete(id) ?? false;
48
+ }
49
+ async getDocument(drive, id) {
50
+ return this.cache.get(drive)?.get(id);
51
+ }
52
+ };
53
+ var memory_default = InMemoryCache;
54
+
55
+ // ../document-drive/src/server/error.ts
56
+ var DocumentModelNotFoundError = class extends Error {
57
+ constructor(id, cause) {
58
+ super(`Document model "${id}" not found`, { cause });
59
+ this.id = id;
60
+ }
61
+ };
62
+ var OperationError = class extends Error {
63
+ status;
64
+ operation;
65
+ constructor(status, operation, message, cause) {
66
+ super(message, { cause: cause ?? operation });
67
+ this.status = status;
68
+ this.operation = operation;
69
+ }
70
+ };
71
+ var ConflictOperationError = class extends OperationError {
72
+ constructor(existingOperation, newOperation) {
73
+ super(
74
+ "CONFLICT",
75
+ newOperation,
76
+ `Conflicting operation on index ${newOperation.index}`,
77
+ { existingOperation, newOperation }
78
+ );
79
+ }
80
+ };
81
+ var DriveAlreadyExistsError = class extends Error {
82
+ driveId;
83
+ constructor(driveId) {
84
+ super(`Drive already exists. ID: ${driveId}`);
85
+ this.driveId = driveId;
86
+ }
87
+ };
88
+ var DriveNotFoundError = class extends Error {
89
+ driveId;
90
+ constructor(driveId) {
91
+ super(`Drive with id ${driveId} not found`);
92
+ this.driveId = driveId;
93
+ }
94
+ };
95
+ var SynchronizationUnitNotFoundError = class extends Error {
96
+ syncUnitId;
97
+ constructor(message, syncUnitId) {
98
+ super(message);
99
+ this.syncUnitId = syncUnitId;
100
+ }
101
+ };
102
+
103
+ // ../document-drive/src/utils/run-asap.ts
104
+ var RunAsap;
105
+ ((RunAsap2) => {
106
+ RunAsap2.useMessageChannel = (() => {
107
+ if (typeof MessageChannel === "undefined") {
108
+ return new Error("MessageChannel is not supported");
109
+ }
110
+ return (task) => {
111
+ const controller = new AbortController();
112
+ const signal = controller.signal;
113
+ const mc = new MessageChannel();
114
+ mc.port1.postMessage(null);
115
+ mc.port2.addEventListener(
116
+ "message",
117
+ () => {
118
+ task();
119
+ mc.port1.close();
120
+ mc.port2.close();
121
+ },
122
+ { once: true, signal }
123
+ );
124
+ mc.port2.start();
125
+ return () => controller.abort();
126
+ };
127
+ })();
128
+ RunAsap2.usePostMessage = (() => {
129
+ const _main = (
130
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
131
+ typeof window === "object" && window || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
132
+ typeof global === "object" && global || typeof self === "object" && self
133
+ );
134
+ if (!_main) {
135
+ return new Error("No global object found");
136
+ }
137
+ const main = _main;
138
+ if (
139
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
140
+ !main.postMessage || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
141
+ !main.addEventListener || main.importScripts
142
+ ) {
143
+ return new Error("postMessage is not supported");
144
+ }
145
+ let index = 0;
146
+ const tasks = /* @__PURE__ */ new Map();
147
+ function getNewIndex() {
148
+ if (index === 9007199254740991) {
149
+ return 0;
150
+ }
151
+ return ++index;
152
+ }
153
+ const MESSAGE_PREFIX = "com.usePostMessage" + Math.random();
154
+ main.addEventListener(
155
+ "message",
156
+ (e) => {
157
+ const event = e;
158
+ if (typeof event.data !== "string") {
159
+ return;
160
+ }
161
+ if (event.source !== main || !event.data.startsWith(MESSAGE_PREFIX)) {
162
+ return;
163
+ }
164
+ const index2 = event.data.split(":").at(1);
165
+ if (index2 === void 0) {
166
+ return;
167
+ }
168
+ const i = +index2;
169
+ const task = tasks.get(i);
170
+ if (task) {
171
+ task();
172
+ tasks.delete(i);
173
+ }
174
+ },
175
+ false
176
+ );
177
+ return (task) => {
178
+ const i = getNewIndex();
179
+ tasks.set(i, task);
180
+ main.postMessage(MESSAGE_PREFIX + ":" + i, { targetOrigin: "*" });
181
+ return () => {
182
+ tasks.delete(i);
183
+ };
184
+ };
185
+ })();
186
+ RunAsap2.useSetImmediate = (() => {
187
+ if (typeof window !== "undefined") {
188
+ return new Error("setImmediate is not supported on the browser");
189
+ }
190
+ if (typeof setImmediate === "undefined") {
191
+ return new Error("setImmediate is not supported");
192
+ }
193
+ return (task) => {
194
+ const id = setImmediate(task);
195
+ return () => clearImmediate(id);
196
+ };
197
+ })();
198
+ RunAsap2.useSetTimeout = /* @__PURE__ */ (() => {
199
+ return (task) => {
200
+ const id = setTimeout(task, 0);
201
+ return () => clearTimeout(id);
202
+ };
203
+ })();
204
+ function runAsap2(task) {
205
+ if (!(RunAsap2.useSetImmediate instanceof Error)) {
206
+ return (0, RunAsap2.useSetImmediate)(task);
207
+ } else if (!(RunAsap2.useMessageChannel instanceof Error)) {
208
+ return (0, RunAsap2.useMessageChannel)(task);
209
+ } else if (!(RunAsap2.usePostMessage instanceof Error)) {
210
+ return (0, RunAsap2.usePostMessage)(task);
211
+ } else {
212
+ return (0, RunAsap2.useSetTimeout)(task);
213
+ }
214
+ }
215
+ RunAsap2.runAsap = runAsap2;
216
+ function runAsapAsync2(task, queueMethod = runAsap2) {
217
+ if (queueMethod instanceof Error) {
218
+ throw new Error("queueMethod is not supported", {
219
+ cause: queueMethod
220
+ });
221
+ }
222
+ return new Promise((resolve, reject) => {
223
+ queueMethod(() => {
224
+ task().then(resolve).catch(reject);
225
+ });
226
+ });
227
+ }
228
+ RunAsap2.runAsapAsync = runAsapAsync2;
229
+ })(RunAsap || (RunAsap = {}));
230
+
231
+ // ../document-drive/src/utils/index.ts
232
+ var runAsap = RunAsap.runAsap;
233
+ var runAsapAsync = RunAsap.runAsapAsync;
234
+ function isDocumentDrive(document) {
235
+ return document.documentType === documentModel.id;
236
+ }
237
+ function mergeOperations(currentOperations, newOperations) {
238
+ const minIndexByScope = Object.keys(currentOperations).reduce((acc, curr) => {
239
+ const scope = curr;
240
+ acc[scope] = currentOperations[scope].at(-1)?.index ?? 0;
241
+ return acc;
242
+ }, {});
243
+ const conflictOp = newOperations.find(
244
+ (op) => op.index < (minIndexByScope[op.scope] ?? 0)
245
+ );
246
+ if (conflictOp) {
247
+ throw new OperationError(
248
+ "ERROR",
249
+ conflictOp,
250
+ `Tried to add operation with index ${conflictOp.index} and document is at index ${minIndexByScope[conflictOp.scope]}`
251
+ );
252
+ }
253
+ return newOperations.sort((a, b) => a.index - b.index).reduce((acc, curr) => {
254
+ const existingOperations = acc[curr.scope] || [];
255
+ return { ...acc, [curr.scope]: [...existingOperations, curr] };
256
+ }, currentOperations);
257
+ }
258
+ function generateUUID() {
259
+ return v4();
260
+ }
261
+ function isBefore(dateA, dateB) {
262
+ return new Date(dateA) < new Date(dateB);
263
+ }
264
+
265
+ // ../document-drive/src/utils/logger.ts
266
+ var Logger = class {
267
+ #logger = console;
268
+ set logger(logger2) {
269
+ this.#logger = logger2;
270
+ }
271
+ log(...data) {
272
+ return this.#logger.log(...data);
273
+ }
274
+ info(...data) {
275
+ return this.#logger.info(...data);
276
+ }
277
+ warn(...data) {
278
+ return this.#logger.warn(...data);
279
+ }
280
+ error(...data) {
281
+ return this.#logger.error(...data);
282
+ }
283
+ debug(...data) {
284
+ return this.#logger.debug(...data);
285
+ }
286
+ trace(...data) {
287
+ return this.#logger.trace(...data);
288
+ }
289
+ };
290
+ var loggerInstance = new Logger();
291
+ var logger = loggerInstance;
292
+
293
+ // ../document-drive/src/queue/types.ts
294
+ function isOperationJob(job) {
295
+ return "operations" in job;
296
+ }
297
+ function isActionJob(job) {
298
+ return "actions" in job;
299
+ }
300
+
301
+ // ../document-drive/src/queue/base.ts
302
+ var MemoryQueue = class {
303
+ id;
304
+ blocked = false;
305
+ deleted = false;
306
+ items = [];
307
+ dependencies = new Array();
308
+ constructor(id) {
309
+ this.id = id;
310
+ }
311
+ async setDeleted(deleted) {
312
+ this.deleted = deleted;
313
+ }
314
+ async isDeleted() {
315
+ return this.deleted;
316
+ }
317
+ async addJob(data) {
318
+ this.items.push(data);
319
+ return Promise.resolve();
320
+ }
321
+ async getNextJob() {
322
+ const job = this.items.shift();
323
+ return Promise.resolve(job);
324
+ }
325
+ async amountOfJobs() {
326
+ return Promise.resolve(this.items.length);
327
+ }
328
+ getId() {
329
+ return this.id;
330
+ }
331
+ async setBlocked(blocked) {
332
+ this.blocked = blocked;
333
+ }
334
+ async isBlocked() {
335
+ return this.blocked;
336
+ }
337
+ async getJobs() {
338
+ return this.items;
339
+ }
340
+ async addDependencies(job) {
341
+ if (!this.dependencies.find((j) => j.jobId === job.jobId)) {
342
+ this.dependencies.push(job);
343
+ }
344
+ if (!this.isBlocked()) {
345
+ this.setBlocked(true);
346
+ }
347
+ }
348
+ async removeDependencies(job) {
349
+ this.dependencies = this.dependencies.filter(
350
+ (j) => j.jobId !== job.jobId && j.driveId !== job.driveId
351
+ );
352
+ if (this.dependencies.length === 0) {
353
+ await this.setBlocked(false);
354
+ }
355
+ }
356
+ };
357
+ var BaseQueueManager = class {
358
+ emitter = createNanoEvents();
359
+ ticker = 0;
360
+ queues = [];
361
+ workers;
362
+ timeout;
363
+ delegate;
364
+ constructor(workers = 3, timeout = 0) {
365
+ this.workers = workers;
366
+ this.timeout = timeout;
367
+ }
368
+ async init(delegate, onError) {
369
+ this.delegate = delegate;
370
+ for (let i = 0; i < this.workers; i++) {
371
+ setTimeout(
372
+ () => this.processNextJob.bind(this)().catch(onError),
373
+ 100 * i
374
+ );
375
+ }
376
+ return Promise.resolve();
377
+ }
378
+ async addJob(job) {
379
+ if (!this.delegate) {
380
+ throw new Error("No server delegate defined");
381
+ }
382
+ const jobId = generateUUID();
383
+ const queue = this.getQueue(job.driveId, job.documentId);
384
+ if (await queue.isDeleted()) {
385
+ throw new Error("Queue is deleted");
386
+ }
387
+ const newDocument = job.documentId && !await this.delegate.checkDocumentExists(job.driveId, job.documentId);
388
+ if (newDocument && !await queue.isBlocked()) {
389
+ await queue.setBlocked(true);
390
+ const driveQueue = this.getQueue(job.driveId);
391
+ const jobs = await driveQueue.getJobs();
392
+ for (const driveJob of jobs) {
393
+ const actions3 = isOperationJob(driveJob) ? driveJob.operations : driveJob.actions;
394
+ const op = actions3.find((j) => {
395
+ const input = j.input;
396
+ return j.type === "ADD_FILE" && input.id === job.documentId;
397
+ });
398
+ if (op) {
399
+ await queue.addDependencies(driveJob);
400
+ }
401
+ }
402
+ }
403
+ const actions2 = isOperationJob(job) ? job.operations : job.actions;
404
+ const addFileOps = actions2.filter((j) => j.type === "ADD_FILE");
405
+ for (const addFileOp of addFileOps) {
406
+ const input = addFileOp.input;
407
+ const q = this.getQueue(job.driveId, input.id);
408
+ await q.addDependencies({ jobId, ...job });
409
+ }
410
+ const removeFileOps = actions2.filter(
411
+ (j) => j.type === "DELETE_NODE"
412
+ );
413
+ for (const removeFileOp of removeFileOps) {
414
+ const input = removeFileOp.input;
415
+ const queue2 = this.getQueue(job.driveId, input.id);
416
+ await queue2.setDeleted(true);
417
+ }
418
+ await queue.addJob({ jobId, ...job });
419
+ return jobId;
420
+ }
421
+ getQueue(driveId, documentId) {
422
+ const queueId = this.getQueueId(driveId, documentId);
423
+ let queue = this.queues.find((q) => q.getId() === queueId);
424
+ if (!queue) {
425
+ queue = new MemoryQueue(queueId);
426
+ this.queues.push(queue);
427
+ }
428
+ return queue;
429
+ }
430
+ removeQueue(driveId, documentId) {
431
+ const queueId = this.getQueueId(driveId, documentId);
432
+ this.queues = this.queues.filter((q) => q.getId() !== queueId);
433
+ this.emit("queueRemoved", queueId);
434
+ }
435
+ getQueueByIndex(index) {
436
+ const queue = this.queues[index];
437
+ if (queue) {
438
+ return queue;
439
+ }
440
+ return null;
441
+ }
442
+ getQueues() {
443
+ return this.queues.map((q) => q.getId());
444
+ }
445
+ retryNextJob(timeout) {
446
+ const _timeout = timeout !== void 0 ? timeout : this.timeout;
447
+ const retry = _timeout > 0 ? (fn) => setTimeout(fn, _timeout) : runAsap;
448
+ retry(() => this.processNextJob());
449
+ }
450
+ async findFirstNonEmptyQueue(ticker) {
451
+ const numQueues = this.queues.length;
452
+ for (let i = 0; i < numQueues; i++) {
453
+ const index = (ticker + i) % numQueues;
454
+ const queue = this.queues[index];
455
+ if (queue && await queue.amountOfJobs() > 0) {
456
+ return index;
457
+ }
458
+ }
459
+ return null;
460
+ }
461
+ async processNextJob() {
462
+ if (!this.delegate) {
463
+ throw new Error("No server delegate defined");
464
+ }
465
+ if (this.queues.length === 0) {
466
+ this.retryNextJob();
467
+ return;
468
+ }
469
+ const queue = this.queues[this.ticker];
470
+ if (!queue) {
471
+ this.ticker = 0;
472
+ this.retryNextJob();
473
+ return;
474
+ }
475
+ const amountOfJobs = await queue.amountOfJobs();
476
+ if (amountOfJobs === 0) {
477
+ const nextTicker = await this.findFirstNonEmptyQueue(this.ticker);
478
+ if (nextTicker !== null) {
479
+ this.ticker = nextTicker;
480
+ this.retryNextJob(0);
481
+ } else {
482
+ this.retryNextJob();
483
+ }
484
+ return;
485
+ }
486
+ this.ticker = this.ticker === this.queues.length - 1 ? 0 : this.ticker + 1;
487
+ const isBlocked = await queue.isBlocked();
488
+ if (isBlocked) {
489
+ this.retryNextJob();
490
+ return;
491
+ }
492
+ await queue.setBlocked(true);
493
+ const nextJob = await queue.getNextJob();
494
+ if (!nextJob) {
495
+ this.retryNextJob();
496
+ return;
497
+ }
498
+ try {
499
+ const result = await this.delegate.processJob(nextJob);
500
+ const actions2 = isOperationJob(nextJob) ? nextJob.operations : nextJob.actions;
501
+ const addFileActions = actions2.filter((op) => op.type === "ADD_FILE");
502
+ if (addFileActions.length > 0) {
503
+ for (const addFile of addFileActions) {
504
+ const documentQueue = this.getQueue(
505
+ nextJob.driveId,
506
+ addFile.input.id
507
+ );
508
+ await documentQueue.removeDependencies(nextJob);
509
+ }
510
+ }
511
+ this.emit("jobCompleted", nextJob, result);
512
+ } catch (e) {
513
+ logger.error(`job failed`, e);
514
+ this.emit("jobFailed", nextJob, e);
515
+ } finally {
516
+ await queue.setBlocked(false);
517
+ this.retryNextJob(0);
518
+ }
519
+ }
520
+ emit(event, ...args) {
521
+ this.emitter.emit(event, ...args);
522
+ }
523
+ on(event, cb) {
524
+ return this.emitter.on(event, cb);
525
+ }
526
+ getQueueId(driveId, documentId) {
527
+ return `queue:${driveId}${documentId ? `:${documentId}` : ""}`;
528
+ }
529
+ };
530
+ async function requestGraphql(...args) {
531
+ const [url, ...requestArgs] = args;
532
+ const client = new GraphQLClient(url, { fetch });
533
+ const { errors, ...response } = await client.request(...requestArgs);
534
+ const result = { ...response };
535
+ if (errors?.length) {
536
+ result.errors = errors.map(
537
+ ({ message, ...options2 }) => new GraphQLError(message, options2)
538
+ );
539
+ }
540
+ return result;
541
+ }
542
+ function getFields(type) {
543
+ if (type instanceof GraphQLObjectType) {
544
+ return Object.entries(type.getFields()).map(([fieldName, field]) => {
545
+ const fieldType = field.type instanceof GraphQLNonNull ? field.type.ofType : field.type;
546
+ if (fieldType instanceof GraphQLObjectType || fieldType instanceof GraphQLUnionType) {
547
+ return `${fieldName} { ${getFields(fieldType)} }`;
548
+ }
549
+ if (fieldType instanceof GraphQLList) {
550
+ const listItemType = fieldType.ofType instanceof GraphQLNonNull ? fieldType.ofType.ofType : fieldType.ofType;
551
+ if (listItemType instanceof GraphQLScalarType) {
552
+ return fieldName;
553
+ } else if (listItemType instanceof GraphQLObjectType || listItemType instanceof GraphQLUnionType) {
554
+ return `${fieldName} { ${getFields(listItemType)} }`;
555
+ } else {
556
+ throw new Error(
557
+ `List item type ${listItemType.toString()} is not handled`
558
+ );
559
+ }
560
+ }
561
+ return fieldName;
562
+ }).join(" ");
563
+ } else if (type instanceof GraphQLUnionType) {
564
+ return type.getTypes().map((unionType) => {
565
+ return `... on ${unionType.name} { ${getFields(unionType)} }`;
566
+ }).join(" ");
567
+ }
568
+ return "";
569
+ }
570
+ function generateDocumentStateQueryFields(documentModel2, options2) {
571
+ const name = pascalCase(documentModel2.name);
572
+ const spec = documentModel2.specifications.at(-1);
573
+ if (!spec) {
574
+ throw new Error("No document model specification found");
575
+ }
576
+ const source = `${spec.state.global.schema} type Query { ${name}: ${name}State }`;
577
+ const schema = buildSchema(source, options2);
578
+ const queryType = schema.getQueryType();
579
+ if (!queryType) {
580
+ throw new Error("No query type found");
581
+ }
582
+ const fields = queryType.getFields();
583
+ const stateQuery = fields[name];
584
+ if (!stateQuery) {
585
+ throw new Error("No state query found");
586
+ }
587
+ const queryFields = getFields(stateQuery.type);
588
+ return queryFields;
589
+ }
590
+ async function requestPublicDrive(url) {
591
+ let drive;
592
+ try {
593
+ const result = await requestGraphql(
594
+ url,
595
+ gql`
596
+ query getDrive {
597
+ drive {
598
+ id
599
+ name
600
+ icon
601
+ slug
602
+ }
603
+ }
604
+ `
605
+ );
606
+ if (result.errors?.length || !result.drive) {
607
+ throw result.errors?.at(0) ?? new Error("Drive not found");
608
+ }
609
+ drive = result.drive;
610
+ } catch (e) {
611
+ logger.error(e);
612
+ throw new Error("Couldn't find drive info");
613
+ }
614
+ return drive;
615
+ }
616
+ async function fetchDocument(url, documentId, documentModelLib) {
617
+ const { documentModel: documentModel2, utils: utils3 } = documentModelLib;
618
+ const stateFields = generateDocumentStateQueryFields(documentModel2);
619
+ const name = pascalCase(documentModel2.name);
620
+ const result = await requestGraphql(
621
+ url,
622
+ gql`
623
+ query ($id: String!) {
624
+ document(id: $id) {
625
+ id
626
+ name
627
+ created
628
+ documentType
629
+ lastModified
630
+ revision
631
+ operations {
632
+ id
633
+ error
634
+ hash
635
+ index
636
+ skip
637
+ timestamp
638
+ type
639
+ inputText
640
+ context {
641
+ signer {
642
+ user {
643
+ address
644
+ networkId
645
+ chainId
646
+ }
647
+ app {
648
+ name
649
+ key
650
+ }
651
+ signatures
652
+ }
653
+ }
654
+ }
655
+ ... on ${name} {
656
+ state {
657
+ ${stateFields}
658
+ }
659
+ initialState {
660
+ ${stateFields}
661
+ }
662
+ }
663
+ }
664
+ }
665
+ `,
666
+ { id: documentId }
667
+ );
668
+ const document = result.document ? {
669
+ ...result.document,
670
+ revision: {
671
+ global: result.document.revision,
672
+ local: 0
673
+ },
674
+ state: utils3.createState({ global: result.document.state }),
675
+ operations: {
676
+ global: result.document.operations.map(({ inputText, ...o }) => ({
677
+ ...o,
678
+ error: o.error ?? void 0,
679
+ scope: "global",
680
+ input: JSON.parse(inputText)
681
+ })),
682
+ local: []
683
+ },
684
+ attachments: {},
685
+ initialState: utils3.createExtendedState({
686
+ // TODO: getDocument should return all the initial state fields
687
+ created: result.document.created,
688
+ lastModified: result.document.created,
689
+ state: utils3.createState({
690
+ global: result.document.initialState
691
+ })
692
+ }),
693
+ clipboard: []
694
+ } : null;
695
+ return {
696
+ ...result,
697
+ document
698
+ };
699
+ }
700
+
701
+ // ../document-drive/src/read-mode/errors.ts
702
+ var ReadDriveError = class extends Error {
703
+ };
704
+ var ReadDriveNotFoundError = class extends ReadDriveError {
705
+ constructor(driveId) {
706
+ super(`Read drive ${driveId} not found.`);
707
+ }
708
+ };
709
+ var ReadDriveSlugNotFoundError = class extends ReadDriveError {
710
+ constructor(slug) {
711
+ super(`Read drive with slug ${slug} not found.`);
712
+ }
713
+ };
714
+ var ReadDocumentNotFoundError = class extends ReadDriveError {
715
+ constructor(drive, id) {
716
+ super(`Document with id ${id} not found on read drive ${drive}.`);
717
+ }
718
+ };
719
+
720
+ // ../document-drive/src/read-mode/service.ts
721
+ var ReadModeService = class {
722
+ #getDocumentModel;
723
+ #drives = /* @__PURE__ */ new Map();
724
+ constructor(getDocumentModel) {
725
+ this.#getDocumentModel = getDocumentModel;
726
+ }
727
+ #parseGraphQLErrors(errors, driveId, documentId) {
728
+ for (const error of errors) {
729
+ if (error.message === `Drive with id ${driveId} not found`) {
730
+ return new ReadDriveNotFoundError(driveId);
731
+ } else if (documentId && error.message === `Document with id ${documentId} not found`) {
732
+ return new ReadDocumentNotFoundError(driveId, documentId);
733
+ }
734
+ }
735
+ const firstError = errors.at(0);
736
+ if (firstError) {
737
+ return firstError;
738
+ }
739
+ }
740
+ async #fetchDrive(id, url) {
741
+ const { errors, document } = await fetchDocument(
742
+ url,
743
+ id,
744
+ DocumentDrive
745
+ );
746
+ const error = errors ? this.#parseGraphQLErrors(errors, id) : void 0;
747
+ return error || document;
748
+ }
749
+ async fetchDrive(id) {
750
+ const drive = this.#drives.get(id);
751
+ if (!drive) {
752
+ return new ReadDriveNotFoundError(id);
753
+ }
754
+ const document = await this.fetchDocument(
755
+ id,
756
+ id,
757
+ DocumentDrive.documentModel.id
758
+ );
759
+ if (document instanceof Error) {
760
+ return document;
761
+ }
762
+ const result = { ...document, readContext: drive.context };
763
+ drive.drive = result;
764
+ return result;
765
+ }
766
+ async fetchDocument(driveId, documentId, documentType) {
767
+ const drive = this.#drives.get(driveId);
768
+ if (!drive) {
769
+ return new ReadDriveNotFoundError(driveId);
770
+ }
771
+ let documentModel2 = void 0;
772
+ try {
773
+ documentModel2 = this.#getDocumentModel(
774
+ documentType
775
+ );
776
+ } catch (error) {
777
+ return new DocumentModelNotFoundError(documentType, error);
778
+ }
779
+ const { url } = drive.context;
780
+ const { errors, document } = await fetchDocument(
781
+ url,
782
+ documentId,
783
+ documentModel2
784
+ );
785
+ if (errors) {
786
+ const error = this.#parseGraphQLErrors(errors, driveId, documentId);
787
+ if (error instanceof ReadDriveError) {
788
+ return error;
789
+ } else if (error) {
790
+ throw error;
791
+ }
792
+ }
793
+ if (!document) {
794
+ return new ReadDocumentNotFoundError(driveId, documentId);
795
+ }
796
+ return document;
797
+ }
798
+ async addReadDrive(url, options2) {
799
+ const { id } = options2?.expectedDriveInfo ?? await requestPublicDrive(url);
800
+ const result = await this.#fetchDrive(id, url);
801
+ if (result instanceof Error) {
802
+ throw result;
803
+ } else if (!result) {
804
+ throw new Error(`Drive "${id}" not found at ${url}`);
805
+ }
806
+ this.#drives.set(id, {
807
+ drive: result,
808
+ context: {
809
+ ...options2,
810
+ url
811
+ }
812
+ });
813
+ }
814
+ async getReadDrives() {
815
+ return Promise.resolve([...this.#drives.keys()]);
816
+ }
817
+ async getReadDrive(id) {
818
+ const result = this.#drives.get(id);
819
+ return Promise.resolve(
820
+ result ? { ...result.drive, readContext: result.context } : new ReadDriveNotFoundError(id)
821
+ );
822
+ }
823
+ async getReadDriveBySlug(slug) {
824
+ const readDrive = [...this.#drives.values()].find(
825
+ ({ drive }) => drive.state.global.slug === slug
826
+ );
827
+ return Promise.resolve(
828
+ readDrive ? { ...readDrive.drive, readContext: readDrive.context } : new ReadDriveSlugNotFoundError(slug)
829
+ );
830
+ }
831
+ getReadDriveContext(id) {
832
+ return Promise.resolve(
833
+ this.#drives.get(id)?.context ?? new ReadDriveNotFoundError(id)
834
+ );
835
+ }
836
+ deleteReadDrive(id) {
837
+ const deleted = this.#drives.delete(id);
838
+ return Promise.resolve(
839
+ deleted ? void 0 : new ReadDriveNotFoundError(id)
840
+ );
841
+ }
842
+ };
843
+
844
+ // ../document-drive/src/read-mode/index.ts
845
+ function ReadModeServer(Base) {
846
+ return class ReadMode extends Base {
847
+ #readModeStorage;
848
+ #listeners = /* @__PURE__ */ new Set();
849
+ constructor(...args) {
850
+ super(...args);
851
+ this.#readModeStorage = new ReadModeService(
852
+ this.getDocumentModel.bind(this)
853
+ );
854
+ this.#buildDrives().then((drives) => {
855
+ if (drives.length) {
856
+ this.#notifyListeners(drives, "add");
857
+ }
858
+ }).catch(logger.error);
859
+ }
860
+ async #buildDrives() {
861
+ const driveIds = await this.getReadDrives();
862
+ const drives = (await Promise.all(driveIds.map((driveId) => this.getReadDrive(driveId)))).filter((drive) => !(drive instanceof Error));
863
+ return drives;
864
+ }
865
+ #notifyListeners(drives, operation) {
866
+ this.#listeners.forEach((listener) => listener(drives, operation));
867
+ }
868
+ getReadDrives() {
869
+ return this.#readModeStorage.getReadDrives();
870
+ }
871
+ getReadDrive(id) {
872
+ return this.#readModeStorage.getReadDrive(id);
873
+ }
874
+ getReadDriveBySlug(slug) {
875
+ return this.#readModeStorage.getReadDriveBySlug(slug);
876
+ }
877
+ getReadDriveContext(id) {
878
+ return this.#readModeStorage.getReadDriveContext(id);
879
+ }
880
+ async addReadDrive(url, options2) {
881
+ await this.#readModeStorage.addReadDrive(url, options2);
882
+ this.#notifyListeners(await this.#buildDrives(), "add");
883
+ }
884
+ fetchDrive(id) {
885
+ return this.#readModeStorage.fetchDrive(id);
886
+ }
887
+ fetchDocument(driveId, documentId, documentType) {
888
+ return this.#readModeStorage.fetchDocument(
889
+ driveId,
890
+ documentId,
891
+ documentType
892
+ );
893
+ }
894
+ async deleteReadDrive(id) {
895
+ const error = await this.#readModeStorage.deleteReadDrive(id);
896
+ if (error) {
897
+ return error;
898
+ }
899
+ this.#notifyListeners(await this.#buildDrives(), "delete");
900
+ }
901
+ async migrateReadDrive(id, options2) {
902
+ const result = await this.getReadDriveContext(id);
903
+ if (result instanceof Error) {
904
+ return result;
905
+ }
906
+ const { url, ...readOptions } = result;
907
+ try {
908
+ const newDrive = await this.addRemoteDrive(url, options2);
909
+ return newDrive;
910
+ } catch (error) {
911
+ logger.error(error);
912
+ await this.addReadDrive(result.url, readOptions);
913
+ throw error;
914
+ }
915
+ }
916
+ onReadDrivesUpdate(listener) {
917
+ this.#listeners.add(listener);
918
+ return Promise.resolve(() => this.#listeners.delete(listener));
919
+ }
920
+ };
921
+ }
922
+
923
+ // ../document-drive/src/storage/memory.ts
924
+ var MemoryStorage = class {
925
+ documents;
926
+ drives;
927
+ slugToDriveId = {};
928
+ constructor() {
929
+ this.documents = {};
930
+ this.drives = {};
931
+ }
932
+ checkDocumentExists(drive, id) {
933
+ return Promise.resolve(this.documents[drive]?.[id] !== void 0);
934
+ }
935
+ async getDocuments(drive) {
936
+ return Object.keys(this.documents[drive] ?? {});
937
+ }
938
+ async getDocument(driveId, id) {
939
+ const drive = this.documents[driveId];
940
+ if (!drive) {
941
+ throw new DriveNotFoundError(driveId);
942
+ }
943
+ const document = drive[id];
944
+ if (!document) {
945
+ throw new Error(`Document with id ${id} not found`);
946
+ }
947
+ return document;
948
+ }
949
+ async saveDocument(drive, id, document) {
950
+ this.documents[drive] = this.documents[drive] ?? {};
951
+ this.documents[drive][id] = document;
952
+ }
953
+ async clearStorage() {
954
+ this.documents = {};
955
+ this.drives = {};
956
+ }
957
+ async createDocument(drive, id, document) {
958
+ this.documents[drive] = this.documents[drive] ?? {};
959
+ const {
960
+ operations,
961
+ initialState,
962
+ name,
963
+ revision,
964
+ documentType,
965
+ created,
966
+ lastModified,
967
+ clipboard,
968
+ state
969
+ } = document;
970
+ this.documents[drive][id] = {
971
+ operations,
972
+ initialState,
973
+ name,
974
+ revision,
975
+ documentType,
976
+ created,
977
+ lastModified,
978
+ clipboard,
979
+ state
980
+ };
981
+ }
982
+ async addDocumentOperations(drive, id, operations, header) {
983
+ const document = await this.getDocument(drive, id);
984
+ if (!document) {
985
+ throw new Error(`Document with id ${id} not found`);
986
+ }
987
+ const mergedOperations = mergeOperations(document.operations, operations);
988
+ this.documents[drive][id] = {
989
+ ...document,
990
+ ...header,
991
+ operations: mergedOperations
992
+ };
993
+ }
994
+ async deleteDocument(drive, id) {
995
+ if (!this.documents[drive]) {
996
+ throw new DriveNotFoundError(drive);
997
+ }
998
+ delete this.documents[drive][id];
999
+ }
1000
+ async getDrives() {
1001
+ return Object.keys(this.drives);
1002
+ }
1003
+ async getDrive(id) {
1004
+ const drive = this.drives[id];
1005
+ if (!drive) {
1006
+ throw new DriveNotFoundError(id);
1007
+ }
1008
+ return drive;
1009
+ }
1010
+ async getDriveBySlug(slug) {
1011
+ const driveId = this.slugToDriveId[slug];
1012
+ if (!driveId) {
1013
+ throw new Error(`Drive with slug ${slug} not found`);
1014
+ }
1015
+ return this.getDrive(driveId);
1016
+ }
1017
+ async createDrive(id, drive) {
1018
+ this.drives[id] = drive;
1019
+ this.documents[id] = {};
1020
+ const { slug } = drive.initialState.state.global;
1021
+ if (slug) {
1022
+ this.slugToDriveId[slug] = id;
1023
+ }
1024
+ }
1025
+ async addDriveOperations(id, operations, header) {
1026
+ const drive = await this.getDrive(id);
1027
+ const mergedOperations = mergeOperations(drive.operations, operations);
1028
+ this.drives[id] = {
1029
+ ...drive,
1030
+ ...header,
1031
+ operations: mergedOperations
1032
+ };
1033
+ }
1034
+ async deleteDrive(id) {
1035
+ delete this.documents[id];
1036
+ delete this.drives[id];
1037
+ }
1038
+ async getSynchronizationUnitsRevision(units) {
1039
+ const results = await Promise.allSettled(
1040
+ units.map(async (unit) => {
1041
+ try {
1042
+ const document = await (unit.documentId ? this.getDocument(unit.driveId, unit.documentId) : this.getDrive(unit.driveId));
1043
+ if (!document) {
1044
+ return void 0;
1045
+ }
1046
+ const operation = document.operations[unit.scope].at(-1);
1047
+ if (operation) {
1048
+ return {
1049
+ driveId: unit.driveId,
1050
+ documentId: unit.documentId,
1051
+ scope: unit.scope,
1052
+ branch: unit.branch,
1053
+ lastUpdated: operation.timestamp,
1054
+ revision: operation.index
1055
+ };
1056
+ }
1057
+ } catch {
1058
+ return void 0;
1059
+ }
1060
+ })
1061
+ );
1062
+ return results.reduce((acc, curr) => {
1063
+ if (curr.status === "fulfilled" && curr.value !== void 0) {
1064
+ acc.push(curr.value);
1065
+ }
1066
+ return acc;
1067
+ }, []);
1068
+ }
1069
+ };
1070
+
1071
+ // ../document-drive/src/utils/default-drives-manager.ts
1072
+ function isReadModeDriveServer(obj) {
1073
+ return typeof obj.getReadDrives === "function";
1074
+ }
1075
+ var DefaultDrivesManager = class {
1076
+ constructor(server, delegate, options2) {
1077
+ this.server = server;
1078
+ this.delegate = delegate;
1079
+ if (options2?.defaultDrives.remoteDrives) {
1080
+ for (const defaultDrive of options2.defaultDrives.remoteDrives) {
1081
+ this.defaultRemoteDrives.set(defaultDrive.url, {
1082
+ ...defaultDrive,
1083
+ status: "PENDING"
1084
+ });
1085
+ }
1086
+ }
1087
+ this.removeOldRemoteDrivesConfig = options2?.defaultDrives.removeOldRemoteDrives || {
1088
+ strategy: "preserve-all"
1089
+ };
1090
+ }
1091
+ defaultRemoteDrives = /* @__PURE__ */ new Map();
1092
+ removeOldRemoteDrivesConfig;
1093
+ getDefaultRemoteDrives() {
1094
+ return new Map(
1095
+ JSON.parse(
1096
+ JSON.stringify(Array.from(this.defaultRemoteDrives))
1097
+ )
1098
+ );
1099
+ }
1100
+ async deleteDriveById(driveId) {
1101
+ try {
1102
+ await this.server.deleteDrive(driveId);
1103
+ } catch (error) {
1104
+ if (!(error instanceof DriveNotFoundError)) {
1105
+ logger.error(error);
1106
+ }
1107
+ }
1108
+ }
1109
+ async preserveDrivesById(driveIdsToPreserve, drives, removeStrategy = "detach") {
1110
+ const getAllDrives = drives.map((driveId) => this.server.getDrive(driveId));
1111
+ const drivesToRemove = (await Promise.all(getAllDrives)).filter(
1112
+ (drive) => drive.state.local.listeners.length > 0 || drive.state.local.triggers.length > 0
1113
+ ).filter((drive) => !driveIdsToPreserve.includes(drive.state.global.id));
1114
+ const driveIds = drivesToRemove.map((drive) => drive.state.global.id);
1115
+ if (removeStrategy === "detach") {
1116
+ await this.detachDrivesById(driveIds);
1117
+ } else {
1118
+ await this.removeDrivesById(driveIds);
1119
+ }
1120
+ }
1121
+ async removeDrivesById(driveIds) {
1122
+ for (const driveId of driveIds) {
1123
+ await this.deleteDriveById(driveId);
1124
+ }
1125
+ }
1126
+ async detachDrivesById(driveIds) {
1127
+ const detachDrivesPromises = driveIds.map(
1128
+ (driveId) => this.delegate.detachDrive(driveId)
1129
+ );
1130
+ await Promise.all(detachDrivesPromises);
1131
+ }
1132
+ async removeOldremoteDrives() {
1133
+ const driveids = await this.server.getDrives();
1134
+ switch (this.removeOldRemoteDrivesConfig.strategy) {
1135
+ case "preserve-by-id-and-detach":
1136
+ case "preserve-by-id": {
1137
+ const detach = this.removeOldRemoteDrivesConfig.strategy === "preserve-by-id-and-detach" ? "detach" : "remove";
1138
+ await this.preserveDrivesById(
1139
+ this.removeOldRemoteDrivesConfig.ids,
1140
+ driveids,
1141
+ detach
1142
+ );
1143
+ break;
1144
+ }
1145
+ case "preserve-by-url-and-detach":
1146
+ case "preserve-by-url": {
1147
+ const detach = this.removeOldRemoteDrivesConfig.strategy === "preserve-by-url-and-detach" ? "detach" : "remove";
1148
+ const getDrivesInfo = this.removeOldRemoteDrivesConfig.urls.map(
1149
+ (url) => requestPublicDrive(url)
1150
+ );
1151
+ const drivesIdsToPreserve = (await Promise.all(getDrivesInfo)).map(
1152
+ (driveInfo) => driveInfo.id
1153
+ );
1154
+ await this.preserveDrivesById(drivesIdsToPreserve, driveids, detach);
1155
+ break;
1156
+ }
1157
+ case "remove-by-id": {
1158
+ const drivesIdsToRemove = this.removeOldRemoteDrivesConfig.ids.filter(
1159
+ (driveId) => driveids.includes(driveId)
1160
+ );
1161
+ await this.removeDrivesById(drivesIdsToRemove);
1162
+ break;
1163
+ }
1164
+ case "remove-by-url": {
1165
+ const getDrivesInfo = this.removeOldRemoteDrivesConfig.urls.map(
1166
+ (driveUrl) => requestPublicDrive(driveUrl)
1167
+ );
1168
+ const drivesInfo = await Promise.all(getDrivesInfo);
1169
+ const drivesIdsToRemove = drivesInfo.map((driveInfo) => driveInfo.id).filter((driveId) => driveids.includes(driveId));
1170
+ await this.removeDrivesById(drivesIdsToRemove);
1171
+ break;
1172
+ }
1173
+ case "remove-all": {
1174
+ const getDrives = driveids.map(
1175
+ (driveId) => this.server.getDrive(driveId)
1176
+ );
1177
+ const drives = await Promise.all(getDrives);
1178
+ const drivesToRemove = drives.filter(
1179
+ (drive) => drive.state.local.listeners.length > 0 || drive.state.local.triggers.length > 0
1180
+ ).map((drive) => drive.state.global.id);
1181
+ await this.removeDrivesById(drivesToRemove);
1182
+ break;
1183
+ }
1184
+ case "detach-by-id": {
1185
+ const drivesIdsToRemove = this.removeOldRemoteDrivesConfig.ids.filter(
1186
+ (driveId) => driveids.includes(driveId)
1187
+ );
1188
+ const detachDrivesPromises = drivesIdsToRemove.map(
1189
+ (driveId) => this.delegate.detachDrive(driveId)
1190
+ );
1191
+ await Promise.all(detachDrivesPromises);
1192
+ break;
1193
+ }
1194
+ case "detach-by-url": {
1195
+ const getDrivesInfo = this.removeOldRemoteDrivesConfig.urls.map(
1196
+ (driveUrl) => requestPublicDrive(driveUrl)
1197
+ );
1198
+ const drivesInfo = await Promise.all(getDrivesInfo);
1199
+ const drivesIdsToRemove = drivesInfo.map((driveInfo) => driveInfo.id).filter((driveId) => driveids.includes(driveId));
1200
+ const detachDrivesPromises = drivesIdsToRemove.map(
1201
+ (driveId) => this.delegate.detachDrive(driveId)
1202
+ );
1203
+ await Promise.all(detachDrivesPromises);
1204
+ break;
1205
+ }
1206
+ }
1207
+ }
1208
+ async setAllDefaultDrivesAccessLevel(level) {
1209
+ const drives = this.defaultRemoteDrives.values();
1210
+ for (const drive of drives) {
1211
+ await this.setDefaultDriveAccessLevel(drive.url, level);
1212
+ }
1213
+ }
1214
+ async setDefaultDriveAccessLevel(url, level) {
1215
+ const drive = this.defaultRemoteDrives.get(url);
1216
+ if (drive && drive.options.accessLevel !== level) {
1217
+ const newDriveValue = {
1218
+ ...drive,
1219
+ options: { ...drive.options, accessLevel: level }
1220
+ };
1221
+ this.defaultRemoteDrives.set(url, newDriveValue);
1222
+ await this.initializeDefaultRemoteDrives([newDriveValue]);
1223
+ }
1224
+ }
1225
+ async initializeDefaultRemoteDrives(defaultDrives = Array.from(
1226
+ this.defaultRemoteDrives.values()
1227
+ )) {
1228
+ const drives = await this.server.getDrives();
1229
+ const readServer = isReadModeDriveServer(this.server) ? this.server : void 0;
1230
+ const readDrives = await readServer?.getReadDrives();
1231
+ for (const remoteDrive of defaultDrives) {
1232
+ let remoteDriveInfo = { ...remoteDrive };
1233
+ try {
1234
+ const driveInfo = remoteDrive.metadata ?? await requestPublicDrive(remoteDrive.url);
1235
+ remoteDriveInfo = { ...remoteDrive, metadata: driveInfo };
1236
+ this.defaultRemoteDrives.set(remoteDrive.url, remoteDriveInfo);
1237
+ const driveIsAdded = drives.includes(driveInfo.id);
1238
+ const readDriveIsAdded = readDrives?.includes(driveInfo.id);
1239
+ const hasAccessLevel = remoteDrive.options.accessLevel !== void 0;
1240
+ const readMode = readServer && remoteDrive.options.accessLevel === "READ";
1241
+ const isAdded = readMode ? readDriveIsAdded : driveIsAdded;
1242
+ const driveToDelete = hasAccessLevel && (readMode ? driveIsAdded : readDriveIsAdded);
1243
+ if (driveToDelete) {
1244
+ try {
1245
+ await (readMode ? this.server.deleteDrive(driveInfo.id) : readServer?.deleteReadDrive(driveInfo.id));
1246
+ } catch (e) {
1247
+ logger.error(e);
1248
+ }
1249
+ }
1250
+ if (isAdded) {
1251
+ remoteDriveInfo.status = "ALREADY_ADDED";
1252
+ this.defaultRemoteDrives.set(remoteDrive.url, remoteDriveInfo);
1253
+ this.delegate.emit(
1254
+ "ALREADY_ADDED",
1255
+ this.defaultRemoteDrives,
1256
+ remoteDriveInfo,
1257
+ driveInfo.id,
1258
+ driveInfo.name
1259
+ );
1260
+ continue;
1261
+ }
1262
+ remoteDriveInfo.status = "ADDING";
1263
+ this.defaultRemoteDrives.set(remoteDrive.url, remoteDriveInfo);
1264
+ this.delegate.emit("ADDING", this.defaultRemoteDrives, remoteDriveInfo);
1265
+ if (!hasAccessLevel && readServer || readMode) {
1266
+ await readServer.addReadDrive(remoteDrive.url, {
1267
+ ...remoteDrive.options,
1268
+ expectedDriveInfo: driveInfo
1269
+ });
1270
+ } else {
1271
+ await this.server.addRemoteDrive(remoteDrive.url, {
1272
+ ...remoteDrive.options,
1273
+ expectedDriveInfo: driveInfo
1274
+ });
1275
+ }
1276
+ remoteDriveInfo.status = "SUCCESS";
1277
+ this.defaultRemoteDrives.set(remoteDrive.url, remoteDriveInfo);
1278
+ this.delegate.emit(
1279
+ "SUCCESS",
1280
+ this.defaultRemoteDrives,
1281
+ remoteDriveInfo,
1282
+ driveInfo.id,
1283
+ driveInfo.name
1284
+ );
1285
+ } catch (error) {
1286
+ remoteDriveInfo.status = "ERROR";
1287
+ this.defaultRemoteDrives.set(remoteDrive.url, remoteDriveInfo);
1288
+ this.delegate.emit(
1289
+ "ERROR",
1290
+ this.defaultRemoteDrives,
1291
+ remoteDriveInfo,
1292
+ void 0,
1293
+ void 0,
1294
+ error
1295
+ );
1296
+ }
1297
+ }
1298
+ }
1299
+ };
1300
+ var {
1301
+ attachBranch,
1302
+ garbageCollect,
1303
+ groupOperationsByScope,
1304
+ merge,
1305
+ split,
1306
+ precedes,
1307
+ removeExistingOperations,
1308
+ reshuffleByTimestamp,
1309
+ sortOperations,
1310
+ addUndo,
1311
+ checkOperationsIntegrity,
1312
+ checkCleanedOperationsIntegrity,
1313
+ reshuffleByTimestampAndIndex,
1314
+ nextSkipNumber,
1315
+ prepareOperations,
1316
+ IntegrityIssueSubType,
1317
+ IntegrityIssueType
1318
+ } = utils.documentHelpers;
1319
+
1320
+ // ../document-drive/src/server/types.ts
1321
+ var AbstractDocumentDriveServer = class {
1322
+ };
1323
+ var DefaultListenerManagerOptions = {
1324
+ sequentialUpdates: true
1325
+ };
1326
+ var BaseListenerManager = class {
1327
+ drive;
1328
+ listenerState = /* @__PURE__ */ new Map();
1329
+ options;
1330
+ transmitters = {};
1331
+ constructor(drive, listenerState = /* @__PURE__ */ new Map(), options2 = DefaultListenerManagerOptions) {
1332
+ this.drive = drive;
1333
+ this.listenerState = listenerState;
1334
+ this.options = { ...DefaultListenerManagerOptions, ...options2 };
1335
+ }
1336
+ };
1337
+
1338
+ // ../document-drive/src/server/utils.ts
1339
+ function buildRevisionsFilter(strands, driveId, documentId) {
1340
+ return strands.reduce((acc, s) => {
1341
+ if (!(s.driveId === driveId && s.documentId === documentId)) {
1342
+ return acc;
1343
+ }
1344
+ acc[s.scope] = s.operations[s.operations.length - 1]?.index ?? -1;
1345
+ return acc;
1346
+ }, {});
1347
+ }
1348
+ function filterOperationsByRevision(operations, revisions) {
1349
+ if (!revisions) {
1350
+ return operations;
1351
+ }
1352
+ return Object.keys(operations).reduce((acc, scope) => {
1353
+ const revision = revisions[scope];
1354
+ if (revision !== void 0) {
1355
+ acc[scope] = operations[scope].filter((op) => op.index <= revision);
1356
+ }
1357
+ return acc;
1358
+ }, operations);
1359
+ }
1360
+
1361
+ // ../document-drive/src/server/listener/transmitter/internal.ts
1362
+ var InternalTransmitter = class {
1363
+ drive;
1364
+ listener;
1365
+ receiver;
1366
+ constructor(listener, drive) {
1367
+ this.listener = listener;
1368
+ this.drive = drive;
1369
+ }
1370
+ async transmit(strands) {
1371
+ if (!this.receiver) {
1372
+ return [];
1373
+ }
1374
+ const retrievedDocuments = /* @__PURE__ */ new Map();
1375
+ const updates = [];
1376
+ for (const strand of strands) {
1377
+ let document = retrievedDocuments.get(
1378
+ `${strand.driveId}:${strand.documentId}`
1379
+ );
1380
+ if (!document) {
1381
+ const revisions = buildRevisionsFilter(
1382
+ strands,
1383
+ strand.driveId,
1384
+ strand.documentId
1385
+ );
1386
+ document = await (strand.documentId ? this.drive.getDocument(strand.driveId, strand.documentId, {
1387
+ revisions
1388
+ }) : this.drive.getDrive(strand.driveId, { revisions }));
1389
+ retrievedDocuments.set(
1390
+ `${strand.driveId}:${strand.documentId}`,
1391
+ document
1392
+ );
1393
+ }
1394
+ updates.push({ ...strand, state: document.state[strand.scope] });
1395
+ }
1396
+ try {
1397
+ await this.receiver.transmit(updates);
1398
+ return strands.map(({ operations, ...s }) => ({
1399
+ ...s,
1400
+ status: "SUCCESS",
1401
+ revision: operations[operations.length - 1]?.index ?? -1
1402
+ }));
1403
+ } catch (error) {
1404
+ logger.error(error);
1405
+ return strands.map(({ operations, ...s }) => ({
1406
+ ...s,
1407
+ status: "ERROR",
1408
+ revision: (operations[0]?.index ?? 0) - 1
1409
+ }));
1410
+ }
1411
+ }
1412
+ setReceiver(receiver) {
1413
+ this.receiver = receiver;
1414
+ }
1415
+ async disconnect() {
1416
+ await this.receiver?.disconnect();
1417
+ }
1418
+ };
1419
+
1420
+ // ../document-drive/src/server/listener/transmitter/pull-responder.ts
1421
+ var PullResponderTransmitter = class _PullResponderTransmitter {
1422
+ drive;
1423
+ listener;
1424
+ manager;
1425
+ constructor(listener, drive, manager) {
1426
+ this.listener = listener;
1427
+ this.drive = drive;
1428
+ this.manager = manager;
1429
+ }
1430
+ getStrands(options2) {
1431
+ return this.manager.getStrands(
1432
+ this.listener.driveId,
1433
+ this.listener.listenerId,
1434
+ options2
1435
+ );
1436
+ }
1437
+ disconnect() {
1438
+ return Promise.resolve();
1439
+ }
1440
+ async processAcknowledge(driveId, listenerId, revisions) {
1441
+ const syncUnits = await this.manager.getListenerSyncUnitIds(
1442
+ driveId,
1443
+ listenerId
1444
+ );
1445
+ let success = true;
1446
+ for (const revision of revisions) {
1447
+ const syncUnit = syncUnits.find(
1448
+ (s) => s.scope === revision.scope && s.branch === revision.branch && s.driveId === revision.driveId && s.documentId == revision.documentId
1449
+ );
1450
+ if (!syncUnit) {
1451
+ logger.warn("Unknown sync unit was acknowledged", revision);
1452
+ success = false;
1453
+ continue;
1454
+ }
1455
+ await this.manager.updateListenerRevision(
1456
+ listenerId,
1457
+ driveId,
1458
+ syncUnit.syncId,
1459
+ revision.revision
1460
+ );
1461
+ }
1462
+ return success;
1463
+ }
1464
+ static async registerPullResponder(driveId, url, filter) {
1465
+ const result = await requestGraphql(
1466
+ url,
1467
+ gql`
1468
+ mutation registerPullResponderListener($filter: InputListenerFilter!) {
1469
+ registerPullResponderListener(filter: $filter) {
1470
+ listenerId
1471
+ }
1472
+ }
1473
+ `,
1474
+ { filter }
1475
+ );
1476
+ const error = result.errors?.at(0);
1477
+ if (error) {
1478
+ throw error;
1479
+ }
1480
+ if (!result.registerPullResponderListener) {
1481
+ throw new Error("Error registering listener");
1482
+ }
1483
+ return result.registerPullResponderListener.listenerId;
1484
+ }
1485
+ static async pullStrands(driveId, url, listenerId, options2) {
1486
+ const result = await requestGraphql(
1487
+ url,
1488
+ gql`
1489
+ query strands($listenerId: ID!) {
1490
+ system {
1491
+ sync {
1492
+ strands(listenerId: $listenerId) {
1493
+ driveId
1494
+ documentId
1495
+ scope
1496
+ branch
1497
+ operations {
1498
+ id
1499
+ timestamp
1500
+ skip
1501
+ type
1502
+ input
1503
+ hash
1504
+ index
1505
+ context {
1506
+ signer {
1507
+ user {
1508
+ address
1509
+ networkId
1510
+ chainId
1511
+ }
1512
+ app {
1513
+ name
1514
+ key
1515
+ }
1516
+ signatures
1517
+ }
1518
+ }
1519
+ }
1520
+ }
1521
+ }
1522
+ }
1523
+ }
1524
+ `,
1525
+ { listenerId }
1526
+ );
1527
+ const error = result.errors?.at(0);
1528
+ if (error) {
1529
+ throw error;
1530
+ }
1531
+ if (!result.system) {
1532
+ return [];
1533
+ }
1534
+ return result.system.sync.strands.map((s) => ({
1535
+ ...s,
1536
+ operations: s.operations.map((o) => ({
1537
+ ...o,
1538
+ input: JSON.parse(o.input)
1539
+ }))
1540
+ }));
1541
+ }
1542
+ static async acknowledgeStrands(driveId, url, listenerId, revisions) {
1543
+ const result = await requestGraphql(
1544
+ url,
1545
+ gql`
1546
+ mutation acknowledge(
1547
+ $listenerId: String!
1548
+ $revisions: [ListenerRevisionInput]
1549
+ ) {
1550
+ acknowledge(listenerId: $listenerId, revisions: $revisions)
1551
+ }
1552
+ `,
1553
+ { listenerId, revisions }
1554
+ );
1555
+ const error = result.errors?.at(0);
1556
+ if (error) {
1557
+ throw error;
1558
+ }
1559
+ if (result.acknowledge === null) {
1560
+ throw new Error("Error acknowledging strands");
1561
+ }
1562
+ return result.acknowledge;
1563
+ }
1564
+ static async executePull(driveId, trigger, onStrandUpdate, onError, onRevisions, onAcknowledge) {
1565
+ try {
1566
+ const { url, listenerId } = trigger.data;
1567
+ const strands = await _PullResponderTransmitter.pullStrands(
1568
+ driveId,
1569
+ url,
1570
+ listenerId
1571
+ // since ?
1572
+ );
1573
+ if (!strands.length) {
1574
+ onRevisions?.([]);
1575
+ return;
1576
+ }
1577
+ const listenerRevisions = [];
1578
+ for (const strand of strands) {
1579
+ const operations = strand.operations.map((op) => ({
1580
+ ...op,
1581
+ scope: strand.scope,
1582
+ branch: strand.branch
1583
+ }));
1584
+ let error = void 0;
1585
+ try {
1586
+ const result = await onStrandUpdate(strand, {
1587
+ type: "trigger",
1588
+ trigger
1589
+ });
1590
+ if (result.error) {
1591
+ throw result.error;
1592
+ }
1593
+ } catch (e) {
1594
+ error = e;
1595
+ onError(error);
1596
+ }
1597
+ listenerRevisions.push({
1598
+ branch: strand.branch,
1599
+ documentId: strand.documentId || "",
1600
+ driveId: strand.driveId,
1601
+ revision: operations.pop()?.index ?? -1,
1602
+ scope: strand.scope,
1603
+ status: error ? error instanceof OperationError ? error.status : "ERROR" : "SUCCESS",
1604
+ error
1605
+ });
1606
+ }
1607
+ onRevisions?.(listenerRevisions);
1608
+ await _PullResponderTransmitter.acknowledgeStrands(
1609
+ driveId,
1610
+ url,
1611
+ listenerId,
1612
+ listenerRevisions.map((revision) => {
1613
+ const { error, ...rest } = revision;
1614
+ return rest;
1615
+ })
1616
+ ).then((result) => onAcknowledge?.(result)).catch((error) => logger.error("ACK error", error));
1617
+ } catch (error) {
1618
+ onError(error);
1619
+ }
1620
+ }
1621
+ static setupPull(driveId, trigger, onStrandUpdate, onError, onRevisions, onAcknowledge) {
1622
+ const { interval } = trigger.data;
1623
+ let loopInterval = PULL_DRIVE_INTERVAL;
1624
+ if (interval) {
1625
+ try {
1626
+ const intervalNumber = parseInt(interval);
1627
+ if (intervalNumber) {
1628
+ loopInterval = intervalNumber;
1629
+ }
1630
+ } catch {
1631
+ }
1632
+ }
1633
+ let isCancelled = false;
1634
+ let timeout;
1635
+ const executeLoop = async () => {
1636
+ while (!isCancelled) {
1637
+ await this.executePull(
1638
+ driveId,
1639
+ trigger,
1640
+ onStrandUpdate,
1641
+ onError,
1642
+ onRevisions,
1643
+ onAcknowledge
1644
+ );
1645
+ await new Promise((resolve) => {
1646
+ timeout = setTimeout(resolve, loopInterval);
1647
+ });
1648
+ }
1649
+ };
1650
+ executeLoop().catch(logger.error);
1651
+ return () => {
1652
+ isCancelled = true;
1653
+ if (timeout !== void 0) {
1654
+ clearTimeout(timeout);
1655
+ }
1656
+ };
1657
+ }
1658
+ static async createPullResponderTrigger(driveId, url, options2) {
1659
+ const { pullFilter, pullInterval } = options2;
1660
+ const listenerId = await _PullResponderTransmitter.registerPullResponder(
1661
+ driveId,
1662
+ url,
1663
+ pullFilter ?? {
1664
+ documentId: ["*"],
1665
+ documentType: ["*"],
1666
+ branch: ["*"],
1667
+ scope: ["*"]
1668
+ }
1669
+ );
1670
+ const pullTrigger = {
1671
+ id: generateUUID(),
1672
+ type: "PullResponder",
1673
+ data: {
1674
+ url,
1675
+ listenerId,
1676
+ interval: pullInterval?.toString() ?? ""
1677
+ }
1678
+ };
1679
+ return pullTrigger;
1680
+ }
1681
+ static isPullResponderTrigger(trigger) {
1682
+ return trigger.type === "PullResponder";
1683
+ }
1684
+ };
1685
+ var SwitchboardPushTransmitter = class {
1686
+ drive;
1687
+ listener;
1688
+ targetURL;
1689
+ constructor(listener, drive) {
1690
+ this.listener = listener;
1691
+ this.drive = drive;
1692
+ this.targetURL = listener.callInfo.data;
1693
+ }
1694
+ async transmit(strands, source) {
1695
+ if (source.type === "trigger" && source.trigger.data?.url === this.targetURL) {
1696
+ return strands.map((strand) => ({
1697
+ driveId: strand.driveId,
1698
+ documentId: strand.documentId,
1699
+ scope: strand.scope,
1700
+ branch: strand.branch,
1701
+ status: "SUCCESS",
1702
+ revision: strand.operations.at(-1)?.index ?? -1
1703
+ }));
1704
+ }
1705
+ try {
1706
+ const { pushUpdates } = await requestGraphql(
1707
+ this.targetURL,
1708
+ gql`
1709
+ mutation pushUpdates($strands: [InputStrandUpdate!]) {
1710
+ pushUpdates(strands: $strands) {
1711
+ driveId
1712
+ documentId
1713
+ scope
1714
+ branch
1715
+ status
1716
+ revision
1717
+ error
1718
+ }
1719
+ }
1720
+ `,
1721
+ {
1722
+ strands: strands.map((strand) => ({
1723
+ ...strand,
1724
+ operations: strand.operations.map((op) => ({
1725
+ ...op,
1726
+ input: stringify(op.input)
1727
+ }))
1728
+ }))
1729
+ }
1730
+ );
1731
+ if (!pushUpdates) {
1732
+ throw new Error("Couldn't update listener revision");
1733
+ }
1734
+ return pushUpdates;
1735
+ } catch (e) {
1736
+ logger.error(e);
1737
+ throw e;
1738
+ }
1739
+ return [];
1740
+ }
1741
+ };
1742
+
1743
+ // ../document-drive/src/server/listener/manager.ts
1744
+ function debounce(func, delay = 250) {
1745
+ let timer;
1746
+ return (immediate, ...args) => {
1747
+ if (timer) {
1748
+ clearTimeout(timer);
1749
+ }
1750
+ return new Promise((resolve, reject) => {
1751
+ if (immediate) {
1752
+ func(...args).then(resolve).catch(reject);
1753
+ } else {
1754
+ timer = setTimeout(() => {
1755
+ func(...args).then(resolve).catch(reject);
1756
+ }, delay);
1757
+ }
1758
+ });
1759
+ };
1760
+ }
1761
+ var ListenerManager = class _ListenerManager extends BaseListenerManager {
1762
+ static LISTENER_UPDATE_DELAY = 250;
1763
+ async getTransmitter(driveId, listenerId) {
1764
+ return Promise.resolve(this.transmitters[driveId]?.[listenerId]);
1765
+ }
1766
+ driveHasListeners(driveId) {
1767
+ return this.listenerState.has(driveId);
1768
+ }
1769
+ async addListener(listener) {
1770
+ const drive = listener.driveId;
1771
+ if (!this.listenerState.has(drive)) {
1772
+ this.listenerState.set(drive, /* @__PURE__ */ new Map());
1773
+ }
1774
+ const driveMap = this.listenerState.get(drive);
1775
+ driveMap.set(listener.listenerId, {
1776
+ block: listener.block,
1777
+ driveId: listener.driveId,
1778
+ pendingTimeout: "0",
1779
+ listener,
1780
+ listenerStatus: "CREATED",
1781
+ syncUnits: /* @__PURE__ */ new Map()
1782
+ });
1783
+ let transmitter;
1784
+ switch (listener.callInfo?.transmitterType) {
1785
+ case "SwitchboardPush": {
1786
+ transmitter = new SwitchboardPushTransmitter(listener, this.drive);
1787
+ break;
1788
+ }
1789
+ case "PullResponder": {
1790
+ transmitter = new PullResponderTransmitter(listener, this.drive, this);
1791
+ break;
1792
+ }
1793
+ case "Internal": {
1794
+ transmitter = new InternalTransmitter(listener, this.drive);
1795
+ break;
1796
+ }
1797
+ }
1798
+ if (!transmitter) {
1799
+ throw new Error("Transmitter not found");
1800
+ }
1801
+ const driveTransmitters = this.transmitters[drive] || {};
1802
+ driveTransmitters[listener.listenerId] = transmitter;
1803
+ this.transmitters[drive] = driveTransmitters;
1804
+ return Promise.resolve(transmitter);
1805
+ }
1806
+ async removeListener(driveId, listenerId) {
1807
+ const driveMap = this.listenerState.get(driveId);
1808
+ if (!driveMap) {
1809
+ return false;
1810
+ }
1811
+ return Promise.resolve(driveMap.delete(listenerId));
1812
+ }
1813
+ async removeSyncUnits(driveId, syncUnits) {
1814
+ const listeners = this.listenerState.get(driveId);
1815
+ if (!listeners) {
1816
+ return;
1817
+ }
1818
+ for (const [, listener] of listeners) {
1819
+ for (const syncUnit of syncUnits) {
1820
+ listener.syncUnits.delete(syncUnit.syncId);
1821
+ }
1822
+ }
1823
+ return Promise.resolve();
1824
+ }
1825
+ async updateSynchronizationRevisions(driveId, syncUnits, source, willUpdate, onError, forceSync = false) {
1826
+ const drive = this.listenerState.get(driveId);
1827
+ if (!drive) {
1828
+ return [];
1829
+ }
1830
+ const outdatedListeners = [];
1831
+ for (const [, listener] of drive) {
1832
+ if (outdatedListeners.find(
1833
+ (l) => l.listenerId === listener.listener.listenerId
1834
+ )) {
1835
+ continue;
1836
+ }
1837
+ const transmitter = await this.getTransmitter(
1838
+ driveId,
1839
+ listener.listener.listenerId
1840
+ );
1841
+ if (!transmitter?.transmit) {
1842
+ continue;
1843
+ }
1844
+ for (const syncUnit of syncUnits) {
1845
+ if (!this._checkFilter(listener.listener.filter, syncUnit)) {
1846
+ continue;
1847
+ }
1848
+ const listenerRev = listener.syncUnits.get(syncUnit.syncId);
1849
+ if (!listenerRev || listenerRev.listenerRev < syncUnit.revision) {
1850
+ outdatedListeners.push(listener.listener);
1851
+ break;
1852
+ }
1853
+ }
1854
+ }
1855
+ if (outdatedListeners.length) {
1856
+ willUpdate?.(outdatedListeners);
1857
+ return this.triggerUpdate(forceSync, source, onError);
1858
+ }
1859
+ return [];
1860
+ }
1861
+ async updateListenerRevision(listenerId, driveId, syncId, listenerRev) {
1862
+ const drive = this.listenerState.get(driveId);
1863
+ if (!drive) {
1864
+ return;
1865
+ }
1866
+ const listener = drive.get(listenerId);
1867
+ if (!listener) {
1868
+ return;
1869
+ }
1870
+ const lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
1871
+ const entry = listener.syncUnits.get(syncId);
1872
+ if (entry) {
1873
+ entry.listenerRev = listenerRev;
1874
+ entry.lastUpdated = lastUpdated;
1875
+ } else {
1876
+ listener.syncUnits.set(syncId, { listenerRev, lastUpdated });
1877
+ }
1878
+ return Promise.resolve();
1879
+ }
1880
+ triggerUpdate = debounce(
1881
+ this._triggerUpdate.bind(this),
1882
+ _ListenerManager.LISTENER_UPDATE_DELAY
1883
+ );
1884
+ async _triggerUpdate(source, onError) {
1885
+ const listenerUpdates = [];
1886
+ for (const [driveId, drive] of this.listenerState) {
1887
+ for (const [id, listener] of drive) {
1888
+ const transmitter = await this.getTransmitter(driveId, id);
1889
+ if (!transmitter?.transmit) {
1890
+ continue;
1891
+ }
1892
+ const syncUnits = await this.getListenerSyncUnits(
1893
+ driveId,
1894
+ listener.listener.listenerId
1895
+ );
1896
+ const strandUpdates = [];
1897
+ const tasks = syncUnits.map((syncUnit) => async () => {
1898
+ const unitState = listener.syncUnits.get(syncUnit.syncId);
1899
+ if (unitState && unitState.listenerRev >= syncUnit.revision) {
1900
+ return;
1901
+ }
1902
+ const opData = [];
1903
+ try {
1904
+ const data = await this.drive.getOperationData(
1905
+ // TODO - join queries, DEAL WITH INVALID SYNC ID ERROR
1906
+ driveId,
1907
+ syncUnit.syncId,
1908
+ {
1909
+ fromRevision: unitState?.listenerRev
1910
+ }
1911
+ );
1912
+ opData.push(...data);
1913
+ } catch (e) {
1914
+ logger.error(e);
1915
+ }
1916
+ if (!opData.length) {
1917
+ return;
1918
+ }
1919
+ strandUpdates.push({
1920
+ driveId,
1921
+ documentId: syncUnit.documentId,
1922
+ branch: syncUnit.branch,
1923
+ operations: opData,
1924
+ scope: syncUnit.scope
1925
+ });
1926
+ });
1927
+ if (this.options.sequentialUpdates) {
1928
+ for (const task of tasks) {
1929
+ await task();
1930
+ }
1931
+ } else {
1932
+ await Promise.all(tasks.map((task) => task()));
1933
+ }
1934
+ if (strandUpdates.length == 0) {
1935
+ continue;
1936
+ }
1937
+ listener.pendingTimeout = new Date(
1938
+ (/* @__PURE__ */ new Date()).getTime() / 1e3 + 300
1939
+ ).toISOString();
1940
+ listener.listenerStatus = "PENDING";
1941
+ try {
1942
+ const listenerRevisions = await transmitter.transmit(
1943
+ strandUpdates,
1944
+ source
1945
+ );
1946
+ listener.pendingTimeout = "0";
1947
+ listener.listenerStatus = "PENDING";
1948
+ const lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
1949
+ for (const revision of listenerRevisions) {
1950
+ const syncUnit = syncUnits.find(
1951
+ (unit) => revision.documentId === unit.documentId && revision.scope === unit.scope && revision.branch === unit.branch
1952
+ );
1953
+ if (syncUnit) {
1954
+ listener.syncUnits.set(syncUnit.syncId, {
1955
+ lastUpdated,
1956
+ listenerRev: revision.revision
1957
+ });
1958
+ } else {
1959
+ logger.warn(
1960
+ `Received revision for untracked unit for listener ${listener.listener.listenerId}`,
1961
+ revision
1962
+ );
1963
+ }
1964
+ }
1965
+ for (const revision of listenerRevisions) {
1966
+ const error = revision.status === "ERROR";
1967
+ if (revision.error?.includes("Missing operations")) {
1968
+ const updates = await this._triggerUpdate(source, onError);
1969
+ listenerUpdates.push(...updates);
1970
+ } else {
1971
+ listenerUpdates.push({
1972
+ listenerId: listener.listener.listenerId,
1973
+ listenerRevisions
1974
+ });
1975
+ if (error) {
1976
+ throw new OperationError(
1977
+ revision.status,
1978
+ void 0,
1979
+ revision.error,
1980
+ revision.error
1981
+ );
1982
+ }
1983
+ }
1984
+ }
1985
+ listener.listenerStatus = "SUCCESS";
1986
+ } catch (e) {
1987
+ onError?.(e, driveId, listener);
1988
+ listener.listenerStatus = e instanceof OperationError ? e.status : "ERROR";
1989
+ }
1990
+ }
1991
+ }
1992
+ return listenerUpdates;
1993
+ }
1994
+ _checkFilter(filter, syncUnit) {
1995
+ const { branch, documentId, scope, documentType } = syncUnit;
1996
+ if ((!filter.branch || filter.branch.includes(branch) || filter.branch.includes("*")) && (!filter.documentId || filter.documentId.includes(documentId) || filter.documentId.includes("*")) && (!filter.scope || filter.scope.includes(scope) || filter.scope.includes("*")) && (!filter.documentType || filter.documentType.includes(documentType) || filter.documentType.includes("*"))) {
1997
+ return true;
1998
+ }
1999
+ return false;
2000
+ }
2001
+ getListenerSyncUnits(driveId, listenerId, loadedDrive) {
2002
+ const listener = this.listenerState.get(driveId)?.get(listenerId);
2003
+ if (!listener) {
2004
+ return [];
2005
+ }
2006
+ const filter = listener.listener.filter;
2007
+ return this.drive.getSynchronizationUnits(
2008
+ driveId,
2009
+ filter.documentId ?? ["*"],
2010
+ filter.scope ?? ["*"],
2011
+ filter.branch ?? ["*"],
2012
+ filter.documentType ?? ["*"],
2013
+ loadedDrive
2014
+ );
2015
+ }
2016
+ getListenerSyncUnitIds(driveId, listenerId) {
2017
+ const listener = this.listenerState.get(driveId)?.get(listenerId);
2018
+ if (!listener) {
2019
+ return [];
2020
+ }
2021
+ const filter = listener.listener.filter;
2022
+ return this.drive.getSynchronizationUnitsIds(
2023
+ driveId,
2024
+ filter.documentId ?? ["*"],
2025
+ filter.scope ?? ["*"],
2026
+ filter.branch ?? ["*"],
2027
+ filter.documentType ?? ["*"]
2028
+ );
2029
+ }
2030
+ async initDrive(drive) {
2031
+ const {
2032
+ state: {
2033
+ local: { listeners }
2034
+ }
2035
+ } = drive;
2036
+ for (const listener of listeners) {
2037
+ await this.addListener({
2038
+ block: listener.block,
2039
+ driveId: drive.state.global.id,
2040
+ filter: {
2041
+ branch: listener.filter.branch ?? [],
2042
+ documentId: listener.filter.documentId ?? [],
2043
+ documentType: listener.filter.documentType,
2044
+ scope: listener.filter.scope ?? []
2045
+ },
2046
+ listenerId: listener.listenerId,
2047
+ system: listener.system,
2048
+ callInfo: listener.callInfo ?? void 0,
2049
+ label: listener.label ?? ""
2050
+ });
2051
+ }
2052
+ }
2053
+ async removeDrive(driveId) {
2054
+ this.listenerState.delete(driveId);
2055
+ const transmitters = this.transmitters[driveId];
2056
+ if (transmitters) {
2057
+ await Promise.all(
2058
+ Object.values(transmitters).map((t) => t.disconnect?.())
2059
+ );
2060
+ }
2061
+ }
2062
+ getListener(driveId, listenerId) {
2063
+ const drive = this.listenerState.get(driveId);
2064
+ if (!drive) throw new Error("Drive not found");
2065
+ const listener = drive.get(listenerId);
2066
+ if (!listener) throw new Error("Listener not found");
2067
+ return Promise.resolve(listener);
2068
+ }
2069
+ async getStrands(driveId, listenerId, options2) {
2070
+ const listener = await this.getListener(driveId, listenerId);
2071
+ const strands = [];
2072
+ const drive = await this.drive.getDrive(driveId);
2073
+ const syncUnits = await this.getListenerSyncUnits(
2074
+ driveId,
2075
+ listenerId,
2076
+ drive
2077
+ );
2078
+ const limit = options2?.limit;
2079
+ let operationsCount = 0;
2080
+ const tasks = syncUnits.map((syncUnit) => async () => {
2081
+ if (limit && operationsCount >= limit) {
2082
+ return;
2083
+ }
2084
+ if (syncUnit.revision < 0) {
2085
+ return;
2086
+ }
2087
+ const entry = listener.syncUnits.get(syncUnit.syncId);
2088
+ if (entry && entry.listenerRev >= syncUnit.revision) {
2089
+ return;
2090
+ }
2091
+ const { documentId, driveId: driveId2, scope, branch } = syncUnit;
2092
+ try {
2093
+ const operations = await this.drive.getOperationData(
2094
+ // DEAL WITH INVALID SYNC ID ERROR
2095
+ driveId2,
2096
+ syncUnit.syncId,
2097
+ {
2098
+ since: options2?.since,
2099
+ fromRevision: options2?.fromRevision ?? entry?.listenerRev,
2100
+ limit: limit ? limit - operationsCount : void 0
2101
+ },
2102
+ drive
2103
+ );
2104
+ if (!operations.length) {
2105
+ return;
2106
+ }
2107
+ operationsCount += operations.length;
2108
+ strands.push({
2109
+ driveId: driveId2,
2110
+ documentId,
2111
+ scope,
2112
+ branch,
2113
+ operations
2114
+ });
2115
+ } catch (error) {
2116
+ logger.error(error);
2117
+ return;
2118
+ }
2119
+ });
2120
+ if (this.options.sequentialUpdates) {
2121
+ for (const task of tasks) {
2122
+ await task();
2123
+ }
2124
+ } else {
2125
+ await Promise.all(tasks.map((task) => task()));
2126
+ }
2127
+ return strands;
2128
+ }
2129
+ };
2130
+
2131
+ // ../document-drive/src/server/index.ts
2132
+ var PULL_DRIVE_INTERVAL = 5e3;
2133
+ var BaseDocumentDriveServer = class extends AbstractDocumentDriveServer {
2134
+ emitter = createNanoEvents();
2135
+ cache;
2136
+ documentModels;
2137
+ storage;
2138
+ listenerStateManager;
2139
+ triggerMap = /* @__PURE__ */ new Map();
2140
+ syncStatus = /* @__PURE__ */ new Map();
2141
+ queueManager;
2142
+ initializePromise;
2143
+ defaultDrivesManager;
2144
+ options;
2145
+ constructor(documentModels, storage = new MemoryStorage(), cache = new memory_default(), queueManager = new BaseQueueManager(), options2) {
2146
+ super();
2147
+ this.options = {
2148
+ ...options2,
2149
+ defaultDrives: {
2150
+ ...options2?.defaultDrives
2151
+ },
2152
+ listenerManager: {
2153
+ ...DefaultListenerManagerOptions,
2154
+ ...options2?.listenerManager
2155
+ },
2156
+ taskQueueMethod: options2?.taskQueueMethod === void 0 ? RunAsap.runAsap : options2.taskQueueMethod
2157
+ };
2158
+ this.listenerStateManager = new ListenerManager(
2159
+ this,
2160
+ void 0,
2161
+ options2?.listenerManager
2162
+ );
2163
+ this.documentModels = documentModels;
2164
+ this.storage = storage;
2165
+ this.cache = cache;
2166
+ this.queueManager = queueManager;
2167
+ this.defaultDrivesManager = new DefaultDrivesManager(
2168
+ this,
2169
+ this.defaultDrivesManagerDelegate,
2170
+ options2
2171
+ );
2172
+ this.storage.setStorageDelegate?.({
2173
+ getCachedOperations: async (drive, id) => {
2174
+ try {
2175
+ const document = await this.cache.getDocument(drive, id);
2176
+ return document?.operations;
2177
+ } catch (error) {
2178
+ logger.error(error);
2179
+ return void 0;
2180
+ }
2181
+ }
2182
+ });
2183
+ this.initializePromise = this._initialize();
2184
+ }
2185
+ setDocumentModels(models) {
2186
+ this.documentModels = [...models];
2187
+ this.emit("documentModels", [...models]);
2188
+ }
2189
+ initializeDefaultRemoteDrives() {
2190
+ return this.defaultDrivesManager.initializeDefaultRemoteDrives();
2191
+ }
2192
+ getDefaultRemoteDrives() {
2193
+ return this.defaultDrivesManager.getDefaultRemoteDrives();
2194
+ }
2195
+ setDefaultDriveAccessLevel(url, level) {
2196
+ return this.defaultDrivesManager.setDefaultDriveAccessLevel(url, level);
2197
+ }
2198
+ setAllDefaultDrivesAccessLevel(level) {
2199
+ return this.defaultDrivesManager.setAllDefaultDrivesAccessLevel(level);
2200
+ }
2201
+ getOperationSource(source) {
2202
+ return source.type === "local" ? "push" : "pull";
2203
+ }
2204
+ getCombinedSyncUnitStatus(syncUnitStatus) {
2205
+ if (!syncUnitStatus.pull && !syncUnitStatus.push) return "INITIAL_SYNC";
2206
+ if (syncUnitStatus.pull === "INITIAL_SYNC") return "INITIAL_SYNC";
2207
+ if (syncUnitStatus.push === "INITIAL_SYNC")
2208
+ return syncUnitStatus.pull || "INITIAL_SYNC";
2209
+ const order = [
2210
+ "ERROR",
2211
+ "MISSING",
2212
+ "CONFLICT",
2213
+ "SYNCING",
2214
+ "SUCCESS"
2215
+ ];
2216
+ const sortedStatus = Object.values(syncUnitStatus).sort(
2217
+ (a, b) => order.indexOf(a) - order.indexOf(b)
2218
+ );
2219
+ return sortedStatus[0];
2220
+ }
2221
+ initSyncStatus(syncUnitId, status) {
2222
+ const defaultSyncUnitStatus = Object.entries(
2223
+ status
2224
+ ).reduce((acc, [key, _status]) => {
2225
+ return {
2226
+ ...acc,
2227
+ [key]: _status !== "SYNCING" ? _status : "INITIAL_SYNC"
2228
+ };
2229
+ }, {});
2230
+ this.syncStatus.set(syncUnitId, defaultSyncUnitStatus);
2231
+ this.emit(
2232
+ "syncStatus",
2233
+ syncUnitId,
2234
+ this.getCombinedSyncUnitStatus(defaultSyncUnitStatus),
2235
+ void 0,
2236
+ defaultSyncUnitStatus
2237
+ );
2238
+ }
2239
+ async initializeDriveSyncStatus(driveId, drive) {
2240
+ const syncUnits = await this.getSynchronizationUnitsIds(driveId);
2241
+ const syncStatus = {
2242
+ pull: drive.state.local.triggers.length > 0 ? "INITIAL_SYNC" : void 0,
2243
+ push: drive.state.local.listeners.length > 0 ? "SUCCESS" : void 0
2244
+ };
2245
+ if (!syncStatus.pull && !syncStatus.push) return;
2246
+ const syncUnitsIds = [driveId, ...syncUnits.map((s) => s.syncId)];
2247
+ for (const syncUnitId of syncUnitsIds) {
2248
+ this.initSyncStatus(syncUnitId, syncStatus);
2249
+ }
2250
+ }
2251
+ updateSyncUnitStatus(syncUnitId, status, error) {
2252
+ if (status === null) {
2253
+ this.syncStatus.delete(syncUnitId);
2254
+ return;
2255
+ }
2256
+ const syncUnitStatus = this.syncStatus.get(syncUnitId);
2257
+ if (!syncUnitStatus) {
2258
+ this.initSyncStatus(syncUnitId, status);
2259
+ return;
2260
+ }
2261
+ const shouldUpdateStatus = Object.entries(status).some(
2262
+ ([key, _status]) => syncUnitStatus[key] !== _status
2263
+ );
2264
+ if (shouldUpdateStatus) {
2265
+ const newstatus = Object.entries(status).reduce((acc, [key, _status]) => {
2266
+ return {
2267
+ ...acc,
2268
+ // do not replace initial_syncing if it has not finished yet
2269
+ [key]: acc[key] === "INITIAL_SYNC" && _status === "SYNCING" ? "INITIAL_SYNC" : _status
2270
+ };
2271
+ }, syncUnitStatus);
2272
+ const previousCombinedStatus = this.getCombinedSyncUnitStatus(syncUnitStatus);
2273
+ const newCombinedStatus = this.getCombinedSyncUnitStatus(newstatus);
2274
+ this.syncStatus.set(syncUnitId, newstatus);
2275
+ if (previousCombinedStatus !== newCombinedStatus) {
2276
+ this.emit(
2277
+ "syncStatus",
2278
+ syncUnitId,
2279
+ this.getCombinedSyncUnitStatus(newstatus),
2280
+ error,
2281
+ newstatus
2282
+ );
2283
+ }
2284
+ }
2285
+ }
2286
+ async saveStrand(strand, source) {
2287
+ const operations = strand.operations.map((op) => ({
2288
+ ...op,
2289
+ scope: strand.scope,
2290
+ branch: strand.branch
2291
+ }));
2292
+ const result = await (!strand.documentId ? this.queueDriveOperations(
2293
+ strand.driveId,
2294
+ operations,
2295
+ { source }
2296
+ ) : this.queueOperations(strand.driveId, strand.documentId, operations, {
2297
+ source
2298
+ }));
2299
+ if (result.status === "ERROR") {
2300
+ const syncUnits = strand.documentId !== "" ? (await this.getSynchronizationUnitsIds(
2301
+ strand.driveId,
2302
+ [strand.documentId],
2303
+ [strand.scope],
2304
+ [strand.branch]
2305
+ )).map((s) => s.syncId) : [strand.driveId];
2306
+ const operationSource = this.getOperationSource(source);
2307
+ for (const syncUnit of syncUnits) {
2308
+ this.updateSyncUnitStatus(
2309
+ syncUnit,
2310
+ { [operationSource]: result.status },
2311
+ result.error
2312
+ );
2313
+ }
2314
+ }
2315
+ this.emit("strandUpdate", strand);
2316
+ return result;
2317
+ }
2318
+ handleListenerError(error, driveId, listener) {
2319
+ logger.error(
2320
+ `Listener ${listener.listener.label ?? listener.listener.listenerId} error:`,
2321
+ error
2322
+ );
2323
+ const status = error instanceof OperationError ? error.status : "ERROR";
2324
+ this.updateSyncUnitStatus(driveId, { push: status }, error);
2325
+ }
2326
+ shouldSyncRemoteDrive(drive) {
2327
+ return drive.state.local.availableOffline && drive.state.local.triggers.length > 0;
2328
+ }
2329
+ async startSyncRemoteDrive(driveId) {
2330
+ const drive = await this.getDrive(driveId);
2331
+ let driveTriggers = this.triggerMap.get(driveId);
2332
+ const syncUnits = await this.getSynchronizationUnitsIds(
2333
+ driveId,
2334
+ void 0,
2335
+ void 0,
2336
+ void 0,
2337
+ void 0,
2338
+ drive
2339
+ );
2340
+ for (const trigger of drive.state.local.triggers) {
2341
+ if (driveTriggers?.get(trigger.id)) {
2342
+ continue;
2343
+ }
2344
+ if (!driveTriggers) {
2345
+ driveTriggers = /* @__PURE__ */ new Map();
2346
+ }
2347
+ this.updateSyncUnitStatus(driveId, { pull: "SYNCING" });
2348
+ for (const syncUnit of syncUnits) {
2349
+ this.updateSyncUnitStatus(syncUnit.syncId, { pull: "SYNCING" });
2350
+ }
2351
+ if (PullResponderTransmitter.isPullResponderTrigger(trigger)) {
2352
+ let firstPull = true;
2353
+ const cancelPullLoop = PullResponderTransmitter.setupPull(
2354
+ driveId,
2355
+ trigger,
2356
+ this.saveStrand.bind(this),
2357
+ (error) => {
2358
+ const statusError = error instanceof OperationError ? error.status : "ERROR";
2359
+ this.updateSyncUnitStatus(driveId, { pull: statusError }, error);
2360
+ if (error instanceof ClientError) {
2361
+ this.emit(
2362
+ "clientStrandsError",
2363
+ driveId,
2364
+ trigger,
2365
+ error.response.status,
2366
+ error.message
2367
+ );
2368
+ }
2369
+ },
2370
+ (revisions) => {
2371
+ const errorRevision = revisions.filter(
2372
+ (r) => r.status !== "SUCCESS"
2373
+ );
2374
+ if (errorRevision.length < 1) {
2375
+ this.updateSyncUnitStatus(driveId, {
2376
+ pull: "SUCCESS"
2377
+ });
2378
+ }
2379
+ const documentIdsFromRevision = revisions.filter((rev) => rev.documentId !== "").map((rev) => rev.documentId);
2380
+ this.getSynchronizationUnitsIds(driveId, documentIdsFromRevision).then((revSyncUnits) => {
2381
+ for (const syncUnit of revSyncUnits) {
2382
+ const fileErrorRevision = errorRevision.find(
2383
+ (r) => r.documentId === syncUnit.documentId
2384
+ );
2385
+ if (fileErrorRevision) {
2386
+ this.updateSyncUnitStatus(
2387
+ syncUnit.syncId,
2388
+ { pull: fileErrorRevision.status },
2389
+ fileErrorRevision.error
2390
+ );
2391
+ } else {
2392
+ this.updateSyncUnitStatus(syncUnit.syncId, {
2393
+ pull: "SUCCESS"
2394
+ });
2395
+ }
2396
+ }
2397
+ }).catch(console.error);
2398
+ if (firstPull) {
2399
+ firstPull = false;
2400
+ const pushListener = drive.state.local.listeners.find(
2401
+ (listener) => trigger.data.url === listener.callInfo?.data
2402
+ );
2403
+ if (pushListener) {
2404
+ this.getSynchronizationUnitsRevision(driveId, syncUnits).then((syncUnitRevisions) => {
2405
+ for (const revision of syncUnitRevisions) {
2406
+ this.listenerStateManager.updateListenerRevision(
2407
+ pushListener.listenerId,
2408
+ driveId,
2409
+ revision.syncId,
2410
+ revision.revision
2411
+ ).catch(logger.error);
2412
+ }
2413
+ }).catch(logger.error);
2414
+ }
2415
+ }
2416
+ }
2417
+ );
2418
+ driveTriggers.set(trigger.id, cancelPullLoop);
2419
+ this.triggerMap.set(driveId, driveTriggers);
2420
+ }
2421
+ }
2422
+ }
2423
+ async stopSyncRemoteDrive(driveId) {
2424
+ const syncUnits = await this.getSynchronizationUnitsIds(driveId);
2425
+ const filesNodeSyncId = syncUnits.filter((syncUnit) => syncUnit.documentId !== "").map((syncUnit) => syncUnit.syncId);
2426
+ const triggers = this.triggerMap.get(driveId);
2427
+ triggers?.forEach((cancel) => cancel());
2428
+ this.updateSyncUnitStatus(driveId, null);
2429
+ for (const fileNodeSyncId of filesNodeSyncId) {
2430
+ this.updateSyncUnitStatus(fileNodeSyncId, null);
2431
+ }
2432
+ return this.triggerMap.delete(driveId);
2433
+ }
2434
+ defaultDrivesManagerDelegate = {
2435
+ detachDrive: this.detachDrive.bind(this),
2436
+ emit: (...args) => this.emit("defaultRemoteDrive", ...args)
2437
+ };
2438
+ queueDelegate = {
2439
+ checkDocumentExists: (driveId, documentId) => this.storage.checkDocumentExists(driveId, documentId),
2440
+ processOperationJob: async ({
2441
+ driveId,
2442
+ documentId,
2443
+ operations,
2444
+ options: options2
2445
+ }) => {
2446
+ return documentId ? this.addOperations(driveId, documentId, operations, options2) : this.addDriveOperations(
2447
+ driveId,
2448
+ operations,
2449
+ options2
2450
+ );
2451
+ },
2452
+ processActionJob: async ({
2453
+ driveId,
2454
+ documentId,
2455
+ actions: actions2,
2456
+ options: options2
2457
+ }) => {
2458
+ return documentId ? this.addActions(driveId, documentId, actions2, options2) : this.addDriveActions(
2459
+ driveId,
2460
+ actions2,
2461
+ options2
2462
+ );
2463
+ },
2464
+ processJob: async (job) => {
2465
+ if (isOperationJob(job)) {
2466
+ return this.queueDelegate.processOperationJob(job);
2467
+ } else if (isActionJob(job)) {
2468
+ return this.queueDelegate.processActionJob(job);
2469
+ } else {
2470
+ throw new Error("Unknown job type", job);
2471
+ }
2472
+ }
2473
+ };
2474
+ initialize() {
2475
+ return this.initializePromise;
2476
+ }
2477
+ async _initialize() {
2478
+ await this.queueManager.init(this.queueDelegate, (error) => {
2479
+ logger.error(`Error initializing queue manager`, error);
2480
+ errors.push(error);
2481
+ });
2482
+ try {
2483
+ await this.defaultDrivesManager.removeOldremoteDrives();
2484
+ } catch (error) {
2485
+ logger.error(error);
2486
+ }
2487
+ const errors = [];
2488
+ const drives = await this.getDrives();
2489
+ for (const drive of drives) {
2490
+ await this._initializeDrive(drive).catch((error) => {
2491
+ logger.error(`Error initializing drive ${drive}`, error);
2492
+ errors.push(error);
2493
+ });
2494
+ }
2495
+ if (this.options.defaultDrives.loadOnInit !== false) {
2496
+ await this.defaultDrivesManager.initializeDefaultRemoteDrives();
2497
+ }
2498
+ if (typeof window !== "undefined") {
2499
+ window.addEventListener("online", () => {
2500
+ this.listenerStateManager.triggerUpdate(
2501
+ false,
2502
+ { type: "local" },
2503
+ this.handleListenerError.bind(this)
2504
+ ).catch((error) => {
2505
+ logger.error("Non handled error updating listeners", error);
2506
+ });
2507
+ });
2508
+ }
2509
+ return errors.length === 0 ? null : errors;
2510
+ }
2511
+ async _initializeDrive(driveId) {
2512
+ const drive = await this.getDrive(driveId);
2513
+ await this.initializeDriveSyncStatus(driveId, drive);
2514
+ if (this.shouldSyncRemoteDrive(drive)) {
2515
+ await this.startSyncRemoteDrive(driveId);
2516
+ }
2517
+ await this.listenerStateManager.initDrive(drive);
2518
+ }
2519
+ async getSynchronizationUnits(driveId, documentId, scope, branch, documentType, loadedDrive) {
2520
+ const drive = loadedDrive || await this.getDrive(driveId);
2521
+ const synchronizationUnitsQuery = await this.getSynchronizationUnitsIds(
2522
+ driveId,
2523
+ documentId,
2524
+ scope,
2525
+ branch,
2526
+ documentType,
2527
+ drive
2528
+ );
2529
+ return this.getSynchronizationUnitsRevision(
2530
+ driveId,
2531
+ synchronizationUnitsQuery,
2532
+ drive
2533
+ );
2534
+ }
2535
+ async getSynchronizationUnitsRevision(driveId, syncUnitsQuery, loadedDrive) {
2536
+ const drive = loadedDrive || await this.getDrive(driveId);
2537
+ const revisions = await this.storage.getSynchronizationUnitsRevision(syncUnitsQuery);
2538
+ const synchronizationUnits = syncUnitsQuery.map(
2539
+ (s) => ({
2540
+ ...s,
2541
+ lastUpdated: drive.created,
2542
+ revision: -1
2543
+ })
2544
+ );
2545
+ for (const revision of revisions) {
2546
+ const syncUnit = synchronizationUnits.find(
2547
+ (s) => revision.driveId === s.driveId && revision.documentId === s.documentId && revision.scope === s.scope && revision.branch === s.branch
2548
+ );
2549
+ if (syncUnit) {
2550
+ syncUnit.revision = revision.revision;
2551
+ syncUnit.lastUpdated = revision.lastUpdated;
2552
+ }
2553
+ }
2554
+ return synchronizationUnits;
2555
+ }
2556
+ async getSynchronizationUnitsIds(driveId, documentId, scope, branch, documentType, loadedDrive) {
2557
+ const drive = loadedDrive ?? await this.getDrive(driveId);
2558
+ const nodes = drive.state.global.nodes.filter(
2559
+ (node) => isFileNode(node) && (!documentId?.length || documentId.includes(node.id) || documentId.includes("*")) && (!documentType?.length || documentType.includes(node.documentType) || documentType.includes("*"))
2560
+ );
2561
+ if ((!documentId || documentId.includes("*") || documentId.includes("")) && (!documentType?.length || documentType.includes("powerhouse/document-drive") || documentType.includes("*"))) {
2562
+ nodes.unshift({
2563
+ id: "",
2564
+ documentType: "powerhouse/document-drive",
2565
+ synchronizationUnits: [
2566
+ {
2567
+ syncId: "0",
2568
+ scope: "global",
2569
+ branch: "main"
2570
+ }
2571
+ ]
2572
+ });
2573
+ }
2574
+ const synchronizationUnitsQuery = [];
2575
+ for (const node of nodes) {
2576
+ const nodeUnits = scope?.length || branch?.length ? node.synchronizationUnits.filter(
2577
+ (unit) => (!scope?.length || scope.includes(unit.scope) || scope.includes("*")) && (!branch?.length || branch.includes(unit.branch) || branch.includes("*"))
2578
+ ) : node.synchronizationUnits;
2579
+ if (!nodeUnits.length) {
2580
+ continue;
2581
+ }
2582
+ synchronizationUnitsQuery.push(
2583
+ ...nodeUnits.map((n) => ({
2584
+ driveId,
2585
+ documentId: node.id,
2586
+ syncId: n.syncId,
2587
+ documentType: node.documentType,
2588
+ scope: n.scope,
2589
+ branch: n.branch
2590
+ }))
2591
+ );
2592
+ }
2593
+ return synchronizationUnitsQuery;
2594
+ }
2595
+ async getSynchronizationUnitIdInfo(driveId, syncId, loadedDrive) {
2596
+ const drive = loadedDrive || await this.getDrive(driveId);
2597
+ const node = drive.state.global.nodes.find(
2598
+ (node2) => isFileNode(node2) && node2.synchronizationUnits.find((unit) => unit.syncId === syncId)
2599
+ );
2600
+ if (!node || !isFileNode(node)) {
2601
+ return void 0;
2602
+ }
2603
+ const syncUnit = node.synchronizationUnits.find(
2604
+ (unit) => unit.syncId === syncId
2605
+ );
2606
+ if (!syncUnit) {
2607
+ return void 0;
2608
+ }
2609
+ return {
2610
+ syncId,
2611
+ scope: syncUnit.scope,
2612
+ branch: syncUnit.branch,
2613
+ driveId,
2614
+ documentId: node.id,
2615
+ documentType: node.documentType
2616
+ };
2617
+ }
2618
+ async getSynchronizationUnit(driveId, syncId, loadedDrive) {
2619
+ const syncUnit = await this.getSynchronizationUnitIdInfo(
2620
+ driveId,
2621
+ syncId,
2622
+ loadedDrive
2623
+ );
2624
+ if (!syncUnit) {
2625
+ return void 0;
2626
+ }
2627
+ const { scope, branch, documentId, documentType } = syncUnit;
2628
+ const document = await this.getDocument(driveId, documentId);
2629
+ const operations = document.operations[scope] ?? [];
2630
+ const lastOperation = operations[operations.length - 1];
2631
+ return {
2632
+ syncId,
2633
+ scope,
2634
+ branch,
2635
+ driveId,
2636
+ documentId,
2637
+ documentType,
2638
+ lastUpdated: lastOperation?.timestamp ?? document.lastModified,
2639
+ revision: lastOperation?.index ?? 0
2640
+ };
2641
+ }
2642
+ async getOperationData(driveId, syncId, filter, loadedDrive) {
2643
+ const syncUnit = syncId === "0" ? { documentId: "", scope: "global" } : await this.getSynchronizationUnitIdInfo(driveId, syncId, loadedDrive);
2644
+ if (!syncUnit) {
2645
+ throw new Error(`Invalid Sync Id ${syncId} in drive ${driveId}`);
2646
+ }
2647
+ const document = syncId === "0" ? loadedDrive || await this.getDrive(driveId) : await this.getDocument(driveId, syncUnit.documentId);
2648
+ const operations = document.operations[syncUnit.scope] ?? [];
2649
+ const filteredOperations = operations.filter(
2650
+ (operation) => Object.keys(filter).length === 0 || (filter.since === void 0 || isBefore(filter.since, operation.timestamp)) && (filter.fromRevision === void 0 || operation.index > filter.fromRevision)
2651
+ );
2652
+ const limitedOperations = filter.limit ? filteredOperations.slice(0, filter.limit) : filteredOperations;
2653
+ return limitedOperations.map((operation) => ({
2654
+ hash: operation.hash,
2655
+ index: operation.index,
2656
+ timestamp: operation.timestamp,
2657
+ type: operation.type,
2658
+ input: operation.input,
2659
+ skip: operation.skip,
2660
+ context: operation.context,
2661
+ id: operation.id
2662
+ }));
2663
+ }
2664
+ getDocumentModel(documentType) {
2665
+ const documentModel2 = this.documentModels.find(
2666
+ (model) => model.documentModel.id === documentType
2667
+ );
2668
+ if (!documentModel2) {
2669
+ throw new Error(`Document type ${documentType} not supported`);
2670
+ }
2671
+ return documentModel2;
2672
+ }
2673
+ getDocumentModels() {
2674
+ return [...this.documentModels];
2675
+ }
2676
+ async addDrive(drive) {
2677
+ const id = drive.global.id || generateUUID();
2678
+ if (!id) {
2679
+ throw new Error("Invalid Drive Id");
2680
+ }
2681
+ const drives = await this.storage.getDrives();
2682
+ if (drives.includes(id)) {
2683
+ throw new DriveAlreadyExistsError(id);
2684
+ }
2685
+ const document = utils$1.createDocument({
2686
+ state: drive
2687
+ });
2688
+ await this.storage.createDrive(id, document);
2689
+ if (drive.global.slug) {
2690
+ await this.cache.deleteDocument("drives-slug", drive.global.slug);
2691
+ }
2692
+ await this._initializeDrive(id);
2693
+ this.emit("driveAdded", document);
2694
+ return document;
2695
+ }
2696
+ async addRemoteDrive(url, options2) {
2697
+ const { id, name, slug, icon } = options2.expectedDriveInfo || await requestPublicDrive(url);
2698
+ const {
2699
+ pullFilter,
2700
+ pullInterval,
2701
+ availableOffline,
2702
+ sharingType,
2703
+ listeners,
2704
+ triggers
2705
+ } = options2;
2706
+ const pullTrigger = await PullResponderTransmitter.createPullResponderTrigger(id, url, {
2707
+ pullFilter,
2708
+ pullInterval
2709
+ });
2710
+ return await this.addDrive({
2711
+ global: {
2712
+ id,
2713
+ name,
2714
+ slug,
2715
+ icon: icon ?? null
2716
+ },
2717
+ local: {
2718
+ triggers: [...triggers, pullTrigger],
2719
+ listeners,
2720
+ availableOffline,
2721
+ sharingType
2722
+ }
2723
+ });
2724
+ }
2725
+ async registerPullResponderTrigger(id, url, options2) {
2726
+ const pullTrigger = await PullResponderTransmitter.createPullResponderTrigger(
2727
+ id,
2728
+ url,
2729
+ options2
2730
+ );
2731
+ return pullTrigger;
2732
+ }
2733
+ async deleteDrive(id) {
2734
+ const result = await Promise.allSettled([
2735
+ this.stopSyncRemoteDrive(id),
2736
+ this.listenerStateManager.removeDrive(id),
2737
+ this.cache.deleteDocument("drives", id),
2738
+ this.storage.deleteDrive(id)
2739
+ ]);
2740
+ result.forEach((r) => {
2741
+ if (r.status === "rejected") {
2742
+ throw r.reason;
2743
+ }
2744
+ });
2745
+ }
2746
+ getDrives() {
2747
+ return this.storage.getDrives();
2748
+ }
2749
+ async getDrive(drive, options2) {
2750
+ try {
2751
+ const document2 = await this.cache.getDocument("drives", drive);
2752
+ if (document2 && isDocumentDrive(document2)) {
2753
+ return document2;
2754
+ }
2755
+ } catch (e) {
2756
+ logger.error("Error getting drive from cache", e);
2757
+ }
2758
+ const driveStorage = await this.storage.getDrive(drive);
2759
+ const document = this._buildDocument(driveStorage, options2);
2760
+ if (!isDocumentDrive(document)) {
2761
+ throw new Error(`Document with id ${drive} is not a Document Drive`);
2762
+ } else {
2763
+ this.cache.setDocument("drives", drive, document).catch(logger.error);
2764
+ return document;
2765
+ }
2766
+ }
2767
+ async getDriveBySlug(slug, options2) {
2768
+ try {
2769
+ const document2 = await this.cache.getDocument("drives-slug", slug);
2770
+ if (document2 && isDocumentDrive(document2)) {
2771
+ return document2;
2772
+ }
2773
+ } catch (e) {
2774
+ logger.error("Error getting drive from cache", e);
2775
+ }
2776
+ const driveStorage = await this.storage.getDriveBySlug(slug);
2777
+ const document = this._buildDocument(driveStorage, options2);
2778
+ if (!isDocumentDrive(document)) {
2779
+ throw new Error(`Document with slug ${slug} is not a Document Drive`);
2780
+ } else {
2781
+ this.cache.setDocument("drives-slug", slug, document).catch(logger.error);
2782
+ return document;
2783
+ }
2784
+ }
2785
+ async getDocument(drive, id, options2) {
2786
+ try {
2787
+ const document2 = await this.cache.getDocument(drive, id);
2788
+ if (document2) {
2789
+ return document2;
2790
+ }
2791
+ } catch (e) {
2792
+ logger.error("Error getting document from cache", e);
2793
+ }
2794
+ const documentStorage = await this.storage.getDocument(drive, id);
2795
+ const document = this._buildDocument(documentStorage, options2);
2796
+ this.cache.setDocument(drive, id, document).catch(logger.error);
2797
+ return document;
2798
+ }
2799
+ getDocuments(drive) {
2800
+ return this.storage.getDocuments(drive);
2801
+ }
2802
+ async createDocument(driveId, input) {
2803
+ let state = void 0;
2804
+ if (input.document) {
2805
+ if (input.documentType !== input.document.documentType) {
2806
+ throw new Error(`Provided document is not ${input.documentType}`);
2807
+ }
2808
+ const doc = this._buildDocument(input.document);
2809
+ state = doc.state;
2810
+ }
2811
+ const document = input.document ?? this.getDocumentModel(input.documentType).utils.createDocument();
2812
+ const documentStorage = {
2813
+ name: document.name,
2814
+ revision: document.revision,
2815
+ documentType: document.documentType,
2816
+ created: document.created,
2817
+ lastModified: document.lastModified,
2818
+ operations: { global: [], local: [] },
2819
+ initialState: document.initialState,
2820
+ clipboard: [],
2821
+ state: state ?? document.state
2822
+ };
2823
+ await this.storage.createDocument(driveId, input.id, documentStorage);
2824
+ for (const syncUnit of input.synchronizationUnits) {
2825
+ this.initSyncStatus(syncUnit.syncId, {
2826
+ pull: this.triggerMap.get(driveId) ? "INITIAL_SYNC" : void 0,
2827
+ push: this.listenerStateManager.driveHasListeners(driveId) ? "SUCCESS" : void 0
2828
+ });
2829
+ }
2830
+ const operations = Object.values(document.operations).flat();
2831
+ if (operations.length) {
2832
+ if (isDocumentDrive(document)) {
2833
+ await this.storage.addDriveOperations(
2834
+ driveId,
2835
+ operations,
2836
+ document
2837
+ );
2838
+ } else {
2839
+ await this.storage.addDocumentOperations(
2840
+ driveId,
2841
+ input.id,
2842
+ operations,
2843
+ document
2844
+ );
2845
+ }
2846
+ }
2847
+ return document;
2848
+ }
2849
+ async deleteDocument(driveId, id) {
2850
+ try {
2851
+ const syncUnits = await this.getSynchronizationUnitsIds(driveId, [id]);
2852
+ for (const syncUnit of syncUnits) {
2853
+ this.updateSyncUnitStatus(syncUnit.syncId, null);
2854
+ }
2855
+ await this.listenerStateManager.removeSyncUnits(driveId, syncUnits);
2856
+ } catch (error) {
2857
+ logger.warn("Error deleting document", error);
2858
+ }
2859
+ await this.cache.deleteDocument(driveId, id);
2860
+ return this.storage.deleteDocument(driveId, id);
2861
+ }
2862
+ async _processOperations(drive, documentId, documentStorage, operations) {
2863
+ const operationsApplied = [];
2864
+ const signals = [];
2865
+ const documentStorageWithState = await this._addDocumentResultingStage(
2866
+ documentStorage,
2867
+ drive,
2868
+ documentId
2869
+ );
2870
+ let document = this._buildDocument(documentStorageWithState);
2871
+ let error;
2872
+ const operationsByScope = groupOperationsByScope(operations);
2873
+ for (const scope of Object.keys(operationsByScope)) {
2874
+ const storageDocumentOperations = documentStorage.operations[scope];
2875
+ const branch = removeExistingOperations(
2876
+ operationsByScope[scope] || [],
2877
+ storageDocumentOperations
2878
+ );
2879
+ if (branch.length < 1) {
2880
+ continue;
2881
+ }
2882
+ const trunk = garbageCollect(sortOperations(storageDocumentOperations));
2883
+ const [invertedTrunk, tail] = attachBranch(trunk, branch);
2884
+ const newHistory = tail.length < 1 ? invertedTrunk : merge(trunk, invertedTrunk, reshuffleByTimestamp);
2885
+ const newOperations = newHistory.filter(
2886
+ (op) => trunk.length < 1 || precedes(trunk[trunk.length - 1], op)
2887
+ );
2888
+ for (const nextOperation of newOperations) {
2889
+ let skipHashValidation = false;
2890
+ if (tail.length > 0) {
2891
+ const sourceOperation = operations.find(
2892
+ (op) => op.hash === nextOperation.hash
2893
+ );
2894
+ skipHashValidation = !sourceOperation || sourceOperation.index !== nextOperation.index || sourceOperation.skip !== nextOperation.skip;
2895
+ }
2896
+ try {
2897
+ const taskQueueMethod = this.options.taskQueueMethod;
2898
+ const task = () => this._performOperation(
2899
+ drive,
2900
+ documentId,
2901
+ document,
2902
+ nextOperation,
2903
+ skipHashValidation
2904
+ );
2905
+ const appliedResult = await (taskQueueMethod ? runAsapAsync(task, taskQueueMethod) : task());
2906
+ document = appliedResult.document;
2907
+ signals.push(...appliedResult.signals);
2908
+ operationsApplied.push(appliedResult.operation);
2909
+ } catch (e) {
2910
+ error = e instanceof OperationError ? e : new OperationError(
2911
+ "ERROR",
2912
+ nextOperation,
2913
+ e.message,
2914
+ e.cause
2915
+ );
2916
+ break;
2917
+ }
2918
+ }
2919
+ }
2920
+ return {
2921
+ document,
2922
+ operationsApplied,
2923
+ signals,
2924
+ error
2925
+ };
2926
+ }
2927
+ async _addDocumentResultingStage(document, drive, documentId, options2) {
2928
+ const operations = options2?.revisions !== void 0 ? filterOperationsByRevision(document.operations, options2.revisions) : document.operations;
2929
+ const documentOperations = utils.documentHelpers.garbageCollectDocumentOperations(
2930
+ operations
2931
+ );
2932
+ for (const scope of Object.keys(documentOperations)) {
2933
+ const lastRemainingOperation = documentOperations[scope].at(-1);
2934
+ if (lastRemainingOperation && !lastRemainingOperation.resultingState) {
2935
+ lastRemainingOperation.resultingState = await (documentId ? this.storage.getOperationResultingState?.(
2936
+ drive,
2937
+ documentId,
2938
+ lastRemainingOperation.index,
2939
+ lastRemainingOperation.scope,
2940
+ "main"
2941
+ ) : this.storage.getDriveOperationResultingState?.(
2942
+ drive,
2943
+ lastRemainingOperation.index,
2944
+ lastRemainingOperation.scope,
2945
+ "main"
2946
+ ));
2947
+ }
2948
+ }
2949
+ return {
2950
+ ...document,
2951
+ operations: documentOperations
2952
+ };
2953
+ }
2954
+ _buildDocument(documentStorage, options2) {
2955
+ if (documentStorage.state && (!options2 || options2.checkHashes === false)) {
2956
+ return documentStorage;
2957
+ }
2958
+ const documentModel2 = this.getDocumentModel(documentStorage.documentType);
2959
+ const revisionOperations = options2?.revisions !== void 0 ? filterOperationsByRevision(
2960
+ documentStorage.operations,
2961
+ options2.revisions
2962
+ ) : documentStorage.operations;
2963
+ const operations = utils.documentHelpers.garbageCollectDocumentOperations(
2964
+ revisionOperations
2965
+ );
2966
+ return utils.replayDocument(
2967
+ documentStorage.initialState,
2968
+ operations,
2969
+ documentModel2.reducer,
2970
+ void 0,
2971
+ documentStorage,
2972
+ void 0,
2973
+ {
2974
+ ...options2,
2975
+ checkHashes: options2?.checkHashes ?? true,
2976
+ reuseOperationResultingState: options2?.checkHashes ?? true
2977
+ }
2978
+ );
2979
+ }
2980
+ async _performOperation(drive, id, document, operation, skipHashValidation = false) {
2981
+ const documentModel2 = this.getDocumentModel(document.documentType);
2982
+ const signalResults = [];
2983
+ let newDocument = document;
2984
+ const scope = operation.scope;
2985
+ const documentOperations = utils.documentHelpers.garbageCollectDocumentOperations({
2986
+ ...document.operations,
2987
+ [scope]: utils.documentHelpers.skipHeaderOperations(
2988
+ document.operations[scope],
2989
+ operation
2990
+ )
2991
+ });
2992
+ const lastRemainingOperation = documentOperations[scope].at(-1);
2993
+ if (lastRemainingOperation && !lastRemainingOperation.resultingState) {
2994
+ lastRemainingOperation.resultingState = await (id ? this.storage.getOperationResultingState?.(
2995
+ drive,
2996
+ id,
2997
+ lastRemainingOperation.index,
2998
+ lastRemainingOperation.scope,
2999
+ "main"
3000
+ ) : this.storage.getDriveOperationResultingState?.(
3001
+ drive,
3002
+ lastRemainingOperation.index,
3003
+ lastRemainingOperation.scope,
3004
+ "main"
3005
+ ));
3006
+ }
3007
+ const operationSignals = [];
3008
+ newDocument = documentModel2.reducer(
3009
+ newDocument,
3010
+ operation,
3011
+ (signal) => {
3012
+ let handler = void 0;
3013
+ switch (signal.type) {
3014
+ case "CREATE_CHILD_DOCUMENT":
3015
+ handler = () => this.createDocument(drive, signal.input);
3016
+ break;
3017
+ case "DELETE_CHILD_DOCUMENT":
3018
+ handler = () => this.deleteDocument(drive, signal.input.id);
3019
+ break;
3020
+ case "COPY_CHILD_DOCUMENT":
3021
+ handler = () => this.getDocument(drive, signal.input.id).then(
3022
+ (documentToCopy) => this.createDocument(drive, {
3023
+ id: signal.input.newId,
3024
+ documentType: documentToCopy.documentType,
3025
+ document: documentToCopy,
3026
+ synchronizationUnits: signal.input.synchronizationUnits
3027
+ })
3028
+ );
3029
+ break;
3030
+ }
3031
+ if (handler) {
3032
+ operationSignals.push(
3033
+ () => handler().then((result) => ({ signal, result }))
3034
+ );
3035
+ }
3036
+ },
3037
+ { skip: operation.skip, reuseOperationResultingState: true }
3038
+ );
3039
+ const appliedOperations = newDocument.operations[operation.scope].filter(
3040
+ (op) => op.index == operation.index && op.skip == operation.skip
3041
+ );
3042
+ const appliedOperation = appliedOperations.at(0);
3043
+ if (!appliedOperation) {
3044
+ throw new OperationError(
3045
+ "ERROR",
3046
+ operation,
3047
+ `Operation with index ${operation.index}:${operation.skip} was not applied.`
3048
+ );
3049
+ }
3050
+ if (!appliedOperation.error && appliedOperation.hash !== operation.hash && !skipHashValidation) {
3051
+ throw new ConflictOperationError(operation, appliedOperation);
3052
+ }
3053
+ for (const signalHandler of operationSignals) {
3054
+ const result = await signalHandler();
3055
+ signalResults.push(result);
3056
+ }
3057
+ return {
3058
+ document: newDocument,
3059
+ signals: signalResults,
3060
+ operation: appliedOperation
3061
+ };
3062
+ }
3063
+ addOperation(drive, id, operation, options2) {
3064
+ return this.addOperations(drive, id, [operation], options2);
3065
+ }
3066
+ async _addOperations(drive, id, callback) {
3067
+ if (!this.storage.addDocumentOperationsWithTransaction) {
3068
+ const documentStorage = await this.storage.getDocument(drive, id);
3069
+ const result = await callback(documentStorage);
3070
+ if (result.operations.length > 0) {
3071
+ await this.storage.addDocumentOperations(
3072
+ drive,
3073
+ id,
3074
+ result.operations,
3075
+ result.header
3076
+ );
3077
+ }
3078
+ } else {
3079
+ await this.storage.addDocumentOperationsWithTransaction(
3080
+ drive,
3081
+ id,
3082
+ callback
3083
+ );
3084
+ }
3085
+ }
3086
+ queueOperation(drive, id, operation, options2) {
3087
+ return this.queueOperations(drive, id, [operation], options2);
3088
+ }
3089
+ async resultIfExistingOperations(drive, id, operations) {
3090
+ try {
3091
+ const document = await this.getDocument(drive, id);
3092
+ const newOperation = operations.find(
3093
+ (op) => !op.id || !document.operations[op.scope].find(
3094
+ (existingOp) => existingOp.id === op.id && existingOp.index === op.index && existingOp.type === op.type && existingOp.hash === op.hash
3095
+ )
3096
+ );
3097
+ if (!newOperation) {
3098
+ return {
3099
+ status: "SUCCESS",
3100
+ document,
3101
+ operations,
3102
+ signals: []
3103
+ };
3104
+ } else {
3105
+ return void 0;
3106
+ }
3107
+ } catch (error) {
3108
+ if (!error.message.includes(`Document with id ${id} not found`)) {
3109
+ console.error(error);
3110
+ }
3111
+ return void 0;
3112
+ }
3113
+ }
3114
+ async queueOperations(drive, id, operations, options2) {
3115
+ const result = await this.resultIfExistingOperations(drive, id, operations);
3116
+ if (result) {
3117
+ return result;
3118
+ }
3119
+ try {
3120
+ const jobId = await this.queueManager.addJob({
3121
+ driveId: drive,
3122
+ documentId: id,
3123
+ operations,
3124
+ options: options2
3125
+ });
3126
+ return new Promise((resolve, reject) => {
3127
+ const unsubscribe = this.queueManager.on(
3128
+ "jobCompleted",
3129
+ (job, result2) => {
3130
+ if (job.jobId === jobId) {
3131
+ unsubscribe();
3132
+ unsubscribeError();
3133
+ resolve(result2);
3134
+ }
3135
+ }
3136
+ );
3137
+ const unsubscribeError = this.queueManager.on(
3138
+ "jobFailed",
3139
+ (job, error) => {
3140
+ if (job.jobId === jobId) {
3141
+ unsubscribe();
3142
+ unsubscribeError();
3143
+ reject(error);
3144
+ }
3145
+ }
3146
+ );
3147
+ });
3148
+ } catch (error) {
3149
+ logger.error("Error adding job", error);
3150
+ throw error;
3151
+ }
3152
+ }
3153
+ async queueAction(drive, id, action, options2) {
3154
+ return this.queueActions(drive, id, [action], options2);
3155
+ }
3156
+ async queueActions(drive, id, actions2, options2) {
3157
+ try {
3158
+ const jobId = await this.queueManager.addJob({
3159
+ driveId: drive,
3160
+ documentId: id,
3161
+ actions: actions2,
3162
+ options: options2
3163
+ });
3164
+ return new Promise((resolve, reject) => {
3165
+ const unsubscribe = this.queueManager.on(
3166
+ "jobCompleted",
3167
+ (job, result) => {
3168
+ if (job.jobId === jobId) {
3169
+ unsubscribe();
3170
+ unsubscribeError();
3171
+ resolve(result);
3172
+ }
3173
+ }
3174
+ );
3175
+ const unsubscribeError = this.queueManager.on(
3176
+ "jobFailed",
3177
+ (job, error) => {
3178
+ if (job.jobId === jobId) {
3179
+ unsubscribe();
3180
+ unsubscribeError();
3181
+ reject(error);
3182
+ }
3183
+ }
3184
+ );
3185
+ });
3186
+ } catch (error) {
3187
+ logger.error("Error adding job", error);
3188
+ throw error;
3189
+ }
3190
+ }
3191
+ async queueDriveAction(drive, action, options2) {
3192
+ return this.queueDriveActions(drive, [action], options2);
3193
+ }
3194
+ async queueDriveActions(drive, actions2, options2) {
3195
+ try {
3196
+ const jobId = await this.queueManager.addJob({
3197
+ driveId: drive,
3198
+ actions: actions2,
3199
+ options: options2
3200
+ });
3201
+ return new Promise(
3202
+ (resolve, reject) => {
3203
+ const unsubscribe = this.queueManager.on(
3204
+ "jobCompleted",
3205
+ (job, result) => {
3206
+ if (job.jobId === jobId) {
3207
+ unsubscribe();
3208
+ unsubscribeError();
3209
+ resolve(result);
3210
+ }
3211
+ }
3212
+ );
3213
+ const unsubscribeError = this.queueManager.on(
3214
+ "jobFailed",
3215
+ (job, error) => {
3216
+ if (job.jobId === jobId) {
3217
+ unsubscribe();
3218
+ unsubscribeError();
3219
+ reject(error);
3220
+ }
3221
+ }
3222
+ );
3223
+ }
3224
+ );
3225
+ } catch (error) {
3226
+ logger.error("Error adding drive job", error);
3227
+ throw error;
3228
+ }
3229
+ }
3230
+ async addOperations(drive, id, operations, options2) {
3231
+ const result = await this.resultIfExistingOperations(drive, id, operations);
3232
+ if (result) {
3233
+ return result;
3234
+ }
3235
+ let document;
3236
+ const operationsApplied = [];
3237
+ const signals = [];
3238
+ let error;
3239
+ try {
3240
+ await this._addOperations(drive, id, async (documentStorage) => {
3241
+ const result2 = await this._processOperations(
3242
+ drive,
3243
+ id,
3244
+ documentStorage,
3245
+ operations
3246
+ );
3247
+ if (!result2.document) {
3248
+ logger.error("Invalid document");
3249
+ throw result2.error ?? new Error("Invalid document");
3250
+ }
3251
+ document = result2.document;
3252
+ error = result2.error;
3253
+ signals.push(...result2.signals);
3254
+ operationsApplied.push(...result2.operationsApplied);
3255
+ return {
3256
+ operations: result2.operationsApplied,
3257
+ header: result2.document,
3258
+ newState: document.state
3259
+ };
3260
+ });
3261
+ if (document) {
3262
+ this.cache.setDocument(drive, id, document).catch(logger.error);
3263
+ }
3264
+ const { scopes, branches } = operationsApplied.reduce(
3265
+ (acc, operation) => {
3266
+ if (!acc.scopes.includes(operation.scope)) {
3267
+ acc.scopes.push(operation.scope);
3268
+ }
3269
+ return acc;
3270
+ },
3271
+ { scopes: [], branches: ["main"] }
3272
+ );
3273
+ const syncUnits = await this.getSynchronizationUnits(
3274
+ drive,
3275
+ [id],
3276
+ scopes,
3277
+ branches
3278
+ );
3279
+ const newOp = operationsApplied.find(
3280
+ (appliedOp) => !operations.find(
3281
+ (o) => o.id === appliedOp.id && o.index === appliedOp.index && o.skip === appliedOp.skip && o.hash === appliedOp.hash
3282
+ )
3283
+ );
3284
+ const source = newOp ? { type: "local" } : options2?.source ?? { type: "local" };
3285
+ const operationSource = this.getOperationSource(source);
3286
+ this.listenerStateManager.updateSynchronizationRevisions(
3287
+ drive,
3288
+ syncUnits,
3289
+ source,
3290
+ () => {
3291
+ this.updateSyncUnitStatus(drive, {
3292
+ [operationSource]: "SYNCING"
3293
+ });
3294
+ for (const syncUnit of syncUnits) {
3295
+ this.updateSyncUnitStatus(syncUnit.syncId, {
3296
+ [operationSource]: "SYNCING"
3297
+ });
3298
+ }
3299
+ },
3300
+ this.handleListenerError.bind(this),
3301
+ options2?.forceSync ?? source.type === "local"
3302
+ ).then((updates) => {
3303
+ if (updates.length) {
3304
+ this.updateSyncUnitStatus(drive, {
3305
+ [operationSource]: "SUCCESS"
3306
+ });
3307
+ }
3308
+ for (const syncUnit of syncUnits) {
3309
+ this.updateSyncUnitStatus(syncUnit.syncId, {
3310
+ [operationSource]: "SUCCESS"
3311
+ });
3312
+ }
3313
+ }).catch((error2) => {
3314
+ logger.error("Non handled error updating sync revision", error2);
3315
+ this.updateSyncUnitStatus(
3316
+ drive,
3317
+ {
3318
+ [operationSource]: "ERROR"
3319
+ },
3320
+ error2
3321
+ );
3322
+ for (const syncUnit of syncUnits) {
3323
+ this.updateSyncUnitStatus(
3324
+ syncUnit.syncId,
3325
+ {
3326
+ [operationSource]: "ERROR"
3327
+ },
3328
+ error2
3329
+ );
3330
+ }
3331
+ });
3332
+ if (error) {
3333
+ throw error;
3334
+ }
3335
+ return {
3336
+ status: "SUCCESS",
3337
+ document,
3338
+ operations: operationsApplied,
3339
+ signals
3340
+ };
3341
+ } catch (error2) {
3342
+ const operationError = error2 instanceof OperationError ? error2 : new OperationError(
3343
+ "ERROR",
3344
+ void 0,
3345
+ error2.message,
3346
+ error2.cause
3347
+ );
3348
+ return {
3349
+ status: operationError.status,
3350
+ error: operationError,
3351
+ document,
3352
+ operations: operationsApplied,
3353
+ signals
3354
+ };
3355
+ }
3356
+ }
3357
+ addDriveOperation(drive, operation, options2) {
3358
+ return this.addDriveOperations(drive, [operation], options2);
3359
+ }
3360
+ async clearStorage() {
3361
+ for (const drive of await this.getDrives()) {
3362
+ await this.deleteDrive(drive);
3363
+ }
3364
+ await this.storage.clearStorage?.();
3365
+ }
3366
+ async _addDriveOperations(drive, callback) {
3367
+ if (!this.storage.addDriveOperationsWithTransaction) {
3368
+ const documentStorage = await this.storage.getDrive(drive);
3369
+ const result = await callback(documentStorage);
3370
+ if (result.operations.length > 0) {
3371
+ await this.storage.addDriveOperations(
3372
+ drive,
3373
+ result.operations,
3374
+ result.header
3375
+ );
3376
+ }
3377
+ return result;
3378
+ } else {
3379
+ return this.storage.addDriveOperationsWithTransaction(drive, callback);
3380
+ }
3381
+ }
3382
+ queueDriveOperation(drive, operation, options2) {
3383
+ return this.queueDriveOperations(drive, [operation], options2);
3384
+ }
3385
+ async resultIfExistingDriveOperations(driveId, operations) {
3386
+ try {
3387
+ const drive = await this.getDrive(driveId);
3388
+ const newOperation = operations.find(
3389
+ (op) => !op.id || !drive.operations[op.scope].find(
3390
+ (existingOp) => existingOp.id === op.id && existingOp.index === op.index && existingOp.type === op.type && existingOp.hash === op.hash
3391
+ )
3392
+ );
3393
+ if (!newOperation) {
3394
+ return {
3395
+ status: "SUCCESS",
3396
+ document: drive,
3397
+ operations,
3398
+ signals: []
3399
+ };
3400
+ } else {
3401
+ return void 0;
3402
+ }
3403
+ } catch (error) {
3404
+ console.error(error);
3405
+ return void 0;
3406
+ }
3407
+ }
3408
+ async queueDriveOperations(drive, operations, options2) {
3409
+ const result = await this.resultIfExistingDriveOperations(
3410
+ drive,
3411
+ operations
3412
+ );
3413
+ if (result) {
3414
+ return result;
3415
+ }
3416
+ try {
3417
+ const jobId = await this.queueManager.addJob({
3418
+ driveId: drive,
3419
+ operations,
3420
+ options: options2
3421
+ });
3422
+ return new Promise(
3423
+ (resolve, reject) => {
3424
+ const unsubscribe = this.queueManager.on(
3425
+ "jobCompleted",
3426
+ (job, result2) => {
3427
+ if (job.jobId === jobId) {
3428
+ unsubscribe();
3429
+ unsubscribeError();
3430
+ resolve(result2);
3431
+ }
3432
+ }
3433
+ );
3434
+ const unsubscribeError = this.queueManager.on(
3435
+ "jobFailed",
3436
+ (job, error) => {
3437
+ if (job.jobId === jobId) {
3438
+ unsubscribe();
3439
+ unsubscribeError();
3440
+ reject(error);
3441
+ }
3442
+ }
3443
+ );
3444
+ }
3445
+ );
3446
+ } catch (error) {
3447
+ logger.error("Error adding drive job", error);
3448
+ throw error;
3449
+ }
3450
+ }
3451
+ async addDriveOperations(drive, operations, options2) {
3452
+ let document;
3453
+ const operationsApplied = [];
3454
+ const signals = [];
3455
+ let error;
3456
+ const result = await this.resultIfExistingDriveOperations(
3457
+ drive,
3458
+ operations
3459
+ );
3460
+ if (result) {
3461
+ return result;
3462
+ }
3463
+ try {
3464
+ await this._addDriveOperations(drive, async (documentStorage) => {
3465
+ const result2 = await this._processOperations(drive, void 0, documentStorage, operations.slice());
3466
+ document = result2.document;
3467
+ operationsApplied.push(...result2.operationsApplied);
3468
+ signals.push(...result2.signals);
3469
+ error = result2.error;
3470
+ return {
3471
+ operations: result2.operationsApplied,
3472
+ header: result2.document
3473
+ };
3474
+ });
3475
+ if (!document || !isDocumentDrive(document)) {
3476
+ throw error ?? new Error("Invalid Document Drive document");
3477
+ }
3478
+ this.cache.setDocument("drives", drive, document).catch(logger.error);
3479
+ for (const operation of operationsApplied) {
3480
+ switch (operation.type) {
3481
+ case "ADD_LISTENER": {
3482
+ await this.addListener(drive, operation);
3483
+ break;
3484
+ }
3485
+ case "REMOVE_LISTENER": {
3486
+ await this.removeListener(drive, operation);
3487
+ break;
3488
+ }
3489
+ }
3490
+ }
3491
+ const lastOperation = operationsApplied.filter((op) => op.scope === "global").slice().pop();
3492
+ if (lastOperation) {
3493
+ const newOp = operationsApplied.find(
3494
+ (appliedOp) => !operations.find(
3495
+ (o) => o.id === appliedOp.id && o.index === appliedOp.index && o.skip === appliedOp.skip && o.hash === appliedOp.hash
3496
+ )
3497
+ );
3498
+ const source = newOp ? { type: "local" } : options2?.source ?? { type: "local" };
3499
+ const operationSource = this.getOperationSource(source);
3500
+ this.listenerStateManager.updateSynchronizationRevisions(
3501
+ drive,
3502
+ [
3503
+ {
3504
+ syncId: "0",
3505
+ driveId: drive,
3506
+ documentId: "",
3507
+ scope: "global",
3508
+ branch: "main",
3509
+ documentType: "powerhouse/document-drive",
3510
+ lastUpdated: lastOperation.timestamp,
3511
+ revision: lastOperation.index
3512
+ }
3513
+ ],
3514
+ source,
3515
+ () => {
3516
+ this.updateSyncUnitStatus(drive, {
3517
+ [operationSource]: "SYNCING"
3518
+ });
3519
+ },
3520
+ this.handleListenerError.bind(this),
3521
+ options2?.forceSync ?? source.type === "local"
3522
+ ).then((updates) => {
3523
+ if (updates.length) {
3524
+ this.updateSyncUnitStatus(drive, {
3525
+ [operationSource]: "SUCCESS"
3526
+ });
3527
+ }
3528
+ }).catch((error2) => {
3529
+ logger.error("Non handled error updating sync revision", error2);
3530
+ this.updateSyncUnitStatus(
3531
+ drive,
3532
+ { [operationSource]: "ERROR" },
3533
+ error2
3534
+ );
3535
+ });
3536
+ }
3537
+ if (this.shouldSyncRemoteDrive(document)) {
3538
+ this.startSyncRemoteDrive(document.state.global.id);
3539
+ } else {
3540
+ this.stopSyncRemoteDrive(document.state.global.id);
3541
+ }
3542
+ if (error) {
3543
+ throw error;
3544
+ }
3545
+ return {
3546
+ status: "SUCCESS",
3547
+ document,
3548
+ operations: operationsApplied,
3549
+ signals
3550
+ };
3551
+ } catch (error2) {
3552
+ const operationError = error2 instanceof OperationError ? error2 : new OperationError(
3553
+ "ERROR",
3554
+ void 0,
3555
+ error2.message,
3556
+ error2.cause
3557
+ );
3558
+ return {
3559
+ status: operationError.status,
3560
+ error: operationError,
3561
+ document,
3562
+ operations: operationsApplied,
3563
+ signals
3564
+ };
3565
+ }
3566
+ }
3567
+ _buildOperations(document, actions2) {
3568
+ const operations = [];
3569
+ const { reducer } = this.getDocumentModel(document.documentType);
3570
+ for (const action of actions2) {
3571
+ document = reducer(document, action);
3572
+ const operation = document.operations[action.scope].slice().pop();
3573
+ if (!operation) {
3574
+ throw new Error("Error creating operations");
3575
+ }
3576
+ operations.push(operation);
3577
+ }
3578
+ return operations;
3579
+ }
3580
+ async addAction(drive, id, action, options2) {
3581
+ return this.addActions(drive, id, [action], options2);
3582
+ }
3583
+ async addActions(drive, id, actions2, options2) {
3584
+ const document = await this.getDocument(drive, id);
3585
+ const operations = this._buildOperations(document, actions2);
3586
+ return this.addOperations(drive, id, operations, options2);
3587
+ }
3588
+ async addDriveAction(drive, action, options2) {
3589
+ return this.addDriveActions(drive, [action], options2);
3590
+ }
3591
+ async addDriveActions(drive, actions2, options2) {
3592
+ const document = await this.getDrive(drive);
3593
+ const operations = this._buildOperations(document, actions2);
3594
+ const result = await this.addDriveOperations(drive, operations, options2);
3595
+ return result;
3596
+ }
3597
+ async addInternalListener(driveId, receiver, options2) {
3598
+ const listener = {
3599
+ callInfo: {
3600
+ data: "",
3601
+ name: "Interal",
3602
+ transmitterType: "Internal"
3603
+ },
3604
+ system: true,
3605
+ ...options2
3606
+ };
3607
+ await this.addDriveAction(driveId, actions.addListener({ listener }));
3608
+ const transmitter = await this.getTransmitter(driveId, options2.listenerId);
3609
+ if (!transmitter) {
3610
+ logger.error("Internal listener not found");
3611
+ throw new Error("Internal listener not found");
3612
+ }
3613
+ if (!(transmitter instanceof InternalTransmitter)) {
3614
+ logger.error("Listener is not an internal transmitter");
3615
+ throw new Error("Listener is not an internal transmitter");
3616
+ }
3617
+ transmitter.setReceiver(receiver);
3618
+ return transmitter;
3619
+ }
3620
+ async detachDrive(driveId) {
3621
+ const documentDrive = await this.getDrive(driveId);
3622
+ const listeners = documentDrive.state.local.listeners || [];
3623
+ const triggers = documentDrive.state.local.triggers || [];
3624
+ for (const listener of listeners) {
3625
+ await this.addDriveAction(
3626
+ driveId,
3627
+ actions.removeListener({ listenerId: listener.listenerId })
3628
+ );
3629
+ }
3630
+ for (const trigger of triggers) {
3631
+ await this.addDriveAction(
3632
+ driveId,
3633
+ actions.removeTrigger({ triggerId: trigger.id })
3634
+ );
3635
+ }
3636
+ await this.addDriveAction(
3637
+ driveId,
3638
+ actions.setSharingType({ type: "LOCAL" })
3639
+ );
3640
+ }
3641
+ async addListener(driveId, operation) {
3642
+ const { listener } = operation.input;
3643
+ await this.listenerStateManager.addListener({
3644
+ ...listener,
3645
+ driveId,
3646
+ label: listener.label ?? "",
3647
+ system: listener.system ?? false,
3648
+ filter: {
3649
+ branch: listener.filter.branch ?? [],
3650
+ documentId: listener.filter.documentId ?? [],
3651
+ documentType: listener.filter.documentType ?? [],
3652
+ scope: listener.filter.scope ?? []
3653
+ },
3654
+ callInfo: {
3655
+ data: listener.callInfo?.data ?? "",
3656
+ name: listener.callInfo?.name ?? "PullResponder",
3657
+ transmitterType: listener.callInfo?.transmitterType ?? "PullResponder"
3658
+ }
3659
+ });
3660
+ }
3661
+ async removeListener(driveId, operation) {
3662
+ const { listenerId } = operation.input;
3663
+ await this.listenerStateManager.removeListener(driveId, listenerId);
3664
+ }
3665
+ getTransmitter(driveId, listenerId) {
3666
+ return this.listenerStateManager.getTransmitter(driveId, listenerId);
3667
+ }
3668
+ getListener(driveId, listenerId) {
3669
+ return this.listenerStateManager.getListener(driveId, listenerId);
3670
+ }
3671
+ getSyncStatus(syncUnitId) {
3672
+ const status = this.syncStatus.get(syncUnitId);
3673
+ if (!status) {
3674
+ return new SynchronizationUnitNotFoundError(
3675
+ `Sync status not found for syncUnitId: ${syncUnitId}`,
3676
+ syncUnitId
3677
+ );
3678
+ }
3679
+ return this.getCombinedSyncUnitStatus(status);
3680
+ }
3681
+ on(event, cb) {
3682
+ return this.emitter.on(event, cb);
3683
+ }
3684
+ emit(event, ...args) {
3685
+ return this.emitter.emit(event, ...args);
3686
+ }
3687
+ };
3688
+ var DocumentDriveServer = ReadModeServer(BaseDocumentDriveServer);
3689
+ function ensureDir(dir) {
3690
+ if (!existsSync(dir)) {
3691
+ mkdirSync(dir, { recursive: true });
3692
+ }
3693
+ }
3694
+ var FilesystemStorage = class _FilesystemStorage {
3695
+ basePath;
3696
+ drivesPath;
3697
+ static DRIVES_DIR = "drives";
3698
+ constructor(basePath) {
3699
+ this.basePath = basePath;
3700
+ ensureDir(this.basePath);
3701
+ this.drivesPath = path2.join(this.basePath, _FilesystemStorage.DRIVES_DIR);
3702
+ ensureDir(this.drivesPath);
3703
+ }
3704
+ _buildDocumentPath(...args) {
3705
+ return `${path2.join(
3706
+ this.basePath,
3707
+ ...args.map((arg) => sanitize(arg))
3708
+ )}.json`;
3709
+ }
3710
+ async getDocuments(drive) {
3711
+ let files = [];
3712
+ try {
3713
+ files = readdirSync(path2.join(this.basePath, drive), {
3714
+ withFileTypes: true
3715
+ });
3716
+ } catch (error) {
3717
+ if (error.code !== "ENOENT") {
3718
+ throw error;
3719
+ }
3720
+ }
3721
+ const documents = [];
3722
+ for (const file of files.filter((file2) => file2.isFile())) {
3723
+ try {
3724
+ const documentId = path2.parse(file.name).name;
3725
+ await this.getDocument(drive, documentId);
3726
+ documents.push(documentId);
3727
+ } catch {
3728
+ }
3729
+ }
3730
+ return documents;
3731
+ }
3732
+ checkDocumentExists(drive, id) {
3733
+ const documentExists = existsSync(this._buildDocumentPath(drive, id));
3734
+ return Promise.resolve(documentExists);
3735
+ }
3736
+ async getDocument(drive, id) {
3737
+ try {
3738
+ const content = readFileSync(this._buildDocumentPath(drive, id), {
3739
+ encoding: "utf-8"
3740
+ });
3741
+ return JSON.parse(content);
3742
+ } catch (error) {
3743
+ throw new Error(`Document with id ${id} not found`);
3744
+ }
3745
+ }
3746
+ async createDocument(drive, id, document) {
3747
+ const documentPath = this._buildDocumentPath(drive, id);
3748
+ ensureDir(path2.dirname(documentPath));
3749
+ writeFileSync(documentPath, stringify(document), {
3750
+ encoding: "utf-8"
3751
+ });
3752
+ return Promise.resolve();
3753
+ }
3754
+ async clearStorage() {
3755
+ const drivesPath = path2.join(this.basePath, _FilesystemStorage.DRIVES_DIR);
3756
+ const drives = (await fs.readdir(drivesPath, {
3757
+ withFileTypes: true,
3758
+ recursive: true
3759
+ })).filter((dirent) => !!dirent.name);
3760
+ await Promise.all(
3761
+ drives.map(async (dirent) => {
3762
+ await fs.rm(path2.join(drivesPath, dirent.name), {
3763
+ recursive: true
3764
+ });
3765
+ })
3766
+ );
3767
+ const files = (await fs.readdir(this.basePath, { withFileTypes: true })).filter(
3768
+ (file) => file.name !== _FilesystemStorage.DRIVES_DIR && !!file.name
3769
+ );
3770
+ await Promise.all(
3771
+ files.map(async (dirent) => {
3772
+ await fs.rm(path2.join(this.basePath, dirent.name), {
3773
+ recursive: true
3774
+ });
3775
+ })
3776
+ );
3777
+ }
3778
+ async deleteDocument(drive, id) {
3779
+ return fs.rm(this._buildDocumentPath(drive, id));
3780
+ }
3781
+ async addDocumentOperations(drive, id, operations, header) {
3782
+ const document = await this.getDocument(drive, id);
3783
+ if (!document) {
3784
+ throw new Error(`Document with id ${id} not found`);
3785
+ }
3786
+ const mergedOperations = mergeOperations(document.operations, operations);
3787
+ await this.createDocument(drive, id, {
3788
+ ...document,
3789
+ ...header,
3790
+ operations: mergedOperations
3791
+ });
3792
+ }
3793
+ async getDrives() {
3794
+ const files = readdirSync(this.drivesPath, {
3795
+ withFileTypes: true
3796
+ });
3797
+ const drives = [];
3798
+ for (const file of files.filter((file2) => file2.isFile())) {
3799
+ try {
3800
+ const driveId = path2.parse(file.name).name;
3801
+ await this.getDrive(driveId);
3802
+ drives.push(driveId);
3803
+ } catch {
3804
+ }
3805
+ }
3806
+ return drives;
3807
+ }
3808
+ async getDrive(id) {
3809
+ try {
3810
+ return await this.getDocument(
3811
+ _FilesystemStorage.DRIVES_DIR,
3812
+ id
3813
+ );
3814
+ } catch {
3815
+ throw new DriveNotFoundError(id);
3816
+ }
3817
+ }
3818
+ async getDriveBySlug(slug) {
3819
+ const drives = (await this.getDrives()).reverse();
3820
+ for (const drive of drives) {
3821
+ const {
3822
+ initialState: {
3823
+ state: {
3824
+ global: { slug: driveSlug }
3825
+ }
3826
+ }
3827
+ } = await this.getDrive(drive);
3828
+ if (driveSlug === slug) {
3829
+ return this.getDrive(drive);
3830
+ }
3831
+ }
3832
+ throw new Error(`Drive with slug ${slug} not found`);
3833
+ }
3834
+ createDrive(id, drive) {
3835
+ return this.createDocument(_FilesystemStorage.DRIVES_DIR, id, drive);
3836
+ }
3837
+ async deleteDrive(id) {
3838
+ const documents = await this.getDocuments(id);
3839
+ await this.deleteDocument(_FilesystemStorage.DRIVES_DIR, id);
3840
+ await Promise.all(
3841
+ documents.map((document) => this.deleteDocument(id, document))
3842
+ );
3843
+ }
3844
+ async addDriveOperations(id, operations, header) {
3845
+ const drive = await this.getDrive(id);
3846
+ const mergedOperations = mergeOperations(drive.operations, operations);
3847
+ await this.createDrive(id, {
3848
+ ...drive,
3849
+ ...header,
3850
+ operations: mergedOperations
3851
+ });
3852
+ }
3853
+ async getSynchronizationUnitsRevision(units) {
3854
+ const results = await Promise.allSettled(
3855
+ units.map(async (unit) => {
3856
+ try {
3857
+ const document = await (unit.documentId ? this.getDocument(unit.driveId, unit.documentId) : this.getDrive(unit.driveId));
3858
+ if (!document) {
3859
+ return void 0;
3860
+ }
3861
+ const operation = document.operations[unit.scope].at(-1);
3862
+ if (operation) {
3863
+ return {
3864
+ driveId: unit.driveId,
3865
+ documentId: unit.documentId,
3866
+ scope: unit.scope,
3867
+ branch: unit.branch,
3868
+ lastUpdated: operation.timestamp,
3869
+ revision: operation.index
3870
+ };
3871
+ }
3872
+ } catch {
3873
+ return void 0;
3874
+ }
3875
+ })
3876
+ );
3877
+ return results.reduce((acc, curr) => {
3878
+ if (curr.status === "fulfilled" && curr.value !== void 0) {
3879
+ acc.push(curr.value);
3880
+ }
3881
+ return acc;
3882
+ }, []);
3883
+ }
3884
+ };
3885
+ dotenv.config();
3886
+ var driveServer = new DocumentDriveServer(
3887
+ [module, ...Object.values(DocumentModelsLibs)],
3888
+ new FilesystemStorage(path2.join(__dirname, "../file-storage"))
3889
+ );
3890
+ var serverPort = process.env.PORT ? Number(process.env.PORT) : 4001;
3891
+ var startServer = async () => {
3892
+ const db = await drizzle("pglite", "./dev.db");
3893
+ await driveServer.initialize();
3894
+ try {
3895
+ await driveServer.addDrive({
3896
+ global: {
3897
+ id: "powerhouse",
3898
+ name: "Powerhouse",
3899
+ icon: "powerhouse",
3900
+ slug: "powerhouse"
3901
+ },
3902
+ local: {
3903
+ availableOffline: true,
3904
+ listeners: [],
3905
+ sharingType: "public",
3906
+ triggers: []
3907
+ }
3908
+ });
3909
+ } catch (e) {
3910
+ console.info("Default drive already exists. Skipping...");
3911
+ }
3912
+ try {
3913
+ await startAPI(driveServer, {
3914
+ port: serverPort
3915
+ });
3916
+ setAdditionalContextFields({ db });
3917
+ await registerInternalListener({
3918
+ name: "search",
3919
+ options: searchListener.options,
3920
+ transmit: (strands) => searchListener.transmit(strands, db)
3921
+ });
3922
+ await addSubgraph({
3923
+ getSchema: () => createSchema(
3924
+ driveServer,
3925
+ searchListener.resolvers,
3926
+ searchListener.typeDefs
3927
+ ),
3928
+ name: "search/:drive"
3929
+ });
3930
+ } catch (e) {
3931
+ console.error("App crashed", e);
3932
+ }
3933
+ };
3934
+
3935
+ export { startServer };