@dxos/client-services 0.8.4-main.69d29f4 → 0.8.4-main.6fa680abb7

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 (41) hide show
  1. package/dist/lib/browser/{chunk-KBXLEVD5.mjs → chunk-CZN7GX3A.mjs} +386 -121
  2. package/dist/lib/browser/chunk-CZN7GX3A.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +23 -15
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +1 -1
  7. package/dist/lib/node-esm/{chunk-G62TR33T.mjs → chunk-F36CDLDY.mjs} +386 -121
  8. package/dist/lib/node-esm/chunk-F36CDLDY.mjs.map +7 -0
  9. package/dist/lib/node-esm/index.mjs +23 -15
  10. package/dist/lib/node-esm/index.mjs.map +3 -3
  11. package/dist/lib/node-esm/meta.json +1 -1
  12. package/dist/lib/node-esm/testing/index.mjs +1 -1
  13. package/dist/types/src/packlets/logging/logging-service.d.ts +4 -0
  14. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  15. package/dist/types/src/packlets/services/feed-syncer.d.ts +59 -0
  16. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -0
  17. package/dist/types/src/packlets/services/feed-syncer.test.d.ts +2 -0
  18. package/dist/types/src/packlets/services/feed-syncer.test.d.ts.map +1 -0
  19. package/dist/types/src/packlets/services/platform.d.ts.map +1 -1
  20. package/dist/types/src/packlets/services/service-context.d.ts +1 -2
  21. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  22. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  23. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  24. package/dist/types/src/packlets/worker/worker-runtime.d.ts +11 -3
  25. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  26. package/dist/types/src/version.d.ts +1 -1
  27. package/dist/types/src/version.d.ts.map +1 -1
  28. package/dist/types/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +43 -43
  30. package/src/packlets/logging/logging-service.ts +4 -0
  31. package/src/packlets/services/feed-syncer.test.ts +340 -0
  32. package/src/packlets/services/feed-syncer.ts +330 -0
  33. package/src/packlets/services/platform.ts +7 -1
  34. package/src/packlets/services/service-context.ts +29 -8
  35. package/src/packlets/services/service-host.ts +0 -3
  36. package/src/packlets/spaces/data-space.ts +3 -0
  37. package/src/packlets/worker/worker-runtime.ts +13 -5
  38. package/src/packlets/worker/worker-session.ts +4 -4
  39. package/src/version.ts +1 -1
  40. package/dist/lib/browser/chunk-KBXLEVD5.mjs.map +0 -7
  41. package/dist/lib/node-esm/chunk-G62TR33T.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.
@@ -22,12 +22,13 @@ import { type RuntimeProvider } from '@dxos/effect';
22
22
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
23
23
  import { invariant } from '@dxos/invariant';
24
24
  import { Keyring } from '@dxos/keyring';
25
- import { PublicKey } from '@dxos/keys';
25
+ import { PublicKey, type SpaceId } from '@dxos/keys';
26
26
  import { type LevelDB } from '@dxos/kv-store';
27
27
  import { log } from '@dxos/log';
28
28
  import { type SignalManager } from '@dxos/messaging';
29
29
  import { type SwarmNetworkManager } from '@dxos/network-manager';
30
30
  import { InvalidStorageVersionError, STORAGE_VERSION, trace } from '@dxos/protocols';
31
+ import { FeedProtocol } from '@dxos/protocols';
31
32
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
32
33
  import { type Runtime } from '@dxos/protocols/proto/dxos/config';
33
34
  import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
@@ -56,6 +57,8 @@ import {
56
57
  } from '../invitations';
57
58
  import { DataSpaceManager, type DataSpaceManagerRuntimeProps, type SigningContext } from '../spaces';
58
59
 
60
+ import { FeedSyncer } from './feed-syncer';
61
+
59
62
  export type ServiceContextRuntimeProps = Pick<
60
63
  IdentityManagerProps,
61
64
  'devicePresenceOfflineTimeout' | 'devicePresenceAnnounceInterval'
