@dabble/patches 0.2.32 → 0.3.1

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 (83) hide show
  1. package/dist/algorithms/client/applyCommittedChanges.d.ts +8 -0
  2. package/dist/algorithms/client/applyCommittedChanges.js +40 -0
  3. package/dist/{utils → algorithms/client}/batching.d.ts +1 -1
  4. package/dist/{utils → algorithms/client}/batching.js +2 -2
  5. package/dist/{utils → algorithms/client}/breakChange.d.ts +2 -3
  6. package/dist/algorithms/client/breakChange.js +258 -0
  7. package/dist/algorithms/client/createStateFromSnapshot.d.ts +7 -0
  8. package/dist/algorithms/client/createStateFromSnapshot.js +9 -0
  9. package/dist/algorithms/client/getJSONByteSize.js +12 -0
  10. package/dist/algorithms/client/makeChange.d.ts +3 -0
  11. package/dist/algorithms/client/makeChange.js +37 -0
  12. package/dist/algorithms/server/commitChanges.d.ts +12 -0
  13. package/dist/algorithms/server/commitChanges.js +80 -0
  14. package/dist/algorithms/server/createVersion.d.ts +12 -0
  15. package/dist/algorithms/server/createVersion.js +28 -0
  16. package/dist/algorithms/server/getSnapshotAtRevision.d.ts +10 -0
  17. package/dist/algorithms/server/getSnapshotAtRevision.js +29 -0
  18. package/dist/algorithms/server/getStateAtRevision.d.ts +9 -0
  19. package/dist/algorithms/server/getStateAtRevision.js +18 -0
  20. package/dist/algorithms/server/handleOfflineSessionsAndBatches.d.ts +12 -0
  21. package/dist/algorithms/server/handleOfflineSessionsAndBatches.js +80 -0
  22. package/dist/algorithms/server/transformIncomingChanges.d.ts +11 -0
  23. package/dist/algorithms/server/transformIncomingChanges.js +40 -0
  24. package/dist/algorithms/shared/applyChanges.d.ts +10 -0
  25. package/dist/algorithms/shared/applyChanges.js +17 -0
  26. package/dist/{utils.d.ts → algorithms/shared/rebaseChanges.d.ts} +1 -11
  27. package/dist/{utils.js → algorithms/shared/rebaseChanges.js} +3 -43
  28. package/dist/client/InMemoryStore.d.ts +2 -1
  29. package/dist/client/InMemoryStore.js +9 -3
  30. package/dist/client/IndexedDBStore.d.ts +34 -2
  31. package/dist/client/IndexedDBStore.js +399 -282
  32. package/dist/client/Patches.d.ts +11 -41
  33. package/dist/client/Patches.js +197 -208
  34. package/dist/client/PatchesDoc.d.ts +24 -41
  35. package/dist/client/PatchesDoc.js +57 -214
  36. package/dist/client/PatchesHistoryClient.js +1 -1
  37. package/dist/client/PatchesStore.d.ts +186 -9
  38. package/dist/data/change.d.ts +3 -0
  39. package/dist/data/change.js +20 -0
  40. package/dist/data/version.d.ts +12 -0
  41. package/dist/data/version.js +17 -0
  42. package/dist/json-patch/ops/add.js +1 -1
  43. package/dist/json-patch/ops/move.js +1 -1
  44. package/dist/json-patch/ops/remove.js +1 -1
  45. package/dist/json-patch/ops/replace.js +1 -1
  46. package/dist/json-patch/utils/get.js +0 -1
  47. package/dist/json-patch/utils/log.d.ts +4 -1
  48. package/dist/json-patch/utils/log.js +2 -5
  49. package/dist/json-patch/utils/ops.d.ts +1 -1
  50. package/dist/json-patch/utils/ops.js +4 -1
  51. package/dist/json-patch/utils/paths.js +2 -2
  52. package/dist/json-patch/utils/toArrayIndex.js +1 -1
  53. package/dist/net/PatchesSync.d.ts +55 -24
  54. package/dist/net/PatchesSync.js +336 -258
  55. package/dist/net/protocol/types.d.ts +1 -1
  56. package/dist/net/websocket/AuthorizationProvider.d.ts +9 -2
  57. package/dist/net/websocket/AuthorizationProvider.js +14 -2
  58. package/dist/net/websocket/PatchesWebSocket.d.ts +2 -2
  59. package/dist/net/websocket/PatchesWebSocket.js +3 -2
  60. package/dist/net/websocket/RPCServer.d.ts +2 -2
  61. package/dist/net/websocket/RPCServer.js +3 -3
  62. package/dist/net/websocket/SignalingService.js +1 -1
  63. package/dist/net/websocket/WebSocketServer.d.ts +1 -1
  64. package/dist/net/websocket/WebSocketServer.js +2 -2
  65. package/dist/net/websocket/WebSocketTransport.js +1 -1
  66. package/dist/net/websocket/onlineState.d.ts +2 -2
  67. package/dist/net/websocket/onlineState.js +9 -3
  68. package/dist/server/PatchesBranchManager.js +9 -16
  69. package/dist/server/PatchesHistoryManager.js +1 -1
  70. package/dist/server/PatchesServer.d.ts +11 -38
  71. package/dist/server/PatchesServer.js +32 -255
  72. package/dist/server/index.d.ts +4 -4
  73. package/dist/server/index.js +3 -3
  74. package/dist/server/types.d.ts +1 -1
  75. package/dist/types.d.ts +8 -6
  76. package/dist/utils/concurrency.d.ts +26 -0
  77. package/dist/utils/concurrency.js +60 -0
  78. package/dist/utils/deferred.d.ts +7 -0
  79. package/dist/utils/deferred.js +23 -0
  80. package/package.json +11 -5
  81. package/dist/utils/breakChange.js +0 -302
  82. package/dist/utils/getJSONByteSize.js +0 -12
  83. /package/dist/{utils → algorithms/client}/getJSONByteSize.d.ts +0 -0
