@voidhash/mimic 0.0.1-alpha.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 (57) hide show
  1. package/README.md +17 -0
  2. package/package.json +33 -0
  3. package/src/Document.ts +256 -0
  4. package/src/FractionalIndex.ts +1249 -0
  5. package/src/Operation.ts +59 -0
  6. package/src/OperationDefinition.ts +23 -0
  7. package/src/OperationPath.ts +197 -0
  8. package/src/Presence.ts +142 -0
  9. package/src/Primitive.ts +32 -0
  10. package/src/Proxy.ts +8 -0
  11. package/src/ProxyEnvironment.ts +52 -0
  12. package/src/Transaction.ts +72 -0
  13. package/src/Transform.ts +13 -0
  14. package/src/client/ClientDocument.ts +1163 -0
  15. package/src/client/Rebase.ts +309 -0
  16. package/src/client/StateMonitor.ts +307 -0
  17. package/src/client/Transport.ts +318 -0
  18. package/src/client/WebSocketTransport.ts +572 -0
  19. package/src/client/errors.ts +145 -0
  20. package/src/client/index.ts +61 -0
  21. package/src/index.ts +12 -0
  22. package/src/primitives/Array.ts +457 -0
  23. package/src/primitives/Boolean.ts +128 -0
  24. package/src/primitives/Lazy.ts +89 -0
  25. package/src/primitives/Literal.ts +128 -0
  26. package/src/primitives/Number.ts +169 -0
  27. package/src/primitives/String.ts +189 -0
  28. package/src/primitives/Struct.ts +348 -0
  29. package/src/primitives/Tree.ts +1120 -0
  30. package/src/primitives/TreeNode.ts +113 -0
  31. package/src/primitives/Union.ts +329 -0
  32. package/src/primitives/shared.ts +122 -0
  33. package/src/server/ServerDocument.ts +267 -0
  34. package/src/server/errors.ts +90 -0
  35. package/src/server/index.ts +40 -0
  36. package/tests/Document.test.ts +556 -0
  37. package/tests/FractionalIndex.test.ts +377 -0
  38. package/tests/OperationPath.test.ts +151 -0
  39. package/tests/Presence.test.ts +321 -0
  40. package/tests/Primitive.test.ts +381 -0
  41. package/tests/client/ClientDocument.test.ts +1398 -0
  42. package/tests/client/WebSocketTransport.test.ts +992 -0
  43. package/tests/primitives/Array.test.ts +418 -0
  44. package/tests/primitives/Boolean.test.ts +126 -0
  45. package/tests/primitives/Lazy.test.ts +143 -0
  46. package/tests/primitives/Literal.test.ts +122 -0
  47. package/tests/primitives/Number.test.ts +133 -0
  48. package/tests/primitives/String.test.ts +128 -0
  49. package/tests/primitives/Struct.test.ts +311 -0
  50. package/tests/primitives/Tree.test.ts +467 -0
  51. package/tests/primitives/TreeNode.test.ts +50 -0
  52. package/tests/primitives/Union.test.ts +210 -0
  53. package/tests/server/ServerDocument.test.ts +528 -0
  54. package/tsconfig.build.json +24 -0
  55. package/tsconfig.json +8 -0
  56. package/tsdown.config.ts +18 -0
  57. package/vitest.mts +11 -0
