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

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 (134) hide show
  1. package/dist/lib/browser/chunk-NQSC7HOE.mjs +22 -0
  2. package/dist/lib/browser/chunk-NQSC7HOE.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-CZSLDMSL.mjs → chunk-NTZOEM4P.mjs} +1197 -1338
  4. package/dist/lib/browser/chunk-NTZOEM4P.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 +441 -66
  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 +24 -12
  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-EUNILIU5.mjs → chunk-DQWA5ILA.mjs} +689 -699
  23. package/dist/lib/node-esm/chunk-DQWA5ILA.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 +441 -66
  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 +24 -12
  38. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  39. package/dist/types/src/packlets/devtools/devtools.d.ts +2 -2
  40. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  41. package/dist/types/src/packlets/diagnostics/index.d.ts +1 -1
  42. package/dist/types/src/packlets/diagnostics/index.d.ts.map +1 -1
  43. package/dist/types/src/packlets/identity/authenticator.d.ts +2 -2
  44. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  45. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +2 -2
  46. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +1 -1
  47. package/dist/types/src/packlets/identity/identity-manager.d.ts +4 -4
  48. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  49. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +2 -2
  50. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
  51. package/dist/types/src/packlets/identity/identity.d.ts +2 -2
  52. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  53. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +4 -4
  54. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  55. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
  56. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  57. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +2 -3
  58. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  59. package/dist/types/src/packlets/invitations/invitations-handler.d.ts +4 -4
  60. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  61. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +2 -2
  62. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  63. package/dist/types/src/packlets/locks/index.d.ts +1 -1
  64. package/dist/types/src/packlets/locks/index.d.ts.map +1 -1
  65. package/dist/types/src/packlets/logging/logging-service.d.ts +4 -0
  66. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  67. package/dist/types/src/packlets/services/client-rpc-server.d.ts +2 -2
  68. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
  69. package/dist/types/src/packlets/services/feed-syncer.d.ts +41 -0
  70. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -0
  71. package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
  72. package/dist/types/src/packlets/services/service-context.d.ts +13 -7
  73. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  74. package/dist/types/src/packlets/services/service-host.d.ts +19 -5
  75. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  76. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
  77. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +10 -5
  78. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  79. package/dist/types/src/packlets/spaces/data-space.d.ts +2 -2
  80. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  81. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +2 -2
  82. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  83. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +6 -6
  84. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  85. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  86. package/dist/types/src/packlets/testing/invitation-utils.d.ts +6 -3
  87. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  88. package/dist/types/src/packlets/testing/test-builder.d.ts +6 -5
  89. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  90. package/dist/types/src/packlets/worker/worker-runtime.d.ts +31 -4
  91. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  92. package/dist/types/src/packlets/worker/worker-session.d.ts +2 -2
  93. package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
  94. package/dist/types/src/version.d.ts +1 -1
  95. package/dist/types/src/version.d.ts.map +1 -1
  96. package/dist/types/tsconfig.tsbuildinfo +1 -1
  97. package/package.json +70 -48
  98. package/src/packlets/devtools/devtools.ts +2 -2
  99. package/src/packlets/diagnostics/index.ts +1 -1
  100. package/src/packlets/identity/authenticator.ts +2 -2
  101. package/src/packlets/identity/default-space-state-machine.ts +2 -2
  102. package/src/packlets/identity/identity-manager.ts +6 -6
  103. package/src/packlets/identity/identity-recovery-manager.ts +2 -2
  104. package/src/packlets/identity/identity.ts +2 -2
  105. package/src/packlets/invitations/device-invitation-protocol.ts +5 -5
  106. package/src/packlets/invitations/invitation-guest-extenstion.ts +6 -4
  107. package/src/packlets/invitations/invitation-host-extension.ts +6 -4
  108. package/src/packlets/invitations/invitation-protocol.ts +2 -3
  109. package/src/packlets/invitations/invitations-handler.ts +7 -7
  110. package/src/packlets/invitations/space-invitation-protocol.ts +7 -13
  111. package/src/packlets/locks/index.ts +1 -1
  112. package/src/packlets/logging/logging-service.ts +4 -0
  113. package/src/packlets/services/client-rpc-server.ts +4 -4
  114. package/src/packlets/services/feed-syncer.ts +227 -0
  115. package/src/packlets/services/platform.ts +7 -1
  116. package/src/packlets/services/service-context.ts +47 -21
  117. package/src/packlets/services/service-host.ts +56 -16
  118. package/src/packlets/space-export/space-archive-reader.ts +1 -1
  119. package/src/packlets/space-export/space-archive-writer.ts +3 -2
  120. package/src/packlets/spaces/data-space-manager.ts +43 -20
  121. package/src/packlets/spaces/data-space.ts +10 -6
  122. package/src/packlets/spaces/edge-feed-replicator.ts +2 -2
  123. package/src/packlets/spaces/epoch-migrations.ts +2 -2
  124. package/src/packlets/spaces/notarization-plugin.test.ts +2 -2
  125. package/src/packlets/spaces/notarization-plugin.ts +8 -8
  126. package/src/packlets/spaces/spaces-service.ts +10 -7
  127. package/src/packlets/storage/storage.ts +4 -4
  128. package/src/packlets/testing/invitation-utils.ts +7 -4
  129. package/src/packlets/testing/test-builder.ts +36 -10
  130. package/src/packlets/worker/worker-runtime.ts +149 -11
  131. package/src/packlets/worker/worker-session.ts +4 -4
  132. package/src/version.ts +1 -1
  133. package/dist/lib/browser/chunk-CZSLDMSL.mjs.map +0 -7
  134. package/dist/lib/node-esm/chunk-EUNILIU5.mjs.map +0 -7
