@dxos/client-services 0.8.4-main.ef1bc66f44 → 0.8.4-main.f466a3d56e

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 (203) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/browser/{chunk-NTZOEM4P.mjs → chunk-SNHT4M6V.mjs} +1731 -3089
  4. package/dist/lib/browser/chunk-SNHT4M6V.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-NQSC7HOE.mjs → chunk-XJRPB3GA.mjs} +1 -1
  6. package/dist/lib/browser/index.mjs +106 -194
  7. package/dist/lib/browser/index.mjs.map +3 -3
  8. package/dist/lib/browser/meta.json +1 -1
  9. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +1 -6
  10. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +2 -2
  11. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +1 -1
  12. package/dist/lib/browser/packlets/locks/browser.mjs +9 -49
  13. package/dist/lib/browser/packlets/locks/browser.mjs.map +2 -2
  14. package/dist/lib/browser/packlets/locks/node.mjs +4 -22
  15. package/dist/lib/browser/packlets/locks/node.mjs.map +2 -2
  16. package/dist/lib/browser/testing/index.mjs +9 -22
  17. package/dist/lib/browser/testing/index.mjs.map +3 -3
  18. package/dist/lib/node-esm/{chunk-PKEGMOQ4.mjs → chunk-2DT3MZRL.mjs} +1 -1
  19. package/dist/lib/node-esm/{chunk-DQWA5ILA.mjs → chunk-2TVKX6UU.mjs} +1731 -3089
  20. package/dist/lib/node-esm/chunk-2TVKX6UU.mjs.map +7 -0
  21. package/dist/lib/node-esm/index.mjs +106 -194
  22. package/dist/lib/node-esm/index.mjs.map +3 -3
  23. package/dist/lib/node-esm/meta.json +1 -1
  24. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +1 -6
  25. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +2 -2
  26. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +1 -1
  27. package/dist/lib/node-esm/packlets/locks/browser.mjs +9 -49
  28. package/dist/lib/node-esm/packlets/locks/browser.mjs.map +2 -2
  29. package/dist/lib/node-esm/packlets/locks/node.mjs +4 -22
  30. package/dist/lib/node-esm/packlets/locks/node.mjs.map +2 -2
  31. package/dist/lib/node-esm/testing/index.mjs +9 -22
  32. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  33. package/dist/types/src/index.d.ts +1 -0
  34. package/dist/types/src/index.d.ts.map +1 -1
  35. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +3 -2
  36. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -1
  37. package/dist/types/src/packlets/agents/edge-agent-service.d.ts +2 -1
  38. package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
  39. package/dist/types/src/packlets/devices/devices-service.d.ts.map +1 -1
  40. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  41. package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
  42. package/dist/types/src/packlets/devtools/keys.d.ts.map +1 -1
  43. package/dist/types/src/packlets/devtools/metadata.d.ts.map +1 -1
  44. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  45. package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
  46. package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts.map +1 -1
  47. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
  48. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -1
  49. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts +2 -3
  50. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
  51. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  52. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  53. package/dist/types/src/packlets/identity/identity-manager.d.ts +3 -3
  54. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  55. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +6 -5
  56. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
  57. package/dist/types/src/packlets/identity/identity-service.d.ts +6 -10
  58. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  59. package/dist/types/src/packlets/identity/identity.d.ts +6 -9
  60. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  61. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +2 -1
  62. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  63. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +1 -1
  64. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
  65. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
  66. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  67. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +5 -1
  68. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  69. package/dist/types/src/packlets/invitations/invitation-state.d.ts.map +1 -1
  70. package/dist/types/src/packlets/invitations/invitation-topology.d.ts.map +1 -1
  71. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  72. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +3 -3
  73. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  74. package/dist/types/src/packlets/invitations/invitations-service.d.ts +3 -3
  75. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  76. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +2 -1
  77. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  78. package/dist/types/src/packlets/invitations/utils.d.ts.map +1 -1
  79. package/dist/types/src/packlets/locks/browser.d.ts.map +1 -1
  80. package/dist/types/src/packlets/locks/node.d.ts.map +1 -1
  81. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  82. package/dist/types/src/packlets/network/network-service.d.ts +5 -4
  83. package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
  84. package/dist/types/src/packlets/services/client-rpc-server.d.ts +3 -3
  85. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
  86. package/dist/types/src/packlets/services/feed-syncer.d.ts +22 -4
  87. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -1
  88. package/dist/types/src/packlets/services/feed-syncer.test.d.ts +2 -0
  89. package/dist/types/src/packlets/services/feed-syncer.test.d.ts.map +1 -0
  90. package/dist/types/src/packlets/services/service-context.d.ts +2 -4
  91. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  92. package/dist/types/src/packlets/services/service-host.d.ts +2 -3
  93. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  94. package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
  95. package/dist/types/src/packlets/services/util.d.ts.map +1 -1
  96. package/dist/types/src/packlets/space-export/archive-format.d.ts +9 -0
  97. package/dist/types/src/packlets/space-export/archive-format.d.ts.map +1 -0
  98. package/dist/types/src/packlets/space-export/index.d.ts +4 -1
  99. package/dist/types/src/packlets/space-export/index.d.ts.map +1 -1
  100. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts +23 -0
  101. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts.map +1 -0
  102. package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts +36 -0
  103. package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts.map +1 -0
  104. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts +9 -1
  105. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts.map +1 -1
  106. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts +7 -1
  107. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
  108. package/dist/types/src/packlets/space-export/space-archive.test.d.ts +2 -0
  109. package/dist/types/src/packlets/space-export/space-archive.test.d.ts.map +1 -0
  110. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts.map +1 -1
  111. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +20 -14
  112. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  113. package/dist/types/src/packlets/spaces/data-space.d.ts +24 -7
  114. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  115. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  116. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
  117. package/dist/types/src/packlets/spaces/genesis.d.ts +2 -1
  118. package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
  119. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +1 -4
  120. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  121. package/dist/types/src/packlets/spaces/spaces-service.d.ts +10 -7
  122. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  123. package/dist/types/src/packlets/storage/level.d.ts.map +1 -1
  124. package/dist/types/src/packlets/storage/profile-archive.d.ts.map +1 -1
  125. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  126. package/dist/types/src/packlets/storage/util.d.ts.map +1 -1
  127. package/dist/types/src/packlets/system/system-service.d.ts +1 -1
  128. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  129. package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -1
  130. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  131. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  132. package/dist/types/src/packlets/worker/worker-runtime.d.ts +11 -1
  133. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  134. package/dist/types/src/packlets/worker/worker-session.d.ts +0 -2
  135. package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
  136. package/dist/types/src/testing/setup.d.ts.map +1 -1
  137. package/dist/types/src/version.d.ts +1 -1
  138. package/dist/types/tsconfig.tsbuildinfo +1 -1
  139. package/package.json +43 -50
  140. package/src/index.ts +1 -0
  141. package/src/packlets/agents/edge-agent-manager.ts +8 -5
  142. package/src/packlets/agents/edge-agent-service.ts +4 -2
  143. package/src/packlets/devices/devices-service.test.ts +0 -1
  144. package/src/packlets/devtools/devtools.ts +0 -1
  145. package/src/packlets/diagnostics/diagnostics.ts +1 -2
  146. package/src/packlets/identity/contacts-service.ts +0 -1
  147. package/src/packlets/identity/identity-manager.test.ts +5 -5
  148. package/src/packlets/identity/identity-manager.ts +18 -17
  149. package/src/packlets/identity/identity-recovery-manager.ts +20 -16
  150. package/src/packlets/identity/identity-service.test.ts +6 -27
  151. package/src/packlets/identity/identity-service.ts +13 -81
  152. package/src/packlets/identity/identity.test.ts +2 -2
  153. package/src/packlets/identity/identity.ts +9 -32
  154. package/src/packlets/invitations/device-invitation-protocol.ts +3 -2
  155. package/src/packlets/invitations/edge-invitation-handler.ts +9 -5
  156. package/src/packlets/invitations/invitation-host-extension.ts +7 -10
  157. package/src/packlets/invitations/invitation-protocol.ts +5 -1
  158. package/src/packlets/invitations/invitation-state.ts +1 -15
  159. package/src/packlets/invitations/invitations-handler.test.ts +4 -5
  160. package/src/packlets/invitations/invitations-handler.ts +67 -15
  161. package/src/packlets/invitations/invitations-manager.ts +40 -15
  162. package/src/packlets/invitations/invitations-service.ts +9 -9
  163. package/src/packlets/invitations/space-invitation-protocol.test.ts +17 -16
  164. package/src/packlets/invitations/space-invitation-protocol.ts +4 -3
  165. package/src/packlets/logging/logging-service.ts +15 -15
  166. package/src/packlets/network/network-service.test.ts +0 -1
  167. package/src/packlets/network/network-service.ts +10 -8
  168. package/src/packlets/services/client-rpc-server.ts +15 -12
  169. package/src/packlets/services/feed-syncer.test.ts +340 -0
  170. package/src/packlets/services/feed-syncer.ts +175 -25
  171. package/src/packlets/services/service-context.test.ts +3 -2
  172. package/src/packlets/services/service-context.ts +113 -47
  173. package/src/packlets/services/service-host.test.ts +8 -8
  174. package/src/packlets/services/service-host.ts +17 -27
  175. package/src/packlets/services/service-registry.test.ts +0 -1
  176. package/src/packlets/space-export/archive-format.ts +42 -0
  177. package/src/packlets/space-export/index.ts +4 -1
  178. package/src/packlets/space-export/serialized-space-reader.ts +111 -0
  179. package/src/packlets/space-export/serialized-space-writer.ts +252 -0
  180. package/src/packlets/space-export/space-archive-reader.ts +64 -3
  181. package/src/packlets/space-export/space-archive-writer.ts +38 -2
  182. package/src/packlets/space-export/space-archive.test.ts +461 -0
  183. package/src/packlets/spaces/data-space-manager.test.ts +79 -13
  184. package/src/packlets/spaces/data-space-manager.ts +80 -115
  185. package/src/packlets/spaces/data-space.ts +55 -31
  186. package/src/packlets/spaces/edge-feed-replicator.test.ts +1 -1
  187. package/src/packlets/spaces/edge-feed-replicator.ts +9 -7
  188. package/src/packlets/spaces/epoch-migrations.ts +4 -3
  189. package/src/packlets/spaces/genesis.ts +6 -1
  190. package/src/packlets/spaces/notarization-plugin.ts +2 -1
  191. package/src/packlets/spaces/spaces-service.test.ts +18 -11
  192. package/src/packlets/spaces/spaces-service.ts +115 -17
  193. package/src/packlets/testing/invitation-utils.ts +3 -2
  194. package/src/packlets/worker/worker-runtime.ts +39 -6
  195. package/src/packlets/worker/worker-session.ts +8 -14
  196. package/src/version.ts +1 -1
  197. package/dist/lib/browser/chunk-NTZOEM4P.mjs.map +0 -7
  198. package/dist/lib/node-esm/chunk-DQWA5ILA.mjs.map +0 -7
  199. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +0 -19
  200. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +0 -1
  201. package/src/packlets/identity/default-space-state-machine.ts +0 -44
  202. /package/dist/lib/browser/{chunk-NQSC7HOE.mjs.map → chunk-XJRPB3GA.mjs.map} +0 -0
  203. /package/dist/lib/node-esm/{chunk-PKEGMOQ4.mjs.map → chunk-2DT3MZRL.mjs.map} +0 -0