@@ -40,7 +40,14 @@ export interface AuthorizationProvider<T extends AuthContext = AuthContext> {
40
40
  canAccess(ctx: T | undefined, docId: string, kind: Access, method: string, params?: Record<string, any>): boolean | Promise<boolean>;
41
41
  }
42
42
  /**
43
- * A permissive provider that authorises every action. Used as the default so
44
- * existing callers that don't care about auth continue to work unchanged.
43
+ * A permissive provider that authorises every action.
44
+ * WARNING: This should only be used for development/testing purposes.
45
+ * Never use this in production as it allows unrestricted access.
45
46
  */
46
47
  export declare const allowAll: AuthorizationProvider;
48
+ /**
49
+ * A secure default provider that denies all access.
50
+ * This forces developers to explicitly implement proper authorization.
51
+ * Use this as the default to ensure security by default.
52
+ */
53
+ export declare const denyAll: AuthorizationProvider;
@@ -1,7 +1,19 @@
1
1
  /**
2
- * A permissive provider that authorises every action. Used as the default so
3
- * existing callers that don't care about auth continue to work unchanged.
2
+ * A permissive provider that authorises every action.
3
+ * WARNING: This should only be used for development/testing purposes.
4
+ * Never use this in production as it allows unrestricted access.
4
5
  */
5
6
  export const allowAll = {
6
7
  canAccess: () => true,
7
8
  };
9
+ /**
10
+ * A secure default provider that denies all access.
11
+ * This forces developers to explicitly implement proper authorization.
12
+ * Use this as the default to ensure security by default.
13
+ */
14
+ export const denyAll = {
15
+ canAccess: () => {
16
+ console.warn('Authorization check failed: No authorization provider configured. Access denied.');
17
+ return false;
18
+ },
19
+ };
@@ -1,6 +1,6 @@
1
1
  import { type Signal } from '../../event-signal.js';
2
2
  import type { Change, EditableVersionMetadata, ListVersionsOptions, PatchesSnapshot, PatchesState, VersionMetadata } from '../../types.js';
3
- import type { ConnectionState, PatchesAPI, PatchesNotificationParams } from '../protocol/types.js';
3
+ import type { ConnectionState, PatchesAPI } from '../protocol/types.js';
4
4
  import { type WebSocketOptions } from './WebSocketTransport.js';
5
5
  /**
6
6
  * High-level client for the Patches real-time collaboration service.
@@ -14,7 +14,7 @@ export declare class PatchesWebSocket implements PatchesAPI {
14
14
  /** Signal emitted when the underlying WebSocket connection state changes. */
15
15
  readonly onStateChange: Signal<(state: ConnectionState) => void>;
16
16
  /** Signal emitted when the server pushes document changes. */
