@loro-extended/change 0.8.1 → 0.9.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.
- package/README.md +78 -0
- package/dist/index.d.ts +199 -43
- package/dist/index.js +642 -429
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/change.test.ts +277 -1
- package/src/conversion.test.ts +72 -72
- package/src/conversion.ts +5 -5
- package/src/discriminated-union-assignability.test.ts +45 -0
- package/src/discriminated-union-tojson.test.ts +128 -0
- package/src/index.ts +7 -0
- package/src/overlay-recursion.test.ts +325 -0
- package/src/overlay.ts +45 -8
- package/src/placeholder-proxy.test.ts +52 -0
- package/src/placeholder-proxy.ts +37 -0
- package/src/presence-interface.ts +52 -0
- package/src/shape.ts +44 -50
- package/src/typed-doc.ts +4 -4
- package/src/typed-presence.ts +96 -0
- package/src/{draft-nodes → typed-refs}/base.ts +14 -4
- package/src/{draft-nodes → typed-refs}/counter.test.ts +1 -1
- package/src/{draft-nodes → typed-refs}/counter.ts +9 -3
- package/src/{draft-nodes → typed-refs}/doc.ts +32 -25
- package/src/typed-refs/json-compatibility.test.ts +255 -0
- package/src/{draft-nodes → typed-refs}/list-base.ts +115 -42
- package/src/{draft-nodes → typed-refs}/list.test.ts +1 -1
- package/src/{draft-nodes → typed-refs}/list.ts +4 -4
- package/src/{draft-nodes → typed-refs}/map.ts +50 -66
- package/src/{draft-nodes → typed-refs}/movable-list.test.ts +1 -1
- package/src/{draft-nodes → typed-refs}/movable-list.ts +6 -6
- package/src/{draft-nodes → typed-refs}/proxy-handlers.ts +25 -26
- package/src/{draft-nodes → typed-refs}/record.test.ts +78 -9
- package/src/typed-refs/record.ts +193 -0
- package/src/{draft-nodes → typed-refs}/text.ts +13 -3
- package/src/{draft-nodes → typed-refs}/tree.ts +6 -3
- package/src/typed-refs/utils.ts +177 -0
- package/src/types.test.ts +97 -2
- package/src/types.ts +62 -5
- package/src/draft-nodes/counter.md +0 -31
- package/src/draft-nodes/record.ts +0 -177
- package/src/draft-nodes/utils.ts +0 -96
package/README.md
CHANGED
|
@@ -547,6 +547,26 @@ draft.metadata.values();
|
|
|
547
547
|
const value = draft.metadata.get("key");
|
|
548
548
|
```
|
|
549
549
|
|
|
550
|
+
### JSON Serialization and Snapshots
|
|
551
|
+
|
|
552
|
+
You can easily get a plain JavaScript object snapshot of any part of the document using `JSON.stringify()` or `.toJSON()`. This works for the entire document, nested containers, and even during loading states (placeholders).
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
// Get full document snapshot
|
|
556
|
+
const snapshot = doc.toJSON();
|
|
557
|
+
|
|
558
|
+
// Get snapshot of a specific list
|
|
559
|
+
const todos = doc.value.todos.toJSON(); // returns plain array of todos
|
|
560
|
+
|
|
561
|
+
// Works with nested structures
|
|
562
|
+
const metadata = doc.value.metadata.toJSON(); // returns plain object
|
|
563
|
+
|
|
564
|
+
// Serialize as JSON
|
|
565
|
+
const serializedMetadata = JSON.stringify(doc.value.metadata); // returns string
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
**Note:** `JSON.stringify()` is recommended for serialization as it handles all data types correctly. `.toJSON()` is available on all `TypedRef` objects and proxied placeholders for convenience when you need a direct object snapshot.
|
|
569
|
+
|
|
550
570
|
## Type Safety
|
|
551
571
|
|
|
552
572
|
Full TypeScript support with compile-time validation:
|
|
@@ -616,6 +636,64 @@ loroDoc.subscribe((event) => {
|
|
|
616
636
|
});
|
|
617
637
|
```
|
|
618
638
|
|
|
639
|
+
## TypedPresence
|
|
640
|
+
|
|
641
|
+
The `TypedPresence` class provides type-safe access to ephemeral presence data with placeholder defaults:
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
import { TypedPresence, Shape } from "@loro-extended/change";
|
|
645
|
+
|
|
646
|
+
// Define a presence schema with placeholders
|
|
647
|
+
const PresenceSchema = Shape.plain.object({
|
|
648
|
+
cursor: Shape.plain.object({
|
|
649
|
+
x: Shape.plain.number(),
|
|
650
|
+
y: Shape.plain.number(),
|
|
651
|
+
}),
|
|
652
|
+
name: Shape.plain.string().placeholder("Anonymous"),
|
|
653
|
+
status: Shape.plain.string().placeholder("online"),
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// Create typed presence from a PresenceInterface
|
|
657
|
+
// (Usually obtained from handle.presence in @loro-extended/repo)
|
|
658
|
+
const typedPresence = new TypedPresence(PresenceSchema, presenceInterface);
|
|
659
|
+
|
|
660
|
+
// Read your presence (with placeholder defaults merged in)
|
|
661
|
+
console.log(typedPresence.self);
|
|
662
|
+
// { cursor: { x: 0, y: 0 }, name: "Anonymous", status: "online" }
|
|
663
|
+
|
|
664
|
+
// Set presence values
|
|
665
|
+
typedPresence.set({ cursor: { x: 100, y: 200 }, name: "Alice" });
|
|
666
|
+
|
|
667
|
+
// Read all peers' presence
|
|
668
|
+
console.log(typedPresence.all);
|
|
669
|
+
// { "peer-1": { cursor: { x: 100, y: 200 }, name: "Alice", status: "online" } }
|
|
670
|
+
|
|
671
|
+
// Subscribe to presence changes
|
|
672
|
+
typedPresence.subscribe(({ self, all }) => {
|
|
673
|
+
console.log("My presence:", self);
|
|
674
|
+
console.log("All peers:", all);
|
|
675
|
+
});
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### PresenceInterface
|
|
679
|
+
|
|
680
|
+
`TypedPresence` works with any object implementing `PresenceInterface`:
|
|
681
|
+
|
|
682
|
+
```typescript
|
|
683
|
+
import type { PresenceInterface, ObjectValue } from "@loro-extended/change";
|
|
684
|
+
|
|
685
|
+
interface PresenceInterface {
|
|
686
|
+
set: (values: ObjectValue) => void;
|
|
687
|
+
get: (key: string) => Value;
|
|
688
|
+
readonly self: ObjectValue;
|
|
689
|
+
readonly all: Record<string, ObjectValue>;
|
|
690
|
+
setRaw: (key: string, value: Value) => void;
|
|
691
|
+
subscribe: (cb: (values: ObjectValue) => void) => () => void;
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
This is typically provided by `UntypedDocHandle.presence` in `@loro-extended/repo`.
|
|
696
|
+
|
|
619
697
|
## Performance Considerations
|
|
620
698
|
|
|
621
699
|
- All changes within a `change()` block are batched into a single transaction
|
package/dist/index.d.ts
CHANGED
|
@@ -30,7 +30,15 @@ import { LoroList, LoroMovableList, Container, LoroMap, Value, LoroText, LoroCou
|
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
32
|
type Infer<T> = T extends Shape<infer P, any, any> ? P : never;
|
|
33
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Infers the mutable type from any Shape.
|
|
35
|
+
* This is the type used within change() callbacks for mutation.
|
|
36
|
+
*/
|
|
37
|
+
type InferMutableType<T> = T extends Shape<any, infer M, any> ? M : never;
|
|
38
|
+
/**
|
|
39
|
+
* @deprecated Use InferMutableType<T> instead
|
|
40
|
+
*/
|
|
41
|
+
type InferDraftType<T> = InferMutableType<T>;
|
|
34
42
|
/**
|
|
35
43
|
* Extracts the valid placeholder type from a shape.
|
|
36
44
|
*
|
|
@@ -38,36 +46,80 @@ type InferDraftType<T> = T extends Shape<any, infer D, any> ? D : never;
|
|
|
38
46
|
* empty values ([] or {}) to prevent users from expecting per-entry merging.
|
|
39
47
|
*/
|
|
40
48
|
type InferPlaceholderType<T> = T extends Shape<any, any, infer P> ? P : never;
|
|
41
|
-
|
|
42
|
-
type
|
|
49
|
+
/**
|
|
50
|
+
* Mutable type for use within change() callbacks.
|
|
51
|
+
* This is the type-safe wrapper around CRDT containers that allows mutation.
|
|
52
|
+
*/
|
|
53
|
+
type Mutable<T extends DocShape<Record<string, ContainerShape>>> = InferMutableType<T>;
|
|
54
|
+
/**
|
|
55
|
+
* @deprecated Use Mutable<T> instead
|
|
56
|
+
*/
|
|
57
|
+
type Draft<T extends DocShape<Record<string, ContainerShape>>> = Mutable<T>;
|
|
58
|
+
/**
|
|
59
|
+
* Interface for objects that have a toJSON method.
|
|
60
|
+
* This is separate from the data type to avoid polluting Object.values().
|
|
61
|
+
*/
|
|
62
|
+
interface HasToJSON<T> {
|
|
63
|
+
toJSON(): T;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Deep readonly wrapper for plain objects (no index signature).
|
|
67
|
+
* Includes toJSON() method.
|
|
68
|
+
*/
|
|
69
|
+
type DeepReadonlyObject<T extends object> = {
|
|
43
70
|
readonly [P in keyof T]: DeepReadonly<T[P]>;
|
|
44
|
-
}
|
|
71
|
+
} & HasToJSON<T>;
|
|
72
|
+
/**
|
|
73
|
+
* Deep readonly wrapper for Record types (with string index signature).
|
|
74
|
+
* The toJSON() method is available but NOT part of the index signature,
|
|
75
|
+
* so Object.values() returns clean types.
|
|
76
|
+
*/
|
|
77
|
+
type DeepReadonlyRecord<T> = {
|
|
78
|
+
readonly [K in keyof T]: DeepReadonly<T[K]>;
|
|
79
|
+
} & HasToJSON<Record<string, T[keyof T]>>;
|
|
80
|
+
/**
|
|
81
|
+
* Deep readonly wrapper that makes all properties readonly recursively
|
|
82
|
+
* and adds a toJSON() method for JSON serialization.
|
|
83
|
+
*
|
|
84
|
+
* For arrays: Returns ReadonlyArray with toJSON()
|
|
85
|
+
* For objects with string index signature (Records): toJSON() is available
|
|
86
|
+
* but doesn't pollute Object.values() type inference
|
|
87
|
+
* For plain objects: Returns readonly properties with toJSON()
|
|
88
|
+
* For primitives: Returns as-is
|
|
89
|
+
*/
|
|
90
|
+
type DeepReadonly<T> = T extends any[] ? ReadonlyArray<DeepReadonly<T[number]>> & HasToJSON<T> : T extends object ? string extends keyof T ? DeepReadonlyRecord<T> : DeepReadonlyObject<T> : T;
|
|
45
91
|
|
|
46
|
-
type
|
|
92
|
+
type TypedRefParams<Shape extends DocShape | ContainerShape> = {
|
|
47
93
|
shape: Shape;
|
|
48
94
|
placeholder?: Infer<Shape>;
|
|
49
95
|
getContainer: () => ShapeToContainer<Shape>;
|
|
50
96
|
readonly?: boolean;
|
|
51
97
|
};
|
|
52
|
-
declare abstract class
|
|
53
|
-
protected _params:
|
|
98
|
+
declare abstract class TypedRef<Shape extends DocShape | ContainerShape> {
|
|
99
|
+
protected _params: TypedRefParams<Shape>;
|
|
54
100
|
protected _cachedContainer?: ShapeToContainer<Shape>;
|
|
55
|
-
constructor(_params:
|
|
101
|
+
constructor(_params: TypedRefParams<Shape>);
|
|
56
102
|
abstract absorbPlainValues(): void;
|
|
57
103
|
protected get shape(): Shape;
|
|
58
104
|
protected get placeholder(): Infer<Shape> | undefined;
|
|
59
105
|
protected get readonly(): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Throws an error if this ref is in readonly mode.
|
|
108
|
+
* Call this at the start of any mutating method.
|
|
109
|
+
*/
|
|
110
|
+
protected assertMutable(): void;
|
|
60
111
|
protected get container(): ShapeToContainer<Shape>;
|
|
61
112
|
}
|
|
62
113
|
|
|
63
|
-
declare class
|
|
114
|
+
declare class CounterRef extends TypedRef<CounterContainerShape> {
|
|
64
115
|
absorbPlainValues(): void;
|
|
65
116
|
increment(value: number): void;
|
|
66
117
|
decrement(value: number): void;
|
|
67
118
|
get value(): number;
|
|
119
|
+
toJSON(): number;
|
|
68
120
|
}
|
|
69
121
|
|
|
70
|
-
declare abstract class
|
|
122
|
+
declare abstract class ListRefBase<NestedShape extends ContainerOrValueShape, Item = NestedShape["_plain"], MutableItem = NestedShape["_mutable"]> extends TypedRef<any> {
|
|
71
123
|
private itemCache;
|
|
72
124
|
protected get container(): LoroList | LoroMovableList;
|
|
73
125
|
protected get shape(): ListContainerShape<NestedShape> | MovableListContainerShape<NestedShape>;
|
|
@@ -75,43 +127,47 @@ declare abstract class ListDraftNodeBase<NestedShape extends ContainerOrValueSha
|
|
|
75
127
|
protected abstract absorbValueAtIndex(index: number, value: any): void;
|
|
76
128
|
protected insertWithConversion(index: number, item: Item): void;
|
|
77
129
|
protected pushWithConversion(item: Item): void;
|
|
78
|
-
|
|
130
|
+
getTypedRefParams(index: number, shape: ContainerShape): TypedRefParams<ContainerShape>;
|
|
79
131
|
protected getPredicateItem(index: number): Item;
|
|
80
|
-
protected
|
|
81
|
-
find(predicate: (item: Item, index: number) => boolean):
|
|
132
|
+
protected getMutableItem(index: number): any;
|
|
133
|
+
find(predicate: (item: Item, index: number) => boolean): MutableItem | undefined;
|
|
82
134
|
findIndex(predicate: (item: Item, index: number) => boolean): number;
|
|
83
135
|
map<ReturnType>(callback: (item: Item, index: number) => ReturnType): ReturnType[];
|
|
84
|
-
filter(predicate: (item: Item, index: number) => boolean):
|
|
136
|
+
filter(predicate: (item: Item, index: number) => boolean): MutableItem[];
|
|
85
137
|
forEach(callback: (item: Item, index: number) => void): void;
|
|
86
138
|
some(predicate: (item: Item, index: number) => boolean): boolean;
|
|
87
139
|
every(predicate: (item: Item, index: number) => boolean): boolean;
|
|
140
|
+
slice(start?: number, end?: number): MutableItem[];
|
|
88
141
|
insert(index: number, item: Item): void;
|
|
89
142
|
delete(index: number, len: number): void;
|
|
90
143
|
push(item: Item): void;
|
|
91
144
|
pushContainer(container: Container): Container;
|
|
92
145
|
insertContainer(index: number, container: Container): Container;
|
|
93
|
-
get(index: number):
|
|
146
|
+
get(index: number): MutableItem;
|
|
94
147
|
toArray(): Item[];
|
|
148
|
+
toJSON(): Item[];
|
|
149
|
+
[Symbol.iterator](): IterableIterator<MutableItem>;
|
|
95
150
|
get length(): number;
|
|
96
151
|
private updateCacheForDelete;
|
|
97
152
|
private updateCacheForInsert;
|
|
98
153
|
}
|
|
99
154
|
|
|
100
|
-
declare class
|
|
155
|
+
declare class ListRef<NestedShape extends ContainerOrValueShape> extends ListRefBase<NestedShape> {
|
|
101
156
|
[index: number]: Infer<NestedShape>;
|
|
102
157
|
protected get container(): LoroList;
|
|
103
158
|
protected absorbValueAtIndex(index: number, value: any): void;
|
|
104
159
|
}
|
|
105
160
|
|
|
106
|
-
declare class
|
|
161
|
+
declare class MapRef<NestedShapes extends Record<string, ContainerOrValueShape>> extends TypedRef<any> {
|
|
107
162
|
private propertyCache;
|
|
108
|
-
constructor(params:
|
|
163
|
+
constructor(params: TypedRefParams<MapContainerShape<NestedShapes>>);
|
|
109
164
|
protected get shape(): MapContainerShape<NestedShapes>;
|
|
110
165
|
protected get container(): LoroMap;
|
|
111
166
|
absorbPlainValues(): void;
|
|
112
|
-
|
|
113
|
-
|
|
167
|
+
getTypedRefParams<S extends ContainerShape>(key: string, shape: S): TypedRefParams<ContainerShape>;
|
|
168
|
+
getOrCreateRef<Shape extends ContainerShape | ValueShape>(key: string, shape: Shape): any;
|
|
114
169
|
private createLazyProperties;
|
|
170
|
+
toJSON(): any;
|
|
115
171
|
get(key: string): any;
|
|
116
172
|
set(key: string, value: Value): void;
|
|
117
173
|
setContainer<C extends Container>(key: string, container: C): C;
|
|
@@ -122,7 +178,7 @@ declare class MapDraftNode<NestedShapes extends Record<string, ContainerOrValueS
|
|
|
122
178
|
get size(): number;
|
|
123
179
|
}
|
|
124
180
|
|
|
125
|
-
declare class
|
|
181
|
+
declare class MovableListRef<NestedShape extends ContainerOrValueShape, Item = NestedShape["_plain"]> extends ListRefBase<NestedShape> {
|
|
126
182
|
[index: number]: Infer<NestedShape>;
|
|
127
183
|
protected get container(): LoroMovableList;
|
|
128
184
|
protected absorbValueAtIndex(index: number, value: any): void;
|
|
@@ -130,15 +186,15 @@ declare class MovableListDraftNode<NestedShape extends ContainerOrValueShape, It
|
|
|
130
186
|
set(index: number, item: Exclude<Item, Container>): void;
|
|
131
187
|
}
|
|
132
188
|
|
|
133
|
-
declare class
|
|
189
|
+
declare class RecordRef<NestedShape extends ContainerOrValueShape> extends TypedRef<any> {
|
|
134
190
|
[key: string]: Infer<NestedShape> | any;
|
|
135
|
-
private
|
|
191
|
+
private refCache;
|
|
136
192
|
protected get shape(): RecordContainerShape<NestedShape>;
|
|
137
193
|
protected get container(): LoroMap;
|
|
138
194
|
absorbPlainValues(): void;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
get(key: string):
|
|
195
|
+
getTypedRefParams<S extends ContainerShape>(key: string, shape: S): TypedRefParams<ContainerShape>;
|
|
196
|
+
getOrCreateRef(key: string): any;
|
|
197
|
+
get(key: string): InferMutableType<NestedShape>;
|
|
142
198
|
set(key: string, value: any): void;
|
|
143
199
|
setContainer<C extends Container>(key: string, container: C): C;
|
|
144
200
|
delete(key: string): void;
|
|
@@ -146,13 +202,15 @@ declare class RecordDraftNode<NestedShape extends ContainerOrValueShape> extends
|
|
|
146
202
|
keys(): string[];
|
|
147
203
|
values(): any[];
|
|
148
204
|
get size(): number;
|
|
205
|
+
toJSON(): Record<string, any>;
|
|
149
206
|
}
|
|
150
207
|
|
|
151
|
-
declare class
|
|
208
|
+
declare class TextRef extends TypedRef<TextContainerShape> {
|
|
152
209
|
absorbPlainValues(): void;
|
|
153
210
|
insert(index: number, content: string): void;
|
|
154
211
|
delete(index: number, len: number): void;
|
|
155
212
|
toString(): string;
|
|
213
|
+
toJSON(): string;
|
|
156
214
|
update(text: string): void;
|
|
157
215
|
mark(range: {
|
|
158
216
|
start: number;
|
|
@@ -173,42 +231,42 @@ type WithPlaceholder<S extends Shape<any, any, any>> = S & {
|
|
|
173
231
|
interface DocShape<NestedShapes extends Record<string, ContainerShape> = Record<string, ContainerShape>> extends Shape<{
|
|
174
232
|
[K in keyof NestedShapes]: NestedShapes[K]["_plain"];
|
|
175
233
|
}, {
|
|
176
|
-
[K in keyof NestedShapes]: NestedShapes[K]["
|
|
234
|
+
[K in keyof NestedShapes]: NestedShapes[K]["_mutable"];
|
|
177
235
|
}, {
|
|
178
236
|
[K in keyof NestedShapes]: NestedShapes[K]["_placeholder"];
|
|
179
237
|
}> {
|
|
180
238
|
readonly _type: "doc";
|
|
181
239
|
readonly shapes: NestedShapes;
|
|
182
240
|
}
|
|
183
|
-
interface TextContainerShape extends Shape<string,
|
|
241
|
+
interface TextContainerShape extends Shape<string, TextRef, string> {
|
|
184
242
|
readonly _type: "text";
|
|
185
243
|
}
|
|
186
|
-
interface CounterContainerShape extends Shape<number,
|
|
244
|
+
interface CounterContainerShape extends Shape<number, CounterRef, number> {
|
|
187
245
|
readonly _type: "counter";
|
|
188
246
|
}
|
|
189
247
|
interface TreeContainerShape<NestedShape = ContainerOrValueShape> extends Shape<any, any, never[]> {
|
|
190
248
|
readonly _type: "tree";
|
|
191
249
|
readonly shape: NestedShape;
|
|
192
250
|
}
|
|
193
|
-
interface ListContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<NestedShape["_plain"][],
|
|
251
|
+
interface ListContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<NestedShape["_plain"][], ListRef<NestedShape>, never[]> {
|
|
194
252
|
readonly _type: "list";
|
|
195
253
|
readonly shape: NestedShape;
|
|
196
254
|
}
|
|
197
|
-
interface MovableListContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<NestedShape["_plain"][],
|
|
255
|
+
interface MovableListContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<NestedShape["_plain"][], MovableListRef<NestedShape>, never[]> {
|
|
198
256
|
readonly _type: "movableList";
|
|
199
257
|
readonly shape: NestedShape;
|
|
200
258
|
}
|
|
201
259
|
interface MapContainerShape<NestedShapes extends Record<string, ContainerOrValueShape> = Record<string, ContainerOrValueShape>> extends Shape<{
|
|
202
260
|
[K in keyof NestedShapes]: NestedShapes[K]["_plain"];
|
|
203
|
-
},
|
|
204
|
-
[K in keyof NestedShapes]: NestedShapes[K]["
|
|
261
|
+
}, MapRef<NestedShapes> & {
|
|
262
|
+
[K in keyof NestedShapes]: NestedShapes[K]["_mutable"];
|
|
205
263
|
}, {
|
|
206
264
|
[K in keyof NestedShapes]: NestedShapes[K]["_placeholder"];
|
|
207
265
|
}> {
|
|
208
266
|
readonly _type: "map";
|
|
209
267
|
readonly shapes: NestedShapes;
|
|
210
268
|
}
|
|
211
|
-
interface RecordContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<Record<string, NestedShape["_plain"]>,
|
|
269
|
+
interface RecordContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<Record<string, NestedShape["_plain"]>, RecordRef<NestedShape>, Record<string, never>> {
|
|
212
270
|
readonly _type: "record";
|
|
213
271
|
readonly shape: NestedShape;
|
|
214
272
|
}
|
|
@@ -242,7 +300,7 @@ interface Uint8ArrayValueShape extends Shape<Uint8Array, Uint8Array, Uint8Array>
|
|
|
242
300
|
interface ObjectValueShape<T extends Record<string, ValueShape> = Record<string, ValueShape>> extends Shape<{
|
|
243
301
|
[K in keyof T]: T[K]["_plain"];
|
|
244
302
|
}, {
|
|
245
|
-
[K in keyof T]: T[K]["
|
|
303
|
+
[K in keyof T]: T[K]["_mutable"];
|
|
246
304
|
}, {
|
|
247
305
|
[K in keyof T]: T[K]["_placeholder"];
|
|
248
306
|
}> {
|
|
@@ -250,17 +308,17 @@ interface ObjectValueShape<T extends Record<string, ValueShape> = Record<string,
|
|
|
250
308
|
readonly valueType: "object";
|
|
251
309
|
readonly shape: T;
|
|
252
310
|
}
|
|
253
|
-
interface RecordValueShape<T extends ValueShape = ValueShape> extends Shape<Record<string, T["_plain"]>, Record<string, T["
|
|
311
|
+
interface RecordValueShape<T extends ValueShape = ValueShape> extends Shape<Record<string, T["_plain"]>, Record<string, T["_mutable"]>, Record<string, never>> {
|
|
254
312
|
readonly _type: "value";
|
|
255
313
|
readonly valueType: "record";
|
|
256
314
|
readonly shape: T;
|
|
257
315
|
}
|
|
258
|
-
interface ArrayValueShape<T extends ValueShape = ValueShape> extends Shape<T["_plain"][], T["
|
|
316
|
+
interface ArrayValueShape<T extends ValueShape = ValueShape> extends Shape<T["_plain"][], T["_mutable"][], never[]> {
|
|
259
317
|
readonly _type: "value";
|
|
260
318
|
readonly valueType: "array";
|
|
261
319
|
readonly shape: T;
|
|
262
320
|
}
|
|
263
|
-
interface UnionValueShape<T extends ValueShape[] = ValueShape[]> extends Shape<T[number]["_plain"], T[number]["
|
|
321
|
+
interface UnionValueShape<T extends ValueShape[] = ValueShape[]> extends Shape<T[number]["_plain"], T[number]["_mutable"], T[number]["_placeholder"]> {
|
|
264
322
|
readonly _type: "value";
|
|
265
323
|
readonly valueType: "union";
|
|
266
324
|
readonly shapes: T;
|
|
@@ -278,7 +336,7 @@ interface UnionValueShape<T extends ValueShape[] = ValueShape[]> extends Shape<T
|
|
|
278
336
|
* @typeParam K - The discriminant key (e.g., "type")
|
|
279
337
|
* @typeParam T - A record mapping discriminant values to their object shapes
|
|
280
338
|
*/
|
|
281
|
-
interface DiscriminatedUnionValueShape<K extends string = string, T extends Record<string, ObjectValueShape> = Record<string, ObjectValueShape
|
|
339
|
+
interface DiscriminatedUnionValueShape<K extends string = string, T extends Record<string, ObjectValueShape> = Record<string, ObjectValueShape>, Plain = T[keyof T]["_plain"], Mutable = T[keyof T]["_mutable"], Placeholder = T[keyof T]["_placeholder"]> extends Shape<Plain, Mutable, Placeholder> {
|
|
282
340
|
readonly _type: "value";
|
|
283
341
|
readonly valueType: "discriminatedUnion";
|
|
284
342
|
readonly discriminantKey: K;
|
|
@@ -286,10 +344,10 @@ interface DiscriminatedUnionValueShape<K extends string = string, T extends Reco
|
|
|
286
344
|
}
|
|
287
345
|
type ValueShape = StringValueShape | NumberValueShape | BooleanValueShape | NullValueShape | UndefinedValueShape | Uint8ArrayValueShape | ObjectValueShape | RecordValueShape | ArrayValueShape | UnionValueShape | DiscriminatedUnionValueShape;
|
|
288
346
|
type ContainerOrValueShape = ContainerShape | ValueShape;
|
|
289
|
-
interface Shape<Plain,
|
|
347
|
+
interface Shape<Plain, Mutable, Placeholder = Plain> {
|
|
290
348
|
readonly _type: string;
|
|
291
349
|
readonly _plain: Plain;
|
|
292
|
-
readonly
|
|
350
|
+
readonly _mutable: Mutable;
|
|
293
351
|
readonly _placeholder: Placeholder;
|
|
294
352
|
}
|
|
295
353
|
/**
|
|
@@ -380,6 +438,59 @@ declare function overlayPlaceholder<Shape extends DocShape>(shape: Shape, crdtVa
|
|
|
380
438
|
*/
|
|
381
439
|
declare function mergeValue<Shape extends ContainerShape | ValueShape>(shape: Shape, crdtValue: Value, placeholderValue: Value): Value;
|
|
382
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Creates a proxy around a placeholder value (plain object/array) that mimics
|
|
443
|
+
* the behavior of TypedRef, specifically adding a .toJSON() method.
|
|
444
|
+
*
|
|
445
|
+
* This ensures consistent UX where users can call .toJSON() on document state
|
|
446
|
+
* regardless of whether it's loading (placeholder) or loaded (live ref).
|
|
447
|
+
*/
|
|
448
|
+
declare function createPlaceholderProxy<T extends object>(target: T): T;
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* A record of string keys to Loro values, used for presence data.
|
|
452
|
+
*/
|
|
453
|
+
type ObjectValue = Record<string, Value>;
|
|
454
|
+
/**
|
|
455
|
+
* Interface for presence management that can be implemented by different backends.
|
|
456
|
+
* This abstraction allows TypedPresence to work with any presence provider.
|
|
457
|
+
*/
|
|
458
|
+
interface PresenceInterface {
|
|
459
|
+
/**
|
|
460
|
+
* Set multiple presence values at once.
|
|
461
|
+
*/
|
|
462
|
+
set: (values: ObjectValue) => void;
|
|
463
|
+
/**
|
|
464
|
+
* Get a single presence value by key.
|
|
465
|
+
*/
|
|
466
|
+
get: (key: string) => Value;
|
|
467
|
+
/**
|
|
468
|
+
* The current peer's presence state.
|
|
469
|
+
*/
|
|
470
|
+
readonly self: ObjectValue;
|
|
471
|
+
/**
|
|
472
|
+
* Other peers' presence states, keyed by peer ID.
|
|
473
|
+
* Does NOT include self. Use this for iterating over remote peers.
|
|
474
|
+
*/
|
|
475
|
+
readonly peers: Map<string, ObjectValue>;
|
|
476
|
+
/**
|
|
477
|
+
* All peers' presence states, keyed by peer ID (includes self).
|
|
478
|
+
* @deprecated Use `peers` and `self` separately. This property is synthesized
|
|
479
|
+
* from `peers` and `self` for backward compatibility.
|
|
480
|
+
*/
|
|
481
|
+
readonly all: Record<string, ObjectValue>;
|
|
482
|
+
/**
|
|
483
|
+
* Set a single raw value by key (escape hatch for arbitrary keys).
|
|
484
|
+
*/
|
|
485
|
+
setRaw: (key: string, value: Value) => void;
|
|
486
|
+
/**
|
|
487
|
+
* Subscribe to presence changes.
|
|
488
|
+
* @param cb Callback that receives the aggregated presence values
|
|
489
|
+
* @returns Unsubscribe function
|
|
490
|
+
*/
|
|
491
|
+
subscribe: (cb: (values: ObjectValue) => void) => () => void;
|
|
492
|
+
}
|
|
493
|
+
|
|
383
494
|
/** biome-ignore-all lint/suspicious/noExplicitAny: JSON Patch values can be any type */
|
|
384
495
|
|
|
385
496
|
type JsonPatchAddOperation = {
|
|
@@ -462,10 +573,55 @@ declare class TypedDoc<Shape extends DocShape> {
|
|
|
462
573
|
}
|
|
463
574
|
declare function createTypedDoc<Shape extends DocShape>(shape: Shape, existingDoc?: LoroDoc): TypedDoc<Shape>;
|
|
464
575
|
|
|
576
|
+
/**
|
|
577
|
+
* A strongly-typed wrapper around a PresenceInterface.
|
|
578
|
+
* Provides type-safe access to presence data with automatic placeholder merging.
|
|
579
|
+
*
|
|
580
|
+
* @typeParam S - The shape of the presence data
|
|
581
|
+
*/
|
|
582
|
+
declare class TypedPresence<S extends ContainerShape | ValueShape> {
|
|
583
|
+
shape: S;
|
|
584
|
+
private presence;
|
|
585
|
+
private placeholder;
|
|
586
|
+
constructor(shape: S, presence: PresenceInterface);
|
|
587
|
+
/**
|
|
588
|
+
* Get the current peer's presence state with placeholder values merged in.
|
|
589
|
+
*/
|
|
590
|
+
get self(): Infer<S>;
|
|
591
|
+
/**
|
|
592
|
+
* Get other peers' presence states with placeholder values merged in.
|
|
593
|
+
* Does NOT include self. Use this for iterating over remote peers.
|
|
594
|
+
*/
|
|
595
|
+
get peers(): Map<string, Infer<S>>;
|
|
596
|
+
/**
|
|
597
|
+
* Get all peers' presence states with placeholder values merged in.
|
|
598
|
+
* @deprecated Use `peers` and `self` separately. This property is synthesized
|
|
599
|
+
* from `peers` and `self` for backward compatibility.
|
|
600
|
+
*/
|
|
601
|
+
get all(): Record<string, Infer<S>>;
|
|
602
|
+
/**
|
|
603
|
+
* Set presence values for the current peer.
|
|
604
|
+
*/
|
|
605
|
+
set(value: Partial<Infer<S>>): void;
|
|
606
|
+
/**
|
|
607
|
+
* Subscribe to presence changes.
|
|
608
|
+
* The callback is called immediately with the current state, then on each change.
|
|
609
|
+
*
|
|
610
|
+
* @param cb Callback that receives the typed presence state
|
|
611
|
+
* @returns Unsubscribe function
|
|
612
|
+
*/
|
|
613
|
+
subscribe(cb: (state: {
|
|
614
|
+
self: Infer<S>;
|
|
615
|
+
peers: Map<string, Infer<S>>;
|
|
616
|
+
/** @deprecated Use `peers` and `self` separately */
|
|
617
|
+
all: Record<string, Infer<S>>;
|
|
618
|
+
}) => void): () => void;
|
|
619
|
+
}
|
|
620
|
+
|
|
465
621
|
/**
|
|
466
622
|
* Validates placeholder against schema structure without using Zod
|
|
467
623
|
* Combines the functionality of createPlaceholderValidator and createValueValidator
|
|
468
624
|
*/
|
|
469
625
|
declare function validatePlaceholder<T extends DocShape>(placeholder: unknown, schema: T): Infer<T>;
|
|
470
626
|
|
|
471
|
-
export { type ArrayValueShape, type ContainerOrValueShape, type ContainerShape, type CounterContainerShape, type DeepReadonly, type DiscriminatedUnionValueShape, type DocShape, type Draft, type Infer, type InferDraftType, type InferPlaceholderType, type ListContainerShape, type MapContainerShape, type MovableListContainerShape, type ObjectValueShape, type RecordContainerShape, type RecordValueShape, type ContainerType as RootContainerType, Shape, type TextContainerShape, type TreeContainerShape, TypedDoc, type UnionValueShape, type ValueShape, type WithPlaceholder, createTypedDoc, derivePlaceholder, deriveShapePlaceholder, mergeValue, overlayPlaceholder, validatePlaceholder };
|
|
627
|
+
export { type ArrayValueShape, type ContainerOrValueShape, type ContainerShape, type CounterContainerShape, type DeepReadonly, type DiscriminatedUnionValueShape, type DocShape, type Draft, type Infer, type InferDraftType, type InferMutableType, type InferPlaceholderType, type ListContainerShape, type MapContainerShape, type MovableListContainerShape, type Mutable, type ObjectValue, type ObjectValueShape, type PresenceInterface, type RecordContainerShape, type RecordValueShape, type ContainerType as RootContainerType, Shape, type TextContainerShape, type TreeContainerShape, TypedDoc, TypedPresence, type UnionValueShape, type ValueShape, type WithPlaceholder, createPlaceholderProxy, createTypedDoc, derivePlaceholder, deriveShapePlaceholder, mergeValue, overlayPlaceholder, validatePlaceholder };
|