@meshagent/meshagent 0.37.2 → 0.38.0

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 (77) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/dist/browser/agent.js +74 -10
  3. package/dist/browser/developer-client.js +3 -0
  4. package/dist/browser/helpers.d.ts +2 -2
  5. package/dist/browser/helpers.js +1 -1
  6. package/dist/browser/meshagent-client.d.ts +25 -0
  7. package/dist/browser/meshagent-client.js +65 -0
  8. package/dist/browser/messaging-client.d.ts +29 -16
  9. package/dist/browser/messaging-client.js +256 -154
  10. package/dist/browser/participant.d.ts +7 -2
  11. package/dist/browser/participant.js +9 -9
  12. package/dist/browser/protocol.d.ts +85 -28
  13. package/dist/browser/protocol.js +356 -119
  14. package/dist/browser/room-client.d.ts +165 -29
  15. package/dist/browser/room-client.js +1114 -74
  16. package/dist/browser/room-event.d.ts +11 -0
  17. package/dist/browser/room-event.js +21 -1
  18. package/dist/browser/room-server-client.d.ts +2 -0
  19. package/dist/browser/room-server-client.js +6 -0
  20. package/dist/browser/runtime.d.ts +1 -1
  21. package/dist/browser/runtime.js +3 -1
  22. package/dist/browser/secrets-client.js +6 -2
  23. package/dist/browser/storage-client.d.ts +1 -0
  24. package/dist/browser/storage-client.js +9 -0
  25. package/dist/browser/sync-client.d.ts +16 -14
  26. package/dist/browser/sync-client.js +195 -116
  27. package/dist/esm/agent.js +74 -10
  28. package/dist/esm/developer-client.js +3 -0
  29. package/dist/esm/helpers.d.ts +2 -2
  30. package/dist/esm/helpers.js +1 -1
  31. package/dist/esm/meshagent-client.d.ts +25 -0
  32. package/dist/esm/meshagent-client.js +65 -0
  33. package/dist/esm/messaging-client.d.ts +29 -16
  34. package/dist/esm/messaging-client.js +256 -154
  35. package/dist/esm/participant.d.ts +7 -2
  36. package/dist/esm/participant.js +9 -9
  37. package/dist/esm/protocol.d.ts +85 -28
  38. package/dist/esm/protocol.js +352 -118
  39. package/dist/esm/room-client.d.ts +165 -29
  40. package/dist/esm/room-client.js +1112 -73
  41. package/dist/esm/room-event.d.ts +11 -0
  42. package/dist/esm/room-event.js +19 -0
  43. package/dist/esm/room-server-client.d.ts +2 -0
  44. package/dist/esm/room-server-client.js +7 -1
  45. package/dist/esm/runtime.d.ts +1 -1
  46. package/dist/esm/runtime.js +1 -1
  47. package/dist/esm/secrets-client.js +6 -2
  48. package/dist/esm/storage-client.d.ts +1 -0
  49. package/dist/esm/storage-client.js +9 -0
  50. package/dist/esm/sync-client.d.ts +16 -14
  51. package/dist/esm/sync-client.js +196 -117
  52. package/dist/node/agent.js +74 -10
  53. package/dist/node/developer-client.js +3 -0
  54. package/dist/node/helpers.d.ts +2 -2
  55. package/dist/node/helpers.js +1 -1
  56. package/dist/node/meshagent-client.d.ts +25 -0
  57. package/dist/node/meshagent-client.js +65 -0
  58. package/dist/node/messaging-client.d.ts +29 -16
  59. package/dist/node/messaging-client.js +256 -154
  60. package/dist/node/participant.d.ts +7 -2
  61. package/dist/node/participant.js +9 -9
  62. package/dist/node/protocol.d.ts +85 -28
  63. package/dist/node/protocol.js +356 -119
  64. package/dist/node/room-client.d.ts +165 -29
  65. package/dist/node/room-client.js +1114 -74
  66. package/dist/node/room-event.d.ts +11 -0
  67. package/dist/node/room-event.js +21 -1
  68. package/dist/node/room-server-client.d.ts +2 -0
  69. package/dist/node/room-server-client.js +6 -0
  70. package/dist/node/runtime.d.ts +1 -1
  71. package/dist/node/runtime.js +3 -1
  72. package/dist/node/secrets-client.js +6 -2
  73. package/dist/node/storage-client.d.ts +1 -0
  74. package/dist/node/storage-client.js +9 -0
  75. package/dist/node/sync-client.d.ts +16 -14
  76. package/dist/node/sync-client.js +195 -116
  77. package/package.json +6 -3
