@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
@@ -7,13 +7,22 @@ import {
7
7
  } from '@verdant-web/common';
8
8
  import { Context } from '../context/context.js';
9
9
  import { AbstractTransaction, PersistenceMetadataDb } from './interfaces.js';
10
+ import type { PersistenceMetadata } from './PersistenceMetadata.js';
10
11
 
11
12
  export class PersistenceRebaser {
12
13
  constructor(
13
14
  private db: PersistenceMetadataDb,
15
+ private meta: PersistenceMetadata,
14
16
  private ctx: Pick<
15
17
  Context,
16
- 'closing' | 'log' | 'time' | 'internalEvents' | 'globalEvents' | 'config'
18
+ | 'closing'
19
+ | 'log'
20
+ | 'time'
21
+ | 'internalEvents'
22
+ | 'globalEvents'
23
+ | 'config'
24
+ | 'closeLock'
25
+ | 'persistenceShutdownHandler'
17
26
  >,
18
27
  ) {}
19
28
 
@@ -22,9 +31,12 @@ export class PersistenceRebaser {
22
31
  * keep storage clean for non-syncing clients by compressing history.
23
32
  */
24
33
  tryAutonomousRebase = async () => {
25
- const localReplicaInfo = await this.db.getLocalReplica();
34
+ const localReplicaInfo = await this.meta.getLocalReplica();
26
35
  if (localReplicaInfo.lastSyncedLogicalTime) return; // cannot autonomously rebase if we've synced
36
+ if (this.ctx.closing || this.ctx.persistenceShutdownHandler.isShuttingDown)
37
+ return;
27
38
  // but if we have never synced... we can rebase everything!
39
+ this.ctx.log('debug', 'Running autonomous library rebase');
28
40
  await this.runRebase(this.ctx.time.now);
29
41
  };
30
42
 
@@ -35,47 +47,55 @@ export class PersistenceRebaser {
35
47
  * their undo stack.
36
48
  */
37
49
  private runRebase = async (globalAckTimestamp: string) => {
38
- if (this.ctx.closing) return;
39
-
40
- // find all operations before the global ack
41
- let lastTimestamp;
42
- const toRebase = new Set<ObjectIdentifier>();
43
- const transaction = this.db.transaction({
44
- storeNames: ['baselines', 'operations'],
45
- mode: 'readwrite',
46
- });
47
- let operationCount = 0;
48
- await this.db.iterateAllOperations(
49
- (patch) => {
50
- toRebase.add(patch.oid);
51
- lastTimestamp = patch.timestamp;
52
- operationCount++;
53
- },
50
+ if (this.ctx.closing || this.ctx.persistenceShutdownHandler.isShuttingDown)
51
+ return;
52
+
53
+ await this.db.transaction(
54
54
  {
55
- before: globalAckTimestamp,
56
- transaction,
55
+ storeNames: ['baselines', 'operations'],
56
+ mode: 'readwrite',
57
57
  },
58
- );
58
+ async (transaction) => {
59
+ // find all operations before the global ack
60
+ const toRebase = new Set<ObjectIdentifier>();
61
+ let lastTimestamp;
62
+ let operationCount = 0;
63
+ await this.db.iterateAllOperations(
64
+ (patch) => {
65
+ toRebase.add(patch.oid);
66
+ lastTimestamp = patch.timestamp;
67
+ operationCount++;
68
+ },
69
+ {
70
+ before: globalAckTimestamp,
71
+ transaction,
72
+ },
73
+ );
59
74
 
60
- if (!toRebase.size) {
61
- return;
62
- }
75
+ if (!toRebase.size) {
76
+ return;
77
+ }
63
78
 
64
- if (this.ctx.closing) {
65
- return;
66
- }
79
+ if (
80
+ this.ctx.closing ||
81
+ this.ctx.persistenceShutdownHandler.isShuttingDown
82
+ ) {
83
+ return;
84
+ }
67
85
 
68
- // rebase each affected document
69
- let newBaselines = [];
70
- for (const oid of toRebase) {
71
- newBaselines.push(
72
- await this.rebase(
73
- oid,
74
- lastTimestamp || globalAckTimestamp,
75
- transaction,
76
- ),
77
- );
78
- }
86
+ // rebase each affected document
87
+ let newBaselines = [];
88
+ for (const oid of toRebase) {
89
+ newBaselines.push(
90
+ await this.rebase(
91
+ oid,
92
+ lastTimestamp || globalAckTimestamp,
93
+ transaction,
94
+ ),
95
+ );
96
+ }
97
+ },
98
+ );
79
99
  this.ctx.globalEvents.emit('rebase');
80
100
  };
81
101
 
@@ -92,26 +112,28 @@ export class PersistenceRebaser {
92
112
  this.ctx.config.persistence?.rebaseTimeout ?? 10000,
93
113
  timestamp,
94
114
  );
115
+ this.ctx.log('debug', 'Scheduled rebase up to global ack', timestamp);
95
116
  };
96
117
  private rebaseTimeout: NodeJS.Timeout | null = null;
97
118
 
98
- rebase = async (
119
+ private rebase = async (
99
120
  oid: ObjectIdentifier,
100
121
  upTo: string,
101
- providedTx?: AbstractTransaction,
122
+ transaction: AbstractTransaction,
102
123
  ) => {
103
- const transaction =
104
- providedTx ||
105
- this.db.transaction({
106
- storeNames: ['operations', 'baselines'],
107
- mode: 'readwrite',
108
- });
124
+ if (this.ctx.closing || this.ctx.persistenceShutdownHandler.isShuttingDown)
125
+ return;
126
+
109
127
  const baseline = await this.db.getBaseline(oid, { transaction });
110
128
  let current: any = baseline?.snapshot || undefined;
111
129
  let operationsApplied = 0;
112
130
  let authz = baseline?.authz;
113
131
  const deletedRefs: Ref[] = [];
114
- await this.db.consumeEntityOperations(
132
+
133
+ if (this.ctx.closing || this.ctx.persistenceShutdownHandler.isShuttingDown)
134
+ return;
135
+
136
+ await this.db.iterateEntityOperations(
115
137
  oid,
116
138
  (patch) => {
117
139
  // FIXME: this seems like the wrong place to do this
@@ -139,32 +161,45 @@ export class PersistenceRebaser {
139
161
  timestamp: upTo,
140
162
  authz,
141
163
  };
142
- if (newBaseline.snapshot) {
143
- await this.db.setBaselines([newBaseline], { transaction });
144
- } else {
145
- await this.db.deleteBaseline(oid, { transaction });
146
- }
147
164
 
148
- this.ctx.log(
149
- 'debug',
150
- 'rebased',
151
- oid,
152
- 'up to',
153
- upTo,
154
- ':',
155
- current,
156
- 'and deleted',
157
- operationsApplied,
158
- 'operations',
159
- );
165
+ // still time to cancel now...
166
+ if (this.ctx.closing || this.ctx.persistenceShutdownHandler.isShuttingDown)
167
+ return;
160
168
 
161
- // cleanup deleted refs
162
- if (deletedRefs.length) {
163
- const fileRefs = deletedRefs.filter(isFileRef);
164
- if (fileRefs.length) {
165
- this.ctx.internalEvents.emit('filesDeleted', fileRefs);
169
+ // FROM HERE, WE ARE COMMITTED TO THE REBASE -- otherwise data will be corrupted.
170
+ this.ctx.closeLock = (async () => {
171
+ if (newBaseline.snapshot) {
172
+ await this.db.setBaselines([newBaseline], { transaction });
173
+ } else {
174
+ await this.db.deleteBaseline(oid, { transaction });
166
175
  }
167
- }
176
+
177
+ await this.db.deleteEntityOperations(oid, {
178
+ to: upTo,
179
+ transaction,
180
+ });
181
+
182
+ this.ctx.log(
183
+ 'debug',
184
+ 'rebased',
185
+ oid,
186
+ 'up to',
187
+ upTo,
188
+ ':',
189
+ current,
190
+ 'and deleted',
191
+ operationsApplied,
192
+ 'operations',
193
+ );
194
+
195
+ // cleanup deleted refs
196
+ if (deletedRefs.length) {
197
+ const fileRefs = deletedRefs.filter(isFileRef);
198
+ if (fileRefs.length) {
199
+ this.ctx.internalEvents.emit('filesDeleted', fileRefs);
200
+ }
201
+ }
202
+ })();
168
203
 
169
204
  return newBaseline;
170
205
  };
@@ -1,6 +1,5 @@
1
1
  import { Context } from '../../context/context.js';
2
2
  import {
3
- copyDatabase,
4
3
  createAbortableTransaction,
5
4
  isAbortError,
6
5
  storeRequestPromise,
@@ -33,21 +32,40 @@ export class IdbService extends Disposable {
33
32
  abort?: AbortSignal;
34
33
  },
35
34
  ) => {
36
- const tx = createAbortableTransaction(
37
- this.db,
38
- storeNames,
39
- opts?.mode || 'readonly',
40
- opts?.abort,
41
- this.log,
42
- );
43
- this.globalAbortController.signal.addEventListener('abort', tx.abort);
44
- tx.addEventListener('complete', () => {
45
- this.globalAbortController.signal.removeEventListener('abort', tx.abort);
46
- });
47
- tx.addEventListener('error', () => {
48
- this.globalAbortController.signal.removeEventListener('abort', tx.abort);
49
- });
50
- return tx;
35
+ try {
36
+ if (this.globalAbortController.signal.aborted) {
37
+ throw new Error('Global abort signal is already aborted');
38
+ }
39
+ const tx = createAbortableTransaction(
40
+ this.db,
41
+ storeNames,
42
+ opts?.mode || 'readonly',
43
+ opts?.abort,
44
+ this.log,
45
+ );
46
+ this.globalAbortController.signal.addEventListener('abort', tx.abort);
47
+ tx.addEventListener('complete', () => {
48
+ this.globalAbortController.signal.removeEventListener(
49
+ 'abort',
50
+ tx.abort,
51
+ );
52
+ });
53
+ tx.addEventListener('error', () => {
54
+ this.globalAbortController.signal.removeEventListener(
55
+ 'abort',
56
+ tx.abort,
57
+ );
58
+ });
59
+ return tx;
60
+ } catch (err) {
61
+ this.log?.(
62
+ 'error',
63
+ 'Failed to create abortable transaction for store names',
64
+ storeNames,
65
+ err,
66
+ );
67
+ throw err;
68
+ }
51
69
  };
52
70
 
53
71
  run = async <T = any>(
@@ -163,18 +181,18 @@ export class IdbService extends Disposable {
163
181
  });
164
182
  };
165
183
 
166
- cloneTo = async (otherDb: IDBDatabase) => {
167
- await copyDatabase(this.db, otherDb);
168
- };
169
-
170
- private onVersionChange = () => {
184
+ private onVersionChange = (ev: IDBVersionChangeEvent) => {
171
185
  this.log?.(
172
186
  'warn',
173
187
  `Another tab has requested a version change for ${this.db.name}`,
174
188
  );
175
189
  this.db.close();
176
190
  if (typeof window !== 'undefined') {
177
- window.location.reload();
191
+ try {
192
+ window.location.reload();
193
+ } catch (err) {
194
+ this.log?.('error', 'Failed to reload the page', err);
195
+ }
178
196
  }
179
197
  };
180
198
  }
@@ -3,10 +3,10 @@ import {
3
3
  AbstractTransaction,
4
4
  PersistedFileData,
5
5
  PersistenceFileDb,
6
- QueryMode,
7
6
  } from '../../interfaces.js';
8
7
  import { IdbService } from '../IdbService.js';
9
8
  import { getAllFromObjectStores, getSizeOfObjectStore } from '../util.js';
9
+ import { Context } from '../../../internal.js';
10
10
 
11
11
  /**
12
12
  * When stored in IDB, replace the file blob with an array buffer
@@ -24,35 +24,11 @@ export class IdbPersistenceFileDb
24
24
  extends IdbService
25
25
  implements PersistenceFileDb
26
26
  {
27
- transaction = (opts: {
28
- mode?: QueryMode;
29
- storeNames: string[];
30
- abort?: AbortSignal;
31
- }): AbstractTransaction => {
32
- return this.createTransaction(opts.storeNames, {
33
- mode: opts.mode,
34
- abort: opts.abort,
35
- });
36
- };
37
-
38
27
  add = async (
39
28
  file: FileData,
40
- options?: { transaction?: AbstractTransaction; downloadRemote?: boolean },
41
29
  ): Promise<void> => {
42
30
  let buffer = file.file ? await fileToArrayBuffer(file.file) : undefined;
43
- if (!buffer && options?.downloadRemote && file.url) {
44
- try {
45
- buffer = await fetch(file.url, {
46
- method: 'GET',
47
- credentials: 'include',
48
- }).then((r) => r.arrayBuffer());
49
- } catch (err) {
50
- console.error(
51
- "Failed to download file to cache it locally. The file will still be available using its URL. Check the file server's CORS configuration.",
52
- err,
53
- );
54
- }
55
- }
31
+
56
32
  await this.run(
57
33
  'files',
58
34
  (store) => {
@@ -65,19 +41,16 @@ export class IdbPersistenceFileDb
65
41
  type: file.type,
66
42
  url: file.url,
67
43
  buffer,
44
+ timestamp: file.timestamp,
68
45
  } satisfies StoredFileData);
69
46
  },
70
47
  {
71
48
  mode: 'readwrite',
72
- transaction: options?.transaction as IDBTransaction,
73
49
  },
74
50
  );
75
51
  };
76
- markUploaded = async (
77
- id: string,
78
- options?: { transaction?: AbstractTransaction },
79
- ): Promise<void> => {
80
- const current = await this.getFileRaw(id, options);
52
+ markUploaded = async (id: string): Promise<void> => {
53
+ const current = await this.getFileRaw(id);
81
54
 
82
55
  if (!current) {
83
56
  throw new Error('File is not in local database');
@@ -93,24 +66,17 @@ export class IdbPersistenceFileDb
93
66
  },
94
67
  {
95
68
  mode: 'readwrite',
96
- transaction: options?.transaction as IDBTransaction,
97
69
  },
98
70
  );
99
71
  };
100
- get = async (
101
- fileId: string,
102
- options?: { transaction?: AbstractTransaction },
103
- ): Promise<PersistedFileData | null> => {
104
- const raw = await this.getFileRaw(fileId, options);
72
+ get = async (fileId: string): Promise<PersistedFileData | null> => {
73
+ const raw = await this.getFileRaw(fileId);
105
74
  if (!raw) {
106
75
  return null;
107
76
  }
108
77
  return this.hydrateFileData(raw);
109
78
  };
110
- delete = (
111
- fileId: string,
112
- options?: { transaction?: AbstractTransaction },
113
- ): Promise<void> => {
79
+ delete = (fileId: string): Promise<void> => {
114
80
  return this.run<undefined>(
115
81
  'files',
116
82
  (store) => {
@@ -118,15 +84,11 @@ export class IdbPersistenceFileDb
118
84
  },
119
85
  {
120
86
  mode: 'readwrite',
121
- transaction: options?.transaction as IDBTransaction,
122
87
  },
123
88
  );
124
89
  };
125
- markPendingDelete = async (
126
- fileId: string,
127
- options?: { transaction?: AbstractTransaction },
128
- ): Promise<void> => {
129
- const current = await this.getFileRaw(fileId, options);
90
+ markPendingDelete = async (fileId: string): Promise<void> => {
91
+ const current = await this.getFileRaw(fileId);
130
92
 
131
93
  if (!current) {
132
94
  throw new Error('File is not in local database');
@@ -142,29 +104,23 @@ export class IdbPersistenceFileDb
142
104
  },
143
105
  {
144
106
  mode: 'readwrite',
145
- transaction: options?.transaction as IDBTransaction,
146
107
  },
147
108
  );
148
109
  };
149
- listUnsynced = async (options?: {
150
- transaction?: AbstractTransaction;
151
- }): Promise<PersistedFileData[]> => {
110
+ listUnsynced = async (): Promise<PersistedFileData[]> => {
152
111
  const raw = await this.run<StoredFileData[]>(
153
112
  'files',
154
113
  (store) => {
155
114
  return store.index('remote').getAll('false');
156
115
  },
157
- { mode: 'readonly', transaction: options?.transaction as IDBTransaction },
116
+ { mode: 'readonly' },
158
117
  );
159
118
  return raw.map(this.hydrateFileData);
160
119
  };
161
- resetSyncedStatusSince = async (
162
- since: string | null,
163
- options?: { transaction?: AbstractTransaction },
164
- ): Promise<void> => {
165
- const tx: IDBTransaction =
166
- (options?.transaction as any) ??
167
- this.createTransaction(['files'], { mode: 'readwrite' });
120
+ resetSyncedStatusSince = async (since: string | null): Promise<void> => {
121
+ const tx: IDBTransaction = this.createTransaction(['files'], {
122
+ mode: 'readwrite',
123
+ });
168
124
  const raw = await this.run<StoredFileData[]>(
169
125
  'files',
170
126
  (store) => {
@@ -194,7 +150,6 @@ export class IdbPersistenceFileDb
194
150
  };
195
151
  iterateOverPendingDelete = (
196
152
  iterator: (file: PersistedFileData, store: IDBObjectStore) => void,
197
- options?: { transaction?: IDBTransaction },
198
153
  ): Promise<void> => {
199
154
  return this.iterate<StoredFileData>(
200
155
  'files',
@@ -208,7 +163,6 @@ export class IdbPersistenceFileDb
208
163
  },
209
164
  {
210
165
  mode: 'readwrite',
211
- transaction: options?.transaction as IDBTransaction,
212
166
  },
213
167
  );
214
168
  };
@@ -223,6 +177,20 @@ export class IdbPersistenceFileDb
223
177
  size: await getSizeOfObjectStore(this.db, 'files'),
224
178
  };
225
179
  };
180
+ loadFileContents = async (file: FileData, ctx: Context): Promise<Blob> => {
181
+ if (file.file) return file.file;
182
+ if (file.localPath) {
183
+ throw new Error('Local file paths are not supported in browser');
184
+ }
185
+ if (file.url) {
186
+ const response = await ctx.environment.fetch(file.url);
187
+ if (!response.ok) {
188
+ throw new Error(`Failed to download file: ${response.statusText}`);
189
+ }
190
+ return response.blob();
191
+ }
192
+ throw new Error('File is missing url, file, and localPath');
193
+ }
226
194
 
227
195
  private hydrateFileData = (raw: StoredFileData): PersistedFileData => {
228
196
  (raw as any).remote = raw.remote === 'true';