@dabble/patches 0.7.8 → 0.7.9

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.
@@ -5,9 +5,9 @@ import { JSONRPCClient } from './protocol/JSONRPCClient.js';
5
5
  import { PatchesWebSocket } from './websocket/PatchesWebSocket.js';
6
6
  import { WebSocketOptions } from './websocket/WebSocketTransport.js';
7
7
  import { SizeCalculator } from '../algorithms/ot/shared/changeBatching.js';
8
+ import { ClientAlgorithm } from '../client/ClientAlgorithm.js';
8
9
  import { Patches } from '../client/Patches.js';
9
10
  import { AlgorithmName } from '../client/PatchesStore.js';
10
- import { ClientAlgorithm } from '../client/ClientAlgorithm.js';
11
11
  import '../json-patch/JSONPatch.js';
12
12
  import '@dabble/delta';
13
13
  import '../json-patch/types.js';
@@ -142,6 +142,16 @@ declare class PatchesSync {
142
142
  * Cleans up local state and notifies the application with any pending changes that were lost.
143
143
  */
144
144
  protected _handleRemoteDocDeleted(docId: string): Promise<void>;
145
+ /**
146
+ * Applies the subscribeFilter option to a list of doc IDs, returning the subset
147
+ * that should be sent to ws.subscribe/unsubscribe. Returns the full list if no filter is set.
148
+ */
149
+ protected _filterSubscribeIds(docIds: string[]): string[];
150
+ /**
151
+ * Returns the set of doc IDs currently subscribed on the server, derived by
152
+ * applying the subscribe filter to the full tracked set.
153
+ */
154
+ protected _getActiveSubscriptions(): Set<string>;
145
155
  /**
146
156
  * Helper to detect DOC_DELETED (410) errors from the server.
147
157
  */
@@ -155,7 +155,7 @@ class PatchesSync {
155
155
  this.trackedDocs = new Set(activeDocIds);
156
156
  if (activeDocIds.length > 0) {
157
157
  try {
158
- const subscribeIds = this.options?.subscribeFilter?.(activeDocIds) || activeDocIds;
158
+ const subscribeIds = this._filterSubscribeIds(activeDocIds);
159
159
  if (subscribeIds.length) {
160
160
  await this.ws.subscribe(subscribeIds);
161
161
  }
@@ -318,6 +318,7 @@ class PatchesSync {
318
318
  async _handleDocsTracked(docIds) {
319
319
  const newIds = docIds.filter((id) => !this.trackedDocs.has(id));
320
320
  if (!newIds.length) return;
321
+ const alreadySubscribed = this._getActiveSubscriptions();
321
322
  newIds.forEach((id) => this.trackedDocs.add(id));
322
323
  for (const docId of newIds) {
323
324
  for (const algorithm of Object.values(this.patches.algorithms)) {
@@ -330,13 +331,9 @@ class PatchesSync {
330
331
  }
331
332
  }
332
333
  }
333
- let subscribeIds = newIds;
334
- if (this.options?.subscribeFilter) {
335
- const alreadyTracked = this.options.subscribeFilter([...this.trackedDocs]);
336
- subscribeIds = subscribeIds.filter((id) => !alreadyTracked.includes(id));
337
- }
338
334
  if (this.state.connected) {
339
335
  try {
336
+ const subscribeIds = this._filterSubscribeIds(newIds).filter((id) => !alreadySubscribed.has(id));
340
337
  if (subscribeIds.length) {
341
338
  await this.ws.subscribe(subscribeIds);
342
339
  }
@@ -350,8 +347,10 @@ class PatchesSync {
350
347
  async _handleDocsUntracked(docIds) {
351
348
  const existingIds = docIds.filter((id) => this.trackedDocs.has(id));
352
349
  if (!existingIds.length) return;
350
+ const subscribedBefore = this._getActiveSubscriptions();
353
351
  existingIds.forEach((id) => this.trackedDocs.delete(id));
354
- const unsubscribeIds = this.options?.subscribeFilter?.(existingIds) || existingIds;
352
+ const subscribedAfter = this._getActiveSubscriptions();
353
+ const unsubscribeIds = [...subscribedBefore].filter((id) => !subscribedAfter.has(id));
355
354
  if (this.state.connected && unsubscribeIds.length) {
356
355
  try {
357
356
  await this.ws.unsubscribe(unsubscribeIds);
@@ -380,6 +379,20 @@ class PatchesSync {
380
379
  await algorithm.confirmDeleteDoc(docId);
381
380
  await this.onRemoteDocDeleted.emit(docId, pendingChanges);
382
381
  }
382
+ /**
383
+ * Applies the subscribeFilter option to a list of doc IDs, returning the subset
384
+ * that should be sent to ws.subscribe/unsubscribe. Returns the full list if no filter is set.
385
+ */
386
+ _filterSubscribeIds(docIds) {
387
+ return this.options?.subscribeFilter?.(docIds) || docIds;
388
+ }
389
+ /**
390
+ * Returns the set of doc IDs currently subscribed on the server, derived by
391
+ * applying the subscribe filter to the full tracked set.
392
+ */
393
+ _getActiveSubscriptions() {
394
+ return new Set(this._filterSubscribeIds([...this.trackedDocs]));
395
+ }
383
396
  /**
384
397
  * Helper to detect DOC_DELETED (410) errors from the server.
385
398
  */
@@ -15,11 +15,11 @@ export { WebSocketOptions, WebSocketTransport } from './websocket/WebSocketTrans
15
15
  export { CommitChangesOptions } from '../types.js';
16
16
  import '../event-signal.js';
17
17
  import '../algorithms/ot/shared/changeBatching.js';
18
- import '../client/Patches.js';
19
- import '../json-patch/types.js';
20
18
  import '../client/ClientAlgorithm.js';
19
+ import '../json-patch/types.js';
21
20
  import '../BaseDoc-_Rsau70J.js';
22
21
  import '../client/PatchesStore.js';
22
+ import '../client/Patches.js';
23
23
  import '../server/types.js';
24
24
  import '../utils/deferred.js';
25
25
  import '../json-patch/JSONPatch.js';
@@ -54,7 +54,10 @@ class JSONRPCServer {
54
54
  this.registerMethod(method, async (...args) => {
55
55
  const docId = args[0];
56
56
  if (typeof docId !== "string" || !docId) {
57
- throw new StatusError(400, `INVALID_REQUEST: docId is required (got ${docId === "" ? "empty string" : String(docId)})`);
57
+ throw new StatusError(
58
+ 400,
59
+ `INVALID_REQUEST: docId is required (got ${docId === "" ? "empty string" : String(docId)})`
60
+ );
58
61
  }
59
62
  const ctx = getAuthContext();
60
63
  await this.assertAccess(access, ctx, method, args);
@@ -137,7 +140,10 @@ class JSONRPCServer {
137
140
  if (!this.auth) return;
138
141
  const docId = args?.[0];
139
142
  if (typeof docId !== "string" || !docId) {
140
- throw new StatusError(400, `INVALID_REQUEST: docId is required (got ${docId === "" ? "empty string" : String(docId)})`);
143
+ throw new StatusError(
144
+ 400,
145
+ `INVALID_REQUEST: docId is required (got ${docId === "" ? "empty string" : String(docId)})`
146
+ );
141
147
  }
142
148
  const ok = await this.auth.canAccess(ctx, docId, access, method);
143
149
  if (!ok) {
@@ -66,7 +66,24 @@ function createDocReactiveState(options) {
66
66
  change,
67
67
  doc
68
68
  };
69
- return { doc, setDoc, data, setData, loading, setLoading, error, setError, rev, setRev, hasPending, setHasPending, setupDoc, resetSignals, change, baseReturn };
69
+ return {
70
+ doc,
71
+ setDoc,
72
+ data,
73
+ setData,
74
+ loading,
75
+ setLoading,
76
+ error,
77
+ setError,
78
+ rev,
79
+ setRev,
80
+ hasPending,
81
+ setHasPending,
82
+ setupDoc,
83
+ resetSignals,
84
+ change,
85
+ baseReturn
86
+ };
70
87
  }
71
88
  function usePatchesDoc(docIdOrOptions, options) {
72
89
  if (typeof docIdOrOptions === "string" || typeof docIdOrOptions === "function") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dabble/patches",
3
- "version": "0.7.8",
3
+ "version": "0.7.9",
4
4
  "description": "Immutable JSON Patch implementation based on RFC 6902 supporting operational transformation and last-writer-wins",
5
5
  "author": "Jacob Wright <jacwright@gmail.com>",
6
6
  "bugs": {