@@ -0,0 +1,227 @@
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 { type SpaceId } from '@dxos/keys';
16
+ import { FeedProtocol } from '@dxos/protocols';
17
+ import { EdgeService } from '@dxos/protocols';
18
+ import { createBuf } from '@dxos/protocols/buf';
19
+ import { type Message as RouterMessage } from '@dxos/protocols/buf/dxos/edge/messenger_pb';
20
+ import type { SqlTransaction } from '@dxos/sql-sqlite';
21
+ import { bufferToArray } from '@dxos/util';
22
+
23
+ const encoder = new Encoder({ tagUint8Array: false, useRecords: false });
24
+
25
+ const DEFAULT_MESSAGE_BLOCKS_LIMIT = 50;
26
+ const DEFAULT_SYNC_CONCURRENCY = 5;
27
+ const DEFAULT_POLLING_INTERVAL = 10_000;
28
+
29
+ interface FeedSyncerOptions {
30
+ runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
31
+ feedStore: FeedStore;
32
+ edgeClient: EdgeConnection;
33
+ peerId: string;
34
+ getSpaceIds: () => SpaceId[];
35
+
36
+ /**
37
+ * Namespace to sync.
38
+ */
39
+ // TODO(dmaretskyi): Syncing only one namespace is supported.
40
+ syncNamespace: string;
41
+
42
+ /**
43
+ * Maximum number of blocks to sync in a single message.
44
+ * @default 50
45
+ */
46
+ messageBlocksLimit?: number;
47
+
48
+ /**
49
+ * Maximum number of spaces to sync concurrently.
50
+ * @default 5
51
+ */
52
+ syncConcurrency?: number;
53
+
54
+ /**
55
+ * Interval between full polls.
56
+ * @default 10 seconds
57
+ */
58
+ pollingInterval?: number;
59
+ }
60
+
61
+ export class FeedSyncer extends Resource {
62
+ readonly #syncNamespace: string;
63
+ readonly #messageBlocksLimit: number;
64
+ readonly #syncConcurrency: number;
65
+ readonly #pollingInterval: number;
66
+
67
+ readonly #runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>;
68
+ readonly #feedStore: FeedStore;
69
+ readonly #edgeClient: EdgeConnection;
70
+ readonly #syncClient: SyncClient;
71
+ readonly #getSpaceIds: () => SpaceId[];
72
+
73
+ #spacesToPoll = new Set<SpaceId>();
74
+ /** Last time full poll was completed. */
75
+ #lastFullPoll: number | null = null;
76
+
77
+ constructor(options: FeedSyncerOptions) {
78
+ super();
79
+ this.#runtime = options.runtime;
80
+ this.#feedStore = options.feedStore;
81
+ this.#edgeClient = options.edgeClient;
82
+ this.#syncClient = new SyncClient({
83
+ peerId: options.peerId,
84
+ feedStore: options.feedStore,
85
+ sendMessage: this.#sendMessage.bind(this),
86
+ });
87
+ this.#getSpaceIds = options.getSpaceIds;
88
+ this.#syncNamespace = options.syncNamespace;
89
+ this.#messageBlocksLimit = options.messageBlocksLimit ?? DEFAULT_MESSAGE_BLOCKS_LIMIT;
90
+ this.#syncConcurrency = options.syncConcurrency ?? DEFAULT_SYNC_CONCURRENCY;
91
+ this.#pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
92
+ }
93
+
94
+ protected override async _open(): Promise<void> {
95
+ this._ctx.onDispose(
96
+ this.#edgeClient.onMessage((msg: RouterMessage) => {
97
+ if (!msg.serviceId) {
98
+ return;
99
+ }
100
+ const service = msg.serviceId.split(':')[0];
101
+ if (service !== EdgeService.QUEUE_REPLICATOR) {
102
+ return;
103
+ }
104
+ const handleMessageEffect = Effect.gen(this, function* () {
105
+ const decoded = yield* Effect.try({
106
+ try: () => cborXdecode(msg.payload!.value),
107
+ catch: (error) => new Error(`Failed to decode feed sync message: ${error}`),
108
+ });
109
+ const payload = yield* Schema.decodeUnknown(FeedProtocol.ProtocolMessage)(decoded);
110
+ yield* this.#syncClient.handleMessage(payload);
111
+ });
112
+
113
+ void RuntimeProvider.runPromise(this.#runtime)(handleMessageEffect);
114
+ }),
115
+ );
116
+
117
+ this._ctx.onDispose(
118
+ // NOTE: This will fire immediately if the connection is already open.
119
+ this.#edgeClient.onReconnected(async () => {}),
120
+ );
121
+
122
+ this.#feedStore.onNewBlocks.on(this._ctx, () => {
123
+ this.#pushTask.schedule();
124
+ });
125
+
126
+ await this.#pollTask.open();
127
+ await this.#pushTask.open();
128
+
129
+ this.#resetSpacesToPoll();
130
+ }
131
+
132
+ protected override async _close(): Promise<void> {
133
+ await this.#pollTask.close();
134
+ await this.#pushTask.close();
135
+ }
136
+
137
+ #resetSpacesToPoll(): void {
138
+ this.#spacesToPoll.clear();
139
+ this.#getSpaceIds().forEach((spaceId) => {
140
+ this.#spacesToPoll.add(spaceId);
141
+ });
142
+ this.#lastFullPoll = Date.now();
143
+ }
144
+
145
+ #sendMessage(message: FeedProtocol.QueryRequest | FeedProtocol.AppendRequest): Effect.Effect<void, unknown, never> {
146
+ return Effect.gen(this, function* () {
147
+ const encoded = encoder.encode(message);
148
+ yield* Effect.tryPromise(async () =>
149
+ this.#edgeClient.send(
150
+ createBuf(MessageSchema, {
151
+ source: {
152
+ identityKey: this.#edgeClient.identityKey,
153
+ peerKey: this.#edgeClient.peerKey,
154
+ },
155
+ serviceId: this.#getTargetServiceId(message),
156
+ payload: { value: bufferToArray(encoded) },
157
+ }),
158
+ ),
159
+ );
160
+ });
161
+ }
162
+
163
+ #getTargetServiceId(message: FeedProtocol.QueryRequest | FeedProtocol.AppendRequest): string {
164
+ // TODO(dmaretskyi): Perhaps in the future we will want to include the queue namespace here as well.
165
+ // This would require putting it at the top level of the message.
166
+ // For now, we let the edge router handle it.
167
+ return FeedProtocol.encodeServiceId(message.feedNamespace, message.spaceId as SpaceId);
168
+ }
169
+
170
+ readonly #pollTask = new AsyncTask(async () =>
171
+ Effect.gen(this, function* () {
172
+ yield* Effect.forEach(
173
+ this.#spacesToPoll,
174
+ (spaceId) =>
175
+ 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) {
182
+ this.#spacesToPoll.delete(spaceId);
183
+ }
184
+ }),
185
+ { concurrency: this.#syncConcurrency },
186
+ );
187
+
188
+ // If its time to do a full poll, reset the spaces to poll and schedule the next poll immediately.
189
+ if (this.#lastFullPoll == null || Date.now() - this.#lastFullPoll > this.#pollingInterval) {
190
+ this.#resetSpacesToPoll();
191
+ this.#pollTask.schedule();
192
+ } else if (this.#spacesToPoll.size > 0) {
193
+ // If there are some spaces still syncing, poll them immediately.
194
+ this.#pollTask.schedule();
195
+ } else {
196
+ // All spaces sync, and there's time before the next full poll, schedule it later.
197
+ this.#resetSpacesToPoll();
198
+ scheduleTask(
199
+ this._ctx,
200
+ () => this.#pollTask.schedule(),
201
+ Math.max(this.#pollingInterval - (Date.now() - (this.#lastFullPoll ?? 0)), 0),
202
+ );
203
+ }
204
+ }).pipe(RuntimeProvider.runPromise(this.#runtime)),
205
+ );
206
+
207
+ readonly #pushTask = new AsyncTask(async () =>
208
+ Effect.gen(this, function* () {
209
+ yield* Effect.forEach(
210
+ this.#getSpaceIds(),
211
+ (spaceId) =>
212
+ 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) {
219
+ // Keep pushing until all blocks are pushed.
220
+ this.#pushTask.schedule();
221
+ }
222
+ }),
223
+ { concurrency: this.#syncConcurrency },
224
+ );
225
+ }).pipe(RuntimeProvider.runPromise(this.#runtime)),
226
+ );
227
+ }
@@ -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.
@@ -2,6 +2,8 @@
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
9
  import { type CredentialProcessor, getCredentialAssertion } from '@dxos/credentials';
