@dxos/client-services 0.8.4-main.c1de068 → 0.8.4-main.c85a9c8dae

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 (207) hide show
  1. package/dist/lib/browser/{chunk-Q7DAO5CH.mjs → chunk-MQ6PWJ76.mjs} +3614 -3438
  2. package/dist/lib/browser/chunk-MQ6PWJ76.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-NQSC7HOE.mjs +22 -0
  4. package/dist/lib/browser/chunk-NQSC7HOE.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-QCWEHHJW.mjs +24 -0
  6. package/dist/lib/browser/chunk-QCWEHHJW.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +471 -77
  8. package/dist/lib/browser/index.mjs.map +4 -4
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +93 -0
  11. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +7 -0
  12. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +11 -0
  13. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs.map +7 -0
  14. package/dist/lib/browser/packlets/locks/browser.mjs +126 -0
  15. package/dist/lib/browser/packlets/locks/browser.mjs.map +7 -0
  16. package/dist/lib/browser/packlets/locks/node.mjs +66 -0
  17. package/dist/lib/browser/packlets/locks/node.mjs.map +7 -0
  18. package/dist/lib/browser/testing/index.mjs +34 -22
  19. package/dist/lib/browser/testing/index.mjs.map +3 -3
  20. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs +24 -0
  21. package/dist/lib/node-esm/chunk-2SZHAWBN.mjs.map +7 -0
  22. package/dist/lib/node-esm/{chunk-LBKPPVZO.mjs → chunk-GUAL4U7S.mjs} +3573 -3265
  23. package/dist/lib/node-esm/chunk-GUAL4U7S.mjs.map +7 -0
  24. package/dist/lib/node-esm/chunk-PKEGMOQ4.mjs +22 -0
  25. package/dist/lib/node-esm/chunk-PKEGMOQ4.mjs.map +7 -0
  26. package/dist/lib/node-esm/index.mjs +471 -77
  27. package/dist/lib/node-esm/index.mjs.map +4 -4
  28. package/dist/lib/node-esm/meta.json +1 -1
  29. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +93 -0
  30. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +7 -0
  31. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +11 -0
  32. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs.map +7 -0
  33. package/dist/lib/node-esm/packlets/locks/browser.mjs +126 -0
  34. package/dist/lib/node-esm/packlets/locks/browser.mjs.map +7 -0
  35. package/dist/lib/node-esm/packlets/locks/node.mjs +66 -0
  36. package/dist/lib/node-esm/packlets/locks/node.mjs.map +7 -0
  37. package/dist/lib/node-esm/testing/index.mjs +34 -22
  38. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  39. package/dist/types/src/packlets/agents/edge-agent-service.d.ts +1 -1
  40. package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
  41. package/dist/types/src/packlets/devices/devices-service.d.ts.map +1 -1
  42. package/dist/types/src/packlets/devtools/devtools.d.ts +20 -20
  43. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  44. package/dist/types/src/packlets/devtools/feeds.d.ts +1 -1
  45. package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
  46. package/dist/types/src/packlets/devtools/network.d.ts +1 -1
  47. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  48. package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts +1 -1
  49. package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts.map +1 -1
  50. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts +1 -1
  51. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
  52. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts +1 -1
  53. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
  54. package/dist/types/src/packlets/diagnostics/index.d.ts +1 -1
  55. package/dist/types/src/packlets/diagnostics/index.d.ts.map +1 -1
  56. package/dist/types/src/packlets/identity/authenticator.d.ts +2 -2
  57. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  58. package/dist/types/src/packlets/identity/contacts-service.d.ts +1 -1
  59. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  60. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +3 -3
  61. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +1 -1
  62. package/dist/types/src/packlets/identity/identity-manager.d.ts +5 -5
  63. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  64. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +2 -2
  65. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
  66. package/dist/types/src/packlets/identity/identity-service.d.ts +1 -1
  67. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  68. package/dist/types/src/packlets/identity/identity.d.ts +3 -3
  69. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  70. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +4 -4
  71. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  72. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +1 -1
  73. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
  74. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
  75. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  76. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +3 -4
  77. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  78. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +4 -4
  79. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  80. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  81. package/dist/types/src/packlets/invitations/invitations-service.d.ts +1 -1
  82. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  83. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +3 -3
  84. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  85. package/dist/types/src/packlets/invitations/utils.d.ts.map +1 -1
  86. package/dist/types/src/packlets/locks/index.d.ts +1 -1
  87. package/dist/types/src/packlets/locks/index.d.ts.map +1 -1
  88. package/dist/types/src/packlets/logging/logging-service.d.ts +5 -1
  89. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  90. package/dist/types/src/packlets/network/network-service.d.ts +2 -2
  91. package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
  92. package/dist/types/src/packlets/services/client-rpc-server.d.ts +2 -2
  93. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
  94. package/dist/types/src/packlets/services/feed-syncer.d.ts +59 -0
  95. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -0
  96. package/dist/types/src/packlets/services/feed-syncer.test.d.ts +2 -0
  97. package/dist/types/src/packlets/services/feed-syncer.test.d.ts.map +1 -0
  98. package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
  99. package/dist/types/src/packlets/services/service-context.d.ts +13 -8
  100. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  101. package/dist/types/src/packlets/services/service-host.d.ts +19 -5
  102. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  103. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts +1 -1
  104. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
  105. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts +1 -1
  106. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts.map +1 -1
  107. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +12 -7
  108. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  109. package/dist/types/src/packlets/spaces/data-space.d.ts +6 -6
  110. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  111. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +2 -2
  112. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  113. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +6 -6
  114. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  115. package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -2
  116. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  117. package/dist/types/src/packlets/storage/profile-archive.d.ts.map +1 -1
  118. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  119. package/dist/types/src/packlets/system/system-service.d.ts +1 -1
  120. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  121. package/dist/types/src/packlets/testing/invitation-utils.d.ts +6 -3
  122. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  123. package/dist/types/src/packlets/testing/test-builder.d.ts +8 -7
  124. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  125. package/dist/types/src/packlets/worker/worker-runtime.d.ts +31 -4
  126. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  127. package/dist/types/src/packlets/worker/worker-session.d.ts +2 -2
  128. package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
  129. package/dist/types/src/version.d.ts +1 -1
  130. package/dist/types/src/version.d.ts.map +1 -1
  131. package/dist/types/tsconfig.tsbuildinfo +1 -1
  132. package/package.json +72 -48
  133. package/src/packlets/agents/edge-agent-manager.ts +2 -2
  134. package/src/packlets/agents/edge-agent-service.ts +13 -3
  135. package/src/packlets/devices/devices-service.test.ts +4 -3
  136. package/src/packlets/devices/devices-service.ts +2 -2
  137. package/src/packlets/devtools/devtools.ts +30 -29
  138. package/src/packlets/devtools/feeds.ts +2 -2
  139. package/src/packlets/devtools/network.ts +1 -1
  140. package/src/packlets/diagnostics/browser-diagnostics-broadcast.ts +1 -1
  141. package/src/packlets/diagnostics/diagnostics-broadcast.ts +1 -1
  142. package/src/packlets/diagnostics/diagnostics-collector.ts +1 -1
  143. package/src/packlets/diagnostics/diagnostics.ts +1 -1
  144. package/src/packlets/diagnostics/index.ts +1 -1
  145. package/src/packlets/identity/authenticator.node.test.ts +1 -1
  146. package/src/packlets/identity/authenticator.ts +3 -3
  147. package/src/packlets/identity/contacts-service.ts +3 -2
  148. package/src/packlets/identity/default-space-state-machine.ts +3 -3
  149. package/src/packlets/identity/identity-manager.test.ts +3 -3
  150. package/src/packlets/identity/identity-manager.ts +9 -9
  151. package/src/packlets/identity/identity-recovery-manager.ts +2 -2
  152. package/src/packlets/identity/identity-service.test.ts +3 -2
  153. package/src/packlets/identity/identity-service.ts +2 -1
  154. package/src/packlets/identity/identity.test.ts +9 -9
  155. package/src/packlets/identity/identity.ts +9 -8
  156. package/src/packlets/invitations/device-invitation-protocol.test.ts +1 -1
  157. package/src/packlets/invitations/device-invitation-protocol.ts +6 -5
  158. package/src/packlets/invitations/edge-invitation-handler.ts +1 -1
  159. package/src/packlets/invitations/invitation-guest-extenstion.ts +7 -5
  160. package/src/packlets/invitations/invitation-host-extension.ts +8 -6
  161. package/src/packlets/invitations/invitation-protocol.ts +3 -4
  162. package/src/packlets/invitations/invitations-handler.test.ts +4 -3
  163. package/src/packlets/invitations/invitations-handler.ts +10 -10
  164. package/src/packlets/invitations/invitations-manager.ts +3 -3
  165. package/src/packlets/invitations/invitations-service.ts +1 -1
  166. package/src/packlets/invitations/space-invitation-protocol.test.ts +2 -2
  167. package/src/packlets/invitations/space-invitation-protocol.ts +10 -15
  168. package/src/packlets/invitations/utils.ts +1 -1
  169. package/src/packlets/locks/browser.ts +1 -1
  170. package/src/packlets/locks/index.ts +1 -1
  171. package/src/packlets/logging/logging-service.ts +7 -3
  172. package/src/packlets/logging/logging.test.ts +1 -1
  173. package/src/packlets/network/network-service.test.ts +4 -3
  174. package/src/packlets/network/network-service.ts +2 -2
  175. package/src/packlets/services/client-rpc-server.ts +5 -5
  176. package/src/packlets/services/feed-syncer.test.ts +340 -0
  177. package/src/packlets/services/feed-syncer.ts +330 -0
  178. package/src/packlets/services/platform.ts +7 -1
  179. package/src/packlets/services/service-context.test.ts +1 -1
  180. package/src/packlets/services/service-context.ts +62 -29
  181. package/src/packlets/services/service-host.test.ts +3 -2
  182. package/src/packlets/services/service-host.ts +69 -24
  183. package/src/packlets/services/service-registry.test.ts +2 -1
  184. package/src/packlets/space-export/space-archive-reader.ts +2 -2
  185. package/src/packlets/space-export/space-archive-writer.ts +7 -5
  186. package/src/packlets/space-export/tar.test.ts +1 -1
  187. package/src/packlets/spaces/automerge-space-state.ts +1 -1
  188. package/src/packlets/spaces/data-space-manager.ts +76 -36
  189. package/src/packlets/spaces/data-space.ts +18 -13
  190. package/src/packlets/spaces/edge-feed-replicator.test.ts +3 -3
  191. package/src/packlets/spaces/edge-feed-replicator.ts +4 -4
  192. package/src/packlets/spaces/epoch-migrations.ts +2 -2
  193. package/src/packlets/spaces/notarization-plugin.test.ts +3 -3
  194. package/src/packlets/spaces/notarization-plugin.ts +11 -11
  195. package/src/packlets/spaces/spaces-service.test.ts +3 -2
  196. package/src/packlets/spaces/spaces-service.ts +27 -23
  197. package/src/packlets/storage/profile-archive.ts +1 -1
  198. package/src/packlets/storage/storage.ts +7 -8
  199. package/src/packlets/system/system-service.test.ts +1 -1
  200. package/src/packlets/system/system-service.ts +4 -4
  201. package/src/packlets/testing/invitation-utils.ts +8 -5
  202. package/src/packlets/testing/test-builder.ts +39 -13
  203. package/src/packlets/worker/worker-runtime.ts +151 -12
  204. package/src/packlets/worker/worker-session.ts +11 -11
  205. package/src/version.ts +1 -1
  206. package/dist/lib/browser/chunk-Q7DAO5CH.mjs.map +0 -7
  207. package/dist/lib/node-esm/chunk-LBKPPVZO.mjs.map +0 -7
