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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/browser/{chunk-NTZOEM4P.mjs → chunk-SNHT4M6V.mjs} +1731 -3089
  4. package/dist/lib/browser/chunk-SNHT4M6V.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-NQSC7HOE.mjs → chunk-XJRPB3GA.mjs} +1 -1
  6. package/dist/lib/browser/index.mjs +106 -194
  7. package/dist/lib/browser/index.mjs.map +3 -3
  8. package/dist/lib/browser/meta.json +1 -1
  9. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs +1 -6
  10. package/dist/lib/browser/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +2 -2
  11. package/dist/lib/browser/packlets/diagnostics/diagnostics-broadcast.mjs +1 -1
  12. package/dist/lib/browser/packlets/locks/browser.mjs +9 -49
  13. package/dist/lib/browser/packlets/locks/browser.mjs.map +2 -2
  14. package/dist/lib/browser/packlets/locks/node.mjs +4 -22
  15. package/dist/lib/browser/packlets/locks/node.mjs.map +2 -2
  16. package/dist/lib/browser/testing/index.mjs +9 -22
  17. package/dist/lib/browser/testing/index.mjs.map +3 -3
  18. package/dist/lib/node-esm/{chunk-PKEGMOQ4.mjs → chunk-2DT3MZRL.mjs} +1 -1
  19. package/dist/lib/node-esm/{chunk-DQWA5ILA.mjs → chunk-2TVKX6UU.mjs} +1731 -3089
  20. package/dist/lib/node-esm/chunk-2TVKX6UU.mjs.map +7 -0
  21. package/dist/lib/node-esm/index.mjs +106 -194
  22. package/dist/lib/node-esm/index.mjs.map +3 -3
  23. package/dist/lib/node-esm/meta.json +1 -1
  24. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs +1 -6
  25. package/dist/lib/node-esm/packlets/diagnostics/browser-diagnostics-broadcast.mjs.map +2 -2
  26. package/dist/lib/node-esm/packlets/diagnostics/diagnostics-broadcast.mjs +1 -1
  27. package/dist/lib/node-esm/packlets/locks/browser.mjs +9 -49
  28. package/dist/lib/node-esm/packlets/locks/browser.mjs.map +2 -2
  29. package/dist/lib/node-esm/packlets/locks/node.mjs +4 -22
  30. package/dist/lib/node-esm/packlets/locks/node.mjs.map +2 -2
  31. package/dist/lib/node-esm/testing/index.mjs +9 -22
  32. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  33. package/dist/types/src/index.d.ts +1 -0
  34. package/dist/types/src/index.d.ts.map +1 -1
  35. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +3 -2
  36. package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -1
  37. package/dist/types/src/packlets/agents/edge-agent-service.d.ts +2 -1
  38. package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -1
  39. package/dist/types/src/packlets/devices/devices-service.d.ts.map +1 -1
  40. package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
  41. package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
  42. package/dist/types/src/packlets/devtools/keys.d.ts.map +1 -1
  43. package/dist/types/src/packlets/devtools/metadata.d.ts.map +1 -1
  44. package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
  45. package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
  46. package/dist/types/src/packlets/diagnostics/browser-diagnostics-broadcast.d.ts.map +1 -1
  47. package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
  48. package/dist/types/src/packlets/diagnostics/diagnostics-collector.d.ts.map +1 -1
  49. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts +2 -3
  50. package/dist/types/src/packlets/diagnostics/diagnostics.d.ts.map +1 -1
  51. package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
  52. package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
  53. package/dist/types/src/packlets/identity/identity-manager.d.ts +3 -3
  54. package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
  55. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +6 -5
  56. package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -1
  57. package/dist/types/src/packlets/identity/identity-service.d.ts +6 -10
  58. package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
  59. package/dist/types/src/packlets/identity/identity.d.ts +6 -9
  60. package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
  61. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts +2 -1
  62. package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
  63. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +1 -1
  64. package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -1
  65. package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
  66. package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
  67. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts +5 -1
  68. package/dist/types/src/packlets/invitations/invitation-protocol.d.ts.map +1 -1
  69. package/dist/types/src/packlets/invitations/invitation-state.d.ts.map +1 -1
  70. package/dist/types/src/packlets/invitations/invitation-topology.d.ts.map +1 -1
  71. package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
  72. package/dist/types/src/packlets/invitations/invitations-manager.d.ts +3 -3
  73. package/dist/types/src/packlets/invitations/invitations-manager.d.ts.map +1 -1
  74. package/dist/types/src/packlets/invitations/invitations-service.d.ts +3 -3
  75. package/dist/types/src/packlets/invitations/invitations-service.d.ts.map +1 -1
  76. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts +2 -1
  77. package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
  78. package/dist/types/src/packlets/invitations/utils.d.ts.map +1 -1
  79. package/dist/types/src/packlets/locks/browser.d.ts.map +1 -1
  80. package/dist/types/src/packlets/locks/node.d.ts.map +1 -1
  81. package/dist/types/src/packlets/logging/logging-service.d.ts.map +1 -1
  82. package/dist/types/src/packlets/network/network-service.d.ts +5 -4
  83. package/dist/types/src/packlets/network/network-service.d.ts.map +1 -1
  84. package/dist/types/src/packlets/services/client-rpc-server.d.ts +3 -3
  85. package/dist/types/src/packlets/services/client-rpc-server.d.ts.map +1 -1
  86. package/dist/types/src/packlets/services/feed-syncer.d.ts +22 -4
  87. package/dist/types/src/packlets/services/feed-syncer.d.ts.map +1 -1
  88. package/dist/types/src/packlets/services/feed-syncer.test.d.ts +2 -0
  89. package/dist/types/src/packlets/services/feed-syncer.test.d.ts.map +1 -0
  90. package/dist/types/src/packlets/services/service-context.d.ts +2 -4
  91. package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
  92. package/dist/types/src/packlets/services/service-host.d.ts +2 -3
  93. package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
  94. package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
  95. package/dist/types/src/packlets/services/util.d.ts.map +1 -1
  96. package/dist/types/src/packlets/space-export/archive-format.d.ts +9 -0
  97. package/dist/types/src/packlets/space-export/archive-format.d.ts.map +1 -0
  98. package/dist/types/src/packlets/space-export/index.d.ts +4 -1
  99. package/dist/types/src/packlets/space-export/index.d.ts.map +1 -1
  100. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts +23 -0
  101. package/dist/types/src/packlets/space-export/serialized-space-reader.d.ts.map +1 -0
  102. package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts +36 -0
  103. package/dist/types/src/packlets/space-export/serialized-space-writer.d.ts.map +1 -0
  104. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts +9 -1
  105. package/dist/types/src/packlets/space-export/space-archive-reader.d.ts.map +1 -1
  106. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts +7 -1
  107. package/dist/types/src/packlets/space-export/space-archive-writer.d.ts.map +1 -1
  108. package/dist/types/src/packlets/space-export/space-archive.test.d.ts +2 -0
  109. package/dist/types/src/packlets/space-export/space-archive.test.d.ts.map +1 -0
  110. package/dist/types/src/packlets/spaces/automerge-space-state.d.ts.map +1 -1
  111. package/dist/types/src/packlets/spaces/data-space-manager.d.ts +20 -14
  112. package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
  113. package/dist/types/src/packlets/spaces/data-space.d.ts +24 -7
  114. package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
  115. package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
  116. package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
  117. package/dist/types/src/packlets/spaces/genesis.d.ts +2 -1
  118. package/dist/types/src/packlets/spaces/genesis.d.ts.map +1 -1
  119. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +1 -4
  120. package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
  121. package/dist/types/src/packlets/spaces/spaces-service.d.ts +10 -7
  122. package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
  123. package/dist/types/src/packlets/storage/level.d.ts.map +1 -1
  124. package/dist/types/src/packlets/storage/profile-archive.d.ts.map +1 -1
  125. package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
  126. package/dist/types/src/packlets/storage/util.d.ts.map +1 -1
  127. package/dist/types/src/packlets/system/system-service.d.ts +1 -1
  128. package/dist/types/src/packlets/system/system-service.d.ts.map +1 -1
  129. package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -1
  130. package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -1
  131. package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
  132. package/dist/types/src/packlets/worker/worker-runtime.d.ts +11 -1
  133. package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
  134. package/dist/types/src/packlets/worker/worker-session.d.ts +0 -2
  135. package/dist/types/src/packlets/worker/worker-session.d.ts.map +1 -1
  136. package/dist/types/src/testing/setup.d.ts.map +1 -1
  137. package/dist/types/src/version.d.ts +1 -1
  138. package/dist/types/tsconfig.tsbuildinfo +1 -1
  139. package/package.json +43 -50
  140. package/src/index.ts +1 -0
  141. package/src/packlets/agents/edge-agent-manager.ts +8 -5
  142. package/src/packlets/agents/edge-agent-service.ts +4 -2
  143. package/src/packlets/devices/devices-service.test.ts +0 -1
  144. package/src/packlets/devtools/devtools.ts +0 -1
  145. package/src/packlets/diagnostics/diagnostics.ts +1 -2
  146. package/src/packlets/identity/contacts-service.ts +0 -1
  147. package/src/packlets/identity/identity-manager.test.ts +5 -5
  148. package/src/packlets/identity/identity-manager.ts +18 -17
  149. package/src/packlets/identity/identity-recovery-manager.ts +20 -16
  150. package/src/packlets/identity/identity-service.test.ts +6 -27
  151. package/src/packlets/identity/identity-service.ts +13 -81
  152. package/src/packlets/identity/identity.test.ts +2 -2
  153. package/src/packlets/identity/identity.ts +9 -32
  154. package/src/packlets/invitations/device-invitation-protocol.ts +3 -2
  155. package/src/packlets/invitations/edge-invitation-handler.ts +9 -5
  156. package/src/packlets/invitations/invitation-host-extension.ts +7 -10
  157. package/src/packlets/invitations/invitation-protocol.ts +5 -1
  158. package/src/packlets/invitations/invitation-state.ts +1 -15
  159. package/src/packlets/invitations/invitations-handler.test.ts +4 -5
  160. package/src/packlets/invitations/invitations-handler.ts +67 -15
  161. package/src/packlets/invitations/invitations-manager.ts +40 -15
  162. package/src/packlets/invitations/invitations-service.ts +9 -9
  163. package/src/packlets/invitations/space-invitation-protocol.test.ts +17 -16
  164. package/src/packlets/invitations/space-invitation-protocol.ts +4 -3
  165. package/src/packlets/logging/logging-service.ts +15 -15
  166. package/src/packlets/network/network-service.test.ts +0 -1
  167. package/src/packlets/network/network-service.ts +10 -8
  168. package/src/packlets/services/client-rpc-server.ts +15 -12
  169. package/src/packlets/services/feed-syncer.test.ts +340 -0
  170. package/src/packlets/services/feed-syncer.ts +175 -25
  171. package/src/packlets/services/service-context.test.ts +3 -2
  172. package/src/packlets/services/service-context.ts +113 -47
  173. package/src/packlets/services/service-host.test.ts +8 -8
  174. package/src/packlets/services/service-host.ts +17 -27
  175. package/src/packlets/services/service-registry.test.ts +0 -1
  176. package/src/packlets/space-export/archive-format.ts +42 -0
  177. package/src/packlets/space-export/index.ts +4 -1
  178. package/src/packlets/space-export/serialized-space-reader.ts +111 -0
  179. package/src/packlets/space-export/serialized-space-writer.ts +252 -0
  180. package/src/packlets/space-export/space-archive-reader.ts +64 -3
  181. package/src/packlets/space-export/space-archive-writer.ts +38 -2
  182. package/src/packlets/space-export/space-archive.test.ts +461 -0
  183. package/src/packlets/spaces/data-space-manager.test.ts +79 -13
  184. package/src/packlets/spaces/data-space-manager.ts +80 -115
  185. package/src/packlets/spaces/data-space.ts +55 -31
  186. package/src/packlets/spaces/edge-feed-replicator.test.ts +1 -1
  187. package/src/packlets/spaces/edge-feed-replicator.ts +9 -7
  188. package/src/packlets/spaces/epoch-migrations.ts +4 -3
  189. package/src/packlets/spaces/genesis.ts +6 -1
  190. package/src/packlets/spaces/notarization-plugin.ts +2 -1
  191. package/src/packlets/spaces/spaces-service.test.ts +18 -11
  192. package/src/packlets/spaces/spaces-service.ts +115 -17
  193. package/src/packlets/testing/invitation-utils.ts +3 -2
  194. package/src/packlets/worker/worker-runtime.ts +39 -6
  195. package/src/packlets/worker/worker-session.ts +8 -14
  196. package/src/version.ts +1 -1
  197. package/dist/lib/browser/chunk-NTZOEM4P.mjs.map +0 -7
  198. package/dist/lib/node-esm/chunk-DQWA5ILA.mjs.map +0 -7
  199. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts +0 -19
  200. package/dist/types/src/packlets/identity/default-space-state-machine.d.ts.map +0 -1
  201. package/src/packlets/identity/default-space-state-machine.ts +0 -44
  202. /package/dist/lib/browser/{chunk-NQSC7HOE.mjs.map → chunk-XJRPB3GA.mjs.map} +0 -0
  203. /package/dist/lib/node-esm/{chunk-PKEGMOQ4.mjs.map → chunk-2DT3MZRL.mjs.map} +0 -0
