@verdant-web/store 3.12.1 → 4.0.0-next.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.
Files changed (279) hide show
  1. package/dist/bundle/index.js +11 -13
  2. package/dist/bundle/index.js.map +4 -4
  3. package/dist/esm/__tests__/batching.test.js +5 -5
  4. package/dist/esm/__tests__/batching.test.js.map +1 -1
  5. package/dist/esm/__tests__/entities.test.js +1 -1
  6. package/dist/esm/__tests__/entities.test.js.map +1 -1
  7. package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -3
  8. package/dist/esm/__tests__/fixtures/testStorage.js +3 -3
  9. package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
  10. package/dist/esm/__tests__/queries.test.js.map +1 -1
  11. package/dist/esm/backup.d.ts +3 -4
  12. package/dist/esm/backup.js.map +1 -1
  13. package/dist/esm/client/Client.d.ts +28 -33
  14. package/dist/esm/client/Client.js +50 -161
  15. package/dist/esm/client/Client.js.map +1 -1
  16. package/dist/esm/client/ClientDescriptor.d.ts +8 -11
  17. package/dist/esm/client/ClientDescriptor.js +39 -141
  18. package/dist/esm/client/ClientDescriptor.js.map +1 -1
  19. package/dist/esm/context/Time.d.ts +13 -0
  20. package/dist/esm/context/Time.js +27 -0
  21. package/dist/esm/context/Time.js.map +1 -0
  22. package/dist/esm/context/context.d.ts +170 -0
  23. package/dist/esm/{context.js.map → context/context.js.map} +1 -1
  24. package/dist/esm/entities/DocumentManager.js.map +1 -1
  25. package/dist/esm/entities/Entity.d.ts +4 -5
  26. package/dist/esm/entities/Entity.js +5 -3
  27. package/dist/esm/entities/Entity.js.map +1 -1
  28. package/dist/esm/entities/Entity.test.js +4 -3
  29. package/dist/esm/entities/Entity.test.js.map +1 -1
  30. package/dist/esm/entities/EntityCache.d.ts +0 -3
  31. package/dist/esm/entities/EntityCache.js +0 -9
  32. package/dist/esm/entities/EntityCache.js.map +1 -1
  33. package/dist/esm/entities/EntityMetadata.d.ts +1 -1
  34. package/dist/esm/entities/EntityMetadata.js +6 -5
  35. package/dist/esm/entities/EntityMetadata.js.map +1 -1
  36. package/dist/esm/entities/EntityStore.d.ts +2 -6
  37. package/dist/esm/entities/EntityStore.js +22 -16
  38. package/dist/esm/entities/EntityStore.js.map +1 -1
  39. package/dist/esm/entities/OperationBatcher.d.ts +2 -5
  40. package/dist/esm/entities/OperationBatcher.js +9 -7
  41. package/dist/esm/entities/OperationBatcher.js.map +1 -1
  42. package/dist/esm/errors.d.ts +8 -0
  43. package/dist/esm/errors.js +12 -0
  44. package/dist/esm/errors.js.map +1 -0
  45. package/dist/esm/files/EntityFile.d.ts +6 -3
  46. package/dist/esm/files/EntityFile.js +22 -19
  47. package/dist/esm/files/EntityFile.js.map +1 -1
  48. package/dist/esm/files/FileManager.d.ts +8 -39
  49. package/dist/esm/files/FileManager.js +15 -170
  50. package/dist/esm/files/FileManager.js.map +1 -1
  51. package/dist/esm/files/utils.d.ts +0 -1
  52. package/dist/esm/files/utils.js +0 -14
  53. package/dist/esm/files/utils.js.map +1 -1
  54. package/dist/esm/index.d.ts +1 -2
  55. package/dist/esm/index.js +0 -1
  56. package/dist/esm/index.js.map +1 -1
  57. package/dist/esm/{metadata → persistence}/MessageCreator.d.ts +5 -6
  58. package/dist/esm/{metadata → persistence}/MessageCreator.js +31 -38
  59. package/dist/esm/persistence/MessageCreator.js.map +1 -0
  60. package/dist/esm/persistence/PersistenceFiles.d.ts +48 -0
  61. package/dist/esm/persistence/PersistenceFiles.js +160 -0
  62. package/dist/esm/persistence/PersistenceFiles.js.map +1 -0
  63. package/dist/esm/persistence/PersistenceMetadata.d.ts +69 -0
  64. package/dist/esm/persistence/PersistenceMetadata.js +302 -0
  65. package/dist/esm/persistence/PersistenceMetadata.js.map +1 -0
  66. package/dist/esm/persistence/PersistenceQueries.d.ts +34 -0
  67. package/dist/esm/persistence/PersistenceQueries.js +15 -0
  68. package/dist/esm/persistence/PersistenceQueries.js.map +1 -0
  69. package/dist/esm/persistence/PersistenceRebaser.d.ts +32 -0
  70. package/dist/esm/persistence/PersistenceRebaser.js +120 -0
  71. package/dist/esm/persistence/PersistenceRebaser.js.map +1 -0
  72. package/dist/esm/{IDBService.d.ts → persistence/idb/IdbService.d.ts} +9 -7
  73. package/dist/esm/{IDBService.js → persistence/idb/IdbService.js} +29 -8
  74. package/dist/esm/persistence/idb/IdbService.js.map +1 -0
  75. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.d.ts +58 -0
  76. package/dist/esm/{files/FileStorage.js → persistence/idb/files/IdbPersistenceFileDb.js} +85 -50
  77. package/dist/esm/persistence/idb/files/IdbPersistenceFileDb.js.map +1 -0
  78. package/dist/esm/persistence/idb/idbPersistence.d.ts +19 -0
  79. package/dist/esm/persistence/idb/idbPersistence.js +80 -0
  80. package/dist/esm/persistence/idb/idbPersistence.js.map +1 -0
  81. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +72 -0
  82. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +235 -0
  83. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -0
  84. package/dist/esm/{metadata → persistence/idb/metadata}/openMetadataDatabase.d.ts +3 -1
  85. package/dist/esm/{metadata → persistence/idb/metadata}/openMetadataDatabase.js +12 -3
  86. package/dist/esm/persistence/idb/metadata/openMetadataDatabase.js.map +1 -0
  87. package/dist/esm/persistence/idb/queries/IdbQueryDb.d.ts +41 -0
  88. package/dist/esm/persistence/idb/queries/IdbQueryDb.js +174 -0
  89. package/dist/esm/persistence/idb/queries/IdbQueryDb.js.map +1 -0
  90. package/dist/esm/{migration → persistence/idb/queries/migration}/db.d.ts +1 -1
  91. package/dist/esm/{migration → persistence/idb/queries/migration}/db.js +10 -48
  92. package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -0
  93. package/dist/esm/persistence/idb/queries/migration/engine.d.ts +12 -0
  94. package/dist/esm/{migration → persistence/idb/queries/migration}/engine.js +29 -46
  95. package/dist/esm/persistence/idb/queries/migration/engine.js.map +1 -0
  96. package/dist/esm/{migration → persistence/idb/queries/migration}/migrations.d.ts +1 -3
  97. package/dist/esm/{migration → persistence/idb/queries/migration}/migrations.js +11 -10
  98. package/dist/esm/persistence/idb/queries/migration/migrations.js.map +1 -0
  99. package/dist/esm/{migration → persistence/idb/queries/migration}/openQueryDatabase.d.ts +1 -3
  100. package/dist/esm/{migration → persistence/idb/queries/migration}/openQueryDatabase.js +4 -7
  101. package/dist/esm/persistence/idb/queries/migration/openQueryDatabase.js.map +1 -0
  102. package/dist/esm/{migration → persistence/idb/queries/migration}/paths.js +2 -2
  103. package/dist/esm/persistence/idb/queries/migration/paths.js.map +1 -0
  104. package/dist/esm/persistence/idb/queries/migration/paths.test.js.map +1 -0
  105. package/dist/esm/persistence/idb/queries/migration/types.d.ts +6 -0
  106. package/dist/esm/persistence/idb/queries/migration/types.js.map +1 -0
  107. package/dist/esm/persistence/idb/queries/ranges.d.ts +2 -0
  108. package/dist/esm/persistence/idb/queries/ranges.js +66 -0
  109. package/dist/esm/persistence/idb/queries/ranges.js.map +1 -0
  110. package/dist/esm/{idb.d.ts → persistence/idb/util.d.ts} +11 -0
  111. package/dist/esm/{idb.js → persistence/idb/util.js} +58 -1
  112. package/dist/esm/persistence/idb/util.js.map +1 -0
  113. package/dist/esm/persistence/interfaces.d.ts +181 -0
  114. package/dist/esm/persistence/interfaces.js +2 -0
  115. package/dist/esm/persistence/interfaces.js.map +1 -0
  116. package/dist/esm/persistence/persistence.d.ts +4 -0
  117. package/dist/esm/persistence/persistence.js +126 -0
  118. package/dist/esm/persistence/persistence.js.map +1 -0
  119. package/dist/esm/queries/BaseQuery.d.ts +2 -1
  120. package/dist/esm/queries/BaseQuery.js +3 -0
  121. package/dist/esm/queries/BaseQuery.js.map +1 -1
  122. package/dist/esm/queries/CollectionQueries.d.ts +1 -1
  123. package/dist/esm/queries/FindAllQuery.js +1 -3
  124. package/dist/esm/queries/FindAllQuery.js.map +1 -1
  125. package/dist/esm/queries/FindInfiniteQuery.js +2 -5
  126. package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
  127. package/dist/esm/queries/FindOneQuery.js +1 -3
  128. package/dist/esm/queries/FindOneQuery.js.map +1 -1
  129. package/dist/esm/queries/FindPageQuery.js +1 -3
  130. package/dist/esm/queries/FindPageQuery.js.map +1 -1
  131. package/dist/esm/queries/QueryCache.d.ts +1 -1
  132. package/dist/esm/queries/QueryCache.js +4 -0
  133. package/dist/esm/queries/QueryCache.js.map +1 -1
  134. package/dist/esm/sync/FileSync.d.ts +23 -8
  135. package/dist/esm/sync/FileSync.js +76 -28
  136. package/dist/esm/sync/FileSync.js.map +1 -1
  137. package/dist/esm/sync/PresenceManager.d.ts +4 -3
  138. package/dist/esm/sync/PresenceManager.js +2 -2
  139. package/dist/esm/sync/PresenceManager.js.map +1 -1
  140. package/dist/esm/sync/PushPullSync.d.ts +4 -6
  141. package/dist/esm/sync/PushPullSync.js +13 -12
  142. package/dist/esm/sync/PushPullSync.js.map +1 -1
  143. package/dist/esm/sync/Sync.d.ts +9 -11
  144. package/dist/esm/sync/Sync.js +34 -29
  145. package/dist/esm/sync/Sync.js.map +1 -1
  146. package/dist/esm/sync/WebSocketSync.d.ts +4 -6
  147. package/dist/esm/sync/WebSocketSync.js +20 -22
  148. package/dist/esm/sync/WebSocketSync.js.map +1 -1
  149. package/dist/esm/utils/Disposable.d.ts +5 -2
  150. package/dist/esm/utils/Disposable.js +3 -2
  151. package/dist/esm/utils/Disposable.js.map +1 -1
  152. package/dist/esm/utils/wip.d.ts +2 -0
  153. package/dist/esm/utils/wip.js +5 -0
  154. package/dist/esm/utils/wip.js.map +1 -0
  155. package/package.json +2 -2
  156. package/src/__tests__/batching.test.ts +6 -6
  157. package/src/__tests__/entities.test.ts +1 -1
  158. package/src/__tests__/fixtures/testStorage.ts +2 -10
  159. package/src/__tests__/queries.test.ts +1 -1
  160. package/src/backup.ts +3 -4
  161. package/src/client/Client.ts +69 -226
  162. package/src/client/ClientDescriptor.ts +53 -184
  163. package/src/context/Time.ts +35 -0
  164. package/src/context/context.ts +200 -0
  165. package/src/entities/DocumentManager.ts +0 -3
  166. package/src/entities/Entity.test.ts +9 -9
  167. package/src/entities/Entity.ts +6 -12
  168. package/src/entities/EntityCache.ts +0 -9
  169. package/src/entities/EntityMetadata.ts +4 -4
  170. package/src/entities/EntityStore.ts +26 -29
  171. package/src/entities/OperationBatcher.ts +9 -11
  172. package/src/errors.ts +13 -0
  173. package/src/files/EntityFile.ts +16 -5
  174. package/src/files/FileManager.ts +18 -245
  175. package/src/files/utils.ts +0 -15
  176. package/src/index.ts +2 -1
  177. package/src/{metadata → persistence}/MessageCreator.ts +46 -36
  178. package/src/persistence/PersistenceFiles.ts +227 -0
  179. package/src/persistence/PersistenceMetadata.ts +425 -0
  180. package/src/persistence/PersistenceQueries.ts +22 -0
  181. package/src/persistence/PersistenceRebaser.ts +171 -0
  182. package/src/{IDBService.ts → persistence/idb/IdbService.ts} +45 -12
  183. package/src/{files/FileStorage.ts → persistence/idb/files/IdbPersistenceFileDb.ts} +128 -86
  184. package/src/persistence/idb/idbPersistence.ts +116 -0
  185. package/src/persistence/idb/metadata/IdbMetadataDb.ts +460 -0
  186. package/src/{metadata → persistence/idb/metadata}/openMetadataDatabase.ts +21 -4
  187. package/src/persistence/idb/queries/IdbQueryDb.ts +251 -0
  188. package/src/{migration → persistence/idb/queries/migration}/db.ts +18 -72
  189. package/src/{migration → persistence/idb/queries/migration}/engine.ts +39 -62
  190. package/src/{migration → persistence/idb/queries/migration}/migrations.ts +13 -18
  191. package/src/{migration → persistence/idb/queries/migration}/openQueryDatabase.ts +5 -14
  192. package/src/{migration → persistence/idb/queries/migration}/paths.ts +4 -3
  193. package/src/persistence/idb/queries/migration/types.ts +8 -0
  194. package/src/persistence/idb/queries/ranges.ts +107 -0
  195. package/src/{idb.ts → persistence/idb/util.ts} +75 -0
  196. package/src/persistence/interfaces.ts +240 -0
  197. package/src/persistence/persistence.ts +223 -0
  198. package/src/queries/BaseQuery.ts +5 -1
  199. package/src/queries/CollectionQueries.ts +2 -2
  200. package/src/queries/FindAllQuery.ts +1 -3
  201. package/src/queries/FindInfiniteQuery.ts +2 -5
  202. package/src/queries/FindOneQuery.ts +1 -3
  203. package/src/queries/FindPageQuery.ts +1 -3
  204. package/src/queries/QueryCache.ts +20 -1
  205. package/src/sync/FileSync.ts +93 -30
  206. package/src/sync/PresenceManager.ts +5 -7
  207. package/src/sync/PushPullSync.ts +23 -19
  208. package/src/sync/Sync.ts +45 -36
  209. package/src/sync/WebSocketSync.ts +41 -27
  210. package/src/utils/Disposable.ts +7 -4
  211. package/src/utils/wip.ts +5 -0
  212. package/dist/esm/IDBService.js.map +0 -1
  213. package/dist/esm/__tests__/legacyOids.test.d.ts +0 -1
  214. package/dist/esm/__tests__/legacyOids.test.js +0 -352
  215. package/dist/esm/__tests__/legacyOids.test.js.map +0 -1
  216. package/dist/esm/context.d.ts +0 -45
  217. package/dist/esm/files/FileStorage.d.ts +0 -47
  218. package/dist/esm/files/FileStorage.js.map +0 -1
  219. package/dist/esm/idb.js.map +0 -1
  220. package/dist/esm/metadata/AckInfoStore.d.ts +0 -10
  221. package/dist/esm/metadata/AckInfoStore.js +0 -22
  222. package/dist/esm/metadata/AckInfoStore.js.map +0 -1
  223. package/dist/esm/metadata/BaselinesStore.d.ts +0 -40
  224. package/dist/esm/metadata/BaselinesStore.js +0 -102
  225. package/dist/esm/metadata/BaselinesStore.js.map +0 -1
  226. package/dist/esm/metadata/LocalReplicaStore.d.ts +0 -19
  227. package/dist/esm/metadata/LocalReplicaStore.js +0 -56
  228. package/dist/esm/metadata/LocalReplicaStore.js.map +0 -1
  229. package/dist/esm/metadata/MessageCreator.js.map +0 -1
  230. package/dist/esm/metadata/Metadata.d.ts +0 -146
  231. package/dist/esm/metadata/Metadata.js +0 -452
  232. package/dist/esm/metadata/Metadata.js.map +0 -1
  233. package/dist/esm/metadata/OperationsStore.d.ts +0 -62
  234. package/dist/esm/metadata/OperationsStore.js +0 -175
  235. package/dist/esm/metadata/OperationsStore.js.map +0 -1
  236. package/dist/esm/metadata/SchemaStore.d.ts +0 -9
  237. package/dist/esm/metadata/SchemaStore.js +0 -35
  238. package/dist/esm/metadata/SchemaStore.js.map +0 -1
  239. package/dist/esm/metadata/openMetadataDatabase.js.map +0 -1
  240. package/dist/esm/migration/db.js.map +0 -1
  241. package/dist/esm/migration/engine.d.ts +0 -15
  242. package/dist/esm/migration/engine.js.map +0 -1
  243. package/dist/esm/migration/errors.d.ts +0 -5
  244. package/dist/esm/migration/errors.js +0 -8
  245. package/dist/esm/migration/errors.js.map +0 -1
  246. package/dist/esm/migration/migrations.js.map +0 -1
  247. package/dist/esm/migration/openQueryDatabase.js.map +0 -1
  248. package/dist/esm/migration/openWIPDatabase.d.ts +0 -11
  249. package/dist/esm/migration/openWIPDatabase.js +0 -65
  250. package/dist/esm/migration/openWIPDatabase.js.map +0 -1
  251. package/dist/esm/migration/paths.js.map +0 -1
  252. package/dist/esm/migration/paths.test.js.map +0 -1
  253. package/dist/esm/migration/types.d.ts +0 -3
  254. package/dist/esm/migration/types.js.map +0 -1
  255. package/dist/esm/queries/QueryableStorage.d.ts +0 -20
  256. package/dist/esm/queries/QueryableStorage.js +0 -90
  257. package/dist/esm/queries/QueryableStorage.js.map +0 -1
  258. package/dist/esm/queries/dbQueries.d.ts +0 -22
  259. package/dist/esm/queries/dbQueries.js +0 -130
  260. package/dist/esm/queries/dbQueries.js.map +0 -1
  261. package/src/__tests__/legacyOids.test.ts +0 -375
  262. package/src/context.ts +0 -55
  263. package/src/metadata/AckInfoStore.ts +0 -30
  264. package/src/metadata/BaselinesStore.ts +0 -188
  265. package/src/metadata/LocalReplicaStore.ts +0 -79
  266. package/src/metadata/Metadata.ts +0 -685
  267. package/src/metadata/OperationsStore.ts +0 -332
  268. package/src/metadata/SchemaStore.ts +0 -47
  269. package/src/migration/errors.ts +0 -7
  270. package/src/migration/openWIPDatabase.ts +0 -97
  271. package/src/migration/types.ts +0 -4
  272. package/src/queries/QueryableStorage.ts +0 -122
  273. package/src/queries/dbQueries.ts +0 -161
  274. /package/dist/esm/{context.js → context/context.js} +0 -0
  275. /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.d.ts +0 -0
  276. /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.test.d.ts +0 -0
  277. /package/dist/esm/{migration → persistence/idb/queries/migration}/paths.test.js +0 -0
  278. /package/dist/esm/{migration → persistence/idb/queries/migration}/types.js +0 -0
  279. /package/src/{migration → persistence/idb/queries/migration}/paths.test.ts +0 -0