@@ -2,6 +2,17 @@ export declare abstract class RoomEvent {
2
2
  abstract get name(): string;
3
3
  abstract get description(): string;
4
4
  }
5
+ export declare class RoomStatusEvent extends RoomEvent {
6
+ readonly status: string;
7
+ readonly message: string;
8
+ constructor({ status, message }: {
9
+ status: string;
10
+ message: string;
11
+ });
12
+ get name(): string;
13
+ get description(): string;
14
+ static fromJson(json: Record<string, unknown>): RoomStatusEvent;
15
+ }
5
16
  export declare class RoomMessage {
6
17
  fromParticipantId: string;
7
18
  type: string;
@@ -1,5 +1,24 @@
1
1
  export class RoomEvent {
2
2
  }
3
+ export class RoomStatusEvent extends RoomEvent {
4
+ constructor({ status, message }) {
5
+ super();
6
+ this.status = status;
7
+ this.message = message;
8
+ }
9
+ get name() {
10
+ return this.status;
11
+ }
12
+ get description() {
13
+ return this.message;
14
+ }
15
+ static fromJson(json) {
16
+ return new RoomStatusEvent({
17
+ status: String(json["status"]),
18
+ message: String(json["message"]),
19
+ });
20
+ }
21
+ }
3
22
  export class RoomMessage {
4
23
  constructor({ fromParticipantId, type, message, local = false, attachment }) {
5
24
  this.fromParticipantId = fromParticipantId;
@@ -13,6 +13,8 @@ export declare class MeshDocument extends RuntimeDocument {
13
13
  });
14
14
  onSendUpdateToBackend: (base64: string) => void;
15
15
  onSendUpdateToClient: (base64: string) => void;
16
+ getState(vector?: string): string;
17
+ getStateVector(): string;
16
18
  get synchronized(): Promise<boolean>;
17
19
  get isSynchronized(): boolean;
18
20
  setSynchronizedComplete(): void;
@@ -1,7 +1,7 @@
1
1
  import { v4 as uuidv4 } from "uuid";
2
2
  import { RuntimeDocument } from "./document";
3
3
  import { Completer } from "./completer";
4
- import { registerDocument, unregisterDocument, applyChanges, } from "./runtime";
4
+ import { registerDocument, unregisterDocument, applyChanges, getState, getStateVector, } from "./runtime";
5
5
  export class RoomServerException extends Error {
6
6
  constructor(message, code) {
7
7
  super(message);
@@ -30,6 +30,12 @@ export class MeshDocument extends RuntimeDocument {
30
30
  };
31
31
  registerDocument(this.id, null, false, this.onSendUpdateToBackend, this.onSendUpdateToClient);
32
32
  }
33
+ getState(vector) {
34
+ return getState(this.id, vector ?? null);
35
+ }
36
+ getStateVector() {
37
+ return getStateVector(this.id);
38
+ }
33
39
  get synchronized() {
34
40
  return this._synchronized.fut;
35
41
  }
@@ -1,4 +1,4 @@
1
- export { applyBackendChanges, applyChanges, registerDocument, unregisterDocument, } from './entrypoint.js';
1
+ export { applyBackendChanges, applyChanges, getState, getStateVector, registerDocument, unregisterDocument, } from './entrypoint.js';
2
2
  export type SendUpdateFn = (msg: string) => void;
3
3
  export interface UpdatePayload {
4
4
  documentID: string;
@@ -1 +1 @@
1
- export { applyBackendChanges, applyChanges, registerDocument, unregisterDocument, } from './entrypoint.js';
1
+ export { applyBackendChanges, applyChanges, getState, getStateVector, registerDocument, unregisterDocument, } from './entrypoint.js';
@@ -73,7 +73,9 @@ export class SecretsClient {
73
73
  };
74
74
  }
75
75
  async _handleClientOAuthTokenRequest(protocol, messageId, type, bytes) {
76
- void protocol;
76
+ if (!this.client.isActiveProtocol(protocol)) {
77
+ return;
78
+ }
77
79
  void messageId;
78
80
  void type;
79
81
  if (bytes == null) {
@@ -109,7 +111,9 @@ export class SecretsClient {
109
111
  });
110
112
  }
111
113
  async _handleClientSecretRequest(protocol, messageId, type, bytes) {
112
- void protocol;
114
+ if (!this.client.isActiveProtocol(protocol)) {
115
+ return;
116
+ }
113
117
  void messageId;
114
118
  void type;
115
119
  if (bytes == null) {
@@ -4,6 +4,7 @@ import { RoomEvent } from "./room-event";
4
4
  import { BinaryContent, FileContent } from "./response";
5
5
  import { EventEmitter } from "./event-emitter";
6
6
  type StorageClientRoom = Pick<RoomClient, "invoke" | "invokeStream" | "emit"> & {
7
+ isActiveProtocol(protocol: Protocol): boolean;
7
8
  protocol: Pick<Protocol, "addHandler">;
8
9
  };
9
10
  export declare class FileHandle {
@@ -84,18 +84,27 @@ export class StorageClient extends EventEmitter {
84
84
  this.client.protocol.addHandler("storage.file.updated", this._handleFileUpdated.bind(this));
85
85
  }
86
86
  async _handleFileUpdated(protocol, messageId, type, bytes) {
87
+ if (!this.client.isActiveProtocol(protocol)) {
88
+ return;
89
+ }
87
90
  const [data, _] = unpackMessage(bytes || new Uint8Array());
88
91
  const event = new FileUpdatedEvent({ path: data["path"], participantId: data["participant_id"] });
89
92
  this.client.emit(event);
90
93
  this.emit('file.updated', event);
91
94
  }
92
95
  async _handleFileDeleted(protocol, messageId, type, bytes) {
96
+ if (!this.client.isActiveProtocol(protocol)) {
97
+ return;
98
+ }
93
99
  const [data, _] = unpackMessage(bytes || new Uint8Array());
94
100
  const event = new FileDeletedEvent({ path: data["path"], participantId: data["participant_id"] });
95
101
  this.client.emit(event);
96
102
  this.emit('file.deleted', event);
97
103
  }
98
104
  async _handleFileMoved(protocol, messageId, type, bytes) {
105
+ if (!this.client.isActiveProtocol(protocol)) {
106
+ return;
107
+ }
99
108
  const [data, _] = unpackMessage(bytes || new Uint8Array());
100
109
  const event = new FileMovedEvent({
101
110
  sourcePath: data["source_path"],
@@ -1,36 +1,38 @@
1
1
  import { EventEmitter } from "./event-emitter";
2
- import { RoomClient } from "./room-client";
3
2
  import { MeshSchema } from "./schema";
3
+ import { RoomClient } from "./room-client";
4
4
  import { MeshDocument } from "./room-server-client";
5
5
  export interface SyncClientEvent {
6
6
  type: string;
7
7
  doc?: MeshDocument;
8
- status?: unknown;
9
8
  }
10
9
  export declare class SyncClient extends EventEmitter<SyncClientEvent> {
11
- private readonly client;
12
- private _connectingDocuments;
13
- private _connectedDocuments;
14
- private _documentStreams;
10
+ private readonly room;
11
+ private readonly _connectingDocuments;
12
+ private readonly _closingDocuments;
13
+ private readonly _connectedDocuments;
14
+ private readonly _documentStreams;
15
+ private readonly _documentConfigs;
16
+ private _started;
15
17
  constructor({ room }: {
16
18
  room: RoomClient;
17
19
  });
20
+ start(): void;
21
+ dispose(): void;
18
22
  private _unexpectedResponseError;
19
23
  private _invoke;
20
- start({ onDone, onError }?: {
21
- onDone?: () => void;
22
- onError?: (error: Error) => void;
23
- }): void;
24
- dispose(): void;
25
24
  private _applySyncPayload;
26
- private _handleStatus;
27
- create(path: string, json?: Record<string, any>): Promise<void>;
25
+ create(path: string, json?: Record<string, unknown>): Promise<void>;
28
26
  open(path: string, { create, initialJson, schema, }?: {
29
27
  create?: boolean;
30
- initialJson?: Record<string, any>;
28
+ initialJson?: Record<string, unknown>;
31
29
  schema?: MeshSchema;
32
30
  }): Promise<MeshDocument>;
33
31
  close(path: string): Promise<void>;
34
32
  sync(path: string, data: Uint8Array): Promise<void>;
35
33
  private _consumeOpenStream;
34
+ private _openStream;
35
+ private _attachStreamConsumer;
36
+ _onRoomDisconnect(): Promise<void>;
37
+ _onRoomReconnect(): Promise<void>;
36
38
  }
@@ -1,10 +1,10 @@
1
+ import { Completer } from "./completer";
1
2
  import { EventEmitter } from "./event-emitter";
2
3
  import { MeshSchema } from "./schema";
3
- import { MeshDocument, RoomServerException } from "./room-server-client";
4
4
  import { BinaryContent, ControlContent, ErrorContent } from "./response";
5
- import { decoder, encoder, RefCount, unpackMessage } from "./utils";
6
- import { unregisterDocument, applyBackendChanges } from "./runtime";
7
- import { Completer } from "./completer";
5
+ import { MeshDocument, RoomServerException } from "./room-server-client";
6
+ import { applyBackendChanges, unregisterDocument } from "./runtime";
7
+ import { decoder, encoder, RefCount } from "./utils";
8
8
  function normalizeSyncPath(path) {
9
9
  let normalized = path;
10
10
  while (normalized.startsWith("./")) {
@@ -22,7 +22,8 @@ function parseSyncOpenStateChunkHeaders(headers) {
22
22
  if (headers["kind"] !== "state" ||
23
23
  typeof headers["path"] !== "string" ||
24
24
  typeof headers["schema"] !== "object" ||
25
- headers["schema"] == null) {
25
+ headers["schema"] == null ||
26
+ Array.isArray(headers["schema"])) {
26
27
  throw new RoomServerException("unexpected return type from sync.open");
27
28
  }
28
29
  return {
@@ -33,8 +34,7 @@ function parseSyncOpenStateChunkHeaders(headers) {
33
34
  }
34
35
  function parseSyncOpenOutputChunkHeaders(headers) {
35
36
  const kind = headers["kind"];
36
- if ((kind !== "state" && kind !== "sync") ||
37
- typeof headers["path"] !== "string") {
37
+ if ((kind !== "state" && kind !== "sync") || typeof headers["path"] !== "string") {
38
38
  throw new RoomServerException("unexpected return type from sync.open");
39
39
  }
40
40
  return {
@@ -51,7 +51,7 @@ class SyncOpenStreamState {
51
51
  }
52
52
  _enqueueChunk(chunk) {
53
53
  const waiter = this._inputWaiters.shift();
54
- if (waiter) {
54
+ if (waiter != null) {
55
55
  waiter(chunk);
56
56
  return;
57
57
  }
@@ -129,54 +129,52 @@ export class SyncClient extends EventEmitter {
129
129
  constructor({ room }) {
130
130
  super();
131
131
  this._connectingDocuments = {};
132
+ this._closingDocuments = {};
132
133
  this._connectedDocuments = {};
133
134
  this._documentStreams = {};
134
- this.client = room;
135
- this.client.protocol.addHandler("room.status", this._handleStatus.bind(this));
135
+ this._documentConfigs = {};
136
+ this._started = false;
137
+ this.room = room;
138
+ }
139
+ start() {
140
+ if (this._started) {
141
+ throw new RoomServerException("client already started");
142
+ }
143
+ this._started = true;
144
+ }
145
+ dispose() {
146
+ super.dispose();
147
+ for (const streamState of Object.values(this._documentStreams)) {
148
+ streamState.closeInputStream();
149
+ }
150
+ for (const doc of Object.values(this._connectedDocuments)) {
151
+ unregisterDocument(doc.ref.id);
152
+ }
153
+ Object.keys(this._documentStreams).forEach((key) => delete this._documentStreams[key]);
154
+ Object.keys(this._documentConfigs).forEach((key) => delete this._documentConfigs[key]);
155
+ Object.keys(this._connectedDocuments).forEach((key) => delete this._connectedDocuments[key]);
156
+ Object.keys(this._connectingDocuments).forEach((key) => delete this._connectingDocuments[key]);
157
+ Object.keys(this._closingDocuments).forEach((key) => delete this._closingDocuments[key]);
158
+ this._started = false;
136
159
  }
137
160
  _unexpectedResponseError(operation) {
138
161
  return new RoomServerException(`unexpected return type from sync.${operation}`);
139
162
  }
140
163
  async _invoke(operation, input) {
141
- return await this.client.invoke({
164
+ return await this.room.invoke({
142
165
  toolkit: "sync",
143
166
  tool: operation,
144
167
  input,
145
168
  });
146
169
  }
147
- start({ onDone, onError } = {}) {
148
- this.client.protocol.start({ onDone, onError });
149
- }
150
- dispose() {
151
- super.dispose();
152
- for (const streamState of Object.values(this._documentStreams)) {
153
- streamState.closeInputStream();
154
- }
155
- this._documentStreams = {};
156
- for (const rc of Object.values(this._connectedDocuments)) {
157
- unregisterDocument(rc.ref.id);
158
- }
159
- this._connectedDocuments = {};
160
- this._connectingDocuments = {};
161
- }
162
170
  _applySyncPayload(rc, payload) {
163
- const doc = rc.ref;
164
171
  if (payload.length > 0) {
165
- const base64 = decoder.decode(payload);
166
- applyBackendChanges(doc.id, base64);
172
+ applyBackendChanges(rc.ref.id, decoder.decode(payload));
167
173
  }
168
- this.emit("synced", { type: "sync", doc });
169
- if (!doc.isSynchronized) {
170
- doc.setSynchronizedComplete();
174
+ if (!rc.ref.isSynchronized) {
175
+ rc.ref.setSynchronizedComplete();
171
176
  }
172
177
  }
173
- async _handleStatus(_protocol, _messageId, _type, bytes) {
174
- if (!bytes) {
175
- return;
176
- }
177
- const [header] = unpackMessage(bytes);
178
- this.emit("status", { type: "status", status: header.status });
179
- }
180
178
  async create(path, json) {
181
179
  const normalizedPath = normalizeSyncPath(path);
182
180
  await this._invoke("create", {
@@ -187,129 +185,126 @@ export class SyncClient extends EventEmitter {
187
185
  });
188
186
  }
189
187
  async open(path, { create = true, initialJson, schema, } = {}) {
190
- path = normalizeSyncPath(path);
191
- const pending = this._connectingDocuments[path];
192
- if (pending) {
188
+ const normalizedPath = normalizeSyncPath(path);
189
+ const closing = this._closingDocuments[normalizedPath];
190
+ if (closing != null) {
191
+ await closing;
192
+ }
193
+ const pending = this._connectingDocuments[normalizedPath];
194
+ if (pending != null) {
193
195
  await pending;
194
196
  }
195
- const connected = this._connectedDocuments[path];
196
- if (connected) {
197
- connected.count++;
197
+ const connected = this._connectedDocuments[normalizedPath];
198
+ if (connected != null) {
199
+ connected.count += 1;
198
200
  return connected.ref;
199
201
  }
200
202
  const connecting = new Completer();
201
- this._connectingDocuments[path] = connecting.fut;
202
- let streamState;
203
- let iterator;
203
+ this._connectingDocuments[normalizedPath] = connecting.fut;
204
204
  try {
205
- streamState = new SyncOpenStreamState({
206
- path,
205
+ const config = {
207
206
  create,
208
- vector: null,
209
- schemaJson: schema == null ? null : schema.toJson(),
207
+ schemaJson: schema?.toJson() ?? null,
210
208
  schemaPath: null,
209
+ };
210
+ const openResult = await this._openStream({
211
+ path: normalizedPath,
212
+ config,
213
+ vector: null,
211
214
  initialJson: initialJson ?? null,
212
215
  });
213
- const responseStream = await this.client.invokeStream({
214
- toolkit: "sync",
215
- tool: "open",
216
- input: streamState.inputStream(),
217
- });
218
- iterator = responseStream[Symbol.asyncIterator]();
219
- const first = await iterator.next();
220
- if (first.done || first.value === undefined) {
221
- throw new RoomServerException("sync.open stream closed before the initial document state was returned");
222
- }
223
- const firstChunk = first.value;
224
- if (firstChunk instanceof ErrorContent) {
225
- throw new RoomServerException(firstChunk.text, firstChunk.code);
226
- }
227
- if (!(firstChunk instanceof BinaryContent)) {
228
- throw this._unexpectedResponseError("open");
229
- }
230
- const stateHeaders = parseSyncOpenStateChunkHeaders(firstChunk.headers);
231
- if (normalizeSyncPath(stateHeaders.path) !== path) {
232
- throw new RoomServerException("sync.open stream returned a mismatched path");
233
- }
216
+ const resolvedSchema = MeshSchema.fromJson(openResult.stateHeaders.schema);
234
217
  const doc = new MeshDocument({
235
- schema: MeshSchema.fromJson(stateHeaders.schema),
218
+ schema: resolvedSchema,
236
219
  sendChangesToBackend: (base64) => {
220
+ const currentStream = this._documentStreams[normalizedPath];
221
+ if (currentStream == null) {
222
+ return;
223
+ }
237
224
  try {
238
- streamState?.queueSync(encoder.encode(base64));
225
+ currentStream.queueSync(encoder.encode(base64));
239
226
  }
240
227
  catch {
241
228
  }
242
229
  },
243
230
  });
244
231
  const rc = new RefCount(doc);
245
- this._connectedDocuments[path] = rc;
246
- this._documentStreams[path] = streamState;
247
- this._applySyncPayload(rc, firstChunk.data);
248
- streamState.attachTask(this._consumeOpenStream({
249
- path,
250
- rc,
251
- iterator,
252
- streamState,
253
- }));
232
+ this._connectedDocuments[normalizedPath] = rc;
233
+ this._documentConfigs[normalizedPath] = config;
234
+ this._documentStreams[normalizedPath] = openResult.streamState;
235
+ this._applySyncPayload(rc, openResult.firstChunk.data);
236
+ this._attachStreamConsumer({
237
+ path: normalizedPath,
238
+ doc: rc,
239
+ streamState: openResult.streamState,
240
+ iterator: openResult.iterator,
241
+ });
254
242
  this.emit("connected", { type: "connect", doc });
255
243
  connecting.complete(rc);
256
244
  await doc.synchronized;
257
245
  return doc;
258
246
  }
259
247
  catch (error) {
260
- streamState?.closeInputStream();
261
- if (iterator) {
262
- await iterator.return?.();
263
- }
264
248
  connecting.completeError(error);
265
249
  throw error;
266
250
  }
267
251
  finally {
268
- delete this._connectingDocuments[path];
252
+ delete this._connectingDocuments[normalizedPath];
269
253
  }
270
254
  }
271
255
  async close(path) {
272
- path = normalizeSyncPath(path);
273
- const rc = this._connectedDocuments[path];
274
- if (!rc) {
275
- throw new RoomServerException(`Not connected to ${path}`);
256
+ const normalizedPath = normalizeSyncPath(path);
257
+ const rc = this._connectedDocuments[normalizedPath];
258
+ if (rc == null) {
259
+ throw new RoomServerException(`Not connected to ${normalizedPath}`);
276
260
  }
277
- const doc = rc.ref;
278
- rc.count--;
261
+ rc.count -= 1;
279
262
  if (rc.count === 0) {
280
- delete this._connectedDocuments[path];
281
- const streamState = this._documentStreams[path];
282
- delete this._documentStreams[path];
283
- if (streamState) {
284
- streamState.closeInputStream();
285
- try {
286
- await streamState.wait();
263
+ delete this._connectedDocuments[normalizedPath];
264
+ delete this._documentConfigs[normalizedPath];
265
+ const streamState = this._documentStreams[normalizedPath];
266
+ delete this._documentStreams[normalizedPath];
267
+ const closeFuture = (async () => {
268
+ if (streamState != null) {
269
+ streamState.closeInputStream();
270
+ try {
271
+ await streamState.wait();
272
+ }
273
+ finally {
274
+ unregisterDocument(rc.ref.id);
275
+ }
287
276
  }
288
- finally {
289
- unregisterDocument(doc.id);
277
+ else {
278
+ unregisterDocument(rc.ref.id);
290
279
  }
280
+ })();
281
+ this._closingDocuments[normalizedPath] = closeFuture;
282
+ try {
283
+ await closeFuture;
291
284
  }
292
- else {
293
- unregisterDocument(doc.id);
285
+ finally {
286
+ if (this._closingDocuments[normalizedPath] === closeFuture) {
287
+ delete this._closingDocuments[normalizedPath];
288
+ }
294
289
  }
295
290
  }
296
- this.emit("closed", { type: "close", doc });
291
+ this.emit("closed", { type: "close", doc: rc.ref });
297
292
  }
298
293
  async sync(path, data) {
299
- path = normalizeSyncPath(path);
300
- if (!this._connectedDocuments[path]) {
294
+ const normalizedPath = normalizeSyncPath(path);
295
+ if (this._connectedDocuments[normalizedPath] == null) {
301
296
  throw new RoomServerException("attempted to sync to a document that is not connected");
302
297
  }
303
- const streamState = this._documentStreams[path];
304
- if (!streamState) {
298
+ const streamState = this._documentStreams[normalizedPath];
299
+ if (streamState == null) {
305
300
  throw new RoomServerException("attempted to sync to a document that is not connected");
306
301
  }
307
302
  streamState.queueSync(data);
308
303
  }
309
- async _consumeOpenStream(params) {
304
+ async _consumeOpenStream({ path, rc, iterator, streamState, }) {
310
305
  try {
311
306
  while (true) {
312
- const next = await params.iterator.next();
307
+ const next = await iterator.next();
313
308
  if (next.done || next.value === undefined) {
314
309
  return;
315
310
  }
@@ -327,15 +322,99 @@ export class SyncClient extends EventEmitter {
327
322
  throw this._unexpectedResponseError("open");
328
323
  }
329
324
  const headers = parseSyncOpenOutputChunkHeaders(chunk.headers);
330
- if (normalizeSyncPath(headers.path) !== params.path) {
325
+ if (normalizeSyncPath(headers.path) !== path) {
331
326
  throw new RoomServerException("sync.open stream returned a mismatched path");
332
327
  }
333
- this._applySyncPayload(params.rc, chunk.data);
328
+ this._applySyncPayload(rc, chunk.data);
334
329
  }
335
330
  }
336
331
  finally {
337
- params.streamState.closeInputStream();
338
- await params.iterator.return?.();
332
+ streamState.closeInputStream();
333
+ await iterator.return?.();
334
+ }
335
+ }
336
+ async _openStream({ path, config, vector, initialJson, }) {
337
+ const streamState = new SyncOpenStreamState({
338
+ path,
339
+ create: config.create,
340
+ vector,
341
+ schemaJson: config.schemaJson,
342
+ schemaPath: config.schemaPath,
343
+ initialJson,
344
+ });
345
+ let iterator;
346
+ try {
347
+ const responseStream = await this.room.invokeStream({
348
+ toolkit: "sync",
349
+ tool: "open",
350
+ input: streamState.inputStream(),
351
+ });
352
+ iterator = responseStream[Symbol.asyncIterator]();
353
+ const first = await iterator.next();
354
+ if (first.done || first.value === undefined) {
355
+ throw new RoomServerException("sync.open stream closed before the initial document state was returned");
356
+ }
357
+ const firstChunk = first.value;
358
+ if (firstChunk instanceof ErrorContent) {
359
+ throw new RoomServerException(firstChunk.text, firstChunk.code);
360
+ }
361
+ if (!(firstChunk instanceof BinaryContent)) {
362
+ throw this._unexpectedResponseError("open");
363
+ }
364
+ const stateHeaders = parseSyncOpenStateChunkHeaders(firstChunk.headers);
365
+ if (normalizeSyncPath(stateHeaders.path) !== path) {
366
+ throw new RoomServerException("sync.open stream returned a mismatched path");
367
+ }
368
+ return {
369
+ streamState,
370
+ iterator,
371
+ stateHeaders,
372
+ firstChunk,
373
+ };
374
+ }
375
+ catch (error) {
376
+ streamState.closeInputStream();
377
+ if (iterator != null) {
378
+ await iterator.return?.();
379
+ }
380
+ throw error;
381
+ }
382
+ }
383
+ _attachStreamConsumer({ path, doc, streamState, iterator, }) {
384
+ streamState.attachTask(this._consumeOpenStream({
385
+ path,
386
+ rc: doc,
387
+ iterator,
388
+ streamState,
389
+ }));
390
+ }
391
+ async _onRoomDisconnect() {
392
+ const openStreams = Object.values(this._documentStreams);
393
+ Object.keys(this._documentStreams).forEach((key) => delete this._documentStreams[key]);
394
+ for (const streamState of openStreams) {
395
+ streamState.closeInputStream();
396
+ }
397
+ }
398
+ async _onRoomReconnect() {
399
+ for (const [path, ref] of Object.entries(this._connectedDocuments)) {
400
+ const config = this._documentConfigs[path];
401
+ if (config == null) {
402
+ continue;
403
+ }
404
+ const openResult = await this._openStream({
405
+ path,
406
+ config,
407
+ vector: ref.ref.getStateVector(),
408
+ initialJson: null,
409
+ });
410
+ this._documentStreams[path] = openResult.streamState;
411
+ this._applySyncPayload(ref, openResult.firstChunk.data);
412
+ this._attachStreamConsumer({
413
+ path,
414
+ doc: ref,
415
+ streamState: openResult.streamState,
416
+ iterator: openResult.iterator,
417
+ });
339
418
  }
340
419
  }
341
420
  }