@@ -8,11 +8,13 @@ import * as Effect from 'effect/Effect';
8
8
  import * as Schema from 'effect/Schema';
9
9
 
10
10
  import { AsyncTask, scheduleTask } from '@dxos/async';
11
- import { Resource } from '@dxos/context';
11
+ import { Context, Resource } from '@dxos/context';
12
12
  import { type EdgeConnection, MessageSchema } from '@dxos/edge-client';
13
13
  import { RuntimeProvider } from '@dxos/effect';
14
14
  import { type FeedStore, SyncClient } from '@dxos/feed';
15
- import { type SpaceId } from '@dxos/keys';
15
+ import { invariant } from '@dxos/invariant';
16
+ import { SpaceId } from '@dxos/keys';
17
+ import { log } from '@dxos/log';
16
18
  import { FeedProtocol } from '@dxos/protocols';
17
19
  import { EdgeService } from '@dxos/protocols';
18
20
  import { createBuf } from '@dxos/protocols/buf';
@@ -24,7 +26,9 @@ const encoder = new Encoder({ tagUint8Array: false, useRecords: false });
24
26
 
25
27
  const DEFAULT_MESSAGE_BLOCKS_LIMIT = 50;
26
28
  const DEFAULT_SYNC_CONCURRENCY = 5;