@@ -1,34 +1,27 @@
1
1
  import {
2
2
  EventSubscriber,
3
+ HybridLogicalClockTimestampProvider,
3
4
  Migration,
4
- Operation,
5
+ PatchCreator,
5
6
  StorageSchema,
6
- hashObject,
7
+ noop,
7
8
  } from '@verdant-web/common';
8
- import { Context } from '../context.js';
9
- import { FileManagerConfig } from '../files/FileManager.js';
10
- import { Metadata } from '../metadata/Metadata.js';
11
- import {
12
- openMetadataDatabase,
13
- openWIPMetadataDatabase,
14
- } from '../metadata/openMetadataDatabase.js';
15
- import { openWIPDatabase } from '../migration/openWIPDatabase.js';
9
+ import { FileConfig, InitialContext } from '../context/context.js';
16
10
  import { ServerSyncOptions } from '../sync/Sync.js';
17
11
  import { UndoHistory } from '../UndoHistory.js';
18
12
  import { Client } from './Client.js';
19
- import {
20
- deleteAllDatabases,
21
- deleteDatabase,
22
- getAllDatabaseNamesAndVersions,
23
- } from '../idb.js';
13
+ import { deleteAllDatabases } from '../persistence/idb/util.js';
24
14
  import { FakeWeakRef } from '../FakeWeakRef.js';