@@ -0,0 +1,330 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import type * as SqlClient from '@effect/sql/SqlClient';
6
+ import { Encoder, decode as cborXdecode } from 'cbor-x';
7
+ import * as Effect from 'effect/Effect';
8
+ import * as Schema from 'effect/Schema';
9
+
10
+ import { AsyncTask, scheduleTask } from '@dxos/async';
11
+ import { Resource } from '@dxos/context';
12
+ import { type EdgeConnection, MessageSchema } from '@dxos/edge-client';
13
+ import { RuntimeProvider } from '@dxos/effect';
14
+ import { type FeedStore, SyncClient } from '@dxos/feed';
15
+ import { invariant } from '@dxos/invariant';
16
+ import { SpaceId } from '@dxos/keys';
17
+ import { FeedProtocol } from '@dxos/protocols';
18
+ import { EdgeService } from '@dxos/protocols';
19
+ import { createBuf } from '@dxos/protocols/buf';
20
+ import { type Message as RouterMessage } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
21
+ import type { SqlTransaction } from '@dxos/sql-sqlite';
22
+ import { bufferToArray } from '@dxos/util';
23
+
24
+ const encoder = new Encoder({ tagUint8Array: false, useRecords: false });
25
+
26
+ const DEFAULT_MESSAGE_BLOCKS_LIMIT = 50;
27
+ const DEFAULT_SYNC_CONCURRENCY = 5;
28
+ const DEFAULT_POLLING_INTERVAL = 5_000;
29
+ const DEFAULT_POLL_REQUEST_THROTTLE_MS = 250;
30
+ const MAX_BLOCKING_SYNC_ITERATIONS = 100;
31
+
32
+ interface FeedSyncerOptions {
33
+ runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
34
+ feedStore: FeedStore;
35
+ edgeClient: EdgeConnection;
36
+ peerId: string;
37
+ getSpaceIds: () => SpaceId[];
38
+
39
+ /**
40
+ * Namespaces to sync.
41
+ */
42
+ syncNamespaces: string[];
43
+
44
+ /**
45
+ * Maximum number of blocks to sync in a single message.
46
+ * @default 50
47
+ */
48
+ messageBlocksLimit?: number;
49
+
50
+ /**
51
+ * Maximum number of spaces to sync concurrently.
52
+ * @default 5
53
+ */
54
+ syncConcurrency?: number;
55
+
56
+ /**
57
+ * Interval between full polls.
58
+ * @default 10 seconds
59
+ */
60
+ pollingInterval?: number;
61
+
62
+ /**
63
+ * Minimum delay between externally requested best-effort polls.
64
+ * @default 250 ms
65
+ */
66
+ pollRequestThrottleMs?: number;
67
+ }
68
+
69
+ export class FeedSyncer extends Resource {
70
+ readonly #syncNamespaces: string[];
71
+ readonly #messageBlocksLimit: number;
72
+ readonly #syncConcurrency: number;
73
+ readonly #pollingInterval: number;
74
+ readonly #pollRequestThrottleMs: number;
75
+
76
+ readonly #runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
77
+ readonly #feedStore: FeedStore;
78
+ readonly #edgeClient: EdgeConnection;
79
+ readonly #syncClient: SyncClient;
80
+ readonly #getSpaceIds: () => SpaceId[];
81
+
82
+ #spacesToPoll = new Set<SpaceId>();
83
+ /** Last time full poll was completed. */
84
+ #lastFullPoll: number | null = null;
85
+ #throttledPollScheduled = false;
86
+ #lastRequestedPollAt: number | null = null;
87
+
88
+ constructor(options: FeedSyncerOptions) {
89
+ super();
90
+ this.#runtime = options.runtime;
91
+ this.#feedStore = options.feedStore;
92
+ this.#edgeClient = options.edgeClient;
93
+ this.#syncClient = new SyncClient({
94
+ peerId: options.peerId,
95
+ feedStore: options.feedStore,
96
+ sendMessage: this.#sendMessage.bind(this),
97
+ });
98
+ this.#getSpaceIds = options.getSpaceIds;
99
+ this.#syncNamespaces = options.syncNamespaces;
100
+ this.#messageBlocksLimit = options.messageBlocksLimit ?? DEFAULT_MESSAGE_BLOCKS_LIMIT;
101
+ this.#syncConcurrency = options.syncConcurrency ?? DEFAULT_SYNC_CONCURRENCY;
102
+ this.#pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
103
+ this.#pollRequestThrottleMs = options.pollRequestThrottleMs ?? DEFAULT_POLL_REQUEST_THROTTLE_MS;
104
+ }
105
+
106
+ protected override async _open(): Promise<void> {
107
+ this._ctx.onDispose(
108
+ this.#edgeClient.onMessage((msg: RouterMessage) => {
109
+ if (!msg.serviceId) {
110
+ return;
111
+ }
112
+ const service = msg.serviceId.split(':')[0];
113
+ if (service !== EdgeService.QUEUE_REPLICATOR) {
114
+ return;
115
+ }
116
+ const handleMessageEffect = Effect.gen(this, function* () {
117
+ const decoded = yield* Effect.try({
118
+ try: () => cborXdecode(msg.payload!.value),
119
+ catch: (error) => new Error(`Failed to decode feed sync message: ${error}`),
120
+ });
121
+ const payload = yield* Schema.validate(FeedProtocol.ProtocolMessage)(decoded);
122
+ yield* this.#syncClient.handleMessage(payload);
123
+ });
124
+
125
+ void RuntimeProvider.runPromise(this.#runtime)(handleMessageEffect);
126
+ }),
127
+ );
128
+
129
+ this._ctx.onDispose(
130
+ // NOTE: This will fire immediately if the connection is already open.
131
+ this.#edgeClient.onReconnected(async () => {}),
132
+ );
133
+
134
+ this.#feedStore.onNewBlocks.on(this._ctx, () => {
135
+ this.#pushTask.schedule();
136
+ });
137
+
138
+ await this.#pollTask.open();
139
+ await this.#pushTask.open();
140
+
141
+ this.#resetSpacesToPoll();
142
+ this.#pollTask.schedule();
143
+ }
144
+
145
+ protected override async _close(): Promise<void> {
146
+ await this.#pollTask.close();
147
+ await this.#pushTask.close();
148
+ }
149
+
150
+ /**
151
+ * Schedules a best-effort pull without blocking the caller.
152
+ */
153
+ schedulePoll(): void {
154
+ this.#resetSpacesToPoll();
155
+ if (this.#throttledPollScheduled) {
156
+ return;
157
+ }
158
+
159
+ const now = Date.now();
160
+ const delay =
161
+ this.#lastRequestedPollAt == null
162
+ ? 0
163
+ : Math.max(this.#pollRequestThrottleMs - (now - this.#lastRequestedPollAt), 0);
164
+ this.#throttledPollScheduled = true;
165
+ scheduleTask(
166
+ this._ctx,
167
+ () => {
168
+ this.#throttledPollScheduled = false;
169
+ this.#lastRequestedPollAt = Date.now();
170
+ this.#pollTask.schedule();
171
+ },
172
+ delay,
173
+ );
174
+ }
175
+
176
+ /**
177
+ * Performs queue sync and blocks until there are no pending sync batches.
178
+ */
179
+ async syncBlocking({
180
+ spaceId,
181
+ subspaceTag,
182
+ shouldPush = true,
183
+ shouldPull = true,
184
+ }: {
185
+ spaceId: SpaceId;
186
+ subspaceTag: string;
187
+ shouldPush?: boolean;
188
+ shouldPull?: boolean;
189
+ }): Promise<void> {
190
+ invariant(SpaceId.isValid(spaceId));
191
+ invariant(FeedProtocol.isWellKnownNamespace(subspaceTag));
192
+ if (!shouldPush && !shouldPull) {
193
+ return;
194
+ }
195
+
196
+ await RuntimeProvider.runPromise(this.#runtime)(
197
+ Effect.gen(this, function* () {
198
+ let done = false;
199
+ let iterations = 0;
200
+ while (!done) {
201
+ done = true;
202
+ if (shouldPull) {
203
+ const pullResult = yield* this.#syncClient.pull({
204
+ spaceId,
205
+ feedNamespace: subspaceTag,
206
+ limit: this.#messageBlocksLimit,
207
+ });
208
+ done &&= pullResult.done;
209
+ }
210
+
211
+ if (shouldPush) {
212
+ const pushResult = yield* this.#syncClient.push({
213
+ spaceId,
214
+ feedNamespace: subspaceTag,
215
+ limit: this.#messageBlocksLimit,
216
+ });
217
+ done &&= pushResult.done;
218
+ }
219
+ iterations++;
220
+ if (iterations > MAX_BLOCKING_SYNC_ITERATIONS) {
221
+ throw new Error('Blocking sync exceeded max iterations.');
222
+ }
223
+ }
224
+ }),
225
+ );
226
+ }
227
+
228
+ #resetSpacesToPoll(): void {
229
+ this.#spacesToPoll.clear();
230
+ this.#getSpaceIds().forEach((spaceId) => {
231
+ this.#spacesToPoll.add(spaceId);
232
+ });
233
+ this.#lastFullPoll = Date.now();
234
+ }
235
+
236
+ #sendMessage(message: FeedProtocol.QueryRequest | FeedProtocol.AppendRequest): Effect.Effect<void, unknown, never> {
237
+ return Effect.gen(this, function* () {
238
+ const encoded = encoder.encode(message);
239
+ yield* Effect.tryPromise(async () =>
240
+ this.#edgeClient.send(
241
+ createBuf(MessageSchema, {
242
+ source: {
243
+ identityKey: this.#edgeClient.identityKey,
244
+ peerKey: this.#edgeClient.peerKey,
245
+ },
246
+ serviceId: this.#getTargetServiceId(message),
247
+ payload: { value: bufferToArray(encoded) },
248
+ }),
249
+ ),
250
+ );
251
+ });
252
+ }
253
+
254
+ #getTargetServiceId(message: FeedProtocol.QueryRequest | FeedProtocol.AppendRequest): string {
255
+ // TODO(dmaretskyi): Perhaps in the future we will want to include the queue namespace here as well.
256
+ // This would require putting it at the top level of the message.
257
+ // For now, we let the edge router handle it.
258
+ return FeedProtocol.encodeServiceId(message.feedNamespace, message.spaceId as SpaceId);
259
+ }
260
+
261
+ readonly #pollTask = new AsyncTask(async () =>
262
+ Effect.gen(this, function* () {
263
+ yield* Effect.forEach(
264
+ this.#spacesToPoll,
265
+ (spaceId) =>
266
+ Effect.gen(this, function* () {
267
+ let doneForAllNamespaces = true;
268
+ for (const feedNamespace of this.#syncNamespaces) {
269
+ const { done } = yield* this.#syncClient.pull({
270
+ spaceId,
271
+ feedNamespace,
272
+ limit: this.#messageBlocksLimit,
273
+ });
274
+ if (!done) {
275
+ doneForAllNamespaces = false;
276
+ }
277
+ }
278
+ if (doneForAllNamespaces) {
279
+ this.#spacesToPoll.delete(spaceId);
280
+ }
281
+ }),
282
+ { concurrency: this.#syncConcurrency },
283
+ );
284
+
285
+ // If its time to do a full poll, reset the spaces to poll and schedule the next poll immediately.
286
+ if (this.#lastFullPoll == null || Date.now() - this.#lastFullPoll > this.#pollingInterval) {
287
+ this.#resetSpacesToPoll();
288
+ this.#pollTask.schedule();
289
+ } else if (this.#spacesToPoll.size > 0) {
290
+ // If there are some spaces still syncing, poll them immediately.
291
+ this.#pollTask.schedule();
292
+ } else {
293
+ // All spaces sync, and there's time before the next full poll, schedule it later.
294
+ this.#resetSpacesToPoll();
295
+ scheduleTask(
296
+ this._ctx,
297
+ () => this.#pollTask.schedule(),
298
+ Math.max(this.#pollingInterval - (Date.now() - (this.#lastFullPoll ?? 0)), 0),
299
+ );
300
+ }
301
+ }).pipe(RuntimeProvider.runPromise(this.#runtime)),
302
+ );
303
+
304
+ readonly #pushTask = new AsyncTask(async () =>
305
+ Effect.gen(this, function* () {
306
+ yield* Effect.forEach(
307
+ this.#getSpaceIds(),
308
+ (spaceId) =>
309
+ Effect.gen(this, function* () {
310
+ let doneForAllNamespaces = true;
311
+ for (const feedNamespace of this.#syncNamespaces) {
312
+ const { done } = yield* this.#syncClient.push({
313
+ spaceId,
314
+ feedNamespace,
315
+ limit: this.#messageBlocksLimit,
316
+ });
317
+ if (!done) {
318
+ doneForAllNamespaces = false;
319
+ }
320
+ }
321
+ if (!doneForAllNamespaces) {
322
+ // Keep pushing until all blocks are pushed.
323
+ this.#pushTask.schedule();
324
+ }
325
+ }),
326
+ { concurrency: this.#syncConcurrency },
327
+ );
328
+ }).pipe(RuntimeProvider.runPromise(this.#runtime)),
329
+ );
330
+ }
@@ -14,12 +14,18 @@ export const getPlatform = (): Platform => {
14
14
  userAgent,
15
15
  uptime: Math.floor((Date.now() - window.performance.timeOrigin) / 1_000),
16
16
  };