@@ -64,8 +67,6 @@ export type ServiceContextRuntimeProps = Pick<
64
67
  invitationConnectionDefaultProps?: InvitationConnectionProps;
65
68
  disableP2pReplication?: boolean;
66
69
  enableVectorIndexing?: boolean;
67
- enableSqlite?: boolean;
68
- enableLocalQueues?: boolean;
69
70
  };
70
71
  /**
71
72
  * Shared backend for all client services.
@@ -90,6 +91,7 @@ export class ServiceContext extends Resource {
90
91
  public readonly echoHost: EchoHost;
91
92
  private readonly _meshReplicator?: MeshEchoReplicator = undefined;
92
93
  private readonly _echoEdgeReplicator?: EchoEdgeReplicator = undefined;
94
+ private readonly _feedSyncer?: FeedSyncer = undefined;
93
95
 
94
96
  // Initialized after identity is initialized.
95
97
  public dataSpaceManager?: DataSpaceManager;
@@ -166,12 +168,15 @@ export class ServiceContext extends Resource {
166
168
  kv: this.level,
167
169
  peerIdProvider: () => this.identityManager.identity?.deviceKey?.toHex(),
168
170
  getSpaceKeyByRootDocumentId: (documentId) => this.spaceManager.findSpaceByRootDocumentId(documentId)?.key,
169
- indexing: {
170
- vector: this._runtimeProps?.enableVectorIndexing,
171
- sqlIndex: this._runtimeProps?.enableSqlite,
172
- },
173
171
  runtime: this._runtime,
174
- localQueues: this._runtimeProps?.enableLocalQueues,
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
+ });
179
+ },
175
180
  });
176
181
 
177
182
  this.invitations = new InvitationsHandler(
@@ -206,6 +211,17 @@ export class ServiceContext extends Resource {
206
211
  edgeHttpClient: this._edgeHttpClient,
207
212
  });
208
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],
223
+ });
224
+ }
209
225
  }
210
226
 
211
227
  @Trace.span()
@@ -240,6 +256,8 @@ export class ServiceContext extends Resource {
240
256
  await this._initialize(ctx);
241
257
  }
242
258
 
259
+ await this._feedSyncer?.open();
260
+
243
261
  const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
244
262
  log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
245
263
 
@@ -249,6 +267,9 @@ export class ServiceContext extends Resource {
249
267
 
250
268
  protected override async _close(ctx: Context): Promise<void> {
251
269
  log('closing...');
270
+
271
+ await this._feedSyncer?.close();
272
+
252
273
  if (this._deviceSpaceSync && this.identityManager.identity) {
253
274
  await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
254
275
  }
@@ -244,9 +244,6 @@ export class ClientServicesHost {
244
244
  if (this._runtimeProps.enableVectorIndexing === undefined) {
245
245
  this._runtimeProps.enableVectorIndexing = config?.get('runtime.client.enableVectorIndexing', false);
246
246
  }
247
- if (this._runtimeProps.enableLocalQueues === undefined) {
248
- this._runtimeProps.enableLocalQueues = config?.get('runtime.client.enableLocalQueues', false);
249
- }
250
247
 
251
248
  invariant(!this._config, 'config already set');
252
249
  this._config = config;
@@ -450,6 +450,9 @@ export class DataSpace {
450
450
 
451
451
  log('credentials notarized');
452
452
  } catch (err) {
453
+ if (err instanceof ContextDisposedError) {
454
+ return;
455
+ }
453
456
  log.error('error notarizing credentials for feed admission', err);
454
457
  throw err;
455
458
  }
@@ -37,6 +37,7 @@ export type CreateSessionProps = {
37
37
  appPort: RpcPort;
38
38
  systemPort: RpcPort;
39
39
  shellPort?: RpcPort;
40
+ onClose?: () => Promise<void>;
40
41
  };
41
42
 
42
43
  export type WorkerRuntimeOptions = {
@@ -50,7 +51,11 @@ export type WorkerRuntimeOptions = {
50
51
  */
51
52
  automaticallyConnectWebrtc?: boolean;
52
53
 