27
- const DEFAULT_POLLING_INTERVAL = 10_000;
29
+ const DEFAULT_POLLING_INTERVAL = 5_000;
30
+ const DEFAULT_POLL_REQUEST_THROTTLE_MS = 250;
31
+ const MAX_BLOCKING_SYNC_ITERATIONS = 100;
28
32
 
29
33
  interface FeedSyncerOptions {
30
34
  runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
@@ -34,10 +38,9 @@ interface FeedSyncerOptions {
34
38
  getSpaceIds: () => SpaceId[];
35
39
 
36
40
  /**
37
- * Namespace to sync.
41
+ * Namespaces to sync.
38
42
  */
39
- // TODO(dmaretskyi): Syncing only one namespace is supported.
40
- syncNamespace: string;
43
+ syncNamespaces: string[];
41
44
 
42
45
  /**
43
46
  * Maximum number of blocks to sync in a single message.
@@ -56,13 +59,20 @@ interface FeedSyncerOptions {
56
59
  * @default 10 seconds
57
60
  */
58
61
  pollingInterval?: number;
62
+
63
+ /**
64
+ * Minimum delay between externally requested best-effort polls.
65
+ * @default 250 ms
66
+ */
67
+ pollRequestThrottleMs?: number;
59
68
  }
60
69
 
61
70
  export class FeedSyncer extends Resource {
62
- readonly #syncNamespace: string;
71
+ readonly #syncNamespaces: string[];
63
72
  readonly #messageBlocksLimit: number;
64
73
  readonly #syncConcurrency: number;
65
74
  readonly #pollingInterval: number;
75
+ readonly #pollRequestThrottleMs: number;
66
76
 
67
77
  readonly #runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
68
78
  readonly #feedStore: FeedStore;
@@ -73,6 +83,8 @@ export class FeedSyncer extends Resource {
73
83
  #spacesToPoll = new Set<SpaceId>();
74
84
  /** Last time full poll was completed. */
75
85
  #lastFullPoll: number | null = null;
86
+ #throttledPollScheduled = false;
87
+ #lastRequestedPollAt: number | null = null;
76
88
 
77
89
  constructor(options: FeedSyncerOptions) {
78
90
  super();
@@ -85,10 +97,11 @@ export class FeedSyncer extends Resource {
85
97
  sendMessage: this.#sendMessage.bind(this),
86
98
  });
87
99
  this.#getSpaceIds = options.getSpaceIds;
88
- this.#syncNamespace = options.syncNamespace;
100
+ this.#syncNamespaces = options.syncNamespaces;
89
101
  this.#messageBlocksLimit = options.messageBlocksLimit ?? DEFAULT_MESSAGE_BLOCKS_LIMIT;
90
102
  this.#syncConcurrency = options.syncConcurrency ?? DEFAULT_SYNC_CONCURRENCY;
91
103
  this.#pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
104
+ this.#pollRequestThrottleMs = options.pollRequestThrottleMs ?? DEFAULT_POLL_REQUEST_THROTTLE_MS;
92
105
  }
93
106
 
94
107
  protected override async _open(): Promise<void> {
@@ -101,14 +114,28 @@ export class FeedSyncer extends Resource {
101
114
  if (service !== EdgeService.QUEUE_REPLICATOR) {
102
115
  return;
103
116
  }
117
+ log('feed sync edge ingress', {
118
+ serviceId: msg.serviceId,
119
+ payloadByteLength: msg.payload?.value?.byteLength,
120
+ });
104
121
  const handleMessageEffect = Effect.gen(this, function* () {
105
122
  const decoded = yield* Effect.try({
106
123
  try: () => cborXdecode(msg.payload!.value),
107
124
  catch: (error) => new Error(`Failed to decode feed sync message: ${error}`),
108
125
  });
109
- const payload = yield* Schema.decodeUnknown(FeedProtocol.ProtocolMessage)(decoded);
126
+ const payload = yield* Schema.validate(FeedProtocol.ProtocolMessage)(decoded);
110
127
  yield* this.#syncClient.handleMessage(payload);
111
- });
128
+ }).pipe(
129
+ Effect.tapError((cause) =>
130
+ Effect.sync(() =>
131
+ log('feed sync edge message handling failed', {
132
+ serviceId: msg.serviceId,
133
+ payloadByteLength: msg.payload?.value?.byteLength,
134
+ cause: cause instanceof Error ? cause.message : String(cause),
135
+ }),
136
+ ),
137
+ ),
138
+ );
112
139
 
113
140
  void RuntimeProvider.runPromise(this.#runtime)(handleMessageEffect);
114
141
  }),
@@ -116,7 +143,12 @@ export class FeedSyncer extends Resource {
116
143
 
117
144
  this._ctx.onDispose(
118
145
  // NOTE: This will fire immediately if the connection is already open.
119
- this.#edgeClient.onReconnected(async () => {}),
146
+ this.#edgeClient.onReconnected(async () => {
147
+ log('feed sync edge reconnected', {
148
+ peerKey: this.#edgeClient.peerKey,
149
+ identityKey: this.#edgeClient.identityKey,
150
+ });
151
+ }),
120
152
  );
