@verdant-web/store 4.6.0 → 5.0.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 (130) hide show
  1. package/dist/bundle/index.js +14 -12
  2. package/dist/bundle/index.js.map +4 -4
  3. package/dist/esm/__tests__/fixtures/testStorage.d.ts +1 -1
  4. package/dist/esm/__tests__/fixtures/testStorage.js +3 -3
  5. package/dist/esm/__tests__/fixtures/testStorage.js.map +1 -1
  6. package/dist/esm/__tests__/queries.test.js +3 -3
  7. package/dist/esm/__tests__/queries.test.js.map +1 -1
  8. package/dist/esm/__tests__/schema.test.js +3 -3
  9. package/dist/esm/__tests__/schema.test.js.map +1 -1
  10. package/dist/esm/client/Client.d.ts +12 -10
  11. package/dist/esm/client/Client.js +40 -30
  12. package/dist/esm/client/Client.js.map +1 -1
  13. package/dist/esm/context/Time.d.ts +1 -1
  14. package/dist/esm/context/Time.js +1 -1
  15. package/dist/esm/context/Time.js.map +1 -1
  16. package/dist/esm/context/context.d.ts +84 -15
  17. package/dist/esm/context/context.js +98 -1
  18. package/dist/esm/context/context.js.map +1 -1
  19. package/dist/esm/entities/Entity.test.js +0 -1
  20. package/dist/esm/entities/Entity.test.js.map +1 -1
  21. package/dist/esm/entities/EntityMetadata.js +11 -5
  22. package/dist/esm/entities/EntityMetadata.js.map +1 -1
  23. package/dist/esm/entities/EntityStore.js +9 -7
  24. package/dist/esm/entities/EntityStore.js.map +1 -1
  25. package/dist/esm/files/FileManager.js +5 -5
  26. package/dist/esm/files/FileManager.js.map +1 -1
  27. package/dist/esm/index.d.ts +6 -4
  28. package/dist/esm/index.js +2 -3
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/internal.d.ts +3 -4
  31. package/dist/esm/internal.js +1 -2
  32. package/dist/esm/internal.js.map +1 -1
  33. package/dist/esm/persistence/PersistenceMetadata.d.ts +3 -6
  34. package/dist/esm/persistence/PersistenceMetadata.js +5 -6
  35. package/dist/esm/persistence/PersistenceMetadata.js.map +1 -1
  36. package/dist/esm/persistence/idb/IdbService.d.ts +3 -3
  37. package/dist/esm/persistence/idb/IdbService.js +0 -1
  38. package/dist/esm/persistence/idb/IdbService.js.map +1 -1
  39. package/dist/esm/persistence/idb/idbPersistence.d.ts +9 -10
  40. package/dist/esm/persistence/idb/idbPersistence.js +11 -4
  41. package/dist/esm/persistence/idb/idbPersistence.js.map +1 -1
  42. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.d.ts +2 -2
  43. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js +1 -1
  44. package/dist/esm/persistence/idb/metadata/IdbMetadataDb.js.map +1 -1
  45. package/dist/esm/persistence/idb/queries/IdbDocumentDb.d.ts +3 -2
  46. package/dist/esm/persistence/idb/queries/IdbDocumentDb.js +16 -15
  47. package/dist/esm/persistence/idb/queries/IdbDocumentDb.js.map +1 -1
  48. package/dist/esm/persistence/idb/queries/migration/db.js +7 -0
  49. package/dist/esm/persistence/idb/queries/migration/db.js.map +1 -1
  50. package/dist/esm/persistence/idb/util.js +27 -17
  51. package/dist/esm/persistence/idb/util.js.map +1 -1
  52. package/dist/esm/persistence/interfaces.d.ts +8 -8
  53. package/dist/esm/persistence/migration/engine.d.ts +5 -3
  54. package/dist/esm/persistence/migration/engine.js +23 -14
  55. package/dist/esm/persistence/migration/engine.js.map +1 -1
  56. package/dist/esm/persistence/migration/finalize.d.ts +5 -3
  57. package/dist/esm/persistence/migration/finalize.js +5 -4
  58. package/dist/esm/persistence/migration/finalize.js.map +1 -1
  59. package/dist/esm/persistence/migration/migrate.d.ts +8 -5
  60. package/dist/esm/persistence/migration/migrate.js +10 -4
  61. package/dist/esm/persistence/migration/migrate.js.map +1 -1
  62. package/dist/esm/persistence/persistence.d.ts +9 -2
  63. package/dist/esm/persistence/persistence.js +65 -32
  64. package/dist/esm/persistence/persistence.js.map +1 -1
  65. package/dist/esm/queries/FindAllQuery.js +1 -1
  66. package/dist/esm/queries/FindAllQuery.js.map +1 -1
  67. package/dist/esm/queries/FindInfiniteQuery.js +2 -2
  68. package/dist/esm/queries/FindInfiniteQuery.js.map +1 -1
  69. package/dist/esm/queries/FindOneQuery.js +1 -1
  70. package/dist/esm/queries/FindOneQuery.js.map +1 -1
  71. package/dist/esm/queries/FindPageQuery.js +1 -1
  72. package/dist/esm/queries/FindPageQuery.js.map +1 -1
  73. package/dist/esm/sync/Heartbeat.js +2 -0
  74. package/dist/esm/sync/Heartbeat.js.map +1 -1
  75. package/dist/esm/sync/PresenceManager.d.ts +2 -2
  76. package/dist/esm/sync/PresenceManager.js +5 -2
  77. package/dist/esm/sync/PresenceManager.js.map +1 -1
  78. package/dist/esm/sync/PushPullSync.js +10 -4
  79. package/dist/esm/sync/PushPullSync.js.map +1 -1
  80. package/dist/esm/sync/Sync.d.ts +2 -2
  81. package/dist/esm/sync/Sync.js +17 -14
  82. package/dist/esm/sync/Sync.js.map +1 -1
  83. package/dist/esm/sync/WebSocketSync.js +12 -7
  84. package/dist/esm/sync/WebSocketSync.js.map +1 -1
  85. package/dist/esm/sync/serviceWorker.d.ts +2 -2
  86. package/dist/esm/sync/serviceWorker.js +3 -4
  87. package/dist/esm/sync/serviceWorker.js.map +1 -1
  88. package/package.json +2 -2
  89. package/src/__tests__/fixtures/testStorage.ts +4 -8
  90. package/src/__tests__/queries.test.ts +3 -5
  91. package/src/__tests__/schema.test.ts +3 -3
  92. package/src/client/Client.ts +50 -34
  93. package/src/context/Time.ts +2 -2
  94. package/src/context/context.ts +189 -17
  95. package/src/entities/Entity.test.ts +0 -1
  96. package/src/entities/EntityMetadata.ts +16 -5
  97. package/src/entities/EntityStore.ts +14 -10
  98. package/src/files/FileManager.ts +5 -5
  99. package/src/index.ts +6 -10
  100. package/src/internal.ts +10 -11
  101. package/src/persistence/PersistenceMetadata.ts +9 -11
  102. package/src/persistence/idb/IdbService.ts +2 -3
  103. package/src/persistence/idb/idbPersistence.ts +25 -19
  104. package/src/persistence/idb/metadata/IdbMetadataDb.ts +3 -3
  105. package/src/persistence/idb/queries/IdbDocumentDb.ts +31 -17
  106. package/src/persistence/idb/queries/migration/db.ts +10 -0
  107. package/src/persistence/idb/util.ts +46 -24
  108. package/src/persistence/interfaces.ts +21 -12
  109. package/src/persistence/migration/engine.ts +33 -18
  110. package/src/persistence/migration/finalize.ts +11 -5
  111. package/src/persistence/migration/migrate.ts +15 -8
  112. package/src/persistence/persistence.ts +78 -67
  113. package/src/queries/FindAllQuery.ts +3 -1
  114. package/src/queries/FindInfiniteQuery.ts +6 -2
  115. package/src/queries/FindOneQuery.ts +3 -1
  116. package/src/queries/FindPageQuery.ts +3 -1
  117. package/src/sync/Heartbeat.ts +2 -0
  118. package/src/sync/PresenceManager.ts +10 -7
  119. package/src/sync/PushPullSync.ts +18 -6
  120. package/src/sync/Sync.ts +26 -16
  121. package/src/sync/WebSocketSync.ts +25 -9
  122. package/src/sync/serviceWorker.ts +4 -6
  123. package/dist/esm/client/ClientDescriptor.d.ts +0 -87
  124. package/dist/esm/client/ClientDescriptor.js +0 -133
  125. package/dist/esm/client/ClientDescriptor.js.map +0 -1
  126. package/dist/esm/persistence/migration/types.d.ts +0 -3
  127. package/dist/esm/persistence/migration/types.js +0 -2
  128. package/dist/esm/persistence/migration/types.js.map +0 -1
  129. package/src/client/ClientDescriptor.ts +0 -246
  130. package/src/persistence/migration/types.ts +0 -4
