@loro-extended/change 0.2.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.
- package/LICENSE +21 -0
- package/README.md +565 -0
- package/dist/index.d.ts +339 -0
- package/dist/index.js +1491 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/src/change.test.ts +2006 -0
- package/src/change.ts +105 -0
- package/src/conversion.test.ts +728 -0
- package/src/conversion.ts +220 -0
- package/src/draft-nodes/base.ts +34 -0
- package/src/draft-nodes/counter.ts +21 -0
- package/src/draft-nodes/doc.ts +81 -0
- package/src/draft-nodes/list-base.ts +326 -0
- package/src/draft-nodes/list.ts +18 -0
- package/src/draft-nodes/map.ts +156 -0
- package/src/draft-nodes/movable-list.ts +26 -0
- package/src/draft-nodes/record.ts +215 -0
- package/src/draft-nodes/text.ts +48 -0
- package/src/draft-nodes/tree.ts +31 -0
- package/src/draft-nodes/utils.ts +55 -0
- package/src/index.ts +33 -0
- package/src/json-patch.test.ts +697 -0
- package/src/json-patch.ts +391 -0
- package/src/overlay.ts +90 -0
- package/src/record.test.ts +188 -0
- package/src/schema.fixtures.ts +138 -0
- package/src/shape.ts +348 -0
- package/src/types.ts +15 -0
- package/src/utils/type-guards.ts +210 -0
- package/src/validation.ts +261 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { LoroList, LoroMovableList, Container, LoroMap, Value, LoroText, LoroCounter, LoroTree, LoroDoc } from 'loro-crdt';
|
|
2
|
+
|
|
3
|
+
type InferPlainType<T> = T extends Shape<infer P, any> ? P : never;
|
|
4
|
+
type InferDraftType<T> = T extends Shape<any, infer D> ? D : never;
|
|
5
|
+
type Draft<T extends DocShape<Record<string, ContainerShape>>> = InferDraftType<T>;
|
|
6
|
+
|
|
7
|
+
type DraftNodeParams<Shape extends DocShape | ContainerShape> = {
|
|
8
|
+
shape: Shape;
|
|
9
|
+
emptyState?: InferPlainType<Shape>;
|
|
10
|
+
getContainer: () => ShapeToContainer<Shape>;
|
|
11
|
+
};
|
|
12
|
+
declare abstract class DraftNode<Shape extends DocShape | ContainerShape> {
|
|
13
|
+
protected _params: DraftNodeParams<Shape>;
|
|
14
|
+
protected _cachedContainer?: ShapeToContainer<Shape>;
|
|
15
|
+
constructor(_params: DraftNodeParams<Shape>);
|
|
16
|
+
abstract absorbPlainValues(): void;
|
|
17
|
+
protected get shape(): Shape;
|
|
18
|
+
protected get emptyState(): InferPlainType<Shape> | undefined;
|
|
19
|
+
protected get container(): ShapeToContainer<Shape>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare class CounterDraftNode extends DraftNode<CounterContainerShape> {
|
|
23
|
+
absorbPlainValues(): void;
|
|
24
|
+
increment(value: number): void;
|
|
25
|
+
decrement(value: number): void;
|
|
26
|
+
get value(): number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare abstract class ListDraftNodeBase<NestedShape extends ContainerOrValueShape, Item = NestedShape["_plain"], DraftItem = NestedShape["_draft"]> extends DraftNode<any> {
|
|
30
|
+
private itemCache;
|
|
31
|
+
protected get container(): LoroList | LoroMovableList;
|
|
32
|
+
protected get shape(): ListContainerShape<NestedShape> | MovableListContainerShape<NestedShape>;
|
|
33
|
+
absorbPlainValues(): void;
|
|
34
|
+
protected abstract absorbValueAtIndex(index: number, value: any): void;
|
|
35
|
+
protected insertWithConversion(index: number, item: Item): void;
|
|
36
|
+
protected pushWithConversion(item: Item): void;
|
|
37
|
+
getDraftNodeParams(index: number, shape: ContainerShape): DraftNodeParams<ContainerShape>;
|
|
38
|
+
protected getPredicateItem(index: number): Item;
|
|
39
|
+
protected getDraftItem(index: number): DraftItem;
|
|
40
|
+
find(predicate: (item: Item, index: number) => boolean): DraftItem | undefined;
|
|
41
|
+
findIndex(predicate: (item: Item, index: number) => boolean): number;
|
|
42
|
+
map<ReturnType>(callback: (item: Item, index: number) => ReturnType): ReturnType[];
|
|
43
|
+
filter(predicate: (item: Item, index: number) => boolean): DraftItem[];
|
|
44
|
+
forEach(callback: (item: Item, index: number) => void): void;
|
|
45
|
+
some(predicate: (item: Item, index: number) => boolean): boolean;
|
|
46
|
+
every(predicate: (item: Item, index: number) => boolean): boolean;
|
|
47
|
+
insert(index: number, item: Item): void;
|
|
48
|
+
delete(index: number, len: number): void;
|
|
49
|
+
push(item: Item): void;
|
|
50
|
+
pushContainer(container: Container): Container;
|
|
51
|
+
insertContainer(index: number, container: Container): Container;
|
|
52
|
+
get(index: number): DraftItem;
|
|
53
|
+
toArray(): Item[];
|
|
54
|
+
get length(): number;
|
|
55
|
+
private updateCacheForDelete;
|
|
56
|
+
private updateCacheForInsert;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
declare class ListDraftNode<NestedShape extends ContainerOrValueShape> extends ListDraftNodeBase<NestedShape> {
|
|
60
|
+
protected get container(): LoroList;
|
|
61
|
+
protected absorbValueAtIndex(index: number, value: any): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
declare class MapDraftNode<NestedShapes extends Record<string, ContainerOrValueShape>> extends DraftNode<any> {
|
|
65
|
+
private propertyCache;
|
|
66
|
+
constructor(params: DraftNodeParams<MapContainerShape<NestedShapes>>);
|
|
67
|
+
protected get shape(): MapContainerShape<NestedShapes>;
|
|
68
|
+
protected get container(): LoroMap;
|
|
69
|
+
absorbPlainValues(): void;
|
|
70
|
+
getDraftNodeParams<S extends ContainerShape>(key: string, shape: S): DraftNodeParams<ContainerShape>;
|
|
71
|
+
getOrCreateNode<Shape extends ContainerShape | ValueShape>(key: string, shape: Shape): Shape extends ContainerShape ? DraftNode<Shape> : Value;
|
|
72
|
+
private createLazyProperties;
|
|
73
|
+
get(key: string): any;
|
|
74
|
+
set(key: string, value: Value): void;
|
|
75
|
+
setContainer<C extends Container>(key: string, container: C): C;
|
|
76
|
+
delete(key: string): void;
|
|
77
|
+
has(key: string): boolean;
|
|
78
|
+
keys(): string[];
|
|
79
|
+
values(): any[];
|
|
80
|
+
get size(): number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
declare class MovableListDraftNode<NestedShape extends ContainerOrValueShape, Item = NestedShape["_plain"]> extends ListDraftNodeBase<NestedShape> {
|
|
84
|
+
protected get container(): LoroMovableList;
|
|
85
|
+
protected absorbValueAtIndex(index: number, value: any): void;
|
|
86
|
+
move(from: number, to: number): void;
|
|
87
|
+
set(index: number, item: Exclude<Item, Container>): void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
declare class RecordDraftNode<NestedShape extends ContainerOrValueShape> extends DraftNode<any> {
|
|
91
|
+
private nodeCache;
|
|
92
|
+
constructor(params: DraftNodeParams<RecordContainerShape<NestedShape>>);
|
|
93
|
+
protected get shape(): RecordContainerShape<NestedShape>;
|
|
94
|
+
protected get container(): LoroMap;
|
|
95
|
+
absorbPlainValues(): void;
|
|
96
|
+
getDraftNodeParams<S extends ContainerShape>(key: string, shape: S): DraftNodeParams<ContainerShape>;
|
|
97
|
+
getOrCreateNode(key: string): InferDraftType<NestedShape>;
|
|
98
|
+
get(key: string): InferDraftType<NestedShape>;
|
|
99
|
+
set(key: string, value: any): void;
|
|
100
|
+
setContainer<C extends Container>(key: string, container: C): C;
|
|
101
|
+
delete(key: string): void;
|
|
102
|
+
has(key: string): boolean;
|
|
103
|
+
keys(): string[];
|
|
104
|
+
values(): any[];
|
|
105
|
+
get size(): number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
declare class TextDraftNode extends DraftNode<TextContainerShape> {
|
|
109
|
+
absorbPlainValues(): void;
|
|
110
|
+
insert(index: number, content: string): void;
|
|
111
|
+
delete(index: number, len: number): void;
|
|
112
|
+
toString(): string;
|
|
113
|
+
update(text: string): void;
|
|
114
|
+
mark(range: {
|
|
115
|
+
start: number;
|
|
116
|
+
end: number;
|
|
117
|
+
}, key: string, value: any): void;
|
|
118
|
+
unmark(range: {
|
|
119
|
+
start: number;
|
|
120
|
+
end: number;
|
|
121
|
+
}, key: string): void;
|
|
122
|
+
toDelta(): any[];
|
|
123
|
+
applyDelta(delta: any[]): void;
|
|
124
|
+
get length(): number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface DocShape<NestedShapes extends Record<string, ContainerShape> = Record<string, ContainerShape>> extends Shape<{
|
|
128
|
+
[K in keyof NestedShapes]: NestedShapes[K]["_plain"];
|
|
129
|
+
}, {
|
|
130
|
+
[K in keyof NestedShapes]: NestedShapes[K]["_draft"];
|
|
131
|
+
}> {
|
|
132
|
+
readonly _type: "doc";
|
|
133
|
+
readonly shapes: NestedShapes;
|
|
134
|
+
}
|
|
135
|
+
interface TextContainerShape extends Shape<string, TextDraftNode> {
|
|
136
|
+
readonly _type: "text";
|
|
137
|
+
}
|
|
138
|
+
interface CounterContainerShape extends Shape<number, CounterDraftNode> {
|
|
139
|
+
readonly _type: "counter";
|
|
140
|
+
}
|
|
141
|
+
interface TreeContainerShape<NestedShape = ContainerOrValueShape> extends Shape<any, any> {
|
|
142
|
+
readonly _type: "tree";
|
|
143
|
+
readonly shape: NestedShape;
|
|
144
|
+
}
|
|
145
|
+
interface ListContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<NestedShape["_plain"][], ListDraftNode<NestedShape>> {
|
|
146
|
+
readonly _type: "list";
|
|
147
|
+
readonly shape: NestedShape;
|
|
148
|
+
}
|
|
149
|
+
interface MovableListContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<NestedShape["_plain"][], MovableListDraftNode<NestedShape>> {
|
|
150
|
+
readonly _type: "movableList";
|
|
151
|
+
readonly shape: NestedShape;
|
|
152
|
+
}
|
|
153
|
+
interface MapContainerShape<NestedShapes extends Record<string, ContainerOrValueShape> = Record<string, ContainerOrValueShape>> extends Shape<{
|
|
154
|
+
[K in keyof NestedShapes]: NestedShapes[K]["_plain"];
|
|
155
|
+
}, MapDraftNode<NestedShapes> & {
|
|
156
|
+
[K in keyof NestedShapes]: NestedShapes[K]["_draft"];
|
|
157
|
+
}> {
|
|
158
|
+
readonly _type: "map";
|
|
159
|
+
readonly shapes: NestedShapes;
|
|
160
|
+
}
|
|
161
|
+
interface RecordContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<Record<string, NestedShape["_plain"]>, RecordDraftNode<NestedShape>> {
|
|
162
|
+
readonly _type: "record";
|
|
163
|
+
readonly shape: NestedShape;
|
|
164
|
+
}
|
|
165
|
+
type ContainerShape = CounterContainerShape | ListContainerShape | MapContainerShape | MovableListContainerShape | RecordContainerShape | TextContainerShape | TreeContainerShape;
|
|
166
|
+
type ContainerType = ContainerShape["_type"];
|
|
167
|
+
interface StringValueShape extends Shape<string, string> {
|
|
168
|
+
readonly _type: "value";
|
|
169
|
+
readonly valueType: "string";
|
|
170
|
+
}
|
|
171
|
+
interface NumberValueShape extends Shape<number, number> {
|
|
172
|
+
readonly _type: "value";
|
|
173
|
+
readonly valueType: "number";
|
|
174
|
+
}
|
|
175
|
+
interface BooleanValueShape extends Shape<boolean, boolean> {
|
|
176
|
+
readonly _type: "value";
|
|
177
|
+
readonly valueType: "boolean";
|
|
178
|
+
}
|
|
179
|
+
interface NullValueShape extends Shape<null, null> {
|
|
180
|
+
readonly _type: "value";
|
|
181
|
+
readonly valueType: "null";
|
|
182
|
+
}
|
|
183
|
+
interface UndefinedValueShape extends Shape<undefined, undefined> {
|
|
184
|
+
readonly _type: "value";
|
|
185
|
+
readonly valueType: "undefined";
|
|
186
|
+
}
|
|
187
|
+
interface Uint8ArrayValueShape extends Shape<Uint8Array, Uint8Array> {
|
|
188
|
+
readonly _type: "value";
|
|
189
|
+
readonly valueType: "uint8array";
|
|
190
|
+
}
|
|
191
|
+
interface ObjectValueShape<T extends Record<string, ValueShape> = Record<string, ValueShape>> extends Shape<{
|
|
192
|
+
[K in keyof T]: T[K]["_plain"];
|
|
193
|
+
}, {
|
|
194
|
+
[K in keyof T]: T[K]["_draft"];
|
|
195
|
+
}> {
|
|
196
|
+
readonly _type: "value";
|
|
197
|
+
readonly valueType: "object";
|
|
198
|
+
readonly shape: T;
|
|
199
|
+
}
|
|
200
|
+
interface RecordValueShape<T extends ValueShape = ValueShape> extends Shape<Record<string, T["_plain"]>, Record<string, T["_draft"]>> {
|
|
201
|
+
readonly _type: "value";
|
|
202
|
+
readonly valueType: "record";
|
|
203
|
+
readonly shape: T;
|
|
204
|
+
}
|
|
205
|
+
interface ArrayValueShape<T extends ValueShape = ValueShape> extends Shape<T["_plain"][], T["_draft"][]> {
|
|
206
|
+
readonly _type: "value";
|
|
207
|
+
readonly valueType: "array";
|
|
208
|
+
readonly shape: T;
|
|
209
|
+
}
|
|
210
|
+
interface UnionValueShape<T extends ValueShape[] = ValueShape[]> extends Shape<T[number]["_plain"], T[number]["_draft"]> {
|
|
211
|
+
readonly _type: "value";
|
|
212
|
+
readonly valueType: "union";
|
|
213
|
+
readonly shapes: T;
|
|
214
|
+
}
|
|
215
|
+
type ValueShape = StringValueShape | NumberValueShape | BooleanValueShape | NullValueShape | UndefinedValueShape | Uint8ArrayValueShape | ObjectValueShape | RecordValueShape | ArrayValueShape | UnionValueShape;
|
|
216
|
+
type ContainerOrValueShape = ContainerShape | ValueShape;
|
|
217
|
+
interface Shape<Plain, Draft> {
|
|
218
|
+
readonly _type: string;
|
|
219
|
+
readonly _plain: Plain;
|
|
220
|
+
readonly _draft: Draft;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The LoroShape factory object
|
|
224
|
+
*
|
|
225
|
+
* If a container has a `shape` type variable, it refers to the shape it contains--
|
|
226
|
+
* so for example, a `LoroShape.list(LoroShape.text())` would return a value of type
|
|
227
|
+
* `ListContainerShape<TextContainerShape>`.
|
|
228
|
+
*/
|
|
229
|
+
declare const Shape: {
|
|
230
|
+
doc: <T extends Record<string, ContainerShape>>(shape: T) => DocShape<T>;
|
|
231
|
+
counter: () => CounterContainerShape;
|
|
232
|
+
list: <T extends ContainerOrValueShape>(shape: T) => ListContainerShape<T>;
|
|
233
|
+
map: <T extends Record<string, ContainerOrValueShape>>(shape: T) => MapContainerShape<T>;
|
|
234
|
+
record: <T extends ContainerOrValueShape>(shape: T) => RecordContainerShape<T>;
|
|
235
|
+
movableList: <T extends ContainerOrValueShape>(shape: T) => MovableListContainerShape<T>;
|
|
236
|
+
text: () => TextContainerShape;
|
|
237
|
+
tree: <T extends MapContainerShape>(shape: T) => TreeContainerShape;
|
|
238
|
+
plain: {
|
|
239
|
+
string: () => StringValueShape;
|
|
240
|
+
number: () => NumberValueShape;
|
|
241
|
+
boolean: () => BooleanValueShape;
|
|
242
|
+
null: () => NullValueShape;
|
|
243
|
+
undefined: () => UndefinedValueShape;
|
|
244
|
+
uint8Array: () => Uint8ArrayValueShape;
|
|
245
|
+
object: <T extends Record<string, ValueShape>>(shape: T) => ObjectValueShape<T>;
|
|
246
|
+
record: <T extends ValueShape>(shape: T) => RecordValueShape<T>;
|
|
247
|
+
array: <T extends ValueShape>(shape: T) => ArrayValueShape<T>;
|
|
248
|
+
union: <T extends ValueShape[]>(shapes: T) => UnionValueShape<T>;
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
type ShapeToContainer<T extends DocShape | ContainerShape> = T extends TextContainerShape ? LoroText : T extends CounterContainerShape ? LoroCounter : T extends ListContainerShape ? LoroList : T extends MovableListContainerShape ? LoroMovableList : T extends MapContainerShape | RecordContainerShape ? LoroMap : T extends TreeContainerShape ? LoroTree : never;
|
|
252
|
+
|
|
253
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: JSON Patch values can be any type */
|
|
254
|
+
|
|
255
|
+
type JsonPatchAddOperation = {
|
|
256
|
+
op: "add";
|
|
257
|
+
path: string | (string | number)[];
|
|
258
|
+
value: any;
|
|
259
|
+
};
|
|
260
|
+
type JsonPatchRemoveOperation = {
|
|
261
|
+
op: "remove";
|
|
262
|
+
path: string | (string | number)[];
|
|
263
|
+
};
|
|
264
|
+
type JsonPatchReplaceOperation = {
|
|
265
|
+
op: "replace";
|
|
266
|
+
path: string | (string | number)[];
|
|
267
|
+
value: any;
|
|
268
|
+
};
|
|
269
|
+
type JsonPatchMoveOperation = {
|
|
270
|
+
op: "move";
|
|
271
|
+
path: string | (string | number)[];
|
|
272
|
+
from: string | (string | number)[];
|
|
273
|
+
};
|
|
274
|
+
type JsonPatchCopyOperation = {
|
|
275
|
+
op: "copy";
|
|
276
|
+
path: string | (string | number)[];
|
|
277
|
+
from: string | (string | number)[];
|
|
278
|
+
};
|
|
279
|
+
type JsonPatchTestOperation = {
|
|
280
|
+
op: "test";
|
|
281
|
+
path: string | (string | number)[];
|
|
282
|
+
value: any;
|
|
283
|
+
};
|
|
284
|
+
type JsonPatchOperation = JsonPatchAddOperation | JsonPatchRemoveOperation | JsonPatchReplaceOperation | JsonPatchMoveOperation | JsonPatchCopyOperation | JsonPatchTestOperation;
|
|
285
|
+
type JsonPatch = JsonPatchOperation[];
|
|
286
|
+
|
|
287
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: fix later */
|
|
288
|
+
|
|
289
|
+
declare class TypedDoc<Shape extends DocShape> {
|
|
290
|
+
private shape;
|
|
291
|
+
private emptyState;
|
|
292
|
+
private doc;
|
|
293
|
+
constructor(shape: Shape, emptyState: InferPlainType<Shape>, doc?: LoroDoc);
|
|
294
|
+
get value(): InferPlainType<Shape>;
|
|
295
|
+
change(fn: (draft: Draft<Shape>) => void): InferPlainType<Shape>;
|
|
296
|
+
/**
|
|
297
|
+
* Apply JSON Patch operations to the document
|
|
298
|
+
*
|
|
299
|
+
* @param patch - Array of JSON Patch operations (RFC 6902)
|
|
300
|
+
* @param pathPrefix - Optional path prefix for scoped operations
|
|
301
|
+
* @returns Updated document value
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```typescript
|
|
305
|
+
* const result = typedDoc.applyPatch([
|
|
306
|
+
* { op: 'add', path: '/users/0/name', value: 'Alice' },
|
|
307
|
+
* { op: 'replace', path: '/settings/theme', value: 'dark' }
|
|
308
|
+
* ])
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
applyPatch(patch: JsonPatch, pathPrefix?: (string | number)[]): InferPlainType<Shape>;
|
|
312
|
+
get loroDoc(): LoroDoc;
|
|
313
|
+
get docShape(): Shape;
|
|
314
|
+
get rawValue(): any;
|
|
315
|
+
}
|
|
316
|
+
declare function createTypedDoc<Shape extends DocShape>(shape: Shape, emptyState: InferPlainType<Shape>, existingDoc?: LoroDoc): TypedDoc<Shape>;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Overlays CRDT state with empty state defaults
|
|
320
|
+
*/
|
|
321
|
+
declare function overlayEmptyState<Shape extends DocShape>(shape: Shape, crdtValue: {
|
|
322
|
+
[key: string]: Value;
|
|
323
|
+
}, emptyValue: {
|
|
324
|
+
[key: string]: Value;
|
|
325
|
+
}): {
|
|
326
|
+
[key: string]: Value;
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* Merges individual CRDT values with empty state defaults
|
|
330
|
+
*/
|
|
331
|
+
declare function mergeValue<Shape extends ContainerShape | ValueShape>(shape: Shape, crdtValue: Value, emptyValue: Value): Value;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Validates empty state against schema structure without using Zod
|
|
335
|
+
* Combines the functionality of createEmptyStateValidator and createValueValidator
|
|
336
|
+
*/
|
|
337
|
+
declare function validateEmptyState<T extends DocShape>(emptyState: unknown, schema: T): InferPlainType<T>;
|
|
338
|
+
|
|
339
|
+
export { type ArrayValueShape, type ContainerOrValueShape, type ContainerShape, type CounterContainerShape, type DocShape, type Draft, type InferDraftType, type InferPlainType, type ListContainerShape, type MapContainerShape, type MovableListContainerShape, type RecordContainerShape, type RecordValueShape, type ContainerType as RootContainerType, Shape, type TextContainerShape, type TreeContainerShape, TypedDoc, type ValueShape, createTypedDoc, mergeValue, overlayEmptyState, validateEmptyState };
|