@@ -0,0 +1,59 @@
1
+
2
+ import * as OperationPath from "./OperationPath"
3
+ import * as OperationDefinition from "./OperationDefinition"
4
+ import { Schema } from "effect";
5
+
6
+
7
+ export type Operation<TKind, TPayload extends Schema.Schema.Any, TDef extends OperationDefinition.OperationDefinition<TKind, TPayload, any>> = {
8
+ readonly kind: TKind
9
+ readonly path: OperationPath.OperationPath
10
+ readonly payload: Schema.Schema.Type<TPayload>,
11
+
12
+ } & TDef
13
+
14
+ export const fromDefinition = <TKind, TPayload extends Schema.Schema.Any, TDef extends OperationDefinition.OperationDefinition<TKind, TPayload, any>>(operationPath: OperationPath.OperationPath, definition: TDef, payload: Schema.Schema.Type<TPayload>): Operation<TKind, TPayload, TDef> => {
15
+ return {
16
+ kind: definition.kind,
17
+ path: operationPath,
18
+ payload: payload,
19
+ } as Operation<TKind, TPayload, TDef>
20
+ }
21
+
22
+ /**
23
+ * Encoded representation of an Operation for network transport.
24
+ */
25
+ export interface EncodedOperation {
26
+ readonly kind: unknown
27
+ readonly path: OperationPath.EncodedOperationPath
28
+ readonly payload: unknown
29
+ }
30
+
31
+ /**
32
+ * Encodes an Operation to a JSON-serializable format for network transport.
33
+ * @param operation - The operation to encode.
34
+ * @returns The encoded representation.
35
+ */
36
+ export const encode = <TKind, TPayload extends Schema.Schema.Any, TDef extends OperationDefinition.OperationDefinition<TKind, TPayload, any>>(
37
+ operation: Operation<TKind, TPayload, TDef>
38
+ ): EncodedOperation => {
39
+ return {
40
+ kind: operation.kind,
41
+ path: OperationPath.encode(operation.path),
42
+ payload: operation.payload,
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Decodes an encoded operation back to an Operation.
48
+ * Note: This returns a partial operation without the definition methods.
49
+ * The caller must have the operation definitions to fully reconstruct if needed.
50
+ * @param encoded - The encoded representation.
51
+ * @returns The decoded Operation (without definition-specific methods).
52
+ */
53
+ export const decode = (encoded: EncodedOperation): Operation<unknown, Schema.Schema.Any, any> => {
54
+ return {
55
+ kind: encoded.kind,
56
+ path: OperationPath.decode(encoded.path),
57
+ payload: encoded.payload,
58
+ } as Operation<unknown, Schema.Schema.Any, any>
59
+ }
@@ -0,0 +1,23 @@
1
+ import { Schema } from "effect";
2
+
3
+ type Mutable<T> = T extends ReadonlyArray<infer U> ? Array<U> : { -readonly [K in keyof T]: T[K] };
4
+
5
+ export interface OperationDefinition<TKind, TPayload extends Schema.Schema.Any, TTarget extends Schema.Schema.Any> {
6
+ readonly kind: TKind
7
+ readonly payload: TPayload
8
+ readonly target: TTarget
9
+ }
10
+
11
+ export const make = <TKind, TPayload extends Schema.Schema.Any, TTarget extends Schema.Schema.Any>(options: {
12
+ readonly kind: TKind
13
+ readonly payload: TPayload
14
+ readonly target: TTarget
15
+ readonly apply: (payload: Schema.Schema.Type<TPayload>, target: Mutable<Schema.Schema.Type<TTarget>>) => void
16
+ }) => {
17
+ return {
18
+ kind: options.kind,
19
+ payload: options.payload,
20
+ target: options.target,
21
+ apply: options.apply
22
+ } as const;
23
+ }
@@ -0,0 +1,197 @@
1
+ // export type OperationPath = string
2
+ export type OperationPathToken = string
3
+
4
+ export interface OperationPath {
5
+ readonly _tag: "OperationPath"
6
+ readonly toTokens: () => ReadonlyArray<OperationPathToken>
7
+ readonly concat: (other: OperationPath) => OperationPath
8
+ readonly append: (token: OperationPathToken) => OperationPath
9
+ readonly pop: () => OperationPath
10
+ readonly shift: () => OperationPath
11
+ }
12
+
13
+ const parseStringPath = (stringPath: string): ReadonlyArray<OperationPathToken> => {
14
+ return stringPath.split("/")
15
+ }
16
+
17
+ const makeStringPathFromTokens = (tokens: ReadonlyArray<OperationPathToken>): string => {
18
+ return tokens.join("/")
19
+ }
20
+
21
+ /**
22
+ * Creates a new operation path.
23
+ * @param stringPath - The string path to create the path from.
24
+ * @returns The new operation path.
25
+ */
26
+ export function make(stringPath?: string): OperationPath {
27
+
28
+ const tokensInternal: ReadonlyArray<OperationPathToken> = stringPath ? parseStringPath(stringPath) : []
29
+
30
+ /**
31
+ * Returns the tokens of the path.
32
+ * @returns The tokens of the path.
33
+ */
34
+ const toTokens = () => {
35
+ return tokensInternal
36
+ }
37
+
38
+ /**
39
+ * Concatenates two paths.
40
+ * @param other - The other path to concatenate.
41
+ * @returns The new path.
42
+ */
43
+ const concat = (other: OperationPath): OperationPath => {
44
+ return make(makeStringPathFromTokens(toTokens().concat(other.toTokens())))
45
+ }
46
+
47
+ /**
48
+ * Appends a token to the path.
49
+ * @param token - The token to append.
50
+ * @returns The new path.
51
+ */
52
+ const append = (token: OperationPathToken): OperationPath => {
53
+ return make(makeStringPathFromTokens(toTokens().concat([token])))
54
+ }
55
+
56
+ /**
57
+ * Removes the last token from the path.
58
+ * @returns The new path.
59
+ */
60
+ const pop = (): OperationPath => {
61
+ return make(makeStringPathFromTokens(toTokens().slice(0, -1)))
62
+ }
63
+
64
+ /**
65
+ * Removes the first token from the path.
66
+ * @returns The new path.
67
+ */
68
+ const shift = (): OperationPath => {
69
+ return make(makeStringPathFromTokens(toTokens().slice(1)))
70
+ }
71
+
72
+ return {
73
+ _tag: "OperationPath",
74
+ toTokens,
75
+ concat,
76
+ append,
77
+ pop,
78
+ shift
79
+ } as const
80
+ }
81
+
82
+ /**
83
+ * Creates a new operation path from tokens.
84
+ * @param tokens - The tokens to create the path from.
85
+ * @returns The new operation path.
86
+ */
87
+ export function fromTokens(tokens: ReadonlyArray<OperationPathToken>): OperationPath {
88
+ return make(makeStringPathFromTokens(tokens))
89
+ }
90
+
91
+ // =============================================================================
92
+ // Path Utility Functions
93
+ // =============================================================================
94
+
95
+ /**
96
+ * Checks if two operation paths overlap (one is prefix of the other or equal).
97
+ */
98
+ export const pathsOverlap = (
99
+ pathA: OperationPath,
100
+ pathB: OperationPath
101
+ ): boolean => {
102
+ const tokensA = pathA.toTokens().filter((t) => t !== "");
103
+ const tokensB = pathB.toTokens().filter((t) => t !== "");
104
+
105
+ const minLength = Math.min(tokensA.length, tokensB.length);
106
+
107
+ for (let i = 0; i < minLength; i++) {
108
+ if (tokensA[i] !== tokensB[i]) {
109
+ return false;
110
+ }
111
+ }
112
+
113
+ return true;
114
+ };
115
+
116
+ /**
117
+ * Checks if pathA is a prefix of pathB (pathA is ancestor of pathB).
118
+ */
119
+ export const isPrefix = (
120
+ pathA: OperationPath,
121
+ pathB: OperationPath
122
+ ): boolean => {
123
+ const tokensA = pathA.toTokens().filter((t) => t !== "");
124
+ const tokensB = pathB.toTokens().filter((t) => t !== "");
125
+
126
+ if (tokensA.length > tokensB.length) {
127
+ return false;
128
+ }
129
+
130
+ for (let i = 0; i < tokensA.length; i++) {
131
+ if (tokensA[i] !== tokensB[i]) {
132
+ return false;
133
+ }
134
+ }
135
+
136
+ return true;
137
+ };
138
+
139
+ /**
140
+ * Checks if two paths are exactly equal.
141
+ */
142
+ export const pathsEqual = (
143
+ pathA: OperationPath,
144
+ pathB: OperationPath
145
+ ): boolean => {
146
+ const tokensA = pathA.toTokens().filter((t) => t !== "");
147
+ const tokensB = pathB.toTokens().filter((t) => t !== "");
148
+
149
+ if (tokensA.length !== tokensB.length) {
150
+ return false;
151
+ }
152
+
153
+ for (let i = 0; i < tokensA.length; i++) {
154
+ if (tokensA[i] !== tokensB[i]) {
155
+ return false;
156
+ }
157
+ }
158
+
159
+ return true;
160
+ };
161
+
162
+ /**
163
+ * Gets the relative path of pathB with respect to pathA.
164
+ * Assumes pathA is a prefix of pathB.
165
+ */
166
+ export const getRelativePath = (
167
+ basePath: OperationPath,
168
+ fullPath: OperationPath
169
+ ): string[] => {
170
+ const baseTokens = basePath.toTokens().filter((t) => t !== "");
171
+ const fullTokens = fullPath.toTokens().filter((t) => t !== "");
172
+
173
+ return fullTokens.slice(baseTokens.length);
174
+ };
175
+
176
+ /**
177
+ * Encoded representation of an OperationPath for network transport.
178
+ */
179
+ export type EncodedOperationPath = string;
180
+
181
+ /**
182
+ * Encodes an OperationPath to a string for network transport.
183
+ * @param path - The operation path to encode.
184
+ * @returns The encoded string representation.
185
+ */
186
+ export const encode = (path: OperationPath): EncodedOperationPath => {
187
+ return makeStringPathFromTokens(path.toTokens());
188
+ };
189
+
190
+ /**
191
+ * Decodes an encoded string back to an OperationPath.
192
+ * @param encoded - The encoded string representation.
193
+ * @returns The decoded OperationPath.
194
+ */
195
+ export const decode = (encoded: EncodedOperationPath): OperationPath => {
196
+ return make(encoded);
197
+ };
@@ -0,0 +1,142 @@
1
+ /**
2
+ * @since 0.0.1
3
+ * Presence module for ephemeral per-connection state.
4
+ * Used by both client and server for schema validation.
5
+ */
6
+ import * as Schema from "effect/Schema";
7
+
8
+ // =============================================================================
9
+ // Presence Types
10
+ // =============================================================================
11
+
12
+ /**
13
+ * A Presence schema wrapper that holds an Effect Schema for validation.
14
+ * This is used by both client and server to validate presence data.
15
+ */
16
+ export interface Presence<TData> {
17
+ readonly _tag: "Presence";
18
+ /** The Effect Schema used for validation */
19
+ readonly schema: Schema.Schema<TData>;
20
+ /** Branded type marker for inference */
21
+ readonly _Data: TData;
22
+ }
23
+
24
+ /**
25
+ * Options for creating a Presence instance.
26
+ */
27
+ export interface PresenceOptions<TData> {
28
+ /** The Effect Schema defining the presence data structure */
29
+ readonly schema: Schema.Schema<TData>;
30
+ }
31
+
32
+ /**
33
+ * Infer the data type from a Presence instance.
34
+ */
35
+ export type Infer<P extends Presence<any>> = P["_Data"];
36
+
37
+ /**
38
+ * Any Presence type (for generic constraints).
39
+ */
40
+ export type AnyPresence = Presence<any>;
41
+
42
+ // =============================================================================
43
+ // Presence Entry (for storage/transport)
44
+ // =============================================================================
45
+
46
+ /**
47
+ * A presence entry as stored/transmitted.
48
+ */
49
+ export interface PresenceEntry<TData = unknown> {
50
+ /** The presence data */
51
+ readonly data: TData;
52
+ /** Optional user ID from authentication */
53
+ readonly userId?: string;
54
+ }
55
+
56
+ // =============================================================================
57
+ // Factory Function
58
+ // =============================================================================
59
+
60
+ /**
61
+ * Creates a new Presence schema wrapper.
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * import { Presence } from "@voidhash/mimic";
66
+ * import { Schema } from "effect";
67
+ *
68
+ * const CursorPresence = Presence.make({
69
+ * schema: Schema.Struct({
70
+ * name: Schema.String,
71
+ * cursor: Schema.Struct({
72
+ * x: Schema.Number,
73
+ * y: Schema.Number,
74
+ * }),
75
+ * }),
76
+ * });
77
+ * ```
78
+ */
79
+ export const make = <TData,>(options: PresenceOptions<TData>): Presence<TData> => ({
80
+ _tag: "Presence",
81
+ schema: options.schema,
82
+ _Data: undefined as unknown as TData,
83
+ });
84
+
85
+ // =============================================================================
86
+ // Validation Functions
87
+ // =============================================================================
88
+
89
+ /**
90
+ * Validates unknown data against a Presence schema.
91
+ * Throws a ParseError if validation fails.
92
+ *
93
+ * @param presence - The Presence instance with the schema
94
+ * @param data - Unknown data to validate
95
+ * @returns The validated and typed data
96
+ * @throws ParseError if validation fails
97
+ */
98
+ export const validate = <TData,>(
99
+ presence: Presence<TData>,
100
+ data: unknown
101
+ ): TData => {
102
+ return Schema.decodeUnknownSync(presence.schema)(data);
103
+ };
104
+
105
+ /**
106
+ * Safely validates unknown data against a Presence schema.
107
+ * Returns undefined if validation fails instead of throwing.
108
+ *
109
+ * @param presence - The Presence instance with the schema
110
+ * @param data - Unknown data to validate
111
+ * @returns The validated data or undefined if invalid
112
+ */
113
+ export const validateSafe = <TData,>(
114
+ presence: Presence<TData>,
115
+ data: unknown
116
+ ): TData | undefined => {
117
+ try {
118
+ return Schema.decodeUnknownSync(presence.schema)(data);
119
+ } catch {
120
+ return undefined;
121
+ }
122
+ };
123
+
124
+ /**
125
+ * Checks if unknown data is valid according to a Presence schema.
126
+ *
127
+ * @param presence - The Presence instance with the schema
128
+ * @param data - Unknown data to check
129
+ * @returns true if valid, false otherwise
130
+ */
131
+ export const isValid = <TData,>(
132
+ presence: Presence<TData>,
133
+ data: unknown
134
+ ): data is TData => {
135
+ try {
136
+ Schema.decodeUnknownSync(presence.schema)(data);
137
+ return true;
138
+ } catch {
139
+ return false;
140
+ }
141
+ };
142
+
@@ -0,0 +1,32 @@
1
+
2
+ // =============================================================================
3
+ // Re-export all primitives from separate files
4
+ // =============================================================================
5
+
6
+ export * from "./primitives/shared";
7
+
8
+ // String Primitive
9
+ export * from "./primitives/String";
10
+ // Struct Primitive
11
+ export * from "./primitives/Struct";
12
+
13
+ // Boolean Primitive
14
+ export * from "./primitives/Boolean";
15
+
16
+ // Number Primitive
17
+ export * from "./primitives/Number";
18
+ // Literal Primitive
19
+ export * from "./primitives/Literal";
20
+
21
+ // Array Primitive
22
+ export * from "./primitives/Array";
23
+ // Lazy Primitive
24
+ export * from "./primitives/Lazy";
25
+
26
+ // Union Primitive
27
+ export * from "./primitives/Union";
28
+
29
+ // TreeNode Primitive
30
+ export * from "./primitives/TreeNode";
31
+ // Tree Primitive
32
+ export * from "./primitives/Tree";
package/src/Proxy.ts ADDED
@@ -0,0 +1,8 @@
1
+ import * as ProxyEnvironment from "./ProxyEnvironment";
2
+ import * as OperationPath from "./OperationPath";
3
+
4
+ export type Proxy<T> = T
5
+
6
+ export const factory = <T,>(fn: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath) => Proxy<T>) => {
7
+ return (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath) => fn(env, operationPath)
8
+ }
@@ -0,0 +1,52 @@
1
+ import * as Operation from "./Operation";
2
+ import * as OperationPath from "./OperationPath";
3
+
4
+ export type ProxyEnvironment = {
5
+ /** Adds an operation to be collected/applied */
6
+ readonly addOperation: (operation: Operation.Operation<any, any, any>) => void;
7
+ /** Gets the current state at the given path */
8
+ readonly getState: (path: OperationPath.OperationPath) => unknown;
9
+ /** Generates a unique ID (UUID) for array elements */
10
+ readonly generateId: () => string;
11
+ };
12
+
13
+ export interface ProxyEnvironmentOptions {
14
+ /** Callback when an operation is added */
15
+ readonly onOperation: (operation: Operation.Operation<any, any, any>) => void;
16
+ /** Function to retrieve current state at a path (defaults to returning undefined) */
17
+ readonly getState?: (path: OperationPath.OperationPath) => unknown;
18
+ /** Optional: Custom ID generator (defaults to crypto.randomUUID) */
19
+ readonly generateId?: () => string;
20
+ }
21
+
22
+ /** Default UUID generator using crypto.randomUUID */
23
+ const defaultGenerateId = (): string => {
24
+ return crypto.randomUUID();
25
+ };
26
+
27
+ /** Default state getter that always returns undefined */
28
+ const defaultGetState = (_path: OperationPath.OperationPath): unknown => {
29
+ return undefined;
30
+ };
31
+
32
+ /**
33
+ * Creates a ProxyEnvironment.
34
+ * @param optionsOrCallback - Either an options object or a simple callback for operations
35
+ */
36
+ export const make = (
37
+ optionsOrCallback: ProxyEnvironmentOptions | ((operation: Operation.Operation<any, any, any>) => void)
38
+ ): ProxyEnvironment => {
39
+ // Support both old callback style and new options object
40
+ const options: ProxyEnvironmentOptions =
41
+ typeof optionsOrCallback === "function"
42
+ ? { onOperation: optionsOrCallback }
43
+ : optionsOrCallback;
44
+
45
+ return {
46
+ addOperation: (operation: Operation.Operation<any, any, any>) => {
47
+ options.onOperation(operation);
48
+ },
49
+ getState: options.getState ?? defaultGetState,
50
+ generateId: options.generateId ?? defaultGenerateId,
51
+ };
52
+ };
@@ -0,0 +1,72 @@
1
+ import * as Operation from "./Operation";
2
+
3
+ /**
4
+ * A Transaction represents a group of operations that were applied atomically.
5
+ */
6
+ export interface Transaction {
7
+ /** Unique identifier for this transaction */
8
+ readonly id: string;
9
+ /** Operations contained in this transaction */
10
+ readonly ops: ReadonlyArray<Operation.Operation<any, any, any>>;
11
+ /** Timestamp when the transaction was created */
12
+ readonly timestamp: number;
13
+ }
14
+
15
+ /**
16
+ * Creates a new Transaction with the given operations.
17
+ */
18
+ export const make = (ops: ReadonlyArray<Operation.Operation<any, any, any>>): Transaction => ({
19
+ id: crypto.randomUUID(),
20
+ ops,
21
+ timestamp: Date.now(),
22
+ });
23
+
24
+ /**
25
+ * Creates an empty Transaction.
26
+ */
27
+ export const empty = (): Transaction => make([]);
28
+
29
+ /**
30
+ * Checks if a transaction is empty (has no operations).
31
+ */
32
+ export const isEmpty = (tx: Transaction): boolean => tx.ops.length === 0;
33
+
34
+ /**
35
+ * Merges multiple transactions into one.
36
+ */
37
+ export const merge = (txs: ReadonlyArray<Transaction>): Transaction => {
38
+ const allOps = txs.flatMap(tx => tx.ops);
39
+ return make(allOps);
40
+ };
41
+
42
+
43
+ /**
44
+ * Encoded representation of a Transaction for network transport.
45
+ */
46
+ export interface EncodedTransaction {
47
+ readonly id: string;
48
+ readonly ops: ReadonlyArray<Operation.EncodedOperation>;
49
+ readonly timestamp: number;
50
+ }
51
+
52
+ /**
53
+ * Encodes a Transaction to a JSON-serializable format for network transport.
54
+ * @param transaction - The transaction to encode.
55
+ * @returns The encoded representation.
56
+ */
57
+ export const encode = (transaction: Transaction): EncodedTransaction => ({
58
+ id: transaction.id,
59
+ ops: transaction.ops.map(Operation.encode),
60
+ timestamp: transaction.timestamp,
61
+ });
62
+
63
+ /**
64
+ * Decodes an encoded transaction back to a Transaction.
65
+ * @param encoded - The encoded representation.
66
+ * @returns The decoded Transaction.
67
+ */
68
+ export const decode = (encoded: EncodedTransaction): Transaction => ({
69
+ id: encoded.id,
70
+ ops: encoded.ops.map(Operation.decode),
71
+ timestamp: encoded.timestamp,
72
+ });
@@ -0,0 +1,13 @@
1
+ import type * as Operation from "./Operation";
2
+
3
+ // =============================================================================
4
+ // Transform Result Types
5
+ // =============================================================================
6
+
7
+ /**
8
+ * Result of transforming an operation against another operation.
9
+ */
10
+ export type TransformResult =
11
+ | { type: "transformed"; operation: Operation.Operation<any, any, any> }
12
+ | { type: "noop" } // Operation becomes a no-op (already superseded)
13
+ | { type: "conflict"; reason: string };