@@ -1,5 +1,5 @@
1
- import { EventSubscriber, getOidRoot, VerdantError } from '@verdant-web/common';
2
- import { Context, InitialContext } from '../context/context.js';
1
+ import { getOidRoot, VerdantError } from '@verdant-web/common';
2
+ import { Context } from '../context/context.js';
3
3
  import { ShutdownHandler } from '../context/ShutdownHandler.js';
4
4
  import { getWipNamespace } from '../utils/wip.js';
5
5
  import { ExportedData } from './interfaces.js';
@@ -8,28 +8,26 @@ import { PersistenceFiles } from './PersistenceFiles.js';
8
8
  import { PersistenceMetadata } from './PersistenceMetadata.js';
9
9
  import { PersistenceDocuments } from './PersistenceQueries.js';
10
10
 
11
- export async function initializePersistence(
12
- ctx: InitialContext,
13
- ): Promise<Context> {
14
- let context = ctx as any as Context;
11
+ export async function initializePersistence(ctx: Context) {
12
+ const initialSchema = ctx.schema;
15
13
  if (ctx.schema.wip) {
16
14
  // this is a WIP database, so we need to create a new namespace for the WIP data.
17
- context.namespace = getWipNamespace(ctx.originalNamespace, ctx.schema);
18
- context.log('info', 'Switched to WIP namespace', context.namespace);
15
+ ctx.namespace = getWipNamespace(ctx.originalNamespace, ctx.schema);
16
+ ctx.log('info', '🔨', 'Switched to WIP namespace', ctx.namespace);
19
17
  // check if this WIP database is already in use
20
- const namespaces = await context.persistence.getNamespaces();
18
+ const namespaces = await ctx.persistence.getNamespaces();
21
19
 
22
- if (!namespaces.includes(context.namespace)) {
20
+ if (!namespaces.includes(ctx.namespace)) {
23
21
  // copy all data to WIP namespace -- from the current version of local
24
22
  // data, not the WIP schema version. this may not be n-1, we might
25
23
  // be loading a WIP schema over older data.
26
- const currentVersion = await context.persistence.getNamespaceVersion(
27
- context.originalNamespace,
24
+ const currentVersion = await ctx.persistence.getNamespaceVersion(
25
+ ctx.originalNamespace,
28
26
  );
29
27
 
30
28
  if (currentVersion === 0) {
31
29
  // there is no existing data. nothing to copy.
32
- context.log('debug', 'No existing data to copy to WIP namespace');
30
+ ctx.log('debug', 'No existing data to copy to WIP namespace');
33
31
  } else {
34
32
  const currentSchema = ctx.oldSchemas?.find(
35
33
  (s) => s.version === currentVersion,
@@ -41,52 +39,62 @@ export async function initializePersistence(
41
39
  `Trying to open WIP database for version ${ctx.schema.version}, but the current local data is version ${currentVersion} and a historical schema for that version is not available.`,
42
40
  );
43
41
  }
44
- context.log(
42
+ ctx.log(
45
43
  'info',
46
- `Copying data from ${context.originalNamespace} to ${context.namespace}`,
44
+ `Copying data from ${ctx.originalNamespace} to ${ctx.namespace}`,
47
45
  );
48
- await context.persistence.copyNamespace(
49
- context.originalNamespace,
50
- context.namespace,
46
+ await ctx.persistence.copyNamespace(
47
+ ctx.originalNamespace,
48
+ ctx.namespace,
51
49
  // needs to be the original schema; the copy should be of the original
52
50
  // data and schema structure; the WIP schema migration application happens
53
51
  // below.
54
- {
55
- ...context,
52
+ ctx.cloneWithOptions({
56
53
  schema: currentSchema,
57
- },
54
+ }),
58
55
  );
59
56
  }
60
57
  }
61
58
  }
62
59
 
63
- const namespace = await ctx.persistence.openNamespace(
64
- context.namespace,
65
- context,
66
- );
60
+ const namespace = await ctx.persistence.openNamespace(ctx.namespace, ctx);
67
61
 
68
- context.log('info', 'Opening persistence metadata');
69
- context.meta = new PersistenceMetadata(
70
- await namespace.openMetadata(ctx),
71
- ctx,
72
- );
62
+ ctx.log('info', 'Opening persistence metadata', ctx.namespace);
63
+ const meta = new PersistenceMetadata(await namespace.openMetadata(ctx), ctx);
73
64
 
74
- context.log('info', 'Opening persistence files');
75
- context.files = new PersistenceFiles(
76
- await namespace.openFiles(context),
77
- context,
78
- );
65
+ ctx.log('info', 'Opening persistence files', ctx.namespace);
66
+ const files = new PersistenceFiles(await namespace.openFiles(ctx), ctx);
79
67
 
80
- context.log('info', 'Migrating document database');
68
+ ctx.log('info', 'Migrating document database');
81
69
  await migrate({
82
- context,
70
+ context: ctx,
83
71
  version: ctx.schema.version,
72
+ meta,
84
73
  });
85
74
 
86
- context.log('info', 'Opening persistence documents');
87
- context.documents = new PersistenceDocuments(
88
- await namespace.openDocuments(context),
89
- context,
75
+ ctx.log('info', 'Opening persistence documents');
76
+ if (ctx.schema.version <= 0) {
77
+ // debugging....
78
+ if (ctx.schema !== initialSchema) {
79
+ ctx.log(
80
+ 'critical',
81
+ 'Schema at initialization does not match original schema. This is likely a bug in Verdant!',
82
+ );
83
+ throw new VerdantError(
84
+ VerdantError.Code.ConfigurationError,
85
+ undefined,
86
+ `Schema at initialization does not match original schema. This is likely a bug in Verdant!`,
87
+ );
88
+ }
89
+ throw new VerdantError(
90
+ VerdantError.Code.ConfigurationError,
91
+ undefined,
92
+ `Schema version must be greater than 0. Found version ${ctx.schema.version} with collections [${Object.keys(ctx.schema.collections).join(', ')}]\n${JSON.stringify(ctx.schema)}`,
93
+ );
94
+ }
95
+ const documents = new PersistenceDocuments(
96
+ await namespace.openDocuments(ctx),
97
+ ctx,
90
98
  );
91
99
 
92
100
  if (!ctx.schema.wip) {
@@ -100,7 +108,7 @@ export async function initializePersistence(
100
108
  }
101
109
  }
102
110
 
103
- return context;
111
+ return { meta, files, documents };
104
112
  }
105
113
 
106
114
  export async function importPersistence(
@@ -123,26 +131,17 @@ export async function importPersistence(
123
131
  // using a new namespace to put all of this into a temporary zone
124
132
  const importedNamespace = `@@import_${Date.now()}`;
125
133
 
126
- const importedContext = await initializePersistence({
127
- ...ctx,
134
+ const importedContext = ctx.cloneWithOptions({
128
135
  schema: exportedSchema,
129
136
  namespace: importedNamespace,
130
- originalNamespace: importedNamespace,
131
- // no-op entity events -- don't need to inform queries of changes.
132
- entityEvents: new EventSubscriber(),
133
- internalEvents: new EventSubscriber(),
134
- globalEvents: new EventSubscriber(),
135
- config: {
136
- ...ctx.config,
137
- persistence: {
138
- ...ctx.config.persistence,
139
- disableRebasing: true,
140
- },
141
- },
137
+ disableRebasing: true,
142
138
  persistenceShutdownHandler: new ShutdownHandler(ctx.log),
143
139
  });
140
+ await importedContext.reinitialize();
141
+ const importedMeta = await importedContext.meta;
142
+
144
143
  // load imported data into persistence
145
- await importedContext.meta.resetFrom(exportedData.data);
144
+ await importedMeta.resetFrom(exportedData.data);
146
145
  // need to write indexes here!
147
146
  const affectedOids = new Set<string>();
148
147
  for (const baseline of exportedData.data.baselines) {
@@ -153,15 +152,15 @@ export async function importPersistence(
153
152
  }
154
153
  const toSave = await Promise.all(
155
154
  Array.from(affectedOids).map(async (oid) => {
156
- const snapshot = await importedContext.meta.getDocumentSnapshot(oid);
155
+ const snapshot = await importedMeta.getDocumentSnapshot(oid);
157
156
  return {
158
157
  oid,
159
158
  getSnapshot: () => snapshot,
160
159
  };
161
160
  }),
162
161
  );
163
- await importedContext.documents.saveEntities(toSave);
164
- await importedContext.files.import(exportedData);
162
+ await (await importedContext.documents).saveEntities(toSave);
163
+ await (await importedContext.files).import(exportedData);
165
164
 
166
165
  ctx.log('debug', 'Imported data into temporary namespace', importedNamespace);
167
166
 
@@ -172,13 +171,14 @@ export async function importPersistence(
172
171
  // an upgrade of the imported data is needed ; it's an older version
173
172
  // of the schema.
174
173
 
175
- // upgrade the imported data to the latest schema
176
- const currentSchema = ctx.schema;
177
- const upgradedContext = await initializePersistence({
178
- ...importedContext,
174
+ // upgrade the imported data to the latest schema by re-initializing
175
+ // a context at the latest version, pointing at the imported namespace
176
+ const upgradedContext = importedContext.cloneWithOptions({
179
177
  persistenceShutdownHandler: new ShutdownHandler(ctx.log),
180
- schema: currentSchema,
178
+ schema: ctx.schema,
179
+ oldSchemas: ctx.oldSchemas,
181
180
  });
181
+ await upgradedContext.reinitialize();
182
182
 
183
183
  ctx.log('debug', 'Upgraded imported data to current schema');
184
184
 
@@ -192,15 +192,17 @@ export async function importPersistence(
192
192
 
193
193
  // copy the imported data into the current namespace
194
194
  await ctx.persistence.copyNamespace(importedNamespace, ctx.namespace, ctx);
195
+ ctx.log('debug', 'Copied imported data to primary namespace');
195
196
 
196
197
  // restart the persistence layer
197
- await initializePersistence(ctx);
198
+ await ctx.reinitialize();
199
+ ctx.log('debug', 'Reinitialized primary persistence layer');
198
200
 
199
201
  // verify integrity -- this can only be done if imported data was same
200
202
  // version as current schema, because migrations could add or remove
201
203
  // operations. still, it's a good sanity check.
202
204
  if (exportedData.data.schemaVersion === ctx.schema.version) {
203
- const stats = await ctx.meta.stats();
205
+ const stats = await (await ctx.meta).stats();
204
206
  if (stats.operationsSize.count !== exportedData.data.operations.length) {
205
207
  ctx.log(
206
208
  'critical',
@@ -231,6 +233,15 @@ export async function importPersistence(
231
233
  'Imported documents count mismatch',
232
234
  );
233
235
  }
236
+ } else {
237
+ ctx.log(
238
+ 'debug',
239
+ 'Skipping integrity check due to schema version mismatch (not an error)',
240
+ {
241
+ exportedVersion: exportedData.data.schemaVersion,
242
+ currentVersion: ctx.schema.version,
243
+ },
244
+ );
234
245
  }
235
246
 
236
247
  ctx.log('debug', 'Data copied to primary namespace');
@@ -23,7 +23,9 @@ export class FindAllQuery<T> extends BaseQuery<T[]> {
23
23
  }
24
24
 
25
25
  protected run = async () => {
26
- const { result: oids } = await this.context.documents.findAllOids({
26
+ const { result: oids } = await (
27
+ await this.context.documents
28
+ ).findAllOids({
27
29
  collection: this.collection,
28
30
  index: this.index,
29
31
  });
@@ -37,7 +37,9 @@ export class FindInfiniteQuery<T> extends BaseQuery<T[]> {
37
37
  }
38
38
 
39
39
  protected run = async () => {
40
- const { result, hasNextPage } = await this.context.documents.findAllOids({
40
+ const { result, hasNextPage } = await (
41
+ await this.context.documents
42
+ ).findAllOids({
41
43
  collection: this.collection,
42
44
  limit: this._pageSize * this._upToPage,
43
45
  offset: 0,
@@ -48,7 +50,9 @@ export class FindInfiniteQuery<T> extends BaseQuery<T[]> {
48
50
  };
49
51
 
50
52
  public loadMore = async () => {
51
- const { result, hasNextPage } = await this.context.documents.findAllOids({
53
+ const { result, hasNextPage } = await (
54
+ await this.context.documents
55
+ ).findAllOids({
52
56
  collection: this.collection,
53
57
  limit: this._pageSize,
54
58
  offset: this._pageSize * this._upToPage,
@@ -23,7 +23,9 @@ export class FindOneQuery<T> extends BaseQuery<T | null> {
23
23
  }
24
24
 
25
25
  protected run = async () => {
26
- const oid = await this.context.documents.findOneOid({
26
+ const oid = await (
27
+ await this.context.documents
28
+ ).findOneOid({
27
29
  collection: this.collection,
28
30
  index: this.index,
29
31
  });
@@ -48,7 +48,9 @@ export class FindPageQuery<T> extends BaseQuery<T[]> {
48
48
  }
49
49
 
50
50
  protected run = async () => {
51
- const { result, hasNextPage } = await this.context.documents.findAllOids({
51
+ const { result, hasNextPage } = await (
52
+ await this.context.documents
53
+ ).findAllOids({
52
54
  collection: this.collection,
53
55
  index: this.index,
54
56
  limit: this._pageSize,
@@ -55,9 +55,11 @@ export class Heartbeat extends EventSubscriber<{
55
55
  stop = () => {
56
56
  if (this.nextBeat) {
57
57
  clearTimeout(this.nextBeat);
58
+ this.nextBeat = null;
58
59
  }
59
60
  if (this.deadline) {
60
61
  clearTimeout(this.deadline);
62
+ this.deadline = null;
61
63
  }
62
64
  };
63
65
 
@@ -1,13 +1,13 @@
1
1
  import {
2
- ServerMessage,
3
- EventSubscriber,
4
- Batcher,
5
2
  Batch,
3
+ Batcher,
4
+ EventSubscriber,
5
+ ServerMessage,
6
6
  VerdantInternalPresence,
7
7
  initialInternalPresence,
8
8
  } from '@verdant-web/common';
9
- import type { UserInfo } from '../index.js';
10
9
  import { Context } from '../context/context.js';
10
+ import type { UserInfo } from '../index.js';
11
11
  import { LocalReplicaInfo } from '../persistence/interfaces.js';
12
12
 
13
13
  export const HANDLE_MESSAGE = Symbol('handleMessage');
@@ -90,9 +90,12 @@ export class PresenceManager<
90
90
  this.self.replicaId = '';
91
91
 
92
92
  // set the local replica ID as soon as it's loaded
93
- ctx.meta.getLocalReplica().then((info) => {
94
- this.self.replicaId = info.id;
95
- });
93
+ ctx.waitForInitialization
94
+ .then(() => ctx.meta)
95
+ .then((meta) => meta.getLocalReplica())
96
+ .then((info) => {
97
+ this.self.replicaId = info.id;
98
+ });
96
99
 
97
100
  this._updateBatcher = new Batcher(this.flushPresenceUpdates);
98
101
  this._updateBatch = this._updateBatcher.add({
@@ -140,7 +140,9 @@ export class PushPullSync
140
140
  if (message.ackThisNonce) {
141
141
  this.ctx.log('debug', 'Sending sync ack', message.ackThisNonce);
142
142
  await this.sendRequest([
143
- await this.ctx.meta.messageCreator.createAck(message.ackThisNonce),
143
+ await (
144
+ await this.ctx.meta
145
+ ).messageCreator.createAck(message.ackThisNonce),
144
146
  ]);
145
147
  }
146
148
  }
@@ -154,6 +156,14 @@ export class PushPullSync
154
156
  }, 3000);
155
157
 
156
158
  send = (message: ClientMessage) => {
159
+ if (this.status !== 'active') {
160
+ this.ctx.log(
161
+ 'warn',
162
+ 'Attempted to send message while sync is not active',
163
+ message,
164
+ );
165
+ return;
166
+ }
157
167
  // only certain messages are sent for pull-based sync.
158
168
  switch (message.type) {
159
169
  case 'presence-update':
@@ -173,11 +183,13 @@ export class PushPullSync
173
183
  if (this.status === 'active') {
174
184
  return;
175
185
  }
186
+ this.ctx.log('debug', 'Starting push-pull sync');
176
187
  await this.endpointProvider.getEndpoints();
177
188
  this.heartbeat.start(true);
178
189
  this._status = 'active';
179
190
  };
180
191
  stop(): void {
192
+ this.ctx.log('debug', 'Stopping push-pull sync');
181
193
  this.heartbeat.stop();
182
194
  this._status = 'paused';
183
195
  }
@@ -200,10 +212,10 @@ export class PushPullSync
200
212
  // will include the client's own presence info and fill in missing profile
201
213
  // data on the first request. otherwise it would have to wait for the second.
202
214
  this.sendRequest([
203
- await this.ctx.meta.messageCreator.createPresenceUpdate(
204
- this.presence.self,
205
- ),
206
- await this.ctx.meta.messageCreator.createSyncStep1(),
215
+ await (
216
+ await this.ctx.meta
217
+ ).messageCreator.createPresenceUpdate(this.presence.self),
218
+ await (await this.ctx.meta).messageCreator.createSyncStep1(),
207
219
  ]);
208
220
  };
209
221
 
@@ -217,7 +229,7 @@ export class PushPullSync
217
229
 
218
230
  syncOnce = async () => {
219
231
  await this.sendRequest([
220
- await this.ctx.meta.messageCreator.createSyncStep1(),
232
+ await (await this.ctx.meta).messageCreator.createSyncStep1(),
221
233
  ]);
222
234
  };
223
235
 
package/src/sync/Sync.ts CHANGED
@@ -10,16 +10,16 @@ import {
10
10
  VerdantError,
11
11
  VerdantInternalPresence,
12
12
  } from '@verdant-web/common';
13
- import { HANDLE_MESSAGE, PresenceManager } from './PresenceManager.js';
13
+ import { Context } from '../context/context.js';
14
+ import { attemptToRegisterBackgroundSync } from './background.js';
14
15
  import { FilePullResult, FileSync, FileUploadResult } from './FileSync.js';
16
+ import { HANDLE_MESSAGE, PresenceManager } from './PresenceManager.js';
15
17
  import { PushPullSync } from './PushPullSync.js';
16
18
  import {
17
19
  ServerSyncEndpointProvider,
18
20
  ServerSyncEndpointProviderConfig,
19
21
  } from './ServerSyncEndpointProvider.js';
20
22
  import { WebSocketSync } from './WebSocketSync.js';
21
- import { Context } from '../context/context.js';
22
- import { attemptToRegisterBackgroundSync } from './background.js';
23
23
 
24
24
  type SyncEvents = {
25
25
  onlineChange: (isOnline: boolean) => void;
@@ -282,6 +282,11 @@ export class ServerSync<Presence = any, Profile = any>
282
282
  this.handleBroadcastChannelMessage,
283
283
  );
284
284
  }
285
+ ctx.log(
286
+ 'info',
287
+ 'Sync initialized with transport:',
288
+ initialTransport ?? 'pull',
289
+ );
285
290
  if (initialTransport === 'realtime') {
286
291
  this.activeSync = this.webSocketSync;
287
292
  } else {
@@ -290,8 +295,7 @@ export class ServerSync<Presence = any, Profile = any>
290
295
 
291
296
  this.presence.subscribe('update', this.handlePresenceUpdate);
292
297
 
293
- ctx.meta.events.subscribe('syncMessage', this.send);
294
-
298
+ ctx.internalEvents.subscribe('outgoingSyncMessage', this.send);
295
299
  this.webSocketSync.subscribe('message', this.handleMessage);
296
300
  this.webSocketSync.subscribe('onlineChange', this.handleOnlineChange);
297
301
 
@@ -384,11 +388,11 @@ export class ServerSync<Presence = any, Profile = any>
384
388
  baselines: message.baselines,
385
389
  });
386
390
  if (message.globalAckTimestamp) {
387
- await this.ctx.meta.setGlobalAck(message.globalAckTimestamp);
391
+ await (await this.ctx.meta).setGlobalAck(message.globalAckTimestamp);
388
392
  }
389
393
  break;
390
394
  case 'global-ack':
391
- await this.ctx.meta.setGlobalAck(message.timestamp);
395
+ await (await this.ctx.meta).setGlobalAck(message.timestamp);
392
396
  break;
393
397
  case 'sync-resp':
394
398
  this._activelySyncing = true;
@@ -400,10 +404,10 @@ export class ServerSync<Presence = any, Profile = any>
400
404
  });
401
405
 
402
406
  if (message.globalAckTimestamp) {
403
- await this.ctx.meta.setGlobalAck(message.globalAckTimestamp);
407
+ await (await this.ctx.meta).setGlobalAck(message.globalAckTimestamp);
404
408
  }
405
409
 
406
- await this.ctx.meta.updateLastSynced(message.ackedTimestamp);
410
+ await (await this.ctx.meta).updateLastSynced(message.ackedTimestamp);
407
411
  this._activelySyncing = false;
408
412
  this.emit('syncingChange', false);
409
413
  this._hasSynced = true;
@@ -411,13 +415,15 @@ export class ServerSync<Presence = any, Profile = any>
411
415
  break;
412
416
  case 'need-since':
413
417
  this.emit('serverReset', message.since);
414
- this.ctx.files.onServerReset(message.since);
418
+ (await this.ctx.files).onServerReset(message.since);
415
419
  this.activeSync.send(
416
- await this.ctx.meta.messageCreator.createSyncStep1(message.since),
420
+ await (
421
+ await this.ctx.meta
422
+ ).messageCreator.createSyncStep1(message.since),
417
423
  );
418
424
  break;
419
425
  case 'server-ack':
420
- await this.ctx.meta.updateLastSynced(message.timestamp);
426
+ await (await this.ctx.meta).updateLastSynced(message.timestamp);
421
427
  }
422
428
 
423
429
  // avoid rebroadcasting messages
@@ -430,7 +436,7 @@ export class ServerSync<Presence = any, Profile = any>
430
436
 
431
437
  // update presence if necessary
432
438
  this.presence[HANDLE_MESSAGE](
433
- await this.ctx.meta.getLocalReplica(),
439
+ await (await this.ctx.meta).getLocalReplica(),
434
440
  message,
435
441
  );
436
442
  };
@@ -439,7 +445,7 @@ export class ServerSync<Presence = any, Profile = any>
439
445
 
440
446
  // if online, attempt to upload any unsynced files.
441
447
  if (online) {
442
- const unsyncedFiles = await this.ctx.files.listUnsynced();
448
+ const unsyncedFiles = await (await this.ctx.files).listUnsynced();
443
449
  const results = await Promise.allSettled(
444
450
  unsyncedFiles.map((file) => this.fileSync.uploadFile(file)),
445
451
  );
@@ -458,7 +464,9 @@ export class ServerSync<Presence = any, Profile = any>
458
464
  presence?: Presence;
459
465
  internal?: VerdantInternalPresence;
460
466
  }) => {
461
- this.send(await this.ctx.meta.messageCreator.createPresenceUpdate(data));
467
+ this.send(
468
+ await (await this.ctx.meta).messageCreator.createPresenceUpdate(data),
469
+ );
462
470
  };
463
471
 
464
472
  setMode = (transport: SyncTransportMode) => {
@@ -512,7 +520,7 @@ export class ServerSync<Presence = any, Profile = any>
512
520
  if (message.type === 'sync' || message.type === 'op') {
513
521
  rewriteAuthzOriginator(message, userId);
514
522
  }
515
- await this.activeSync.send(message);
523
+ this.activeSync.send(message);
516
524
  this.onOutgoingMessage?.(message);
517
525
  }
518
526
  };
@@ -570,10 +578,12 @@ export class ServerSync<Presence = any, Profile = any>
570
578
  };
571
579
 
572
580
  public start = () => {
581
+ this.ctx.log('info', 'Starting sync');
573
582
  return this.activeSync.start();
574
583
  };
575
584
 
576
585
  public stop = () => {
586
+ this.ctx.log('info', 'Stopping sync');
577
587
  return this.activeSync.stop();
578
588
  };
579
589
 
@@ -93,12 +93,12 @@ export class WebSocketSync
93
93
  this.ctx.log('debug', 'Starting sync');
94
94
  this.hasStartedSync = true;
95
95
  this.synced = false;
96
+ const meta = await this.ctx.meta;
97
+ this.ctx.log('debug', 'HERE');
96
98
  this.send(
97
- await this.ctx.meta.messageCreator.createPresenceUpdate(
98
- this.presence.self,
99
- ),
99
+ await meta.messageCreator.createPresenceUpdate(this.presence.self),
100
100
  );
101
- this.send(await this.ctx.meta.messageCreator.createSyncStep1());
101
+ this.send(await meta.messageCreator.createSyncStep1());
102
102
  this.heartbeat.start();
103
103
  }
104
104
  this.emit('onlineChange', online);
@@ -122,7 +122,9 @@ export class WebSocketSync
122
122
  if (message.ackThisNonce) {
123
123
  // we need to send the ack to confirm we got the response
124
124
  this.send(
125
- await this.ctx.meta.messageCreator.createAck(message.ackThisNonce),
125
+ await (
126
+ await this.ctx.meta
127
+ ).messageCreator.createAck(message.ackThisNonce),
126
128
  );
127
129
  }
128
130
  this.hasStartedSync = true;
@@ -179,7 +181,7 @@ export class WebSocketSync
179
181
  };
180
182
 
181
183
  private onError = (event: Event) => {
182
- this.ctx.log('error', 'Sync socket error', event);
184
+ this.ctx.log('error', 'Sync socket error', event, event.target);
183
185
  if (this.disposed) return;
184
186
  this.reconnectScheduler.next();
185
187
 
@@ -187,7 +189,7 @@ export class WebSocketSync
187
189
  };
188
190
 
189
191
  private onClose = (event: CloseEvent) => {
190
- this.ctx.log('info', 'Sync socket disconnected');
192
+ this.ctx.log('info', 'Sync socket disconnected', event.code);
191
193
  this.onOnlineChange(false);
192
194
  if (this.disposed) return;
193
195
  this.reconnectScheduler.next();
@@ -209,7 +211,7 @@ export class WebSocketSync
209
211
  };
210
212
 
211
213
  private sendHeartbeat = async () => {
212
- this.send(await this.ctx.meta.messageCreator.createHeartbeat());
214
+ this.send(await (await this.ctx.meta).messageCreator.createHeartbeat());
213
215
  };
214
216
 
215
217
  reconnect = () => {
@@ -227,11 +229,25 @@ export class WebSocketSync
227
229
  };
228
230
 
229
231
  send = (message: ClientMessage) => {
230
- if (this.status !== 'active') return;
232
+ if (this.status !== 'active') {
233
+ this.ctx.log(
234
+ 'debug',
235
+ 'Ignoring outgoing message',
236
+ message.type,
237
+ 'sync is not active',
238
+ );
239
+ return;
240
+ }
231
241
 
232
242
  // wait until a sync has started before doing anything other than sync.
233
243
  // new "op" messages can arrive before sync has started, so we need to wait
234
244
  if (!this.hasStartedSync && !this.canSkipSyncWait(message)) {
245
+ this.ctx.log(
246
+ 'debug',
247
+ 'Ignoring outgoing message',
248
+ message.type,
249
+ 'still waiting to begin initial sync',
250
+ );
235
251
  return;
236
252
  }
237
253
 
@@ -1,19 +1,17 @@
1
- import type { ClientDescriptor } from '../client/ClientDescriptor.js';
1
+ import type { Client } from '../client/Client.js';
2
2
 
3
- export async function registerBackgroundSync(clientDesc: ClientDescriptor) {
3
+ export async function registerBackgroundSync(client: Client) {
4
4
  self.addEventListener('periodicsync', (event: any) => {
5
5
  if (event.tag === 'verdant-sync') {
6
6
  // See the "Think before you sync" section for
7
7
  // checks you could perform before syncing.
8
- event.waitUntil(sync(clientDesc));
8
+ event.waitUntil(sync(client));
9
9
  }
10
10
  });
11
11
  }
12
12
 
13
- async function sync(clientDesc: ClientDescriptor) {
13
+ async function sync(client: Client) {
14
14
  try {
15
- const client = await clientDesc.open();
16
-
17
15
  await client.sync.syncOnce();
18
16
  } catch (err) {
19
17
  console.error('Failed to sync:', err);