53
- enableSqlite?: boolean;
54
+ /**
55
+ * Optional SQLite layer for Effect. Defaults to LocalSqliteOpfsLayer.
56
+ * For testing in Node.js, use `sqliteLayerMemory` from `@dxos/sql-sqlite/platform`.
57
+ */
58
+ sqliteLayer?: Layer.Layer<SqlClient.SqlClient | SqlExport.SqlExport, unknown>;
54
59
  };
55
60
 
56
61
  /**
@@ -87,16 +92,19 @@ export class WorkerRuntime {
87
92
  releaseLock,
88
93
  onStop,
89
94
  automaticallyConnectWebrtc = true,
90
- enableSqlite,
95
+ sqliteLayer,
91
96
  }: WorkerRuntimeOptions) {
92
97
  this._configProvider = configProvider;
93
98
  this._acquireLock = acquireLock;
94
99
  this._releaseLock = releaseLock;
95
100
  this._onStop = onStop;
96
101
  this._channel = channel;
102
+ if (sqliteLayer) {
103
+ log.warn('Using testing SQLite layer');
104
+ }
97
105
  this._runtime = ManagedRuntime.make(
98
106
  SqlTransaction.layer
99
- .pipe(Layer.provideMerge(LocalSqliteOpfsLayer), Layer.provideMerge(Reactivity.layer))
107
+ .pipe(Layer.provideMerge(sqliteLayer ?? LocalSqliteOpfsLayer), Layer.provideMerge(Reactivity.layer))
100
108
  .pipe(Layer.orDie),
101
109
  );
102
110
  this._clientServices = new ClientServicesHost({
@@ -105,7 +113,6 @@ export class WorkerRuntime {
105
113
  },
106
114
  runtime: this._runtime.runtimeEffect,
107
115
  runtimeProps: {
108
- enableSqlite,
109
116
  // Auto-activate spaces that were previously active after leader changeover.
110
117
  autoActivateSpaces: true,
111
118
  },
@@ -178,7 +185,7 @@ export class WorkerRuntime {
178
185
  /**
179
186
  * Create a new session.
180
187
  */
181
- async createSession({ appPort, systemPort, shellPort }: CreateSessionProps): Promise<WorkerSession> {
188
+ async createSession({ appPort, systemPort, shellPort, onClose }: CreateSessionProps): Promise<WorkerSession> {
182
189
  const session = new WorkerSession({
183
190
  serviceHost: this._clientServices,
184
191
  appPort,
@@ -198,6 +205,7 @@ export class WorkerRuntime {
198
205
  this._reconnectWebrtc();
199
206
  }
200
207
  }
208
+ await onClose?.();
201
209
  });
202
210
 
203
211
  await session.open();
@@ -119,7 +119,7 @@ export class WorkerSession {
119
119
  }
120
120
 
121
121
  async open(): Promise<void> {
122
- log.info('opening...');
122
+ log('opening...');
123
123
  await Promise.all([this._clientRpc.open(), this._iframeRpc.open(), this._maybeOpenShell()]);
124
124
 
125
125
  // Wait until the worker's RPC service has started.
@@ -130,11 +130,11 @@ export class WorkerSession {
130
130
  void this._afterLockReleases(this.lockKey, () => this.close());
131
131
  }
132
132
 
133
- log.info('opened');
133
+ log('opened');
134
134
  }
135
135
 
136
136
  async close(): Promise<void> {
137
- log.info('closing...');
137
+ log.debug('closing...');
138
138
  try {
139
139
  await this.onClose.callIfSet();
140
140
  } catch (err: any) {
@@ -142,7 +142,7 @@ export class WorkerSession {
142
142
  }
143
143
 
144
144
  await Promise.all([this._clientRpc.close(), this._iframeRpc.close()]);
145
- log.info('closed');
145
+ log.debug('closed');
146
146
  }
147
147
 
148
148
  private async _maybeOpenShell(): Promise<void> {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const DXOS_VERSION = "0.8.4-main.69d29f4";
1
+ export const DXOS_VERSION = "0.8.4-main.6fa680abb7";