@verdant-web/store 4.0.0 → 4.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/LICENSE +21 -650
  2. package/dist/bundle/index.js +11 -11
  3. package/dist/bundle/index.js.map +4 -4
  4. package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -2
  5. package/dist/esm/__tests__/fixtures/testStorage.js +3 -5
  6. package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
  7. package/dist/esm/client/Client.d.ts +6 -2
  8. package/dist/esm/client/Client.js +18 -6
  9. package/dist/esm/client/Client.js.map +1 -1
  10. package/dist/esm/client/ClientDescriptor.d.ts +7 -5
  11. package/dist/esm/client/ClientDescriptor.js +18 -4
  12. package/dist/esm/client/ClientDescriptor.js.map +1 -1
  13. package/dist/esm/context/ShutdownHandler.d.ts +8 -0
  14. package/dist/esm/context/ShutdownHandler.js +24 -0
  15. package/dist/esm/context/ShutdownHandler.js.map +1 -0
  16. package/dist/esm/context/context.d.ts +15 -4
  17. package/dist/esm/entities/EntityStore.js +6 -3
  18. package/dist/esm/entities/EntityStore.js.map +1 -1
  19. package/dist/esm/files/EntityFile.d.ts +1 -0
  20. package/dist/esm/files/EntityFile.js +16 -11
  21. package/dist/esm/files/EntityFile.js.map +1 -1
  22. package/dist/esm/files/FileManager.d.ts +1 -3
  23. package/dist/esm/files/FileManager.js +12 -10
  24. package/dist/esm/files/FileManager.js.map +1 -1
  25. package/dist/esm/index.d.ts +4 -5
  26. package/dist/esm/index.js +2 -3
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm/internal.d.ts +6 -0
  29. package/dist/esm/internal.js +5 -0
  30. package/dist/esm/internal.js.map +1 -0
  31. package/dist/esm/persistence/MessageCreator.d.ts +3 -1
  32. package/dist/esm/persistence/MessageCreator.js +58 -55
  33. package/dist/esm/persistence/MessageCreator.js.map +1 -1
  34. package/dist/esm/persistence/PersistenceFiles.d.ts +8 -21
  35. package/dist/esm/persistence/PersistenceFiles.js +44 -30
  36. package/dist/esm/persistence/PersistenceFiles.js.map +1 -1
  37. package/dist/esm/persistence/PersistenceMetadata.d.ts +12 -11
  38. package/dist/esm/persistence/PersistenceMetadata.js +201 -137
  39. package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
  40. package/dist/esm/persistence/PersistenceQueries.d.ts +10 -11
  41. package/dist/esm/persistence/PersistenceQueries.js +33 -5
  42. package/dist/esm/persistence/PersistenceQueries.js.map +1 -1
  43. package/dist/esm/persistence/PersistenceRebaser.d.ts +5 -9
  44. package/dist/esm/persistence/PersistenceRebaser.js +63 -47
  45. package/dist/esm/persistence/PersistenceRebaser.js.map +1 -1
  46. package/dist/esm/persistence/idb/IdbService.d.ts +0 -1
  47. package/dist/esm/persistence/idb/IdbService.js +28 -16
  48. package/dist/esm/persistence/idb/IdbService.js.map +1 -1
  49. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.d.ts +11 -31
  50. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js +31 -36
  51. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js.map +1 -1
  52. package/dist/esm/persistence/idb/idbPersistence.d.ts +17 -9
  53. package/dist/esm/persistence/idb/idbPersistence.js +80 -39
  54. package/dist/esm/persistence/idb/idbPersistence.js.map +1 -1
  55. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +7 -10
  56. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +45 -71
  57. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
  58. package/dist/esm/persistence/idb/metadata/openMetadataDatabase.d.ts +1 -12
  59. package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js +3 -56
  60. package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js.map +1 -1
  61. package/dist/esm/persistence/idb/queries/{IdbQueryDb.d.ts → IdbDocumentDb.d.ts} +7 -13
  62. package/dist/esm/persistence/idb/queries/{IdbQueryDb.js → IdbDocumentDb.js} +15 -32
  63. package/dist/esm/persistence/idb/queries/IdbDocumentDb.js.map +1 -0
  64. package/dist/esm/persistence/idb/queries/migration/db.d.ts +3 -5
  65. package/dist/esm/persistence/idb/queries/migration/db.js +13 -28
  66. package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -1
  67. package/dist/esm/persistence/idb/util.d.ts +8 -4
  68. package/dist/esm/persistence/idb/util.js +64 -21
  69. package/dist/esm/persistence/idb/util.js.map +1 -1
  70. package/dist/esm/persistence/interfaces.d.ts +68 -75
  71. package/dist/esm/persistence/{idb/queries/migration → migration}/engine.d.ts +4 -7
  72. package/dist/esm/persistence/{idb/queries/migration → migration}/engine.js +18 -10
  73. package/dist/esm/persistence/migration/engine.js.map +1 -0
  74. package/dist/esm/persistence/migration/finalize.d.ts +9 -0
  75. package/dist/esm/persistence/migration/finalize.js +75 -0
  76. package/dist/esm/persistence/migration/finalize.js.map +1 -0
  77. package/dist/esm/persistence/migration/migrate.d.ts +12 -0
  78. package/dist/esm/persistence/migration/migrate.js +89 -0
  79. package/dist/esm/persistence/migration/migrate.js.map +1 -0
  80. package/dist/esm/persistence/migration/paths.js.map +1 -0
  81. package/dist/esm/persistence/migration/paths.test.js.map +1 -0
  82. package/dist/esm/persistence/migration/types.d.ts +3 -0
  83. package/dist/esm/persistence/migration/types.js.map +1 -0
  84. package/dist/esm/persistence/persistence.js +25 -15
  85. package/dist/esm/persistence/persistence.js.map +1 -1
  86. package/dist/esm/queries/FindAllQuery.js +1 -1
  87. package/dist/esm/queries/FindAllQuery.js.map +1 -1
  88. package/dist/esm/queries/FindInfiniteQuery.js +2 -2
  89. package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
  90. package/dist/esm/queries/FindOneQuery.js +1 -1
  91. package/dist/esm/queries/FindOneQuery.js.map +1 -1
  92. package/dist/esm/queries/FindPageQuery.js +1 -1
  93. package/dist/esm/queries/FindPageQuery.js.map +1 -1
  94. package/dist/esm/sync/FileSync.js +3 -3
  95. package/dist/esm/sync/FileSync.js.map +1 -1
  96. package/dist/esm/sync/PushPullSync.d.ts +2 -3
  97. package/dist/esm/sync/PushPullSync.js +4 -2
  98. package/dist/esm/sync/PushPullSync.js.map +1 -1
  99. package/dist/esm/sync/ServerSyncEndpointProvider.d.ts +3 -7
  100. package/dist/esm/sync/ServerSyncEndpointProvider.js +3 -2
  101. package/dist/esm/sync/ServerSyncEndpointProvider.js.map +1 -1
  102. package/dist/esm/sync/Sync.d.ts +6 -1
  103. package/dist/esm/sync/Sync.js +12 -4
  104. package/dist/esm/sync/Sync.js.map +1 -1
  105. package/dist/esm/sync/WebSocketSync.js +10 -4
  106. package/dist/esm/sync/WebSocketSync.js.map +1 -1
  107. package/dist/esm/utils/wip.js +1 -1
  108. package/package.json +6 -2
  109. package/src/__tests__/fixtures/testStorage.ts +6 -6
  110. package/src/client/Client.ts +26 -8
  111. package/src/client/ClientDescriptor.ts +27 -9
  112. package/src/context/ShutdownHandler.ts +26 -0
  113. package/src/context/context.ts +16 -4
  114. package/src/entities/EntityStore.ts +9 -3
  115. package/src/files/EntityFile.ts +11 -6
  116. package/src/files/FileManager.ts +13 -10
  117. package/src/index.ts +8 -9
  118. package/src/internal.ts +27 -0
  119. package/src/persistence/MessageCreator.ts +79 -73
  120. package/src/persistence/PersistenceFiles.ts +57 -31
  121. package/src/persistence/PersistenceMetadata.ts +287 -195
  122. package/src/persistence/PersistenceQueries.ts +45 -9
  123. package/src/persistence/PersistenceRebaser.ts +105 -70
  124. package/src/persistence/idb/IdbService.ts +40 -22
  125. package/src/persistence/idb/files/IdbPersistenceFileDb.ts +30 -62
  126. package/src/persistence/idb/idbPersistence.ts +123 -47
  127. package/src/persistence/idb/metadata/IdbMetadataDb.ts +75 -97
  128. package/src/persistence/idb/metadata/openMetadataDatabase.ts +2 -96
  129. package/src/persistence/idb/queries/{IdbQueryDb.ts → IdbDocumentDb.ts} +17 -57
  130. package/src/persistence/idb/queries/migration/db.ts +20 -39
  131. package/src/persistence/idb/util.ts +84 -21
  132. package/src/persistence/interfaces.ts +89 -90
  133. package/src/persistence/{idb/queries/migration → migration}/engine.ts +30 -15
  134. package/src/persistence/migration/finalize.ts +126 -0
  135. package/src/persistence/migration/migrate.ts +169 -0
  136. package/src/persistence/migration/types.ts +4 -0
  137. package/src/persistence/persistence.ts +37 -14
  138. package/src/queries/FindAllQuery.ts +1 -1
  139. package/src/queries/FindInfiniteQuery.ts +2 -2
  140. package/src/queries/FindOneQuery.ts +1 -1
  141. package/src/queries/FindPageQuery.ts +1 -1
  142. package/src/sync/FileSync.ts +21 -15
  143. package/src/sync/PushPullSync.ts +3 -4
  144. package/src/sync/ServerSyncEndpointProvider.ts +6 -8
  145. package/src/sync/Sync.ts +20 -7
  146. package/src/sync/WebSocketSync.ts +10 -4
  147. package/src/utils/wip.ts +1 -1
  148. package/dist/esm/client/constants.d.ts +0 -1
  149. package/dist/esm/client/constants.js +0 -2
  150. package/dist/esm/client/constants.js.map +0 -1
  151. package/dist/esm/persistence/idb/queries/IdbQueryDb.js.map +0 -1
  152. package/dist/esm/persistence/idb/queries/migration/engine.js.map +0 -1
  153. package/dist/esm/persistence/idb/queries/migration/migrations.d.ts +0 -15
  154. package/dist/esm/persistence/idb/queries/migration/migrations.js +0 -243
  155. package/dist/esm/persistence/idb/queries/migration/migrations.js.map +0 -1
  156. package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.d.ts +0 -8
  157. package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js +0 -24
  158. package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js.map +0 -1
  159. package/dist/esm/persistence/idb/queries/migration/paths.js.map +0 -1
  160. package/dist/esm/persistence/idb/queries/migration/paths.test.js.map +0 -1
  161. package/dist/esm/persistence/idb/queries/migration/types.d.ts +0 -6
  162. package/dist/esm/persistence/idb/queries/migration/types.js.map +0 -1
  163. package/src/client/constants.ts +0 -1
  164. package/src/persistence/idb/queries/migration/migrations.ts +0 -345
  165. package/src/persistence/idb/queries/migration/openQueryDatabase.ts +0 -54
  166. package/src/persistence/idb/queries/migration/types.ts +0 -8
  167. /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.d.ts +0 -0
  168. /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.js +0 -0
  169. /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.test.d.ts +0 -0
  170. /package/dist/esm/persistence/{idb/queries/migration → migration}/paths.test.js +0 -0
  171. /package/dist/esm/persistence/{idb/queries/migration → migration}/types.js +0 -0
  172. /package/src/persistence/{idb/queries/migration → migration}/paths.test.ts +0 -0
  173. /package/src/persistence/{idb/queries/migration → migration}/paths.ts +0 -0