@@ -10,7 +10,9 @@ import { type CredentialProcessor, getCredentialAssertion } from '@dxos/credenti
10
10
  import { failUndefined, warnAfterTimeout } from '@dxos/debug';
11
11
  import {
12
12
  EchoEdgeReplicator,
13
+ EchoEdgeSubductionReplicator,
13
14
  EchoHost,
15
+ type EdgeAutomergeReplicator,
14
16
  MeshEchoReplicator,
15
17
  MetadataStore,
16
18
  SpaceManager,
@@ -22,12 +24,12 @@ import { type RuntimeProvider } from '@dxos/effect';
22
24
  import { FeedFactory, FeedStore } from '@dxos/feed-store';
23
25
  import { invariant } from '@dxos/invariant';
24
26
  import { Keyring } from '@dxos/keyring';
25
- import { PublicKey } from '@dxos/keys';
27
+ import { type SpaceId } from '@dxos/keys';
26
28
  import { type LevelDB } from '@dxos/kv-store';
27
29
  import { log } from '@dxos/log';
28
30
  import { type SignalManager } from '@dxos/messaging';
29
31
  import { type SwarmNetworkManager } from '@dxos/network-manager';
30
- import { InvalidStorageVersionError, STORAGE_VERSION, trace } from '@dxos/protocols';
32
+ import { InvalidStorageVersionError, STORAGE_VERSION } from '@dxos/protocols';
31
33
  import { FeedProtocol } from '@dxos/protocols';
32
34
  import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
33
35
  import { type Runtime } from '@dxos/protocols/proto/dxos/config';
@@ -42,6 +44,7 @@ import { safeInstanceof } from '@dxos/util';
42
44
  import { EdgeAgentManager } from '../agents';
43
45
  import {
44
46
  type CreateIdentityOptions,
47
+ type Identity,
45
48
  IdentityManager,
46
49
  type IdentityManagerProps,
47
50
  type JoinIdentityProps,
@@ -56,7 +59,6 @@ import {
56
59
  SpaceInvitationProtocol,
57
60
  } from '../invitations';
58
61
  import { DataSpaceManager, type DataSpaceManagerRuntimeProps, type SigningContext } from '../spaces';
59
-
60
62
  import { FeedSyncer } from './feed-syncer';
61
63
 
62
64
  export type ServiceContextRuntimeProps = Pick<
@@ -67,7 +69,6 @@ export type ServiceContextRuntimeProps = Pick<
67
69
  invitationConnectionDefaultProps?: InvitationConnectionProps;
68
70
  disableP2pReplication?: boolean;
69
71
  enableVectorIndexing?: boolean;
70
- enableLocalQueues?: boolean;
71
72
  };
72
73
  /**
73
74
  * Shared backend for all client services.
@@ -75,7 +76,7 @@ export type ServiceContextRuntimeProps = Pick<
75
76
  // TODO(burdon): Rename/break-up into smaller components. And/or make members private.
76
77
  // TODO(dmaretskyi): Gets duplicated in CJS build between normal and testing bundles.
77
78
  @safeInstanceof('dxos.client-services.ServiceContext')
78
- @Trace.resource()
79
+ @Trace.resource({ lifecycle: true })
79
80
  export class ServiceContext extends Resource {
80
81
  private readonly _edgeIdentityUpdateMutex = new Mutex();
81
82
 
@@ -91,7 +92,7 @@ export class ServiceContext extends Resource {
91
92
  public readonly invitationsManager: InvitationsManager;
92
93
  public readonly echoHost: EchoHost;
93
94
  private readonly _meshReplicator?: MeshEchoReplicator = undefined;
94
- private readonly _echoEdgeReplicator?: EchoEdgeReplicator = undefined;
95
+ private readonly _echoEdgeReplicator?: EdgeAutomergeReplicator = undefined;
95
96
  private readonly _feedSyncer?: FeedSyncer = undefined;
96
97
 
97
98
  // Initialized after identity is initialized.
@@ -105,8 +106,6 @@ export class ServiceContext extends Resource {
105
106
 
106
107
  private _deviceSpaceSync?: CredentialProcessor;
107
108
 
108
- private readonly _instanceId = PublicKey.random().toHex();
109
-
110
109
  constructor(
111
110
  public readonly storage: Storage,
112
111
  public readonly level: LevelDB,
@@ -170,7 +169,15 @@ export class ServiceContext extends Resource {
170
169
  peerIdProvider: () => this.identityManager.identity?.deviceKey?.toHex(),
171
170
  getSpaceKeyByRootDocumentId: (documentId) => this.spaceManager.findSpaceByRootDocumentId(documentId)?.key,
172
171
  runtime: this._runtime,
173
- localQueues: this._runtimeProps?.enableLocalQueues,
172
+ useSubduction: this._edgeFeatures?.subductionReplicator,
173
+ syncQueue: async (ctx, request) => {
174
+ return this._feedSyncer?.syncBlocking(ctx, {
175
+ spaceId: request.spaceId as SpaceId,
176
+ subspaceTag: request.subspaceTag,
177
+ shouldPush: request.shouldPush,
178
+ shouldPull: request.shouldPull,
179
+ });
180
+ },
174
181
  });
175
182
 
176
183
  this.invitations = new InvitationsHandler(
@@ -199,11 +206,18 @@ export class ServiceContext extends Resource {
199
206
  if (!this._runtimeProps?.disableP2pReplication) {
200
207
  this._meshReplicator = new MeshEchoReplicator();
201
208
  }
202
- if (this._edgeConnection && this._edgeFeatures?.echoReplicator && this._edgeHttpClient) {
203
- this._echoEdgeReplicator = new EchoEdgeReplicator({
204
- edgeConnection: this._edgeConnection,
205
- edgeHttpClient: this._edgeHttpClient,
206
- });
209
+ if (this._edgeConnection && this._edgeHttpClient) {
210
+ if (this._edgeFeatures?.subductionReplicator) {
211
+ this._echoEdgeReplicator = new EchoEdgeSubductionReplicator({
212
+ edgeConnection: this._edgeConnection,
213
+ edgeHttpClient: this._edgeHttpClient,
214
+ });
215
+ } else if (this._edgeFeatures?.echoReplicator) {
216
+ this._echoEdgeReplicator = new EchoEdgeReplicator({
217
+ edgeConnection: this._edgeConnection,
218
+ edgeHttpClient: this._edgeHttpClient,
219
+ });
220
+ }
207
221
  }
208
222
 
209
223
  if (this.echoHost.feedStore && this._edgeConnection) {
@@ -213,49 +227,80 @@ export class ServiceContext extends Resource {
213
227
  edgeClient: this._edgeConnection,
214
228
  peerId: this.identityManager.identity?.deviceKey?.toHex() ?? '',
215
229
  getSpaceIds: () => this.echoHost!.spaceIds,
216
- syncNamespace: FeedProtocol.WellKnownNamespaces.data,
230
+ syncNamespaces: [FeedProtocol.WellKnownNamespaces.data, FeedProtocol.WellKnownNamespaces.trace],
217
231
  });
218
232
  }
219
233
  }
220
234
 
221
- @Trace.span()
235
+ @Trace.span({ op: 'lifecycle' })
222
236
  protected override async _open(ctx: Context): Promise<void> {
223
237
  await this._checkStorageVersion();
224
238
 
225
239
  log('opening...');
226
- log.trace('dxos.sdk.service-context.open', trace.begin({ id: this._instanceId }));
227
240
 
241
+ log('opening identityManager...');
228
242
  await this.identityManager.open(ctx);
243
+ log('identityManager opened', { hasIdentity: !!this.identityManager.identity });
244
+
245
+ log('setting network identity...');
246
+ await this._setNetworkIdentity({ identity: this.identityManager.identity });
247
+ log('network identity set');
229
248
 
230
- await this._setNetworkIdentity();
249
+ log('opening edge connection...');
250
+ await this._edgeConnection?.open(ctx);
251
+ log('edge connection opened');
231
252
 
232
- await this._edgeConnection?.open();
233
- await this.signalManager.open();
253
+ log('opening signal manager...');
254
+ await this.signalManager.open(ctx);
255
+ log('signal manager opened');
256
+
257
+ log('opening network manager...');
234
258
  await this.networkManager.open();
259
+ log('network manager opened');
235
260
 
261
+ log('opening echo host...');
236
262
  await this.echoHost.open(ctx);
263
+ log('echo host opened');
237
264
 
238
265
  if (this._meshReplicator) {
239
- await this.echoHost.addReplicator(this._meshReplicator);
266
+ log('adding mesh replicator...');
267
+ await this.echoHost.addReplicator(ctx, this._meshReplicator);
268
+ log('mesh replicator added');
240
269
  }
241
270
  if (this._echoEdgeReplicator) {
242
- await this.echoHost.addReplicator(this._echoEdgeReplicator);
271
+ log('adding edge replicator...');
272
+ await this.echoHost.addReplicator(ctx, this._echoEdgeReplicator);
273
+ log('edge replicator added');
243
274
  }
244
275
 
276
+ log('loading metadata store...');
245
277
  await this.metadataStore.load();
278
+ log('metadata store loaded');
279
+
280
+ log('opening space manager...');
246
281
  await this.spaceManager.open();
282
+ log('space manager opened');
247
283
 
248
284
  if (this.identityManager.identity) {
249
- await this.identityManager.identity.joinNetwork();
285
+ log('joining network...');
286
+ await this.identityManager.identity.joinNetwork(ctx);
287
+ log('network joined');
288
+
289
+ log('initializing spaces...(calling _initialize)');
250
290
  await this._initialize(ctx);
291
+ log('spaces initialized');
292
+ } else {
293
+ log('no identity, skipping network join and space initialization');
251
294
  }
252
295
 
253
- await this._feedSyncer?.open();
296
+ log('opening feed syncer...');
297
+ await this._feedSyncer?.open(ctx);
298
+ log('feed syncer opened');
254
299
 
255
- const loadedInvitations = await this.invitationsManager.loadPersistentInvitations();
300
+ log('loading persistent invitations...');
301
+ const loadedInvitations = await this.invitationsManager.loadPersistentInvitations(ctx);
256
302
  log('loaded persistent invitations', { count: loadedInvitations.invitations?.length });
257
303
 
258
- log.trace('dxos.sdk.service-context.open', trace.end({ id: this._instanceId }));
259
304
  log('opened');
260
305
  }
261
306
 
@@ -267,13 +312,13 @@ export class ServiceContext extends Resource {
267
312
  if (this._deviceSpaceSync && this.identityManager.identity) {
268
313
  await this.identityManager.identity.space.spaceState.removeCredentialProcessor(this._deviceSpaceSync);
269
314
  }
270
- await this.dataSpaceManager?.close();
315
+ await this.dataSpaceManager?.close(ctx);
271
316
  await this.edgeAgentManager?.close();
272
- await this.identityManager.close();
317
+ await this.identityManager.close(ctx);
273
318
  await this.spaceManager.close();
274
319
  await this.echoHost.close(ctx);
275
320
 
276
- await this.networkManager.close();
321
+ await this.networkManager.close(ctx);
277
322
  await this.signalManager.close();
278
323
  await this._edgeConnection?.close();
279
324
  await this.feedStore.close();
@@ -282,11 +327,12 @@ export class ServiceContext extends Resource {
282
327
  log('closed');
283
328
  }
284
329
 
285
- async createIdentity(params: CreateIdentityOptions = {}) {
286
- const identity = await this.identityManager.createIdentity(params);
287
- await this._setNetworkIdentity();
288
- await identity.joinNetwork();
289
- await this._initialize(new Context());
330
+ async createIdentity(params: CreateIdentityOptions = {}, ctx?: Context) {
331
+ ctx ??= this._ctx;
332
+ const identity = await this.identityManager.createIdentity(params, ctx);
333
+ await this._setNetworkIdentity({ identity });
334
+ await identity.joinNetwork(ctx);
335
+ await this._initialize(ctx);
290
336
  return identity;
291
337
  }
292
338
 
@@ -310,11 +356,11 @@ export class ServiceContext extends Resource {
310
356
  }
311
357
 
312
358
  private async _acceptIdentity(params: JoinIdentityProps) {
313
- const { identity, identityRecord } = await this.identityManager.prepareIdentity(params);
314
- await this._setNetworkIdentity({ deviceCredential: params.authorizedDeviceCredential! });
315
- await identity.joinNetwork();
359
+ const { identity, identityRecord } = await this.identityManager.prepareIdentity(params, this._ctx);
360
+ await this._setNetworkIdentity({ deviceCredential: params.authorizedDeviceCredential!, identity });
361
+ await identity.joinNetwork(this._ctx);
316
362
  await this.identityManager.acceptIdentity(identity, identityRecord, params.deviceProfile);
317
- await this._initialize(new Context());
363
+ await this._initialize(this._ctx);
318
364
  return identity;
319
365
  }
320
366
 
@@ -329,7 +375,7 @@ export class ServiceContext extends Resource {
329
375
  // Called when identity is created.
330
376
  @Trace.span()
331
377
  private async _initialize(ctx: Context): Promise<void> {
332
- log('initializing spaces...');
378
+ log('_initialize: start');
333
379
  const identity = this.identityManager.identity ?? failUndefined();
334
380
  const signingContext: SigningContext = {
335
381
  credentialSigner: identity.getIdentityCredentialSigner(),
@@ -341,6 +387,7 @@ export class ServiceContext extends Resource {
341
387
  },
342
388
  };
343
389
 
390
+ log('_initialize: creating DataSpaceManager');
344
391
  this.dataSpaceManager = new DataSpaceManager({
345
392
  spaceManager: this.spaceManager,
346
393
  metadataStore: this.metadataStore,
@@ -356,7 +403,9 @@ export class ServiceContext extends Resource {
356
403
  runtimeProps: this._runtimeProps as DataSpaceManagerRuntimeProps,
357
404
  edgeFeatures: this._edgeFeatures,
358
405
  });
359
- await this.dataSpaceManager.open();
406
+ log('_initialize: opening DataSpaceManager...');
407
+ await this.dataSpaceManager.open(ctx);
408
+ log('_initialize: DataSpaceManager opened');
360
409
 
361
410
  this.edgeAgentManager = new EdgeAgentManager(
362
411
  this._edgeFeatures,
@@ -364,13 +413,16 @@ export class ServiceContext extends Resource {
364
413
  this.dataSpaceManager,
365
414
  identity,
366
415
  );
367
- await this.edgeAgentManager.open();
416
+ log('_initialize: opening EdgeAgentManager...');
417
+ await this.edgeAgentManager.open(ctx);
418
+ log('_initialize: EdgeAgentManager opened');
368
419
 
369
420
  this._handlerFactories.set(Invitation.Kind.SPACE, (invitation) => {
370
421
  invariant(this.dataSpaceManager, 'dataSpaceManager not initialized yet');
371
422
  return new SpaceInvitationProtocol(this.dataSpaceManager, signingContext, this.keyring, invitation.spaceKey);
372
423
  });
373
424
  this.initialized.wake();
425
+ log('_initialize: initialized.wake() called');
374
426
 
375
427
  this._deviceSpaceSync = {
376
428
  processCredential: async (credential: Credential) => {
@@ -393,9 +445,10 @@ export class ServiceContext extends Resource {
393
445
 
394
446
  try {
395
447
  log('accepting space recorded in halo', { details: assertion });
396
- await this.dataSpaceManager.acceptSpace({
448
+ await this.dataSpaceManager.acceptSpace(this._ctx, {
397
449
  spaceKey: assertion.spaceKey,
398
450
  genesisFeedKey: assertion.genesisFeedKey,
451
+ tags: assertion.tags,
399
452
  });
400
453
  } catch (err) {
401
454
  log.catch(err);
@@ -406,33 +459,42 @@ export class ServiceContext extends Resource {
406
459
  await identity.space.spaceState.addCredentialProcessor(this._deviceSpaceSync);
407
460
  }
408
461
 
409
- private async _setNetworkIdentity(params?: { deviceCredential: Credential }): Promise<void> {
462
+ private async _setNetworkIdentity(params?: { deviceCredential?: Credential; identity?: Identity }): Promise<void> {
463
+ log('_setNetworkIdentity: acquiring mutex...');
410
464
  using _ = await this._edgeIdentityUpdateMutex.acquire();
465
+ log('_setNetworkIdentity: mutex acquired');
411
466
 
412
467
  let edgeIdentity: EdgeIdentity;
413
- const identity = this.identityManager.identity;
468
+ const identity = params?.identity;
414
469
  if (identity) {
415
- log('setting identity on edge connection', {
470
+ log('_setNetworkIdentity: has identity', {
416
471
  identity: identity.identityKey.toHex(),
417
- swarms: this.networkManager.topics,
472
+ hasDeviceCredential: !!params?.deviceCredential,
418
473
  });
419
474
 
420
475
  if (params?.deviceCredential) {
476
+ log('_setNetworkIdentity: creating chain edge identity with device credential...');
421
477
  edgeIdentity = await createChainEdgeIdentity(
422
478
  identity.signer,
423
479
  identity.identityKey,
424
480
  identity.deviceKey,
425
- params?.deviceCredential && { credential: params.deviceCredential },
481
+ { credential: params.deviceCredential },
426
482
  [], // TODO(dmaretskyi): Service access credentials.
427
483
  );
484
+ log('_setNetworkIdentity: chain edge identity created');
428
485
  } else {
486
+ log('_setNetworkIdentity: waiting for identity.ready()...');
429
487
  // TODO: throw here or from identity if device chain can't be loaded, to avoid indefinite hangup
430
488
  await warnAfterTimeout(10_000, 'Waiting for identity to be ready for edge connection', async () => {
431
489
  await identity.ready();
432
490
  });
491
+ log('_setNetworkIdentity: identity.ready() resolved', {
492
+ hasDeviceCredentialChain: !!identity.deviceCredentialChain,
493
+ });
433
494
 
434
495
  invariant(identity.deviceCredentialChain);
435
496
 
497
+ log('_setNetworkIdentity: creating chain edge identity...');
436
498
  edgeIdentity = await createChainEdgeIdentity(
437
499
  identity.signer,
438
500
  identity.identityKey,
@@ -440,9 +502,12 @@ export class ServiceContext extends Resource {
440
502
  identity.deviceCredentialChain,
441
503
  [], // TODO(dmaretskyi): Service access credentials.
442
504
  );
505
+ log('_setNetworkIdentity: chain edge identity created');
443
506
  }
444
507
  } else {
508
+ log('_setNetworkIdentity: no identity, creating ephemeral edge identity...');
445
509
  edgeIdentity = await createEphemeralEdgeIdentity();
510
+ log('_setNetworkIdentity: ephemeral edge identity created');
446
511
  }
447
512
 
448
513
  this._edgeConnection?.setIdentity(edgeIdentity);
@@ -451,5 +516,6 @@ export class ServiceContext extends Resource {
451
516
  identityKey: edgeIdentity.identityKey,
452
517
  peerKey: edgeIdentity.peerKey,
453
518
  });
519
+ log('_setNetworkIdentity: done');
454
520
  }
455
521
  }
@@ -3,7 +3,6 @@
3
3
  //
4
4
 
5
5
  import { rmSync } from 'node:fs';
6
-
7
6
  import { afterEach, describe, expect, onTestFinished, test } from 'vitest';
8
7
 
9
8
  import { Trigger, asyncTimeout, latch } from '@dxos/async';
@@ -13,6 +12,7 @@ import { verifyPresentation } from '@dxos/credentials';
13
12
  import { type PublicKey } from '@dxos/keys';
14
13
  import { MemorySignalManagerContext } from '@dxos/messaging';
15
14
  import { type Identity } from '@dxos/protocols/proto/dxos/client/services';
15
+ import { MembershipPolicy } from '@dxos/protocols/proto/dxos/halo/credentials';
16
16
  import { type Credential } from '@dxos/protocols/proto/dxos/halo/credentials';
17
17
  import { isNode } from '@dxos/util';
18
18
 
@@ -29,16 +29,16 @@ describe('ClientServicesHost', () => {
29
29
  test('open and close', async () => {
30
30
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
31
31
  await host.open(new Context());
32
- await host.close();
32
+ await host.close(Context.default());
33
33
  });
34
34
 
35
35
  test('queryCredentials', async () => {
36
36
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
37
37
  await host.open(new Context());
38
- onTestFinished(() => host.close());
38
+ onTestFinished(() => host.close(Context.default()));
39
39
 
40
40
  await host.services.IdentityService!.createIdentity({});
41
- const { spaceKey } = await host.services.SpacesService!.createSpace();
41
+ const { spaceKey } = await host.services.SpacesService!.createSpace({ membershipPolicy: MembershipPolicy.INVITE });
42
42
 
43
43
  const stream = host.services.SpacesService!.queryCredentials({ spaceKey });
44
44
  const [done, tick] = latch({ count: 3 });
@@ -54,7 +54,7 @@ describe('ClientServicesHost', () => {
54
54
  test('write and query credentials', async () => {
55
55
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
56
56
  await host.open(new Context());
57
- onTestFinished(() => host.close());
57
+ onTestFinished(() => host.close(Context.default()));
58
58
 
59
59
  await host.services.IdentityService!.createIdentity({});
60
60
 
@@ -91,7 +91,7 @@ describe('ClientServicesHost', () => {
91
91
  test('sign presentation', async () => {
92
92
  const host = createServiceHost(new Config(), new MemorySignalManagerContext());
93
93
  await host.open(new Context());
94
- onTestFinished(() => host.close());
94
+ onTestFinished(() => host.close(Context.default()));
95
95
 
96
96
  await host.services.IdentityService!.createIdentity({});
97
97
 
@@ -128,7 +128,7 @@ describe('ClientServicesHost', () => {
128
128
  expect(host.context.storage.size).to.exist;
129
129
 
130
130
  await asyncTimeout(host.reset(), 1000);
131
- await host.close();
131
+ await host.close(Context.default());
132
132
  }
133
133
 
134
134
  {
@@ -146,7 +146,7 @@ describe('ClientServicesHost', () => {
146
146
  });
147
147
  await expect(asyncTimeout(trigger.wait(), 200)).rejects.toBeInstanceOf(Error);
148
148
  await stream?.close();
149
- await host.close();
149
+ await host.close(Context.default());
150
150
  }
151
151
  });
152
152
  });
@@ -7,12 +7,11 @@ import * as Effect from 'effect/Effect';
7
7
 
8
8
  import { Event, synchronized } from '@dxos/async';
9
9
  import { type ClientServices, clientServiceBundle } from '@dxos/client-protocol';
10
- import { type Config } from '@dxos/config';
10
+ import { type Config, resolveTelemetryTag } from '@dxos/config';
11
11
  import { Context } from '@dxos/context';
12
12
  import { EdgeClient, type EdgeConnection, EdgeHttpClient, createStubEdgeIdentity } from '@dxos/edge-client';
13
13
  import { RuntimeProvider } from '@dxos/effect';
14
14
  import { invariant } from '@dxos/invariant';
15
- import { PublicKey } from '@dxos/keys';
16
15
  import { type LevelDB } from '@dxos/kv-store';
17
16
  import { log } from '@dxos/log';
18
17
  import { EdgeSignalManager, type SignalManager, WebsocketSignalManager } from '@dxos/messaging';
@@ -22,12 +21,11 @@ import {
22
21
  createIceProvider,
23
22
  createRtcTransportFactory,
24
23
  } from '@dxos/network-manager';
25
- import { trace } from '@dxos/protocols';
26
24
  import { SystemStatus } from '@dxos/protocols/proto/dxos/client/services';
27
25
  import { type Storage } from '@dxos/random-access-storage';
28
26
  import * as SqlExport from '@dxos/sql-sqlite/SqlExport';
29
27
  import type * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
30
- import { TRACE_PROCESSOR, trace as Trace } from '@dxos/tracing';
28
+ import { trace as Trace } from '@dxos/tracing';
31
29
  import { WebsocketRpcClient } from '@dxos/websocket-rpc';
32
30
 
33
31
  import { EdgeAgentServiceImpl } from '../agents';
@@ -47,7 +45,6 @@ import { NetworkServiceImpl } from '../network';
47
45
  import { SpacesServiceImpl } from '../spaces';
48
46
  import { createLevel, createStorageObjects } from '../storage';
49
47
  import { SystemServiceImpl } from '../system';
50
-
51
48
  import { ServiceContext, type ServiceContextRuntimeProps } from './service-context';
52
49
  import { ServiceRegistry } from './service-registry';
53
50
 
@@ -87,8 +84,6 @@ export class ClientServicesHost {
87
84
  private readonly _serviceRegistry: ServiceRegistry<ClientServices>;
88
85
  private readonly _systemService: SystemServiceImpl;
89
86
  private readonly _loggingService: LoggingServiceImpl;
90
- private readonly _tracingService = TRACE_PROCESSOR.createTraceSender();
91
-
92
87
  private readonly _statusUpdate = new Event<void>();
93
88
 
94
89
  private _config?: Config;
@@ -147,7 +142,7 @@ export class ClientServicesHost {
147
142
  void this.open(new Context());
148
143
  }
149
144
  },
150
- onRelease: () => this.close(),
145
+ onRelease: () => this.close(Context.default()),
151
146
  });
152
147
  }
153
148
 
@@ -176,7 +171,6 @@ export class ClientServicesHost {
176
171
 
177
172
  this._serviceRegistry = new ServiceRegistry<ClientServices>(clientServiceBundle, {
178
173
  SystemService: this._systemService,
179
- TracingService: this._tracingService,
180
174
  });
181
175
  }
182
176
 
@@ -244,9 +238,6 @@ export class ClientServicesHost {
244
238
  if (this._runtimeProps.enableVectorIndexing === undefined) {
245
239
  this._runtimeProps.enableVectorIndexing = config?.get('runtime.client.enableVectorIndexing', false);
246
240
  }
247
- if (this._runtimeProps.enableLocalQueues === undefined) {
248
- this._runtimeProps.enableLocalQueues = config?.get('runtime.client.enableLocalQueues', false);
249
- }
250
241
 
251
242
  invariant(!this._config, 'config already set');
252
243
  this._config = config;
@@ -262,8 +253,9 @@ export class ClientServicesHost {
262
253
 
263
254
  const endpoint = config?.get('runtime.services.edge.url');
264
255
  if (endpoint) {
265
- this._edgeConnection = new EdgeClient(createStubEdgeIdentity(), { socketEndpoint: endpoint });
266
- this._edgeHttpClient = new EdgeHttpClient(endpoint);
256
+ const clientTag = resolveTelemetryTag(config);
257
+ this._edgeConnection = new EdgeClient(createStubEdgeIdentity(), { socketEndpoint: endpoint, clientTag });
258
+ this._edgeHttpClient = new EdgeHttpClient(endpoint, { clientTag });
267
259
  }
268
260
 
269
261
  const {
@@ -302,8 +294,7 @@ export class ClientServicesHost {
302
294
  return;
303
295
  }
304
296
 
305
- const traceId = PublicKey.random().toHex();
306
- log.trace('dxos.client-services.host.open', trace.begin({ id: traceId }));
297
+ log('opening service host');
307
298
 
308
299
  invariant(this._config, 'config not set');
309
300
  invariant(this._storage, 'storage not set');
@@ -348,8 +339,7 @@ export class ClientServicesHost {
348
339
  this._serviceContext.identityManager,
349
340
  this._serviceContext.recoveryManager,
350
341
  this._serviceContext.keyring,
351
- () => this._serviceContext.dataSpaceManager!,
352
- (params) => this._createIdentity(params),
342
+ (params, ctx) => this._createIdentity(params, ctx),
353
343
  (profile) => this._serviceContext.broadcastProfileUpdate(profile),
354
344
  );
355
345
 
@@ -369,6 +359,7 @@ export class ClientServicesHost {
369
359
  SpacesService: new SpacesServiceImpl(
370
360
  this._serviceContext.identityManager,
371
361
  this._serviceContext.spaceManager,
362
+ this._serviceContext.echoHost,
372
363
  dataSpaceManagerProvider,
373
364
  ),
374
365
 
@@ -383,7 +374,6 @@ export class ClientServicesHost {
383
374
  ),
384
375
 
385
376
  LoggingService: this._loggingService,
386
- TracingService: this._tracingService,
387
377
 
388
378
  // TODO(burdon): Move to new protobuf definitions.
389
379
  DevtoolsHost: new DevtoolsServiceImpl({
@@ -395,8 +385,13 @@ export class ClientServicesHost {
395
385
  EdgeAgentService: new EdgeAgentServiceImpl(agentManagerProvider, this._edgeConnection),
396
386
  });
397
387
 
388
+ log('service-host: opening service context...');
398
389
  await this._serviceContext.open(ctx);
390
+ log('service-host: service context opened');
391
+
392
+ log('service-host: opening identity service...');
399
393
  await identityService.open();
394
+ log('service-host: identity service opened');
400
395
 
401
396
  const devtoolsProxy = this._config?.get('runtime.client.devtoolsProxy');
402
397
  if (devtoolsProxy) {
@@ -415,12 +410,11 @@ export class ClientServicesHost {
415
410
  this._statusUpdate.emit();
416
411
  const deviceKey = this._serviceContext.identityManager.identity?.deviceKey;
417
412
  log('opened', { deviceKey });
418
- log.trace('dxos.client-services.host.open', trace.end({ id: traceId }));
419
413
  }
420
414
 
421
415
  @synchronized
422
416
  @Trace.span()
423
- async close(): Promise<void> {
417
+ async close(ctx: Context): Promise<void> {
424
418
  if (!this._open) {
425
419
  return;
426
420
  }
@@ -439,9 +433,6 @@ export class ClientServicesHost {
439
433
  }
440
434
 
441
435
  async reset(): Promise<void> {
442
- const traceId = PublicKey.random().toHex();
443
- log.trace('dxos.sdk.client-services-host.reset', trace.begin({ id: traceId }));
444
-
445
436
  log.info('resetting...');
446
437
  // Emit this status update immediately so app returns to fallback.
447
438
  // This state is never cleared because the app reloads.
@@ -456,12 +447,11 @@ export class ClientServicesHost {
456
447
  }
457
448
  await this._storage!.reset();
458
449
  log.info('reset');
459
- log.trace('dxos.sdk.client-services-host.reset', trace.end({ id: traceId }));
460
450
  await this._callbacks?.onReset?.();
461
451
  }
462
452
 
463
- private async _createIdentity(params: CreateIdentityOptions) {
464
- const identity = await this._serviceContext.createIdentity(params);
453
+ private async _createIdentity(params: CreateIdentityOptions, ctx?: Context) {
454
+ const identity = await this._serviceContext.createIdentity(params, ctx);
465
455
  await this._serviceContext.initialized.wait();
466
456
  return identity;
467
457
  }
@@ -15,7 +15,6 @@ import { createLinkedPorts, createProtoRpcPeer, createServiceBundle } from '@dxo
15
15
 
16
16
  import { SystemServiceImpl } from '../system';
17
17
  import { createServiceContext } from '../testing';
18
-
19
18
  import { ServiceRegistry } from './service-registry';
20
19
 
21
20
  // TODO(burdon): Create TestService (that doesn't require peers).
@@ -0,0 +1,42 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { SpaceArchive } from '@dxos/protocols/proto/dxos/client/services';
6
+
7
+ const JSON_EXTENSIONS = ['.dx.json', '.dx.json.gz', '.json'];
8
+ const BINARY_EXTENSIONS = ['.tar', '.tar.gz'];
9
+
10
+ /**
11
+ * Detect the format of a space archive.
12
+ *
13
+ * Uses filename extension as the primary signal, and falls back to sniffing
14
+ * the first bytes of the archive contents to distinguish JSON from tar.
15
+ */
16
+ export const detectSpaceArchiveFormat = (archive: Pick<SpaceArchive, 'filename' | 'contents'>): SpaceArchive.Format => {
17
+ const filename = archive.filename?.toLowerCase() ?? '';
18
+ if (JSON_EXTENSIONS.some((ext) => filename.endsWith(ext))) {
19
+ return SpaceArchive.Format.JSON;
20
+ }
21
+ if (BINARY_EXTENSIONS.some((ext) => filename.endsWith(ext))) {
22
+ return SpaceArchive.Format.BINARY;
23
+ }
24
+
25
+ // Fall back to byte sniffing: JSON archives start with '{' (0x7B) or whitespace.
26
+ const bytes = archive.contents;
27
+ if (bytes && bytes.length > 0) {
28
+ for (let i = 0; i < Math.min(bytes.length, 16); i++) {
29
+ const byte = bytes[i];
30
+ // Skip whitespace.
31
+ if (byte === 0x20 || byte === 0x09 || byte === 0x0a || byte === 0x0d) {
32
+ continue;
33
+ }
34
+ if (byte === 0x7b /* '{' */) {
35
+ return SpaceArchive.Format.JSON;
36
+ }
37
+ break;
38
+ }
39
+ }
40
+
41
+ return SpaceArchive.Format.BINARY;
42
+ };
@@ -2,5 +2,8 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './space-archive-writer';
5
+ export * from './archive-format';
6
+ export * from './serialized-space-reader';
7
+ export * from './serialized-space-writer';
6
8
  export * from './space-archive-reader';
9
+ export * from './space-archive-writer';