121
153
 
122
154
  this.#feedStore.onNewBlocks.on(this._ctx, () => {
@@ -127,6 +159,7 @@ export class FeedSyncer extends Resource {
127
159
  await this.#pushTask.open();
128
160
 
129
161
  this.#resetSpacesToPoll();
162
+ this.#pollTask.schedule();
130
163
  }
131
164
 
132
165
  protected override async _close(): Promise<void> {
@@ -134,6 +167,87 @@ export class FeedSyncer extends Resource {
134
167
  await this.#pushTask.close();
135
168
  }
136
169
 
170
+ /**
171
+ * Schedules a best-effort pull without blocking the caller.
172
+ */
173
+ schedulePoll(): void {
174
+ this.#resetSpacesToPoll();
175
+ if (this.#throttledPollScheduled) {
176
+ return;
177
+ }
178
+
179
+ const now = Date.now();
180
+ const delay =
181
+ this.#lastRequestedPollAt == null
182
+ ? 0
183
+ : Math.max(this.#pollRequestThrottleMs - (now - this.#lastRequestedPollAt), 0);
184
+ this.#throttledPollScheduled = true;
185
+ scheduleTask(
186
+ this._ctx,
187
+ () => {
188
+ this.#throttledPollScheduled = false;
189
+ this.#lastRequestedPollAt = Date.now();
190
+ this.#pollTask.schedule();
191
+ },
192
+ delay,
193
+ );
194
+ }
195
+
196
+ /**
197
+ * Performs queue sync and blocks until there are no pending sync batches.
198
+ */
199
+ async syncBlocking(
200
+ ctx: Context,
201
+ {
202
+ spaceId,
203
+ subspaceTag,
204
+ shouldPush = true,
205
+ shouldPull = true,
206
+ }: {
207
+ spaceId: SpaceId;
208
+ subspaceTag: string;
209
+ shouldPush?: boolean;
210
+ shouldPull?: boolean;
211
+ },
212
+ ): Promise<void> {
213
+ invariant(SpaceId.isValid(spaceId));
214
+ invariant(FeedProtocol.isWellKnownNamespace(subspaceTag));
215
+ if (!shouldPush && !shouldPull) {
216
+ return;
217
+ }
218
+
219
+ await RuntimeProvider.runPromise(this.#runtime)(
220
+ Effect.gen(this, function* () {
221
+ let done = false;
222
+ let iterations = 0;
223
+ while (!done) {
224
+ done = true;
225
+ if (shouldPull) {
226
+ const pullResult = yield* this.#syncClient.pull(ctx, {
227
+ spaceId,
228
+ feedNamespace: subspaceTag,
229
+ limit: this.#messageBlocksLimit,
230
+ });
231
+ done &&= pullResult.done;
232
+ }
233
+
234
+ if (shouldPush) {
235
+ const pushResult = yield* this.#syncClient.push(ctx, {
236
+ spaceId,
237
+ feedNamespace: subspaceTag,
238
+ limit: this.#messageBlocksLimit,
239
+ });
240
+ done &&= pushResult.done;
241
+ }
242
+ iterations++;
243
+ if (iterations > MAX_BLOCKING_SYNC_ITERATIONS) {
244
+ throw new Error('Blocking sync exceeded max iterations.');
245
+ }
246
+ }
247
+ }),
248
+ );
249
+ }
250
+
137
251
  #resetSpacesToPoll(): void {