25
15
  import { METADATA_VERSION_KEY } from './constants.js';
26
- import { openQueryDatabase } from '../migration/openQueryDatabase.js';
16
+ import { Time } from '../context/Time.js';
17
+ import { initializePersistence } from '../persistence/persistence.js';
18
+ import { PersistenceImplementation } from '../persistence/interfaces.js';
19
+ import { IdbPersistence } from '../persistence/idb/idbPersistence.js';
27
20
 
28
21
  export interface ClientDescriptorOptions<Presence = any, Profile = any> {
29
22
  /** The schema used to create this client */
30
23
  schema: StorageSchema<any>;
31
- oldSchemas?: StorageSchema<any>[];
24
+ oldSchemas: StorageSchema<any>[];
32
25
  /** Migrations, in order, to upgrade to each successive version of the schema */
33
26
  migrations: Migration<any>[];
34
27
  /** Provide a sync config to turn on synchronization with a server */
@@ -52,6 +45,7 @@ export interface ClientDescriptorOptions<Presence = any, Profile = any> {
52
45
  ...args: any[]
53
46
  ) => void;
54
47
  disableRebasing?: boolean;
48
+ rebaseTimeout?: number;
55
49
  /**
56
50
  * Provide a specific schema number to override the schema version
57
51
  * in the database. This is useful for testing migrations or recovering
@@ -62,14 +56,13 @@ export interface ClientDescriptorOptions<Presence = any, Profile = any> {
62
56
  /**
63
57
  * Configuration for file management
64
58
  */
65
- files?: FileManagerConfig;
59
+ files?: FileConfig;
66
60
 
67
61
  /**
68
- * Listen for operations as they are applied to the database.
69
- * Wouldn't recommend using this unless you know what you're doing.
70
- * It's a very hot code path...
62
+ * Override the default IndexedDB persistence implementation.
71
63
  */
72
- onOperation?: (operation: Operation) => void;
64
+ persistence?: PersistenceImplementation;
65
+
73
66
  /**
74
67
  * Enables experimental WeakRef usage to cull documents
75
68
  * from cache that aren't being used. This is a performance
@@ -121,7 +114,7 @@ export class ClientDescriptor<
121
114
  // we can't initialize the storage
122
115
  if (typeof window === 'undefined' && !init.indexedDb) {
123
116
  throw new Error(
124
- 'A verdant client was initialized in an environment without IndexedDB. If you are using verdant in a server-rendered framework, you must enforce that all clients are initialized on the client-side, or you must provide some mock interface of IDBFactory to the ClientDescriptor options.',
117
+ 'A Verdant client was initialized in an environment without IndexedDB. If you are using verdant in a server-rendered framework, you must enforce that all clients are initialized on the client-side, or you must provide some mock interface of IDBFactory to the ClientDescriptor options.',
125
118
  );
126
119
  }
127
120
 
@@ -130,17 +123,43 @@ export class ClientDescriptor<
130
123
  }
131
124
  this._initializing = true;
132
125
  try {
133
- let storage: ClientImpl;
134
- if (init.schema.wip) {
135
- storage = await this.initializeWIPDatabases(init);
136
- } else {
137
- storage = await this.initializeDatabases(init);
138
- this.cleanupWIPDatabases(init);
139
- }
140
-
141
- this.resolveReady(storage);
142
- this._resolvedValue = storage;
143
- return storage;
126
+ const time = new Time(
127
+ new HybridLogicalClockTimestampProvider(),
128
+ init.schema.version,
129
+ );
130
+ let ctx: InitialContext = {
131
+ closing: false,
132
+ entityEvents: new EventSubscriber(),
133
+ globalEvents: new EventSubscriber(),
134
+ internalEvents: new EventSubscriber(),
135
+ log: init.log || noop,
136
+ migrations: init.migrations,
137
+ namespace: init.namespace,
138
+ originalNamespace: init.namespace,
139
+ schema: init.schema,
140
+ oldSchemas: init.oldSchemas,
141
+ time,
142
+ undoHistory: init.undoHistory || new UndoHistory(),
143
+ weakRef: (val) =>
144
+ init.EXPERIMENTAL_weakRefs
145
+ ? new WeakRef(val)
146
+ : (new FakeWeakRef(val) as any),
147
+ patchCreator: new PatchCreator(() => time.now),
148
+ config: {
149
+ files: init.files,
150
+ sync: init.sync,
151
+ persistence: {
152
+ disableRebasing: init.disableRebasing,
153
+ rebaseTimeout: init.rebaseTimeout,
154
+ },
155
+ },
156
+ persistence: init.persistence || new IdbPersistence(init.indexedDb),
157
+ };
158
+ const context = await initializePersistence(ctx);
159
+ const client = new Client(context) as ClientImpl;
160
+ this.resolveReady(client);
161
+ this._resolvedValue = client;
162
+ return client;
144
163
  } catch (err) {
145
164
  if (err instanceof Error) {
146
165
  this.rejectReady(err as Error);
@@ -153,156 +172,6 @@ export class ClientDescriptor<
153
172
  }
154
173
  };
155
174
 
156
- private initializeDatabases = async (init: ClientDescriptorOptions) => {
157
- const metadataVersion = init[METADATA_VERSION_KEY];
158
- const { db: metaDb } = await openMetadataDatabase({
159
- indexedDB: init.indexedDb,
160
- log: init.log,
161
- namespace: init.namespace,
162
- metadataVersion,
163
- });
164
-
165
- const context: Omit<Context, 'documentDb' | 'getNow'> = {
166
- namespace: this._namespace,
167
- metaDb,
168
- schema: init.schema,
169
- log: init.log || (() => {}),
170
- undoHistory: init.undoHistory || new UndoHistory(),
171
- entityEvents: new EventSubscriber(),
172
- globalEvents: new EventSubscriber(),
173
- internalEvents: new EventSubscriber(),
174
- weakRef: (value) => {
175
- if (init.EXPERIMENTAL_weakRefs) {
176
- return new WeakRef(value);
177
- } else {
178
- return new FakeWeakRef(value) as unknown as WeakRef<typeof value>;
179
- }
180
- },
181
- migrations: init.migrations,
182
- oldSchemas: init.oldSchemas,
183
- };
184
- const meta = new Metadata({
185
- context,
186
- disableRebasing: init.disableRebasing,
187
- onOperation: init.onOperation,
188
- });
189
-
190
- // verify schema integrity
191
- await meta.updateSchema(init.schema, init.overrideSchemaConflict);
192
-
193
- const contextWithNow: Omit<Context, 'documentDb'> = Object.assign(context, {
194
- getNow: () => meta.now,
195
- });
196
-
197
- const documentDb = await openQueryDatabase({
198
- context: contextWithNow,
199
- version: init.schema.version,
200
- meta,
201
- migrations: init.migrations,
202
- indexedDB: init.indexedDb,
203
- });
204
-
205
- const fullContext: Context = Object.assign(contextWithNow, { documentDb });
206
-
207
- const storage = new Client(
208
- {
209
- syncConfig: init.sync,
210
- migrations: init.migrations,
211
- files: init.files,
212
- },
213
- fullContext,
214
- {
215
- meta,
216
- },
217
- ) as ClientImpl;
218
-
219
- return storage;
220
- };
221
-
222
- private initializeWIPDatabases = async (init: ClientDescriptorOptions) => {
223
- const schemaHash = hashObject(init.schema);
224
- console.info(`WIP schema in use. Opening database with hash ${schemaHash}`);
225
-
226
- const wipNamespace = `@@wip_${init.namespace}_${schemaHash}`;
227
- const { db: metaDb } = await openWIPMetadataDatabase({
228
- indexedDB: init.indexedDb,
229
- log: init.log,
230
- namespace: init.namespace,
231
- wipNamespace: wipNamespace,
232
- });
233
-
234
- const context: Omit<Context, 'documentDb' | 'getNow'> = {
235
- namespace: this._namespace,
236
- metaDb,
237
- schema: init.schema,
238
- log: init.log || (() => {}),
239
- undoHistory: init.undoHistory || new UndoHistory(),
240
- entityEvents: new EventSubscriber(),
241
- globalEvents: new EventSubscriber(),
242
- internalEvents: new EventSubscriber(),
243
- weakRef: (value) => {
244
- if (init.EXPERIMENTAL_weakRefs) {
245
- return new WeakRef(value);
246
- } else {
247
- return new FakeWeakRef(value) as unknown as WeakRef<typeof value>;
248
- }
249
- },
250
- migrations: init.migrations,
251
- oldSchemas: init.oldSchemas,
252
- };
253
- const meta = new Metadata({
254
- context,
255
- disableRebasing: init.disableRebasing,
256
- });
257
-
258
- const contextWithNow: Omit<Context, 'documentDb'> = Object.assign(context, {
259
- getNow: () => meta.now,
260
- });
261
-
262
- // verify schema integrity
263
- await meta.updateSchema(init.schema, init.overrideSchemaConflict);
264
-
265
- const documentDb = await openWIPDatabase({
266
- context: contextWithNow,
267
- version: init.schema.version,
268
- meta,
269
- migrations: init.migrations,
270
- indexedDB: init.indexedDb,
271
- wipNamespace,
272
- });
273
-
274
- const fullContext: Context = Object.assign(contextWithNow, { documentDb });
275
-
276
- const storage = new Client(
277
- {
278
- syncConfig: init.sync,
279
- migrations: init.migrations,
280
- files: init.files,
281
- },
282
- fullContext,
283
- {
284
- meta,
285
- },
286
- ) as ClientImpl;
287
-
288
- return storage;
289
- };
290
-
291
- private cleanupWIPDatabases = async (init: ClientDescriptorOptions) => {
292
- const databaseInfo = await getAllDatabaseNamesAndVersions(init.indexedDb);
293
- const wipDatabases = databaseInfo
294
- .filter((db) => db.name?.startsWith('@@wip_'))
295
- .map((db) => db.name!);
296
- // don't clear a current WIP database.
297
- const wipDatabasesToDelete = wipDatabases.filter(
298
- (db) =>
299
- !db.startsWith(`@@wip_${init.namespace}_${hashObject(init.schema)}`),
300
- );
301
- for (const db of wipDatabasesToDelete) {
302
- await deleteDatabase(db, init.indexedDb);
303
- }
304
- };
305
-
306
175
  get current() {
307
176
  // exposing an immediate value if already resolved lets us
308
177
  // skip the promise microtask when accessing this externally if
@@ -0,0 +1,35 @@
1
+ import { TimestampProvider } from '@verdant-web/common';
2
+
3
+ export class Time {
4
+ private overrideNow?: () => string;
5
+ constructor(
6
+ private base: TimestampProvider,
7
+ private version: number,
8
+ ) {}
9
+
10
+ get now() {
11
+ return this.overrideNow ? this.overrideNow() : this.base.now(this.version);
12
+ }
13
+
14
+ withMigrationTime = async (version: number, run: () => Promise<void>) => {
15
+ this.overrideNow = () => {
16
+ return this.base.zero(version);
17
+ };
18
+ await run();
19
+ this.overrideNow = undefined;
20
+ };
21
+
22
+ update = this.base.update.bind(this.base);
23
+
24
+ nowWithVersion = (version: number) => {
25
+ return this.base.now(version);
26
+ };
27
+
28
+ get zero() {
29
+ return this.base.zero(this.version);
30
+ }
31
+
32
+ zeroWithVersion = (version: number) => {
33
+ return this.base.zero(version);
34
+ };
35
+ }
@@ -0,0 +1,200 @@
1
+ import {
2
+ ClientMessage,
3
+ EventSubscriber,
4
+ FileData,
5
+ FileRef,
6
+ Migration,
7
+ ObjectIdentifier,
8
+ Operation,
9
+ PatchCreator,
10
+ StorageSchema,
11
+ } from '@verdant-web/common';
12
+ import { UndoHistory } from '../UndoHistory.js';
13
+ import { Time } from './Time.js';
14
+ import type { PersistenceQueries } from '../persistence/PersistenceQueries.js';
15
+ import type { PersistenceMetadata } from '../persistence/PersistenceMetadata.js';
16
+ import { PersistenceFiles } from '../persistence/PersistenceFiles.js';
17
+ import {
18
+ PersistedFileData,
19
+ PersistenceImplementation,
20
+ } from '../persistence/interfaces.js';
21
+
22
+ /**
23
+ * Common components utilized across various client
24
+ * services.
25
+ */
26
+ export interface Context {
27
+ namespace: string;
28
+ /**
29
+ * when in WIP mode, namespace might be set to a temporary value. This will always point to the
30
+ * namespace the user passed in.
31
+ */
32
+ originalNamespace: string;
33
+ time: Time;
34
+
35
+ meta: PersistenceMetadata;
36
+ queries: PersistenceQueries;
37
+ files: PersistenceFiles;
38
+
39
+ undoHistory: UndoHistory;
40
+ schema: StorageSchema;
41
+ oldSchemas: StorageSchema[];
42
+ log: (
43
+ level: 'debug' | 'info' | 'warn' | 'error' | 'critical',
44
+ ...args: any[]
45
+ ) => void;
46
+ entityEvents: EventSubscriber<{
47
+ collectionsChanged: (names: string[]) => void;
48
+ documentChanged: (oid: ObjectIdentifier) => void;
49
+ }>;
50
+ internalEvents: EventSubscriber<{
51
+ /**
52
+ * Fired when persisted data changes fundamentally, like resetting to 0,
53
+ * or importing different data.
54
+ */
55
+ persistenceReset: () => void;
56
+ filesDeleted: (files: FileRef[]) => void;
57
+ fileAdded: (file: FileData) => void;
58
+ [ev: `fileUploaded:${string}`]: () => void;
59
+ }>;
60
+ globalEvents: EventSubscriber<{
61
+ /**
62
+ * A change from a future version of the application has been
63
+ * witnessed. These changes are not applied but it indicates
64
+ * the app has been updated and we should prompt the user
65
+ * to reload or have their app user manually reload.
66
+ *
67
+ * The parameter is the timestamp of the future change.
68
+ */
69
+ futureSeen: (timestamp: string) => void;
70
+ /**
71
+ * The server requested this replica reset its state
72
+ * completely. This can happen when the replica has
73
+ * been offline for too long and reconnects.
74
+ */
75
+ resetToServer: () => void;
76
+ /**
77
+ * An operation has been processed by the system. This could be a locally sourced
78
+ * operation or a remote operation from sync.
79
+ */
80
+ operation: (operation: Operation) => void;
81
+ /**
82
+ * Emitted when storage rebases history. This should never actually affect application behavior
83
+ * or stored data, but is useful for debugging and testing.
84
+ */
85
+ rebase: () => void;
86
+ }>;
87
+ weakRef<T extends object>(value: T): WeakRef<T>;
88
+ migrations: Migration<any>[];
89
+ closing: boolean;
90
+ patchCreator: PatchCreator;
91
+
92
+ config: {
93
+ files?: FileConfig;
94
+ sync?: SyncConfig;
95
+ persistence?: PersistenceConfig;
96
+ };
97
+
98
+ persistence: PersistenceImplementation;
99
+ }
100
+
101
+ export interface FileConfig {
102
+ /**
103
+ * Override the heuristic for deciding when a deleted file can be cleaned up.
104
+ * By default this waits 3 days since deletion, then deletes the file data.
105
+ * If the file has been synchronized to a server, it could still be restored
106
+ * if the server has not yet deleted it.
107
+ */
108
+ canCleanupDeletedFile?: (file: PersistedFileData) => boolean;
109
+ }
110
+
111
+ export interface ServerSyncEndpointProviderConfig {
112
+ /**
113
+ * The location of the endpoint used to retrieve an
114
+ * authorization token for the client.
115
+ */
116
+ authEndpoint?: string;
117
+ /**
118
+ * A custom function to retrieve authorization
119
+ * data. Use whatever fetching mechanism you want.
120
+ */
121
+ fetchAuth?: () => Promise<{
122
+ accessToken: string;
123
+ }>;
124
+ /**
125
+ * A spec-compliant fetch implementation. If not provided,
126
+ * the global fetch will be used. authEndpoint will
127
+ * be used to fetch the token.
128
+ */
129
+ fetch?: typeof fetch;
130
+ }
131
+
132
+ export type SyncTransportMode = 'realtime' | 'pull';
133
+
134
+ export interface SyncConfig<Profile = any, Presence = any>
135
+ extends ServerSyncEndpointProviderConfig {
136
+ /**
137
+ * When a client first connects, it will use this presence value.
138
+ */
139
+ initialPresence: Presence;
140
+ /**
141
+ * Before connecting to the server, the local client will have
142
+ * this value for their profile data. You can either cache and store
143
+ * profile data from a previous connection or provide defaults like
144
+ * empty strings.
145
+ */
146
+ defaultProfile: Profile;
147
+
148
+ /**
149
+ * Provide `false` to disable transport selection. Transport selection
150
+ * automatically switches between HTTP and WebSocket based sync depending
151
+ * on the number of peers connected. If a user is alone, they will use
152
+ * HTTP push/pull to sync changes. If another user joins, both users will
153
+ * be upgraded to websockets.
154
+ *
155
+ * Provide `peers-only` to only automatically use websockets if other
156
+ * users connect, but not if another device for the current user connects.
157
+ * By default, automatic transport selection will upgrade to websockets if
158
+ * another device from the current user connects, but if realtime sync is
159
+ * not necessary for such cases, you can save bandwidth by disabling this.
160
+ *
161
+ * Turning off this feature allows you more control over the transport
162
+ * which can be useful for low-power devices or to save server traffic.
163
+ * To modify transport modes manually, utilize `client.sync.setMode`.
164
+ * The built-in behavior is essentially switching modes based on
165
+ * the number of peers detected by client.sync.presence.
166
+ */
167
+ automaticTransportSelection?: boolean | 'peers-only';
168
+ initialTransport?: SyncTransportMode;
169
+ autoStart?: boolean;
170
+ /**
171
+ * Optionally specify an interval, in milliseconds, to poll the server
172
+ * when in pull mode.
173
+ */
174
+ pullInterval?: number;
175
+ /**
176
+ * Presence updates are batched to reduce number of requests / messages
177
+ * sent to the server. You can specify the batching time slice, in milliseconds,
178
+ */
179
+ presenceUpdateBatchTimeout?: number;
180
+ /**
181
+ * Experimental: sync messages over a broadcast channel between tabs.
182
+ * Fixes tabs not reactively updating to changes when other tabs are open,
183
+ * but is not yet thoroughly vetted.
184
+ */
185
+ useBroadcastChannel?: boolean;
186
+ /**
187
+ * Listen for outgoing messages from the client to the server.
188
+ * Not sure why you want to do this, but be careful.
189
+ */
190
+ onOutgoingMessage?: (message: ClientMessage) => void;
191
+
192
+ EXPERIMENTAL_backgroundSync?: boolean;
193
+ }
194
+
195
+ export interface PersistenceConfig {
196
+ disableRebasing?: boolean;
197
+ rebaseTimeout?: number;
198
+ }
199
+
200
+ export type InitialContext = Omit<Context, 'queries' | 'meta' | 'files'>;
@@ -11,9 +11,6 @@ import {
11
11
  AuthorizationKey,
12
12
  } from '@verdant-web/common';
13
13
  import { EntityCreateOptions, EntityStore } from '../entities/EntityStore.js';
14
- import { Metadata } from '../metadata/Metadata.js';
15
- import { Sync } from '../sync/Sync.js';
16
- import { Context } from '../context.js';
17
14
  import { ObjectEntity } from '../index.js';
18
15
 
19
16
  /**
@@ -1,16 +1,16 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
2
  import { Entity } from './Entity.js';
3
3
  import { EntityFamilyMetadata } from './EntityMetadata.js';
4
- import { Context } from '../context.js';
4
+ import { Context } from '../context/context.js';
5
5
  import { EntityStoreEvents } from './EntityStore.js';
6
6
  import { WeakEvent } from 'weak-event';
7
7
  import { FileManager } from '../files/FileManager.js';
8
8
  import {
9
9
  NaiveTimestampProvider,
10
10
  PatchCreator,
11
- createRef,
12
11
  groupPatchesByOid,
13
12
  } from '@verdant-web/common';
13
+ import { Time } from '../context/Time.js';
14
14
 
15
15
  describe('Entity', () => {
16
16
  const schema = {
@@ -60,18 +60,19 @@ describe('Entity', () => {
60
60
  resetAll: new WeakEvent(),
61
61
  };
62
62
  const time = new NaiveTimestampProvider();
63
- const mockContext = {
64
- log: vi.fn(),
65
- getNow: () => time.now(1),
66
- } as any as Context;
67
63
  const patchCreator = new PatchCreator(() => time.now(1));
64
+ const mockContext: Partial<Context> = {
65
+ log: vi.fn(),
66
+ time: new Time(time, 1),
67
+ patchCreator,
68
+ };
68
69
  const entity = new Entity({
69
70
  oid: 'test/1',
70
71
  schema,
71
- ctx: mockContext,
72
+ ctx: mockContext as Context,
72
73
  storeEvents: events,
73
74
  metadataFamily: new EntityFamilyMetadata({
74
- ctx: mockContext,
75
+ ctx: mockContext as Context,
75
76
  onPendingOperations,
76
77
  rootOid: 'test/1',
77
78
  }),
@@ -79,7 +80,6 @@ describe('Entity', () => {
79
80
  add: vi.fn(),
80
81
  get: vi.fn(),
81
82
  } as any as FileManager,
82
- patchCreator,
83
83
  readonlyKeys: ['id'],
84
84
  deleteSelf: vi.fn(),
85
85
  });
@@ -1,12 +1,9 @@
1
1
  import {
2
- DocumentBaseline,
3
2
  EntityValidationProblem,
4
3
  EventSubscriber,
5
4
  ObjectIdentifier,
6
5
  Operation,
7
- PatchCreator,
8
6
  StorageFieldSchema,
9
- StorageFieldsSchema,
10
7
  assert,
11
8
  assignOid,
12
9
  cloneDeep,
@@ -20,15 +17,13 @@ import {
20
17
  isFile,
21
18
  isNullable,
22
19
  isObject,
23
- isObjectRef,
24
- isPrunePoint,
25
20
  isRef,
26
21
  maybeGetOid,
27
22
  memoByKeys,
28
23
  traverseCollectionFieldsAndApplyDefaults,
29
24
  validateEntityField,
30
25
  } from '@verdant-web/common';
31
- import { Context } from '../context.js';
26
+ import { Context } from '../context/context.js';
32
27
  import { FileManager } from '../files/FileManager.js';
33
28
  import { processValueFiles } from '../files/utils.js';
34
29
  import { EntityFile } from '../index.js';
@@ -37,7 +32,6 @@ import { EntityFamilyMetadata, EntityMetadataView } from './EntityMetadata.js';
37
32
  import {
38
33
  BaseEntityValue,
39
34
  DataFromInit,
40
- DeepPartial,
41
35
  EntityChange,
42
36
  EntityEvents,
43
37
  ListEntity,
@@ -58,7 +52,6 @@ export interface EntityInit {
58
52
  files: FileManager;
59
53
  readonlyKeys?: string[];
60
54
  fieldPath?: (string | number)[];
61
- patchCreator: PatchCreator;
62
55
  storeEvents: EntityStoreEvents;
63
56
  deleteSelf: () => void;
64
57
  }
@@ -84,7 +77,6 @@ export class Entity<
84
77
  private parent: Entity | undefined;
85
78
  private ctx;
86
79
  private files;
87
- private patchCreator;
88
80
  private storeEvents;
89
81
 
90
82
  // an internal representation of this Entity.
@@ -110,7 +102,6 @@ export class Entity<
110
102
  metadataFamily,
111
103
  readonlyKeys,
112
104
  files,
113
- patchCreator,
114
105
  storeEvents,
115
106
  deleteSelf,
116
107
  }: EntityInit) {
@@ -128,7 +119,6 @@ export class Entity<
128
119
  new EntityCache({
129
120
  initial: [this],
130
121
  });
131
- this.patchCreator = patchCreator;
132
122
  this.metadataFamily = metadataFamily;
133
123
  this.storeEvents = storeEvents;
134
124
  this.parent = parent;
@@ -160,6 +150,10 @@ export class Entity<
160
150
  return this.metadataFamily.get(this.oid);
161
151
  }
162
152
 
153
+ private get patchCreator() {
154
+ return this.ctx.patchCreator;
155
+ }
156
+
163
157
  /**
164
158
  * The view of this Entity, not including nested
165
159
  * entities (that's the snapshot - see #getSnapshot())
@@ -585,7 +579,6 @@ export class Entity<
585
579
  ctx: this.ctx,
586
580
  files: this.files,
587
581
  fieldPath: [...this.fieldPath, key],
588
- patchCreator: this.patchCreator,
589
582
  storeEvents: this.storeEvents,
590
583
  deleteSelf: this.delete.bind(this, key),
591
584
  });
@@ -635,6 +628,7 @@ export class Entity<
635
628
  }
636
629
  const file = this.files.get(child.id, {
637
630
  downloadRemote: !!fieldSchema.downloadRemote,
631
+ ctx: this.ctx,
638
632
  });
639
633
 
640
634
  // FIXME: this seems bad and inconsistent
@@ -26,15 +26,6 @@ export class EntityCache {
26
26
  return this.cache.has(oid);
27
27
  };
28
28
 
29
- getFile = (id: string, options: { downloadRemote: boolean }): EntityFile => {
30
- if (this.cache.has(id)) {
31
- return this.cache.get(id)! as EntityFile;
32
- }
33
- const file = new EntityFile(id, options);
34
- this.cache.set(id, file);
35
- return file;
36
- };
37
-
38
29
  getCached = (oid: string) => {
39
30
  return this.cache.get(oid);
40
31
  };