@@ -14,10 +14,12 @@ import {
14
14
 
15
15
  import { Context } from '../context/context.js';
16
16
  import { PersistenceMetadataDb } from './interfaces.js';
17
+ import type { PersistenceMetadata } from './PersistenceMetadata.js';
17
18
 
18
19
  export class MessageCreator {
19
20
  constructor(
20
21
  private db: PersistenceMetadataDb,
22
+ private meta: PersistenceMetadata,
21
23
  private ctx: Pick<Context, 'time' | 'schema' | 'log'>,
22
24
  ) {}
23
25
 
@@ -26,7 +28,8 @@ export class MessageCreator {
26
28
  timestamp?: string;
27
29
  },
28
30
  ): Promise<OperationMessage> => {
29
- const localInfo = await this.db.getLocalReplica();
31
+ const localInfo = await this.meta.getLocalReplica();
32
+ this.ctx.log('debug', 'Creating operation message', init.operations.length);
30
33
  return {
31
34
  type: 'op',
32
35
  timestamp: this.ctx.time.now,
@@ -39,7 +42,7 @@ export class MessageCreator {
39
42
  * @param since - override local understanding of last sync time
40
43
  */
41
44
  createSyncStep1 = async (since?: string | null): Promise<SyncMessage> => {
42
- const localReplicaInfo = await this.db.getLocalReplica();
45
+ const localReplicaInfo = await this.meta.getLocalReplica();
43
46
 
44
47
  const provideChangesSince =
45
48
  since === null ? null : localReplicaInfo.lastSyncedLogicalTime;
@@ -49,83 +52,86 @@ export class MessageCreator {
49
52
  const operations: Operation[] = [];
50
53
  const affectedDocs = new Set<ObjectIdentifier>();
51
54
 
52
- const tx = await this.db.transaction({
53
- mode: 'readwrite',
54
- storeNames: ['operations', 'baselines'],
55
- });
55
+ return this.db.transaction(
56
+ {
57
+ mode: 'readwrite',
58
+ storeNames: ['operations', 'baselines'],
59
+ },
60
+ async (tx) => {
61
+ // FIXME: this branch gives bad vibes. should we always
62
+ // send all operations from other replicas too? is there
63
+ // ever a case where we have a "since" timestamp and there
64
+ // are foreign ops that match it?
65
+ if (provideChangesSince) {
66
+ this.ctx.log(
67
+ 'debug',
68
+ 'Syncing local operations since',
69
+ provideChangesSince,
70
+ );
71
+ await this.db.iterateLocalOperations(
72
+ (patch) => {
73
+ operations.push(pickValidOperationKeys(patch));
74
+ affectedDocs.add(getOidRoot(patch.oid));
75
+ },
76
+ {
77
+ after: provideChangesSince,
78
+ // block on writes to prevent race conditions
79
+ transaction: tx,
80
+ },
81
+ );
82
+ } else {
83
+ this.ctx.log('debug', 'Syncing all operations');
84
+ // if providing the whole history, don't limit to only local
85
+ // operations
86
+ await this.db.iterateAllOperations(
87
+ (patch) => {
88
+ operations.push(pickValidOperationKeys(patch));
89
+ affectedDocs.add(getOidRoot(patch.oid));
90
+ },
91
+ {
92
+ transaction: tx,
93
+ },
94
+ );
95
+ }
96
+ // we only need to send baselines if we've never synced before
97
+ let baselines: DocumentBaseline[] = [];
98
+ if (!provideChangesSince) {
99
+ await this.db.iterateAllBaselines(
100
+ (b) => {
101
+ baselines.push(b);
102
+ },
103
+ {
104
+ transaction: tx,
105
+ },
106
+ );
107
+ }
56
108
 
57
- // FIXME: this branch gives bad vibes. should we always
58
- // send all operations from other replicas too? is there
59
- // ever a case where we have a "since" timestamp and there
60
- // are foreign ops that match it?
61
- if (provideChangesSince) {
62
- this.ctx.log(
63
- 'debug',
64
- 'Syncing local operations since',
65
- provideChangesSince,
66
- );
67
- await this.db.iterateLocalOperations(
68
- (patch) => {
69
- operations.push(pickValidOperationKeys(patch));
70
- affectedDocs.add(getOidRoot(patch.oid));
71
- },
72
- {
73
- after: provideChangesSince,
74
- // block on writes to prevent race conditions
75
- transaction: tx,
76
- },
77
- );
78
- } else {
79
- this.ctx.log('debug', 'Syncing all operations');
80
- // if providing the whole history, don't limit to only local
81
- // operations
82
- await this.db.iterateAllOperations(
83
- (patch) => {
84
- operations.push(pickValidOperationKeys(patch));
85
- affectedDocs.add(getOidRoot(patch.oid));
86
- },
87
- {
88
- transaction: tx,
89
- },
90
- );
91
- }
92
- // we only need to send baselines if we've never synced before
93
- let baselines: DocumentBaseline[] = [];
94
- if (!provideChangesSince) {
95
- await this.db.iterateAllBaselines(
96
- (b) => {
97
- baselines.push(b);
98
- },
99
- {
100
- transaction: tx,
101
- },
102
- );
103
- }
109
+ if (operations.length > 0) {
110
+ this.ctx.log(
111
+ 'debug',
112
+ `Syncing ${operations.length} operations since ${provideChangesSince}`,
113
+ );
114
+ }
104
115
 
105
- if (operations.length > 0) {
106
- this.ctx.log(
107
- 'debug',
108
- `Syncing ${operations.length} operations since ${provideChangesSince}`,
109
- );
110
- }
111
-
112
- return {
113
- type: 'sync',
114
- schemaVersion: this.ctx.schema.version,
115
- timestamp: this.ctx.time.now,
116
- replicaId: localReplicaInfo.id,
117
- resyncAll: !localReplicaInfo.lastSyncedLogicalTime,
118
- operations,
119
- baselines,
120
- since: provideChangesSince,
121
- };
116
+ return {
117
+ type: 'sync',
118
+ schemaVersion: this.ctx.schema.version,
119
+ timestamp: this.ctx.time.now,
120
+ replicaId: localReplicaInfo.id,
121
+ resyncAll: !localReplicaInfo.lastSyncedLogicalTime,
122
+ operations,
123
+ baselines,
124
+ since: provideChangesSince,
125
+ };
126
+ },
127
+ );
122
128
  };
123
129
 
124
130
  createPresenceUpdate = async (data: {
125
131
  presence?: any;
126
132
  internal?: VerdantInternalPresence;
127
133
  }): Promise<PresenceUpdateMessage> => {
128
- const localReplicaInfo = await this.db.getLocalReplica();
134
+ const localReplicaInfo = await this.meta.getLocalReplica();
129
135
  return {
130
136
  type: 'presence-update',
131
137
  presence: data.presence,
@@ -135,7 +141,7 @@ export class MessageCreator {
135
141
  };
136
142
 
137
143
  createHeartbeat = async (): Promise<HeartbeatMessage> => {
138
- const localReplicaInfo = await this.db.getLocalReplica();
144
+ const localReplicaInfo = await this.meta.getLocalReplica();
139
145
  return {
140
146
  type: 'heartbeat',
141
147
  timestamp: this.ctx.time.now,
@@ -144,7 +150,7 @@ export class MessageCreator {
144
150
  };
145
151
 
146
152
  createAck = async (nonce: string): Promise<AckMessage> => {
147
- const localReplicaInfo = await this.db.getLocalReplica();
153
+ const localReplicaInfo = await this.meta.getLocalReplica();
148
154
  return {
149
155
  type: 'ack',
150
156
  timestamp: this.ctx.time.now,
@@ -1,16 +1,13 @@
1
1
  import { FileData, FileRef } from '@verdant-web/common';
2
2
  import { Context, FileConfig } from '../context/context.js';
3
3
  import { PersistedFileData, PersistenceFileDb } from './interfaces.js';
4
- import { Disposable } from '../utils/Disposable.js';
5
4
 
6
- export class PersistenceFiles extends Disposable {
5
+ export class PersistenceFiles {
7
6
  constructor(
8
7
  private db: PersistenceFileDb,
9
8
  private context: Omit<Context, 'queries'>,
10
9
  ) {
11
- super();
12
10
  context.internalEvents.subscribe('filesDeleted', this.onFileRefsDeleted);
13
- this.compose(this.db);
14
11
  // on startup, try deleting old files.
15
12
  this.cleanupDeletedFiles();
16
13
  }
@@ -29,7 +26,7 @@ export class PersistenceFiles extends Disposable {
29
26
 
30
27
  onServerReset = (since: string | null) =>
31
28
  this.db.resetSyncedStatusSince(since);
32
- add = async (file: FileData, options?: { downloadRemote?: boolean }) => {
29
+ add = async (file: FileData) => {
33
30
  // this method accepts a FileData which refers to a remote
34
31
  // file, as well as local files. in the case of a remote file,
35
32
  // we actually re-download and upload the file again. this powers
@@ -37,16 +34,26 @@ export class PersistenceFiles extends Disposable {
37
34
  // and re-upload to a new file ID. otherwise, when the cloned
38
35
  // filedata was marked deleted, the original file would be deleted
39
36
  // and the clone would refer to a missing file.
40
- if (file.url && !file.file) {
37
+ if (file.url && !(file.localPath || file.file)) {
41
38
  this.context.log(
42
39
  'debug',
43
40
  'Remote file added to an entity. This usually means an entity was cloned. Downloading remote file...',
44
41
  file.id,
45
42
  );
46
- const blob = await this.context.files.downloadRemoteFile(file.url, 0, 3);
43
+ const blob = await this.loadFileContents(file, 0, 3);
47
44
  // convert blob to file with name and type
48
45
  file.file = new File([blob], file.name, { type: file.type });
49
- } else if (!file.file) {
46
+ // remove the URL - it points to the original file's uploaded server version,
47
+ // but this file is a clone
48
+ delete file.url;
49
+ this.context.log(
50
+ 'debug',
51
+ 'Downloaded remote file',
52
+ file.id,
53
+ file.name,
54
+ '. Cleared its remote URL.',
55
+ );
56
+ } else if (!file.url && !file.file && !file.localPath) {
50
57
  this.context.log(
51
58
  'warn',
52
59
  'File added without a file or URL. This file will not be available for use.',
@@ -54,19 +61,30 @@ export class PersistenceFiles extends Disposable {
54
61
  );
55
62
  }
56
63
 
64
+ // always reset remote status to false, this is a new file just created
65
+ // and must be uploaded, even if it is cloned from an uploaded file.
57
66
  file.remote = false;
67
+
58
68
  // fire event for processing immediately
59
69
  this.context.internalEvents.emit('fileAdded', file);
60
70
  // store in persistence db
61
- await this.db.add(file, options);
71
+ await this.db.add(file);
72
+ this.context.globalEvents.emit('fileSaved', file);
62
73
  this.context.log(
63
74
  'debug',
64
75
  'File added',
65
76
  file.id,
66
77
  file.name,
67
78
  file.type,
68
- file.file ? 'with binary file' : file.url ? 'with url' : 'with no data',
79
+ file.file
80
+ ? 'with binary file'
81
+ : file.url
82
+ ? 'with url'
83
+ : file.localPath
84
+ ? 'with local path'
85
+ : 'with no data',
69
86
  );
87
+ return file;
70
88
  };
71
89
  onUploaded = this.db.markUploaded.bind(this.db);
72
90
  get = this.db.get.bind(this.db);
@@ -78,22 +96,29 @@ export class PersistenceFiles extends Disposable {
78
96
  private getFileExportName = (originalFileName: string, id: string) => {
79
97
  return `${id}___${originalFileName}`;
80
98
  };
81
- export = async (downloadRemote = false) => {
99
+ export = async (downloadRemote = true) => {
82
100
  const storedFiles = await this.getAll();
83
101
  if (downloadRemote) {
84
102
  for (const storedFile of storedFiles) {
85
103
  // if it doesn't have a buffer, we need to read one from the server
86
- if (!storedFile.file && storedFile.url) {
104
+ if (!storedFile.file && (storedFile.url || storedFile.localPath)) {
87
105
  try {
88
- const blob = await this.downloadRemoteFile(storedFile.url);
106
+ const blob = await this.loadFileContents(storedFile);
89
107
  storedFile.file = blob;
90
108
  } catch (err) {
91
109
  this.context.log(
92
110
  'error',
93
111
  "Failed to download file to cache it locally. The file will still be available using its URL. Check the file server's CORS configuration.",
112
+ storedFile,
94
113
  err,
95
114
  );
96
115
  }
116
+ } else if (!storedFile.file) {
117
+ this.context.log(
118
+ 'warn',
119
+ `File ${storedFile.id} has no file or URL. It will be missing in the export.`,
120
+ storedFile,
121
+ );
97
122
  }
98
123
  }
99
124
  }
@@ -163,41 +188,46 @@ export class PersistenceFiles extends Disposable {
163
188
  return { id, originalFileName };
164
189
  };
165
190
 
166
- downloadRemoteFile = async (url: string, retries = 0, maxRetries = 0) => {
167
- const resp = await fetch(url, {
168
- method: 'GET',
169
- credentials: 'include',
170
- });
171
- if (!resp.ok) {
191
+ private loadFileContents = async (
192
+ file: FileData,
193
+ retries = 0,
194
+ maxRetries = 0,
195
+ ) => {
196
+ try {
197
+ return await this.db.loadFileContents(file, this.context);
198
+ } catch (err) {
172
199
  if (retries < maxRetries) {
173
200
  return new Promise<Blob>((resolve, reject) => {
174
201
  setTimeout(() => {
175
- this.downloadRemoteFile(url, retries + 1, maxRetries).then(
202
+ this.loadFileContents(file, retries + 1, maxRetries).then(
176
203
  resolve,
177
204
  reject,
178
205
  );
179
206
  }, 1000);
180
207
  });
181
208
  } else {
182
- throw new Error(
183
- `Failed to download file after ${maxRetries} retries (status: ${resp.status})`,
184
- );
209
+ throw new Error(`Failed to download file after ${maxRetries} retries`, {
210
+ cause: err,
211
+ });
185
212
  }
186
213
  }
187
- return await resp.blob();
188
214
  };
189
215
 
190
216
  cleanupDeletedFiles = async () => {
191
217
  let count = 0;
192
218
  let skipCount = 0;
193
- await this.iterateOverPendingDelete((fileData, store) => {
219
+ const deletable: string[] = [];
220
+ await this.iterateOverPendingDelete((fileData) => {
194
221
  if (this.config.canCleanupDeletedFile(fileData)) {
195
222
  count++;
196
- store.delete(fileData.id);
223
+ deletable.push(fileData.id);
197
224
  } else {
198
225
  skipCount++;
199
226
  }
200
227
  });
228
+ for (const id of deletable) {
229
+ await this.db.delete(id);
230
+ }
201
231
 
202
232
  this.context.log(
203
233
  'info',
@@ -206,14 +236,10 @@ export class PersistenceFiles extends Disposable {
206
236
  };
207
237
 
208
238
  private onFileRefsDeleted = async (fileRefs: FileRef[]) => {
209
- const tx = this.db.transaction({
210
- mode: 'readwrite',
211
- storeNames: ['files'],
212
- });
213
239
  await Promise.all(
214
240
  fileRefs.map(async (fileRef) => {
215
241
  try {
216
- await this.db.markPendingDelete(fileRef.id, { transaction: tx });
242
+ await this.db.markPendingDelete(fileRef.id);
217
243
  } catch (err) {
218
244
  this.context.log('error', 'Failed to mark file for deletion', err);
219
245
  }