@dabble/patches 0.3.0 → 0.3.2

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.
@@ -1,4 +1,4 @@
1
- import type { Change, PatchesSnapshot } from '../../types';
1
+ import type { Change, PatchesSnapshot } from '../../types.js';
2
2
  /**
3
3
  * Applies incoming changes from the server that were *not* initiated by this client.
4
4
  * @param snapshot The current state of the document (the state without pending changes applied) and the pending changes.
@@ -1,5 +1,5 @@
1
- import { applyChanges } from '../shared/applyChanges';
2
- import { rebaseChanges } from '../shared/rebaseChanges';
1
+ import { applyChanges } from '../shared/applyChanges.js';
2
+ import { rebaseChanges } from '../shared/rebaseChanges.js';
3
3
  /**
4
4
  * Applies incoming changes from the server that were *not* initiated by this client.
5
5
  * @param snapshot The current state of the document (the state without pending changes applied) and the pending changes.
@@ -1,3 +1,3 @@
1
1
  import type { JSONPatch } from '../..';
2
- import type { Change, PatchesSnapshot } from '../../types';
3
- export declare function makeChange<T = any>(snapshot: PatchesSnapshot<T>, mutator: (draft: T, patch: JSONPatch) => void, changeMetadata?: Record<string, any>, maxPayloadBytes?: number): Change[];
2
+ import type { Change, DeepRequired, PatchesSnapshot } from '../../types.js';
3
+ export declare function makeChange<T = any>(snapshot: PatchesSnapshot<T>, mutator: (draft: DeepRequired<T>, patch: JSONPatch) => void, changeMetadata?: Record<string, any>, maxPayloadBytes?: number): Change[];
@@ -1,7 +1,7 @@
1
- import { createChange } from '../../data/change';
2
- import { createJSONPatch } from '../../json-patch/createJSONPatch';
3
- import { breakChange } from './breakChange';
4
- import { createStateFromSnapshot } from './createStateFromSnapshot';
1
+ import { createChange } from '../../data/change.js';
2
+ import { createJSONPatch } from '../../json-patch/createJSONPatch.js';
3
+ import { breakChange } from './breakChange.js';
4
+ import { createStateFromSnapshot } from './createStateFromSnapshot.js';
5
5
  export function makeChange(snapshot, mutator, changeMetadata, maxPayloadBytes) {
6
6
  const pendingChanges = snapshot.changes;
7
7
  const pendingRev = pendingChanges[pendingChanges.length - 1]?.rev ?? snapshot.rev;
@@ -1,5 +1,5 @@
1
- import type { PatchesStoreBackend } from '../../server/types';
2
- import type { Change } from '../../types';
1
+ import type { PatchesStoreBackend } from '../../server/types.js';
2
+ import type { Change } from '../../types.js';
3
3
  /**
4
4
  * Commits a set of changes to a document, applying operational transformation as needed.
5
5
  * @param docId - The ID of the document.
@@ -1,9 +1,9 @@
1
- import { applyChanges } from '../shared/applyChanges';
2
- import { createVersion } from './createVersion';
3
- import { getSnapshotAtRevision } from './getSnapshotAtRevision';
4
- import { getStateAtRevision } from './getStateAtRevision';
5
- import { handleOfflineSessionsAndBatches } from './handleOfflineSessionsAndBatches';
6
- import { transformIncomingChanges } from './transformIncomingChanges';
1
+ import { applyChanges } from '../shared/applyChanges.js';
2
+ import { createVersion } from './createVersion.js';
3
+ import { getSnapshotAtRevision } from './getSnapshotAtRevision.js';
4
+ import { getStateAtRevision } from './getStateAtRevision.js';
5
+ import { handleOfflineSessionsAndBatches } from './handleOfflineSessionsAndBatches.js';
6
+ import { transformIncomingChanges } from './transformIncomingChanges.js';
7
7
  /**
8
8
  * Commits a set of changes to a document, applying operational transformation as needed.
9
9
  * @param docId - The ID of the document.
@@ -1,5 +1,5 @@
1
- import type { Change, EditableVersionMetadata, VersionMetadata } from '../../types';
2
- import type { PatchesStoreBackend } from '../../server/types';
1
+ import type { PatchesStoreBackend } from '../../server/types.js';
2
+ import type { Change, EditableVersionMetadata, VersionMetadata } from '../../types.js';
3
3
  /**
4
4
  * Creates a new version snapshot of a document's state from changes.
5
5
  * @param store The storage backend to save the version to.
@@ -1,5 +1,5 @@
1
- import type { PatchesStoreBackend } from '../../server';
2
- import type { PatchesSnapshot } from '../../types';
1
+ import type { PatchesStoreBackend } from '../../server.js';
2
+ import type { PatchesSnapshot } from '../../types.js';
3
3
  /**
4
4
  * Retrieves the document state of the version before the given revision and changes after up to that revision or all
5
5
  * changes since that version.
@@ -1,5 +1,5 @@
1
- import type { PatchesStoreBackend } from '../../server';
2
- import type { PatchesState } from '../../types';
1
+ import type { PatchesStoreBackend } from '../../server.js';
2
+ import type { PatchesState } from '../../types.js';
3
3
  /**
4
4
  * Gets the state at a specific revision.
5
5
  * @param docId The document ID.
@@ -1,5 +1,5 @@
1
- import { applyChanges } from '../shared/applyChanges';
2
- import { getSnapshotAtRevision } from './getSnapshotAtRevision';
1
+ import { applyChanges } from '../shared/applyChanges.js';
2
+ import { getSnapshotAtRevision } from './getSnapshotAtRevision.js';
3
3
  /**
4
4
  * Gets the state at a specific revision.
5
5
  * @param docId The document ID.
@@ -1,5 +1,5 @@
1
- import type { PatchesStoreBackend } from '../../server';
2
- import type { Change } from '../../types';
1
+ import type { PatchesStoreBackend } from '../../server.js';
2
+ import type { Change } from '../../types.js';
3
3
  /**
4
4
  * Handles offline/large batch versioning logic for multi-batch uploads.
5
5
  * Groups changes into sessions, merges with previous batch if needed, and creates/extends versions.
@@ -1,7 +1,7 @@
1
1
  import { createSortableId } from 'crypto-id';
2
- import { createVersionMetadata } from '../../data/version';
3
- import { applyChanges } from '../shared/applyChanges';
4
- import { getStateAtRevision } from './getStateAtRevision';
2
+ import { createVersionMetadata } from '../../data/version.js';
3
+ import { applyChanges } from '../shared/applyChanges.js';
4
+ import { getStateAtRevision } from './getStateAtRevision.js';
5
5
  /**
6
6
  * Handles offline/large batch versioning logic for multi-batch uploads.
7
7
  * Groups changes into sessions, merges with previous batch if needed, and creates/extends versions.
@@ -1,4 +1,4 @@
1
- import type { Change } from '../../types';
1
+ import type { Change } from '../../types.js';
2
2
  /**
3
3
  * Transforms incoming changes against committed changes that happened *after* the client's baseRev.
4
4
  * The state used for transformation should be the server state *at the client's baseRev*.
@@ -1,6 +1,6 @@
1
1
  import { type Unsubscriber } from '../event-signal.js';
2
2
  import type { JSONPatch } from '../json-patch/JSONPatch.js';
3
- import type { Change, PatchesSnapshot, SyncingState } from '../types.js';
3
+ import type { Change, DeepRequired, PatchesSnapshot, SyncingState } from '../types.js';
4
4
  /**
5
5
  * Options for creating a PatchesDoc instance
6
6
  */