138
252
  this.#spacesToPoll.clear();
139
253
  this.#getSpaceIds().forEach((spaceId) => {
@@ -142,20 +256,44 @@ export class FeedSyncer extends Resource {
142
256
  this.#lastFullPoll = Date.now();
143
257
  }
144
258
 
145
- #sendMessage(message: FeedProtocol.QueryRequest | FeedProtocol.AppendRequest): Effect.Effect<void, unknown, never> {
259
+ #sendMessage(
260
+ ctx: Context,
261
+ message: FeedProtocol.QueryRequest | FeedProtocol.AppendRequest,
262
+ ): Effect.Effect<void, unknown, never> {
146
263
  return Effect.gen(this, function* () {
147
264
  const encoded = encoder.encode(message);
265
+ const serviceId = this.#getTargetServiceId(message);
266
+ const rpcTag = 'blocks' in message ? 'AppendRequest' : 'QueryRequest';
267
+ log('feed sync edge rpc outgoing', {
268
+ tag: rpcTag,
269
+ serviceId,
270
+ payloadByteLength: encoded.byteLength,
271
+ spaceId: message.spaceId,
272
+ feedNamespace: message.feedNamespace,
273
+ requestId: message.requestId,
274
+ });
148
275
  yield* Effect.tryPromise(async () =>
149
276
  this.#edgeClient.send(
277
+ ctx,
150
278
  createBuf(MessageSchema, {
151
279
  source: {
152
280
  identityKey: this.#edgeClient.identityKey,
153
281
  peerKey: this.#edgeClient.peerKey,
154
282
  },
155
- serviceId: this.#getTargetServiceId(message),
283
+ serviceId,
156
284
  payload: { value: bufferToArray(encoded) },
157
285
  }),
158
286
  ),
287
+ ).pipe(
288
+ Effect.tapError((cause) =>
289
+ Effect.sync(() =>
290
+ log('feed sync edge send failed', {
291
+ serviceId,
292
+ tag: rpcTag,
293
+ cause: cause instanceof Error ? cause.message : String(cause),
294
+ }),
295
+ ),
296
+ ),
159
297
  );
160
298
  });