17
- readonly onChangesCommitted: Signal<(params: PatchesNotificationParams) => void>;
17
+ readonly onChangesCommitted: Signal<(docId: string, changes: Change[]) => void>;
18
18
  /**
19
19
  * Creates a new Patches WebSocket client instance.
20
20
  * @param url - The WebSocket server URL to connect to
@@ -21,8 +21,9 @@ export class PatchesWebSocket {
21
21
  this.onStateChange = this.transport.onStateChange;
22
22
  // Register handlers for server-sent notifications
23
23
  // Note: Type assertions might be needed if rpc.on doesn't infer strongly enough
24
- this.rpc.on('changesCommitted', (params /*: PatchesNotificationParams */) => {
25
- this.onChangesCommitted.emit(params);
24
+ this.rpc.on('changesCommitted', (params) => {
25
+ const { docId, changes } = params;
26
+ this.onChangesCommitted.emit(docId, changes);
26
27
  });
27
28
  }
28
29
  // --- Connection Management ---
@@ -3,7 +3,7 @@ import type { PatchesHistoryManager } from '../../server/PatchesHistoryManager.j
3
3
  import type { PatchesServer } from '../../server/PatchesServer.js';
4
4
  import type { Change, EditableVersionMetadata, ListChangesOptions, ListVersionsOptions } from '../../types.js';
5
5
  import { JSONRPCServer } from '../protocol/JSONRPCServer.js';
6
- import type { AuthContext, AuthorizationProvider } from './AuthorizationProvider.js';
6
+ import { type AuthContext, type AuthorizationProvider } from './AuthorizationProvider.js';
7
7
  /**
8
8
  * High-level client for the Patches real-time collaboration service.
9
9
  * This class provides document subscription, patch notification handling,
@@ -26,7 +26,7 @@ export declare class RPCServer {
26
26
  * @param patches - The patches server instance to handle document operations
27
27
  * @param history - (Optional) History manager instance to handle versioning operations
28
28
  * @param branches - (Optional) Branch manager instance to handle branching operations
29
- * @param auth - (Optional) Authorization provider implementation. Defaults to a permissive provider.
29
+ * @param auth - (Optional) Authorization provider implementation. Defaults to deny-all for security.
30
30
  */
31
31
  constructor({ patches, history, branches, auth }: RPCServerOptions);
32
32
  /**
@@ -1,15 +1,15 @@
1
1
  import { StatusError } from '../error.js';
2
2
  import { JSONRPCServer } from '../protocol/JSONRPCServer.js';
3
- import { allowAll } from './AuthorizationProvider.js';
3
+ import { denyAll } from './AuthorizationProvider.js';
4
4
  export class RPCServer {
5
5
  /**
6
6
  * Creates a new Patches WebSocket client instance.
7
7
  * @param patches - The patches server instance to handle document operations
8
8
  * @param history - (Optional) History manager instance to handle versioning operations
9
9
  * @param branches - (Optional) Branch manager instance to handle branching operations
10
- * @param auth - (Optional) Authorization provider implementation. Defaults to a permissive provider.
10
+ * @param auth - (Optional) Authorization provider implementation. Defaults to deny-all for security.
11
11
  */