@@ -71,7 +71,7 @@ export declare class PatchesDoc<T extends object = object> {
71
71
  * @param mutator Function modifying a draft state.
72
72
  * @returns The generated Change object or null if no changes occurred.
73
73
  */
74
- change(mutator: (draft: T, patch: JSONPatch) => void): Change[];
74
+ change(mutator: (draft: DeepRequired<T>, patch: JSONPatch) => void): Change[];
75
75
  /**
76
76
  * Returns the pending changes for this document.
77
77
  * @returns The pending changes.
@@ -1,3 +1,3 @@
1
- import type { JSONPatchOp } from '../json-patch/types';
2
- import type { Change } from '../types';
1
+ import type { JSONPatchOp } from '../json-patch/types.js';
2
+ import type { Change } from '../types.js';
3
3
  export declare function createChange(baseRev: number, rev: number, ops: JSONPatchOp[], metadata?: Record<string, any>): Change;
@@ -1,4 +1,4 @@
1
- import type { VersionMetadata } from '../types';
1
+ import type { VersionMetadata } from '../types.js';
2
2
  /**
3
3
  * Create a version id for a given document. Uses a sortable 16 character id.
4
4
  * @returns The version id.
@@ -1,3 +1,4 @@
1
+ import type { DeepRequired } from '../types.js';
1
2
  import { JSONPatch } from './JSONPatch.js';
2
3
  /**
3
4
  * Creates a `JSONPatch` instance by tracking changes made to a proxy object within an updater function.
@@ -32,4 +33,4 @@ import { JSONPatch } from './JSONPatch.js';
32
33
  * // ]
33
34
  * ```
34
35
  */
35
- export declare function createJSONPatch<T>(target: T, updater: (proxy: T, patch: JSONPatch) => void): JSONPatch;
36
+ export declare function createJSONPatch<T>(target: T, updater: (proxy: DeepRequired<T>, patch: JSONPatch) => void): JSONPatch;
@@ -1,3 +1,4 @@
1
+ import type { DeepRequired } from '../types.js';
1
2
  import { JSONPatch } from './JSONPatch.js';
2
3
  /**
3
4
  * Creates a proxy object that can be used in two ways:
@@ -37,5 +38,5 @@ import { JSONPatch } from './JSONPatch.js';
37
38
  * @param patch The `JSONPatch` instance to add generated operations to (required for automatic patch generation mode).
38
39
  * @returns A proxy object of type T.
39
40
  */
40
- export declare function createPatchProxy<T>(): T;
41
- export declare function createPatchProxy<T>(target: T, patch: JSONPatch): T;
41
+ export declare function createPatchProxy<T>(): DeepRequired<T>;
42
+ export declare function createPatchProxy<T>(target: T, patch: JSONPatch): DeepRequired<T>;
@@ -1,23 +1,30 @@
1
1
  import { Patches } from '../client/Patches.js';
2
+ import type { PatchesStore } from '../client/PatchesStore.js';
2
3
  import type { Change, SyncingState } from '../types.js';
3
4
  import type { ConnectionState } from './protocol/types.js';
5
+ import { PatchesWebSocket } from './websocket/PatchesWebSocket.js';
4
6
  import type { WebSocketOptions } from './websocket/WebSocketTransport.js';
5
7
  export interface PatchesSyncState {
6
8
  online: boolean;
7
9
  connected: boolean;
8
10
  syncing: SyncingState;
9
11
  }
12
+ export interface PatchesSyncOptions {
13
+ subscribeFilter?: (docIds: string[]) => string[];
14
+ websocket?: WebSocketOptions;
15
+ }
10
16
  /**
11
17
  * Handles WebSocket connection, document subscriptions, and syncing logic between
12
18
  * the Patches instance and the server.
13
19
  */
14
20
  export declare class PatchesSync {
15
- private ws;
16
- private patches;
17
- private store;
18
- private maxPayloadBytes?;
19
- private trackedDocs;
20
- private _state;
21
+ protected options?: PatchesSyncOptions | undefined;
22
+ protected ws: PatchesWebSocket;
23
+ protected patches: Patches;
24
+ protected store: PatchesStore;
25
+ protected maxPayloadBytes?: number;
26
+ protected trackedDocs: Set<string>;
27
+ protected _state: PatchesSyncState;
21
28
  /**
22
29
  * Signal emitted when the sync state changes.
23
30
  */
@@ -28,7 +35,7 @@ export declare class PatchesSync {
28
35
  readonly onError: import("../event-signal.js").Signal<(error: Error, context?: {
29
36
  docId?: string;
30
37
  }) => void>;
31
- constructor(patches: Patches, url: string, wsOptions?: WebSocketOptions);
38
+ constructor(patches: Patches, url: string, options?: PatchesSyncOptions | undefined);
32
39
  /**
33
40
  * Gets the current sync state.
34
41
  */
@@ -50,8 +50,8 @@ let PatchesSync = (() => {
50
50
  let _syncDoc_decorators;
51
51
  let __receiveCommittedChanges_decorators;
52
52
  return _a = class PatchesSync {
53
- constructor(patches, url, wsOptions) {
54
- this.ws = __runInitializers(this, _instanceExtraInitializers);
53
+ constructor(patches, url, options) {
54
+ this.options = (__runInitializers(this, _instanceExtraInitializers), options);
55
55
  this._state = { online: false, connected: false, syncing: null };
56
56
  /**
57
57
  * Signal emitted when the sync state changes.
@@ -64,7 +64,7 @@ let PatchesSync = (() => {
64
64
  this.patches = patches;
65
65
  this.store = patches.store;
66
66
  this.maxPayloadBytes = patches.docOptions?.maxPayloadBytes;
67
- this.ws = new PatchesWebSocket(url, wsOptions);
67
+ this.ws = new PatchesWebSocket(url, options?.websocket);
68
68
  this._state.online = onlineState.isOnline;
69
69
  this.trackedDocs = new Set(patches.trackedDocs);
70
70
  // --- Event Listeners ---
@@ -131,7 +131,10 @@ let PatchesSync = (() => {
131
131
  // Subscribe to active docs
132
132
  if (activeDocIds.length > 0) {
133
133
  try {
134
- await this.ws.subscribe(activeDocIds);
134
+ const subscribeIds = this.options?.subscribeFilter?.(activeDocIds) || activeDocIds;
135
+ if (subscribeIds.length) {
136
+ await this.ws.subscribe(subscribeIds);
137
+ }
135
138
  }
136
139
  catch (err) {
137
140
  console.warn('Error subscribing to active docs during sync:', err);
@@ -338,9 +341,17 @@ let PatchesSync = (() => {
338
341
  if (!newIds.length)
339
342
  return;
340
343
  newIds.forEach(id => this.trackedDocs.add(id));
344
+ let subscribeIds = newIds;
345
+ // If a subscribe filter is provided, filter out docs that are already tracked
346
+ if (this.options?.subscribeFilter) {
347
+ const alreadyTracked = this.options.subscribeFilter([...this.trackedDocs]);
348
+ subscribeIds = subscribeIds.filter(id => !alreadyTracked.includes(id));
349
+ }
341
350
  if (this.state.connected) {
342
351
  try {
343
- await this.ws.subscribe(newIds);
352
+ if (subscribeIds.length) {
353
+ await this.ws.subscribe(subscribeIds);
354
+ }
344
355
  // Trigger sync for newly tracked docs immediately
345
356
  await Promise.all(newIds.map(id => this.syncDoc(id)));
346
357
  }
@@ -1,5 +1,5 @@
1
1
  import type { Unsubscriber } from '../../event-signal.js';
2
- import type { Change, EditableVersionMetadata, ListVersionsOptions, PatchesState, VersionMetadata } from '../../types';
2
+ import type { Change, EditableVersionMetadata, ListVersionsOptions, PatchesState, VersionMetadata } from '../../types.js';
3
3
  /**
4
4
  * Represents the possible states of a network transport connection.
5
5
  * - 'connecting': Connection is being established
@@ -1,5 +1,5 @@
1
1
  declare class OnlineState {
2
- onOnlineChange: import("../../event-signal").Signal<(isOnline: boolean) => void>;
2
+ onOnlineChange: import("../../event-signal.js").Signal<(isOnline: boolean) => void>;
3
3
  protected _isOnline: boolean;
4
4
  constructor();
5
5
  get isOnline(): boolean;
@@ -1,4 +1,4 @@
1
- import { signal } from '../../event-signal';
1
+ import { signal } from '../../event-signal.js';
2
2
  class OnlineState {
3
3
  constructor() {
4
4
  this.onOnlineChange = signal();
@@ -1,5 +1,5 @@
1
1
  import type { JSONPatch } from '../json-patch/JSONPatch.js';
2
- import type { Change, EditableVersionMetadata, PatchesState } from '../types.js';
2
+ import type { Change, DeepRequired, EditableVersionMetadata, PatchesState } from '../types.js';
3
3
  import type { PatchesStoreBackend } from './types.js';
4
4
  /**
5
5
  * Configuration options for the PatchesServer.
@@ -61,7 +61,7 @@ export declare class PatchesServer {
61
61
  * @param mutator
62
62
  * @returns
63
63
  */
64
- change<T = Record<string, any>>(docId: string, mutator: (draft: T, patch: JSONPatch) => void, metadata?: Record<string, any>): Promise<Change | null>;
64
+ change<T = Record<string, any>>(docId: string, mutator: (draft: DeepRequired<T>, patch: JSONPatch) => void, metadata?: Record<string, any>): Promise<Change | null>;
65
65
  /**
66
66
  * Deletes a document.
67
67
  * @param docId The document ID.
@@ -1,4 +1,4 @@
1
- export { PatchesBranchManager } from './PatchesBranchManager';
2
- export { PatchesHistoryManager } from './PatchesHistoryManager';
3
- export { PatchesServer } from './PatchesServer';
4
- export type { BranchingStoreBackend, PatchesStoreBackend } from './types';
1
+ export { PatchesBranchManager } from './PatchesBranchManager.js';
2
+ export { PatchesHistoryManager } from './PatchesHistoryManager.js';
3
+ export { PatchesServer } from './PatchesServer.js';
4
+ export type { BranchingStoreBackend, PatchesStoreBackend } from './types.js';
@@ -1,3 +1,3 @@
1
- export { PatchesBranchManager } from './PatchesBranchManager';
2
- export { PatchesHistoryManager } from './PatchesHistoryManager';
3
- export { PatchesServer } from './PatchesServer';
1
+ export { PatchesBranchManager } from './PatchesBranchManager.js';
2
+ export { PatchesHistoryManager } from './PatchesHistoryManager.js';
3
+ export { PatchesServer } from './PatchesServer.js';
@@ -1,4 +1,4 @@
1
- import type { Branch, Change, EditableVersionMetadata, ListChangesOptions, ListVersionsOptions, PatchesState, VersionMetadata } from '../types';
1
+ import type { Branch, Change, EditableVersionMetadata, ListChangesOptions, ListVersionsOptions, PatchesState, VersionMetadata } from '../types.js';
2
2
  /**
3
3
  * Interface for a backend storage system for patch synchronization.
4
4
  * Defines methods needed by PatchesServer, PatchesHistoryManager, etc.
package/dist/types.d.ts CHANGED
@@ -124,4 +124,14 @@ export interface ListVersionsOptions {
124
124
  /** Filter by the group ID (branch ID or offline batch ID). */
125
125
  groupId?: string;
126
126
  }
127
+ /**
128
+ * Makes optional object properties required for Proxies that allow setting deep properties.
129
+ */
130
+ export type DeepRequired<T> = {
131
+ [P in keyof T as undefined extends T[P] ? never : P]: T[P] extends object ? DeepRequired<T[P]> : T[P];
132
+ } & {
133
+ [P in keyof T as undefined extends T[P] ? T[P] extends object | undefined ? T[P] extends Function | Date | RegExp | Array<any> | null | undefined ? never : P : never : never]-?: T[P] extends object | undefined ? DeepRequired<T[P]> : T[P];
134
+ } & {
135
+ [P in keyof T as undefined extends T[P] ? T[P] extends object | undefined ? T[P] extends Function | Date | RegExp | Array<any> | null | undefined ? P : never : P : never]?: T[P];
136
+ };
127
137
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dabble/patches",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
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": {
@@ -56,6 +56,8 @@
56
56
  "prepare": "npm run build",
57
57
  "test": "vitest run",
58
58
  "tdd": "vitest",
59
+ "format": "prettier --write src/",
60
+ "format:check": "prettier --check src/",
59
61
  "lint": "eslint src tests",
60
62
  "lint:fix": "eslint src tests --fix"
61
63
  },
@@ -67,16 +69,16 @@
67
69
  "simplified-concurrency": "^0.2.0"
68
70
  },
69
71
  "devDependencies": {
70
- "@sveltejs/package": "^2.3.11",
72
+ "@sveltejs/package": "^2.5.0",
71
73
  "@types/simple-peer": "^9.11.8",
72
- "@typescript-eslint/eslint-plugin": "^8.33.1",
73
- "@typescript-eslint/parser": "^8.33.1",
74
- "eslint": "^9.28.0",
75
- "fake-indexeddb": "^6.0.0",
76
- "prettier": "^3.5.3",
77
- "typescript": "^5.8.3",
78
- "vite": "^6.3.5",
79
- "vite-plugin-dts": "^4.5.3",
80
- "vitest": "^3.1.3"
74
+ "@typescript-eslint/eslint-plugin": "^8.42.0",
75
+ "@typescript-eslint/parser": "^8.42.0",
76
+ "eslint": "^9.35.0",
77
+ "fake-indexeddb": "^6.2.2",
78
+ "prettier": "^3.6.2",
79
+ "typescript": "^5.9.2",
80
+ "vite": "^7.1.4",
81
+ "vite-plugin-dts": "^4.5.4",
82
+ "vitest": "^3.2.4"
81
83
  }
82
84
  }