161
299
  }
@@ -173,12 +311,18 @@ export class FeedSyncer extends Resource {
173
311
  this.#spacesToPoll,
174
312
  (spaceId) =>
175
313
  Effect.gen(this, function* () {
176
- const { done } = yield* this.#syncClient.pull({
177
- spaceId,
178
- feedNamespace: this.#syncNamespace,
179
- limit: this.#messageBlocksLimit,
180
- });
181
- if (done) {
314
+ let doneForAllNamespaces = true;
315
+ for (const feedNamespace of this.#syncNamespaces) {
316
+ const { done } = yield* this.#syncClient.pull(this._ctx, {
317
+ spaceId,
318
+ feedNamespace,
319
+ limit: this.#messageBlocksLimit,
320
+ });
321
+ if (!done) {
322
+ doneForAllNamespaces = false;
323
+ }
324
+ }
325
+ if (doneForAllNamespaces) {
182
326
  this.#spacesToPoll.delete(spaceId);
183
327
  }
184
328
  }),
@@ -210,12 +354,18 @@ export class FeedSyncer extends Resource {
210
354
  this.#getSpaceIds(),
211
355
  (spaceId) =>
212
356
  Effect.gen(this, function* () {
213
- const { done } = yield* this.#syncClient.push({
214
- spaceId,
215
- feedNamespace: this.#syncNamespace,
216
- limit: this.#messageBlocksLimit,
217
- });
218
- if (!done) {
357
+ let doneForAllNamespaces = true;
358
+ for (const feedNamespace of this.#syncNamespaces) {
359
+ const { done } = yield* this.#syncClient.push(this._ctx, {
360
+ spaceId,
361
+ feedNamespace,
362
+ limit: this.#messageBlocksLimit,
363
+ });
364
+ if (!done) {
365
+ doneForAllNamespaces = false;
366
+ }
367
+ }
368
+ if (!doneForAllNamespaces) {
219
369
  // Keep pushing until all blocks are pushed.
220
370
  this.#pushTask.schedule();
221
371
  }
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { describe, test } from 'vitest';
6
6
 