17
- } else {
17
+ } else if (typeof SharedWorkerGlobalScope !== 'undefined') {
18
18
  // Shared worker.
19
19
  return {
20
20
  type: Platform.PLATFORM_TYPE.SHARED_WORKER,
21
21
  uptime: Math.floor((Date.now() - performance.timeOrigin) / 1_000),
22
22
  };
23
+ } else {
24
+ // Dedicated worker.
25
+ return {
26
+ type: Platform.PLATFORM_TYPE.DEDICATED_WORKER,
27
+ uptime: Math.floor((Date.now() - performance.timeOrigin) / 1_000),
28
+ };
23
29
  }
24
30
  } else {
25
31
  // Node.
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { describe, test } from 'vitest';
6
6
 
7
- import { MemorySignalManagerContext, MemorySignalManager } from '@dxos/messaging';
7
+ import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
8
8
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
9
9
  import { openAndClose } from '@dxos/test-utils';
10
10
 
@@ -2,9 +2,11 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
+ import type * as SqlClient from '@effect/sql/SqlClient';
6
+
5
7
  import { Mutex, Trigger } from '@dxos/async';
6
8
  import { Context, Resource } from '@dxos/context';
7
- import { getCredentialAssertion, type CredentialProcessor } from '@dxos/credentials';
9
+ import { type CredentialProcessor, getCredentialAssertion } from '@dxos/credentials';
8
10
  import { failUndefined, warnAfterTimeout } from '@dxos/debug';
9
11
  import {
10
12
  EchoEdgeReplicator,
@@ -15,49 +17,54 @@ import {
15
17
  valueEncoding,
16
18
  } from '@dxos/echo-pipeline';
17
19
  import { createChainEdgeIdentity, createEphemeralEdgeIdentity } from '@dxos/edge-client';
18
- import type { EdgeHttpClient, EdgeConnection, EdgeIdentity } from '@dxos/edge-client';
20
+ import type { EdgeConnection, EdgeHttpClient, EdgeIdentity } from '@dxos/edge-client';
21
+ import { type RuntimeProvider } from '@dxos/effect';
19
22
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
20
23
  import { invariant } from '@dxos/invariant';
21
24
  import { Keyring } from '@dxos/keyring';
22
- import { PublicKey } from '@dxos/keys';
25
+ import { PublicKey, type SpaceId } from '@dxos/keys';
23
26
  import { type LevelDB } from '@dxos/kv-store';
24
27
  import { log } from '@dxos/log';
25
28
  import { type SignalManager } from '@dxos/messaging';
26
29
  import { type SwarmNetworkManager } from '@dxos/network-manager';
27
30
  import { InvalidStorageVersionError, STORAGE_VERSION, trace } from '@dxos/protocols';
31
+ import { FeedProtocol } from '@dxos/protocols';
28
32
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
29
33
  import { type Runtime } from '@dxos/protocols/proto/dxos/config';
30
34
  import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
31
35
  import { type Credential, type ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
32
36
  import { type Storage } from '@dxos/random-access-storage';
37
+ import type * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
33
38
  import { BlobStore } from '@dxos/teleport-extension-object-sync';
34
39
  import { trace as Trace } from '@dxos/tracing';
35
40
  import { safeInstanceof } from '@dxos/util';
36
41
 
37
42
  import { EdgeAgentManager } from '../agents';
38
43
  import {
39
- IdentityManager,
40
44
  type CreateIdentityOptions,
41
- type IdentityManagerParams,
42
- type JoinIdentityParams,
45
+ IdentityManager,
46
+ type IdentityManagerProps,
47
+ type JoinIdentityProps,
43
48
  } from '../identity';
44
49
  import { EdgeIdentityRecoveryManager } from '../identity/identity-recovery-manager';
45
50
  import {
46
51
  DeviceInvitationProtocol,
47
- type InvitationConnectionParams,
52
+ type InvitationConnectionProps,
53
+ type InvitationProtocol,
48
54
  InvitationsHandler,
49
55
  InvitationsManager,
50
56
  SpaceInvitationProtocol,
51
- type InvitationProtocol,
52
57
  } from '../invitations';
53
- import { DataSpaceManager, type DataSpaceManagerRuntimeParams, type SigningContext } from '../spaces';
58
+ import { DataSpaceManager, type DataSpaceManagerRuntimeProps, type SigningContext } from '../spaces';
59
+
60
+ import { FeedSyncer } from './feed-syncer';
54
61
 
55
- export type ServiceContextRuntimeParams = Pick<
56
- IdentityManagerParams,
62
+ export type ServiceContextRuntimeProps = Pick<
63
+ IdentityManagerProps,
57
64
  'devicePresenceOfflineTimeout' | 'devicePresenceAnnounceInterval'
58
65
  > &
59
- DataSpaceManagerRuntimeParams & {
60
- invitationConnectionDefaultParams?: InvitationConnectionParams;
66
+ DataSpaceManagerRuntimeProps & {
67
+ invitationConnectionDefaultProps?: InvitationConnectionProps;
61
68
  disableP2pReplication?: boolean;
62
69
  enableVectorIndexing?: boolean;
63
70
  };
@@ -84,6 +91,7 @@ export class ServiceContext extends Resource {
84
91
  public readonly echoHost: EchoHost;
85
92
  private readonly _meshReplicator?: MeshEchoReplicator = undefined;
86
93
  private readonly _echoEdgeReplicator?: EchoEdgeReplicator = undefined;
94
+ private readonly _feedSyncer?: FeedSyncer = undefined;
87
95
 
88
96
  // Initialized after identity is initialized.
89
97
  public dataSpaceManager?: DataSpaceManager;
@@ -105,11 +113,15 @@ export class ServiceContext extends Resource {
105
113
  public readonly signalManager: SignalManager,
106
114
  private readonly _edgeConnection: EdgeConnection | undefined,
107
115
  private readonly _edgeHttpClient: EdgeHttpClient | undefined,
108
- public readonly _runtimeParams?: ServiceContextRuntimeParams,
116
+ private readonly _runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>,
117
+ public readonly _runtimeProps?: ServiceContextRuntimeProps,
109
118
  private readonly _edgeFeatures?: Runtime.Client.EdgeFeatures,
110
119
  ) {
111
120
  super();
112
121
 
122
+ log('runtimeProps', this._runtimeProps);
123
+ log('edgeFeatures', this._edgeFeatures);
124
+
113
125
  // TODO(burdon): Move strings to constants.
114
126
  this.metadataStore = new MetadataStore(storage.createDirectory('metadata'));
115
127
  this.blobStore = new BlobStore(storage.createDirectory('blobs'));
@@ -131,7 +143,7 @@ export class ServiceContext extends Resource {
131
143
  networkManager: this.networkManager,
132
144
  blobStore: this.blobStore,
133
145
  metadataStore: this.metadataStore,
134
- disableP2pReplication: this._runtimeParams?.disableP2pReplication,
146
+ disableP2pReplication: this._runtimeProps?.disableP2pReplication,
135
147
  });
136
148
 
137
149
  this.identityManager = new IdentityManager({
@@ -139,8 +151,8 @@ export class ServiceContext extends Resource {
139
151
  keyring: this.keyring,
140
152
  feedStore: this.feedStore,
141
153
  spaceManager: this.spaceManager,
142
- devicePresenceOfflineTimeout: this._runtimeParams?.devicePresenceOfflineTimeout,
143
- devicePresenceAnnounceInterval: this._runtimeParams?.devicePresenceAnnounceInterval,
154
+ devicePresenceOfflineTimeout: this._runtimeProps?.devicePresenceOfflineTimeout,
155
+ devicePresenceAnnounceInterval: this._runtimeProps?.devicePresenceAnnounceInterval,
144
156
  edgeConnection: this._edgeConnection,
145
157
  edgeFeatures: this._edgeFeatures,
146
158
  });
@@ -156,17 +168,21 @@ export class ServiceContext extends Resource {
156
168
  kv: this.level,
157
169
  peerIdProvider: () => this.identityManager.identity?.deviceKey?.toHex(),
158
170
  getSpaceKeyByRootDocumentId: (documentId) => this.spaceManager.findSpaceByRootDocumentId(documentId)?.key,
159
- indexing: {
160
- vector: this._runtimeParams?.enableVectorIndexing,
171
+ runtime: this._runtime,
172
+ syncQueue: async (request) => {
173
+ return this._feedSyncer?.syncBlocking({
174
+ spaceId: request.spaceId as SpaceId,
175
+ subspaceTag: request.subspaceTag,
176
+ shouldPush: request.shouldPush,
177
+ shouldPull: request.shouldPull,
178
+ });
161
179
  },
162
180
  });
163
181
 
164
- this._meshReplicator = new MeshEchoReplicator();
165
-
166
182
  this.invitations = new InvitationsHandler(
167
183
  this.networkManager, //
168
184
  this._edgeHttpClient,
169
- _runtimeParams?.invitationConnectionDefaultParams,
185
+ _runtimeProps?.invitationConnectionDefaultProps,
170
186
  );
171
187
  this.invitationsManager = new InvitationsManager(
172
188
  this.invitations,
@@ -186,12 +202,24 @@ export class ServiceContext extends Resource {
186
202
  ),
187
203
  );
188
204
 
189
- if (!this._runtimeParams?.disableP2pReplication) {
205
+ if (!this._runtimeProps?.disableP2pReplication) {
190
206
  this._meshReplicator = new MeshEchoReplicator();
191
207
  }
192
- if (this._edgeConnection && this._edgeFeatures?.echoReplicator) {
208
+ if (this._edgeConnection && this._edgeFeatures?.echoReplicator && this._edgeHttpClient) {
193
209
  this._echoEdgeReplicator = new EchoEdgeReplicator({
194
210
  edgeConnection: this._edgeConnection,
211
+ edgeHttpClient: this._edgeHttpClient,
212
+ });
213
+ }
214
+
215
+ if (this.echoHost.feedStore && this._edgeConnection) {
216
+ this._feedSyncer = new FeedSyncer({
217
+ runtime: this._runtime,
218
+ feedStore: this.echoHost.feedStore,
219
+ edgeClient: this._edgeConnection,
220
+ peerId: this.identityManager.identity?.deviceKey?.toHex() ?? '',
221
+ getSpaceIds: () => this.echoHost!.spaceIds,
222
+ syncNamespaces: [FeedProtocol.WellKnownNamespaces.data, FeedProtocol.WellKnownNamespaces.trace],
195
223
  });
196
224
  }
197
225
  }
@@ -228,6 +256,8 @@ export class ServiceContext extends Resource {
228
256
  await this._initialize(ctx);
229
257
  }
230
258
 
259
+ await this._feedSyncer?.open();
260
+
231
261
  const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
232
262
  log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
233
263
 
@@ -237,6 +267,9 @@ export class ServiceContext extends Resource {
237
267
 
238
268
  protected override async _close(ctx: Context): Promise<void> {
239
269
  log('closing...');
270
+
271
+ await this._feedSyncer?.close();
272
+
240
273
  if (this._deviceSpaceSync && this.identityManager.identity) {
241
274
  await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
242
275
  }
@@ -244,13 +277,13 @@ export class ServiceContext extends Resource {
244
277
  await this.edgeAgentManager?.close();
245
278
  await this.identityManager.close();
246
279
  await this.spaceManager.close();
247
- await this.feedStore.close();
248
- await this.metadataStore.close();
249
-
250
280
  await this.echoHost.close(ctx);
281
+
251
282
  await this.networkManager.close();
252
283
  await this.signalManager.close();
253
284
  await this._edgeConnection?.close();
285
+ await this.feedStore.close();
286
+ await this.metadataStore.close();
254
287
 
255
288
  log('closed');
256
289
  }
@@ -282,7 +315,7 @@ export class ServiceContext extends Resource {
282
315
  }
283
316
  }
284
317
 
285
- private async _acceptIdentity(params: JoinIdentityParams) {
318
+ private async _acceptIdentity(params: JoinIdentityProps) {
286
319
  const { identity, identityRecord } = await this.identityManager.prepareIdentity(params);
287
320
  await this._setNetworkIdentity({ deviceCredential: params.authorizedDeviceCredential! });
288
321
  await identity.joinNetwork();
@@ -326,7 +359,7 @@ export class ServiceContext extends Resource {
326
359
  edgeHttpClient: this._edgeHttpClient,
327
360
  echoEdgeReplicator: this._echoEdgeReplicator,
328
361
  meshReplicator: this._meshReplicator,
329
- runtimeParams: this._runtimeParams as DataSpaceManagerRuntimeParams,
362
+ runtimeProps: this._runtimeProps as DataSpaceManagerRuntimeProps,
330
363
  edgeFeatures: this._edgeFeatures,
331
364
  });
332
365
  await this.dataSpaceManager.open();
@@ -3,9 +3,10 @@
3
3
  //
4
4
 
5
5
  import { rmSync } from 'node:fs';
6
- import { afterEach, onTestFinished, describe, expect, test } from 'vitest';
7
6
 
8
- import { asyncTimeout, latch, Trigger } from '@dxos/async';
7
+ import { afterEach, describe, expect, onTestFinished, test } from 'vitest';
8
+
9
+ import { Trigger, asyncTimeout, latch } from '@dxos/async';
9
10
  import { Config } from '@dxos/config';
10
11
  import { Context } from '@dxos/context';
11
12
  import { verifyPresentation } from '@dxos/credentials';