12
- constructor({ patches, history, branches, auth = allowAll }) {
12
+ constructor({ patches, history, branches, auth = denyAll }) {
13
13
  this.rpc = new JSONRPCServer();
14
14
  this.patches = patches;
15
15
  this.history = history;
@@ -56,7 +56,7 @@ export class SignalingService {
56
56
  try {
57
57
  parsed = typeof message === 'string' ? JSON.parse(message) : message;
58
58
  }
59
- catch (err) {
59
+ catch {
60
60
  return false;
61
61
  }
62
62
  if (parsed.jsonrpc !== '2.0' || parsed.method !== 'peer-signal' || !parsed.params?.to)
@@ -1,5 +1,5 @@
1
1
  import type { ServerTransport } from '../protocol/types.js';
2
- import type { AuthContext, AuthorizationProvider } from './AuthorizationProvider.js';
2
+ import { type AuthContext, type AuthorizationProvider } from './AuthorizationProvider.js';
3
3
  import type { RPCServer } from './RPCServer.js';
4
4
  /**
5
5
  * High-level client for the Patches real-time collaboration service.
@@ -1,4 +1,4 @@
1
- import { allowAll } from './AuthorizationProvider.js';
1
+ import { denyAll } from './AuthorizationProvider.js';
2
2
  /**
3
3
  * High-level client for the Patches real-time collaboration service.
4
4
  * This class provides document subscription, patch notification handling,
@@ -13,7 +13,7 @@ export class WebSocketServer {
13
13
  constructor(transport, rpcServer) {
14
14
  this.transport = transport;
15
15
  const { rpc, auth } = rpcServer;
16
- this.auth = auth || allowAll;
16
+ this.auth = auth || denyAll;
17
17
  // Subscription operations
18
18
  rpc.registerMethod('subscribe', this.subscribe.bind(this));
19
19
  rpc.registerMethod('unsubscribe', this.unsubscribe.bind(this));
@@ -1,5 +1,5 @@
1
1
  import { signal } from '../../event-signal.js';
2
- import { deferred } from '../../utils.js';
2
+ import { deferred } from '../../utils/deferred.js';
3
3
  import { onlineState } from './onlineState.js';
4
4
  /**
5
5
  * WebSocket-based transport implementation that provides communication over the WebSocket protocol.
@@ -1,6 +1,6 @@
1
1
  declare class OnlineState {
2
- onOnlineChange: import("../../event-signal").Signal<(isOnline: boolean) => void>;
3
- _isOnline: boolean;
2
+ onOnlineChange: import("../../event-signal.js").Signal<(isOnline: boolean) => void>;
3
+ protected _isOnline: boolean;
4
4
  constructor();
5
5
  get isOnline(): boolean;
6
6
  get isOffline(): boolean;
@@ -1,11 +1,17 @@
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();
5
5
  this._isOnline = typeof navigator !== 'undefined' && navigator.onLine;
6
6
  if (typeof addEventListener === 'function') {
7
- addEventListener('online', () => (this._isOnline = true) && this.onOnlineChange.emit(true));
8
- addEventListener('offline', () => (this._isOnline = false) || this.onOnlineChange.emit(false));
7
+ addEventListener('online', () => {
8
+ this._isOnline = true;
9
+ this.onOnlineChange.emit(true);
10
+ });
11
+ addEventListener('offline', () => {
12
+ this._isOnline = false;
13
+ this.onOnlineChange.emit(false);
14
+ });
9
15
  }
10
16
  }
11
17
  get isOnline() {
@@ -1,4 +1,6 @@
1
1
  import { createId } from 'crypto-id';
2
+ import { createChange } from '../data/change.js';
3
+ import { createVersionMetadata } from '../data/version.js';
2
4
  /**
3
5
  * Helps manage branches for a document. A branch is a document that is branched from another document. Its first
4
6
  * version will be the point-in-time of the original document at the time of the branch. Branches allow for parallel
@@ -36,8 +38,7 @@ export class PatchesBranchManager {
36
38
  const branchDocId = createId();
37
39
  const now = Date.now();
38
40
  // Create an initial version at the branch point rev (for snapshotting/large docs)
39
- const initialVersionMetadata = {
40
- id: createId(),
41
+ const initialVersionMetadata = createVersionMetadata({
41
42
  origin: 'main', // Branch doc versions are 'main' until merged
42
43
  startDate: now,
43
44
  endDate: now,
@@ -46,7 +47,7 @@ export class PatchesBranchManager {
46
47
  name: metadata?.name,
47
48
  groupId: branchDocId,
48
49
  branchName: metadata?.name,
49
- };
50
+ });
50
51
  await this.store.createVersion(branchDocId, initialVersionMetadata, stateAtRev, []);
51
52
  // 2. Create the branch metadata record
52
53
  const branch = {
@@ -106,30 +107,22 @@ export class PatchesBranchManager {
106
107
  // 4. For each version, create a corresponding version in the main doc with updated fields
107
108
  let lastVersionId;
108
109
  for (const v of branchVersions) {
109
- const newVersionId = createId();
110
- const newVersionMetadata = {
110
+ const newVersionMetadata = createVersionMetadata({
111
111
  ...v,
112
- id: newVersionId,
113
112
  origin: 'branch',
114
113
  baseRev: branchStartRevOnSource,
115
114
  groupId: branchId,
116
115
  branchName: branch.name,
117
116
  parentId: lastVersionId,
118
- };
117
+ });
119
118
  const state = await this.store.loadVersionState(branchId, v.id);
120
119
  const changes = await this.store.loadVersionChanges(branchId, v.id);
121
120
  await this.store.createVersion(sourceDocId, newVersionMetadata, state, changes);
122
- lastVersionId = newVersionId;
121
+ lastVersionId = newVersionMetadata.id;
123
122
  }
124
123
  // 5. Flatten all branch changes into a single change for the main doc
125
- const now = Date.now();
126
- const flattenedChange = {
127
- id: createId(12),
128
- ops: branchChanges.flatMap(c => c.ops),
129
- rev: branchStartRevOnSource + branchChanges.length,
130
- baseRev: branchStartRevOnSource,
131
- created: now,
132
- };
124
+ const rev = branchStartRevOnSource + branchChanges.length;
125
+ const flattenedChange = createChange(branchStartRevOnSource, rev, branchChanges.flatMap(c => c.ops));
133
126
  // 6. Commit the flattened change to the main doc
134
127
  let committedMergeChanges = [];
135
128
  try {
@@ -28,7 +28,7 @@ export class PatchesHistoryManager {
28
28
  */
29
29
  async createVersion(docId, metadata) {
30
30
  assertVersionMetadata(metadata);
31
- return await this.patches.createVersion(docId, metadata);
31
+ return await this.patches.captureCurrentVersion(docId, metadata);
32
32
  }
33
33
  /**
34
34
  * Updates the name of a specific version.
@@ -1,5 +1,5 @@
1
1
  import type { JSONPatch } from '../json-patch/JSONPatch.js';
2
- import type { Change, EditableVersionMetadata, PatchesSnapshot, PatchesState, VersionMetadata } from '../types.js';
2
+ import type { Change, EditableVersionMetadata, PatchesState } from '../types.js';
3
3
  import type { PatchesStoreBackend } from './types.js';
4
4
  /**
5
5
  * Configuration options for the PatchesServer.
@@ -32,6 +32,13 @@ export declare class PatchesServer {
32
32
  * @returns The state of the document at the specified revision.
33
33
  */
34
34
  getDoc(docId: string, atRev?: number): Promise<PatchesState>;
35
+ /**
36
+ * Get the state of a document at a specific revision.
37
+ * @param docId - The ID of the document.
38
+ * @param rev - The revision number.
39
+ * @returns The state of the document at the specified revision.
40
+ */
41
+ getStateAtRevision(docId: string, atRev?: number): Promise<PatchesState>;
35
42
  /**
36
43
  * Get changes that occurred after a specific revision.
37
44
  * @param docId - The ID of the document.
@@ -62,45 +69,11 @@ export declare class PatchesServer {
62
69
  */
63
70
  deleteDoc(docId: string, originClientId?: string): Promise<void>;
64
71
  /**
65
- * Create a new named version snapshot of a document's current state.
72
+ * Captures the current state of a document as a new version.
66
73
  * @param docId The document ID.
67
- * @param name The name of the version.
74
+ * @param metadata Optional metadata for the version.
68
75
  * @returns The ID of the created version.
69
76
  */
70
- createVersion(docId: string, metadata?: EditableVersionMetadata): Promise<string>;
71
- /**
72
- * Gets the state at a specific revision.
73
- * @param docId The document ID.
74
- * @param rev The revision number. If not provided, the latest state and its revision is returned.
75
- * @returns The state at the specified revision *and* its revision number.
76
- */
77
- getStateAtRevision(docId: string, rev?: number): Promise<PatchesState>;
78
- /**
79
- * Retrieves the document state of the version before the given revision and changes after up to that revision or all
80
- * changes since that version.
81
- * @param docId The document ID.
82
- * @param rev The revision number. If not provided, the latest state, its revision, and all changes since are returned.
83
- * @returns The document state at the last version before the revision, its revision number, and all changes up to the specified revision (or all changes if no revision is provided).
84
- */
85
- protected _getSnapshotAtRevision(docId: string, rev?: number): Promise<PatchesSnapshot>;
86
- /**
87
- * Creates a new version snapshot of a document's current state.
88
- * @param docId The document ID.
89
- * @param state The document state at the time of the version.
90
- * @param changes The changes since the last version that created the state (the last change's rev is the state's rev and will be the version's rev).
91
- * @param metadata The metadata of the version.
92
- * @returns The ID of the created version.
93
- */
94
- protected _createVersion(docId: string, state: any, changes: Change[], metadata?: EditableVersionMetadata): Promise<VersionMetadata | undefined>;
95
- /**
96
- * Handles offline/large batch versioning logic for multi-batch uploads.
97
- * Groups changes into sessions, merges with previous batch if needed, and creates/extends versions.
98
- * @param docId Document ID
99
- * @param changes The incoming changes (all with the same batchId)
100
- * @param baseRev The base revision for the batch
101
- * @param batchId The batch identifier
102
- * @returns The collapsed changes for transformation
103
- */
104
- protected _handleOfflineBatches(docId: string, changes: Change[], baseRev: number, batchId?: string): Promise<Change[]>;
77
+ captureCurrentVersion(docId: string, metadata?: EditableVersionMetadata): Promise<string>;
105
78
  }
106
79
  export declare function assertVersionMetadata(metadata?: EditableVersionMetadata): void;