7
+ import { Context } from '@dxos/context';
7
8
  import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
8
9
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
9
10
  import { openAndClose } from '@dxos/test-utils';
@@ -19,7 +20,7 @@ describe('services/ServiceContext', () => {
19
20
  const device2 = await createOpenServiceContext(networkContext);
20
21
  await Promise.all(performInvitation({ host: device1, guest: device2, options: { kind: Invitation.Kind.DEVICE } }));
21
22
 
22
- const space1 = await device1.dataSpaceManager!.createSpace();
23
+ const space1 = await device1.dataSpaceManager!.createSpace(new Context());
23
24
  await device2.dataSpaceManager!.waitUntilSpaceReady(space1!.key);
24
25
  const space2 = await device2.dataSpaceManager!.spaces.get(space1.key);
25
26
  await space2!.inner.controlPipeline.state.waitUntilTimeframe(space1.inner.controlPipeline.state.timeframe);
@@ -35,7 +36,7 @@ describe('services/ServiceContext', () => {
35
36
 
36
37
  const identity2 = await createOpenServiceContext(networkContext);
37
38
  await identity2.createIdentity();
38
- const space1 = await identity2.dataSpaceManager!.createSpace();
39
+ const space1 = await identity2.dataSpaceManager!.createSpace(new Context());
39
40
  await Promise.all(
40
41
  performInvitation({
41
42
  host: identity2,