@@ -16,6 +18,7 @@ import {
16
18
  } from '@dxos/echo-pipeline';
17
19
  import { createChainEdgeIdentity, createEphemeralEdgeIdentity } from '@dxos/edge-client';
18
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';
@@ -25,11 +28,13 @@ 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';
@@ -38,28 +43,31 @@ import { EdgeAgentManager } from '../agents';
38
43
  import {
39
44
  type CreateIdentityOptions,
40
45
  IdentityManager,
41
- type IdentityManagerParams,
42
- type JoinIdentityParams,
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,
48
53
  type InvitationProtocol,
49
54
  InvitationsHandler,
50
55
  InvitationsManager,
51
56
  SpaceInvitationProtocol,
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;
70
+ enableLocalQueues?: boolean;
63
71
  };
64
72
  /**
65
73
  * Shared backend for all client services.
@@ -84,6 +92,7 @@ export class ServiceContext extends Resource {
84
92
  public readonly echoHost: EchoHost;
85
93
  private readonly _meshReplicator?: MeshEchoReplicator = undefined;
86
94
  private readonly _echoEdgeReplicator?: EchoEdgeReplicator = undefined;
95
+ private readonly _feedSyncer?: FeedSyncer = undefined;
87
96
 
88
97
  // Initialized after identity is initialized.
89
98
  public dataSpaceManager?: DataSpaceManager;
@@ -105,11 +114,15 @@ export class ServiceContext extends Resource {
105
114
  public readonly signalManager: SignalManager,
106
115
  private readonly _edgeConnection: EdgeConnection | undefined,
107
116
  private readonly _edgeHttpClient: EdgeHttpClient | undefined,
108
- public readonly _runtimeParams?: ServiceContextRuntimeParams,
117
+ private readonly _runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransaction.SqlTransaction>,
118
+ public readonly _runtimeProps?: ServiceContextRuntimeProps,
109
119
  private readonly _edgeFeatures?: Runtime.Client.EdgeFeatures,
110
120
  ) {
111
121
  super();
112
122
 
123
+ log('runtimeProps', this._runtimeProps);
124
+ log('edgeFeatures', this._edgeFeatures);
125
+
113
126
  // TODO(burdon): Move strings to constants.
114
127
  this.metadataStore = new MetadataStore(storage.createDirectory('metadata'));
115
128
  this.blobStore = new BlobStore(storage.createDirectory('blobs'));
@@ -131,7 +144,7 @@ export class ServiceContext extends Resource {
131
144
  networkManager: this.networkManager,
132
145
  blobStore: this.blobStore,
133
146
  metadataStore: this.metadataStore,
134
- disableP2pReplication: this._runtimeParams?.disableP2pReplication,
147
+ disableP2pReplication: this._runtimeProps?.disableP2pReplication,
135
148
  });
136
149
 
137
150
  this.identityManager = new IdentityManager({
@@ -139,8 +152,8 @@ export class ServiceContext extends Resource {
139
152
  keyring: this.keyring,
140
153
  feedStore: this.feedStore,
141
154
  spaceManager: this.spaceManager,
142
- devicePresenceOfflineTimeout: this._runtimeParams?.devicePresenceOfflineTimeout,
143
- devicePresenceAnnounceInterval: this._runtimeParams?.devicePresenceAnnounceInterval,
155
+ devicePresenceOfflineTimeout: this._runtimeProps?.devicePresenceOfflineTimeout,
156
+ devicePresenceAnnounceInterval: this._runtimeProps?.devicePresenceAnnounceInterval,
144
157
  edgeConnection: this._edgeConnection,
145
158
  edgeFeatures: this._edgeFeatures,
146
159
  });
@@ -156,17 +169,14 @@ export class ServiceContext extends Resource {
156
169
  kv: this.level,
157
170
  peerIdProvider: () => this.identityManager.identity?.deviceKey?.toHex(),
158
171
  getSpaceKeyByRootDocumentId: (documentId) => this.spaceManager.findSpaceByRootDocumentId(documentId)?.key,
159
- indexing: {
160
- vector: this._runtimeParams?.enableVectorIndexing,
161
- },
172
+ runtime: this._runtime,
173
+ localQueues: this._runtimeProps?.enableLocalQueues,
162
174
  });
163
175
 
164
- this._meshReplicator = new MeshEchoReplicator();
165
-
166
176
  this.invitations = new InvitationsHandler(
167
177
  this.networkManager, //
168
178
  this._edgeHttpClient,
169
- _runtimeParams?.invitationConnectionDefaultParams,
179
+ _runtimeProps?.invitationConnectionDefaultProps,
170
180
  );
171
181
  this.invitationsManager = new InvitationsManager(
172
182
  this.invitations,
@@ -186,7 +196,7 @@ export class ServiceContext extends Resource {
186
196
  ),
187
197
  );
188
198
 
189
- if (!this._runtimeParams?.disableP2pReplication) {
199
+ if (!this._runtimeProps?.disableP2pReplication) {
190
200
  this._meshReplicator = new MeshEchoReplicator();
191
201
  }
192
202
  if (this._edgeConnection && this._edgeFeatures?.echoReplicator && this._edgeHttpClient) {
@@ -195,6 +205,17 @@ export class ServiceContext extends Resource {
195
205
  edgeHttpClient: this._edgeHttpClient,
196
206
  });
197
207
  }
208
+
209
+ if (this.echoHost.feedStore && this._edgeConnection) {
210
+ this._feedSyncer = new FeedSyncer({
211
+ runtime: this._runtime,
212
+ feedStore: this.echoHost.feedStore,
213
+ edgeClient: this._edgeConnection,
214
+ peerId: this.identityManager.identity?.deviceKey?.toHex() ?? '',
215
+ getSpaceIds: () => this.echoHost!.spaceIds,
216
+ syncNamespace: FeedProtocol.WellKnownNamespaces.data,
217
+ });
218
+ }
198
219
  }
199
220
 
200
221
  @Trace.span()
@@ -229,6 +250,8 @@ export class ServiceContext extends Resource {
229
250
  await this._initialize(ctx);
230
251
  }
231
252
 
253
+ await this._feedSyncer?.open();
254
+
232
255
  const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
233
256
  log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
234
257
 
@@ -238,6 +261,9 @@ export class ServiceContext extends Resource {
238
261
 
239
262
  protected override async _close(ctx: Context): Promise<void> {
240
263
  log('closing...');
264
+
265
+ await this._feedSyncer?.close();
266
+
241
267
  if (this._deviceSpaceSync && this.identityManager.identity) {
242
268
  await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
243
269
  }
@@ -283,7 +309,7 @@ export class ServiceContext extends Resource {
283
309
  }
284
310
  }
285
311
 
286
- private async _acceptIdentity(params: JoinIdentityParams) {
312
+ private async _acceptIdentity(params: JoinIdentityProps) {
287
313
  const { identity, identityRecord } = await this.identityManager.prepareIdentity(params);
288
314
  await this._setNetworkIdentity({ deviceCredential: params.authorizedDeviceCredential! });
289
315
  await identity.joinNetwork();
@@ -327,7 +353,7 @@ export class ServiceContext extends Resource {
327
353
  edgeHttpClient: this._edgeHttpClient,
328
354
  echoEdgeReplicator: this._echoEdgeReplicator,
329
355
  meshReplicator: this._meshReplicator,
330
- runtimeParams: this._runtimeParams as DataSpaceManagerRuntimeParams,
356
+ runtimeProps: this._runtimeProps as DataSpaceManagerRuntimeProps,
331
357
  edgeFeatures: this._edgeFeatures,
332
358
  });
333
359
  await this.dataSpaceManager.open();
@@ -2,11 +2,15 @@
2
2
  // Copyright 2021 DXOS.org
3
3
  //
4
4
 
5
+ import * as SqlClient from '@effect/sql/SqlClient';
6
+ import * as Effect from 'effect/Effect';
7
+
5
8
  import { Event, synchronized } from '@dxos/async';
6
9
  import { type ClientServices, clientServiceBundle } from '@dxos/client-protocol';
7
10
  import { type Config } from '@dxos/config';
8
11
  import { Context } from '@dxos/context';
9
12
  import { EdgeClient, type EdgeConnection, EdgeHttpClient, createStubEdgeIdentity } from '@dxos/edge-client';
13
+ import { RuntimeProvider } from '@dxos/effect';
10
14
  import { invariant } from '@dxos/invariant';
11
15
  import { PublicKey } from '@dxos/keys';
12
16
  import { type LevelDB } from '@dxos/kv-store';
@@ -21,6 +25,8 @@ import {
21
25
  import { trace } from '@dxos/protocols';
22
26
  import { SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
23
27
  import { type Storage } from '@dxos/random-access-storage';
28
+ import * as SqlExport from '@dxos/sql-sqlite/SqlExport';
29
+ import type * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
24
30
  import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
25
31
  import { WebsocketRpcClient } from '@dxos/websocket-rpc';
26
32
 
@@ -42,10 +48,10 @@ import { SpacesServiceImpl } from '../spaces';
42
48
  import { createLevel, createStorageObjects } from '../storage';
43
49
  import { SystemServiceImpl } from '../system';
44
50
 
45
- import { ServiceContext, type ServiceContextRuntimeParams } from './service-context';
51
+ import { ServiceContext, type ServiceContextRuntimeProps } from './service-context';
46
52
  import { ServiceRegistry } from './service-registry';
47
53
 
48
- export type ClientServicesHostParams = {
54
+ export type ClientServicesHostProps = {
49
55
  /**
50
56
  * Can be omitted if `initialize` is later called.
51
57
  */
@@ -57,7 +63,8 @@ export type ClientServicesHostParams = {
57
63
  level?: LevelDB;
58
64
  lockKey?: string;
59
65
  callbacks?: ClientServicesHostCallbacks;
60
- runtimeParams?: ServiceContextRuntimeParams;
66
+ runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlExport.SqlExport | SqlTransaction.SqlTransaction>;
67
+ runtimeProps?: ServiceContextRuntimeProps;
61
68
  };
62
69
 
63
70
  export type ClientServicesHostCallbacks = {
@@ -95,7 +102,10 @@ export class ClientServicesHost {
95
102
  private _edgeHttpClient?: EdgeHttpClient = undefined;
96
103
 
97
104
  private _serviceContext!: ServiceContext;
98
- private readonly _runtimeParams: ServiceContextRuntimeParams;
105
+ private readonly _runtime: RuntimeProvider.RuntimeProvider<
106
+ SqlClient.SqlClient | SqlExport.SqlExport | SqlTransaction.SqlTransaction
107
+ >;
108
+ private readonly _runtimeProps: ServiceContextRuntimeProps;
99
109
  private diagnosticsBroadcastHandler: CollectDiagnosticsBroadcastHandler;
100
110
 
101
111
  @Trace.info()
@@ -116,20 +126,14 @@ export class ClientServicesHost {
116
126
  // TODO(wittjosiah): Turn this on by default.
117
127
  lockKey,
118
128
  callbacks,
119
- runtimeParams,
120
- }: ClientServicesHostParams = {}) {
129
+ runtime,
130
+ runtimeProps,
131
+ }: ClientServicesHostProps) {
121
132
  this._storage = storage;
122
133
  this._level = level;
123
134
  this._callbacks = callbacks;
124
- this._runtimeParams = runtimeParams ?? {};
125
-
126
- if (this._runtimeParams.disableP2pReplication === undefined) {
127
- this._runtimeParams.disableP2pReplication = config?.get('runtime.client.disableP2pReplication', false);
128
- }
129
-
130
- if (this._runtimeParams.enableVectorIndexing === undefined) {
131
- this._runtimeParams.enableVectorIndexing = config?.get('runtime.client.enableVectorIndexing', false);
132
- }
135
+ this._runtime = runtime;
136
+ this._runtimeProps = runtimeProps ?? {};
133
137
 
134
138
  if (config) {
135
139
  this.initialize({ config, transportFactory, signalManager });
@@ -200,6 +204,30 @@ export class ClientServicesHost {
200
204
  return this._serviceRegistry.services;
201
205
  }
202
206
 
207
+ /**
208
+ * Debugging util.
209
+ */
210
+ async exportSqliteDatabase(): Promise<Uint8Array> {
211
+ return await RuntimeProvider.runPromise(this._runtime)(
212
+ Effect.gen(function* () {
213
+ const sql = yield* SqlExport.SqlExport;
214
+ return yield* sql.export;
215
+ }),
216
+ );
217
+ }
218
+
219
+ /**
220
+ * Debugging util.
221
+ */
222
+ async runSqliteQuery(query: string, params?: any[]): Promise<readonly Record<string, unknown>[]> {
223
+ return await RuntimeProvider.runPromise(this._runtime)(
224
+ Effect.gen(function* () {
225
+ const sql = yield* SqlClient.SqlClient;
226
+ return yield* sql`${sql.unsafe(query, params)}`;
227
+ }),
228
+ );
229
+ }
230
+
203
231
  /**
204
232
  * Initialize the service host with the config.
205
233
  * Config can also be provided in the constructor.
@@ -210,6 +238,16 @@ export class ClientServicesHost {
210
238
  log('initializing...');
211
239
 
212
240
  if (config) {
241
+ if (this._runtimeProps.disableP2pReplication === undefined) {
242
+ this._runtimeProps.disableP2pReplication = config?.get('runtime.client.disableP2pReplication', false);
243
+ }
244
+ if (this._runtimeProps.enableVectorIndexing === undefined) {
245
+ this._runtimeProps.enableVectorIndexing = config?.get('runtime.client.enableVectorIndexing', false);
246
+ }
247
+ if (this._runtimeProps.enableLocalQueues === undefined) {
248
+ this._runtimeProps.enableLocalQueues = config?.get('runtime.client.enableLocalQueues', false);
249
+ }
250
+
213
251
  invariant(!this._config, 'config already set');
214
252
  this._config = config;
215
253
  if (!this._storage) {
@@ -291,7 +329,8 @@ export class ClientServicesHost {
291
329
  this._signalManager,
292
330
  this._edgeConnection,
293
331
  this._edgeHttpClient,
294
- this._runtimeParams,
332
+ this._runtime,
333
+ this._runtimeProps,
295
334
  this._config.get('runtime.client.edgeFeatures'),
296
335
  );
297
336
 
@@ -335,6 +374,7 @@ export class ClientServicesHost {
335
374
 
336
375
  DataService: this._serviceContext.echoHost.dataService,
337
376
  QueryService: this._serviceContext.echoHost.queryService,
377
+ QueueService: this._serviceContext.echoHost.queuesService,
338
378
 
339
379
  NetworkService: new NetworkServiceImpl(
340
380
  this._serviceContext.networkManager,
@@ -29,6 +29,6 @@ export const extractSpaceArchive = async (archive: SpaceArchive): Promise<Extrac
29
29
  documents[documentId] = entry.content ?? failedInvariant();
30
30
  }
31
31
 
32
- log.info('extracted space archive', { metadata, documents });
32
+ log('extracted space archive', { metadata, documents });
33
33
  return { metadata, documents };
34
34
  };
@@ -9,6 +9,7 @@ import { assertArgument, assertState } from '@dxos/invariant';
9
9
  import type { IdentityDid, SpaceId } from '@dxos/keys';
10
10
  import { SpaceArchiveFileStructure, type SpaceArchiveMetadata, SpaceArchiveVersion } from '@dxos/protocols';
11
11
  import type { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
12
+ import { createFilename } from '@dxos/util';
12
13
 
13
14
  export type SpaceArchiveBeginProps = {
14
15
  spaceId?: SpaceId;
@@ -59,6 +60,7 @@ export class SpaceArchiveWriter extends Resource {
59
60
  async finish(): Promise<SpaceArchive> {
60
61
  assertState(this._archive, 'Not open');
61
62
  assertState(this._meta, 'Not started');
63
+ assertState(this._meta.spaceId, 'No space ID set');
62
64
  assertState(this._currentRootUrl, 'No root URL set');
63
65
 
64
66
  const metadata: SpaceArchiveMetadata = {
@@ -76,8 +78,7 @@ export class SpaceArchiveWriter extends Resource {
76
78
  const binary = this._archive.toUint8Array();
77
79
 
78
80
  return {
79
- // TODO(wittjosiah): Factor out file name construction.
80
- filename: `${new Date().toISOString()}-${this._meta.spaceId}.tar`,
81
+ filename: createFilename({ parts: [this._meta.spaceId], ext: 'tar' }),
81
82
  contents: binary,
82
83
  };
83
84
  }