@loro-extended/change 0.8.0 → 0.9.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/README.md +78 -0
- package/dist/index.d.ts +190 -39
- package/dist/index.js +480 -295
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/change.test.ts +277 -1
- 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/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 +4 -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 +27 -13
- package/src/typed-refs/json-compatibility.test.ts +255 -0
- package/src/{draft-nodes → typed-refs}/list-base.ts +79 -30
- 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 +33 -22
- 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 +69 -0
- package/src/{draft-nodes → typed-refs}/record.ts +50 -21
- package/src/{draft-nodes → typed-refs}/text.ts +13 -3
- package/src/{draft-nodes → typed-refs}/tree.ts +6 -3
- package/src/{draft-nodes → typed-refs}/utils.ts +23 -27
- package/src/types.test.ts +97 -2
- package/src/types.ts +62 -5
- package/src/draft-nodes/counter.md +0 -31
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,21 +46,59 @@ 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;
|
|
@@ -60,14 +106,15 @@ declare abstract class DraftNode<Shape extends DocShape | ContainerShape> {
|
|
|
60
106
|
protected get container(): ShapeToContainer<Shape>;
|
|
61
107
|
}
|
|
62
108
|
|
|
63
|
-
declare class
|
|
109
|
+
declare class CounterRef extends TypedRef<CounterContainerShape> {
|
|
64
110
|
absorbPlainValues(): void;
|
|
65
111
|
increment(value: number): void;
|
|
66
112
|
decrement(value: number): void;
|
|
67
113
|
get value(): number;
|
|
114
|
+
toJSON(): number;
|
|
68
115
|
}
|
|
69
116
|
|
|
70
|
-
declare abstract class
|
|
117
|
+
declare abstract class ListRefBase<NestedShape extends ContainerOrValueShape, Item = NestedShape["_plain"], MutableItem = NestedShape["_mutable"]> extends TypedRef<any> {
|
|
71
118
|
private itemCache;
|
|
72
119
|
protected get container(): LoroList | LoroMovableList;
|
|
73
120
|
protected get shape(): ListContainerShape<NestedShape> | MovableListContainerShape<NestedShape>;
|
|
@@ -75,43 +122,47 @@ declare abstract class ListDraftNodeBase<NestedShape extends ContainerOrValueSha
|
|
|
75
122
|
protected abstract absorbValueAtIndex(index: number, value: any): void;
|
|
76
123
|
protected insertWithConversion(index: number, item: Item): void;
|
|
77
124
|
protected pushWithConversion(item: Item): void;
|
|
78
|
-
|
|
125
|
+
getTypedRefParams(index: number, shape: ContainerShape): TypedRefParams<ContainerShape>;
|
|
79
126
|
protected getPredicateItem(index: number): Item;
|
|
80
|
-
protected
|
|
81
|
-
find(predicate: (item: Item, index: number) => boolean):
|
|
127
|
+
protected getMutableItem(index: number): any;
|
|
128
|
+
find(predicate: (item: Item, index: number) => boolean): MutableItem | undefined;
|
|
82
129
|
findIndex(predicate: (item: Item, index: number) => boolean): number;
|
|
83
130
|
map<ReturnType>(callback: (item: Item, index: number) => ReturnType): ReturnType[];
|
|
84
|
-
filter(predicate: (item: Item, index: number) => boolean):
|
|
131
|
+
filter(predicate: (item: Item, index: number) => boolean): MutableItem[];
|
|
85
132
|
forEach(callback: (item: Item, index: number) => void): void;
|
|
86
133
|
some(predicate: (item: Item, index: number) => boolean): boolean;
|
|
87
134
|
every(predicate: (item: Item, index: number) => boolean): boolean;
|
|
135
|
+
slice(start?: number, end?: number): MutableItem[];
|
|
88
136
|
insert(index: number, item: Item): void;
|
|
89
137
|
delete(index: number, len: number): void;
|
|
90
138
|
push(item: Item): void;
|
|
91
139
|
pushContainer(container: Container): Container;
|
|
92
140
|
insertContainer(index: number, container: Container): Container;
|
|
93
|
-
get(index: number):
|
|
141
|
+
get(index: number): MutableItem;
|
|
94
142
|
toArray(): Item[];
|
|
143
|
+
toJSON(): Item[];
|
|
144
|
+
[Symbol.iterator](): IterableIterator<MutableItem>;
|
|
95
145
|
get length(): number;
|
|
96
146
|
private updateCacheForDelete;
|
|
97
147
|
private updateCacheForInsert;
|
|
98
148
|
}
|
|
99
149
|
|
|
100
|
-
declare class
|
|
150
|
+
declare class ListRef<NestedShape extends ContainerOrValueShape> extends ListRefBase<NestedShape> {
|
|
101
151
|
[index: number]: Infer<NestedShape>;
|
|
102
152
|
protected get container(): LoroList;
|
|
103
153
|
protected absorbValueAtIndex(index: number, value: any): void;
|
|
104
154
|
}
|
|
105
155
|
|
|
106
|
-
declare class
|
|
156
|
+
declare class MapRef<NestedShapes extends Record<string, ContainerOrValueShape>> extends TypedRef<any> {
|
|
107
157
|
private propertyCache;
|
|
108
|
-
constructor(params:
|
|
158
|
+
constructor(params: TypedRefParams<MapContainerShape<NestedShapes>>);
|
|
109
159
|
protected get shape(): MapContainerShape<NestedShapes>;
|
|
110
160
|
protected get container(): LoroMap;
|
|
111
161
|
absorbPlainValues(): void;
|
|
112
|
-
|
|
162
|
+
getTypedRefParams<S extends ContainerShape>(key: string, shape: S): TypedRefParams<ContainerShape>;
|
|
113
163
|
getOrCreateNode<Shape extends ContainerShape | ValueShape>(key: string, shape: Shape): any;
|
|
114
164
|
private createLazyProperties;
|
|
165
|
+
toJSON(): any;
|
|
115
166
|
get(key: string): any;
|
|
116
167
|
set(key: string, value: Value): void;
|
|
117
168
|
setContainer<C extends Container>(key: string, container: C): C;
|
|
@@ -122,7 +173,7 @@ declare class MapDraftNode<NestedShapes extends Record<string, ContainerOrValueS
|
|
|
122
173
|
get size(): number;
|
|
123
174
|
}
|
|
124
175
|
|
|
125
|
-
declare class
|
|
176
|
+
declare class MovableListRef<NestedShape extends ContainerOrValueShape, Item = NestedShape["_plain"]> extends ListRefBase<NestedShape> {
|
|
126
177
|
[index: number]: Infer<NestedShape>;
|
|
127
178
|
protected get container(): LoroMovableList;
|
|
128
179
|
protected absorbValueAtIndex(index: number, value: any): void;
|
|
@@ -130,13 +181,13 @@ declare class MovableListDraftNode<NestedShape extends ContainerOrValueShape, It
|
|
|
130
181
|
set(index: number, item: Exclude<Item, Container>): void;
|
|
131
182
|
}
|
|
132
183
|
|
|
133
|
-
declare class
|
|
184
|
+
declare class RecordRef<NestedShape extends ContainerOrValueShape> extends TypedRef<any> {
|
|
134
185
|
[key: string]: Infer<NestedShape> | any;
|
|
135
186
|
private nodeCache;
|
|
136
187
|
protected get shape(): RecordContainerShape<NestedShape>;
|
|
137
188
|
protected get container(): LoroMap;
|
|
138
189
|
absorbPlainValues(): void;
|
|
139
|
-
|
|
190
|
+
getTypedRefParams<S extends ContainerShape>(key: string, shape: S): TypedRefParams<ContainerShape>;
|
|
140
191
|
getOrCreateNode(key: string): any;
|
|
141
192
|
get(key: string): InferDraftType<NestedShape>;
|
|
142
193
|
set(key: string, value: any): void;
|
|
@@ -146,13 +197,15 @@ declare class RecordDraftNode<NestedShape extends ContainerOrValueShape> extends
|
|
|
146
197
|
keys(): string[];
|
|
147
198
|
values(): any[];
|
|
148
199
|
get size(): number;
|
|
200
|
+
toJSON(): Record<string, any>;
|
|
149
201
|
}
|
|
150
202
|
|
|
151
|
-
declare class
|
|
203
|
+
declare class TextRef extends TypedRef<TextContainerShape> {
|
|
152
204
|
absorbPlainValues(): void;
|
|
153
205
|
insert(index: number, content: string): void;
|
|
154
206
|
delete(index: number, len: number): void;
|
|
155
207
|
toString(): string;
|
|
208
|
+
toJSON(): string;
|
|
156
209
|
update(text: string): void;
|
|
157
210
|
mark(range: {
|
|
158
211
|
start: number;
|
|
@@ -173,42 +226,42 @@ type WithPlaceholder<S extends Shape<any, any, any>> = S & {
|
|
|
173
226
|
interface DocShape<NestedShapes extends Record<string, ContainerShape> = Record<string, ContainerShape>> extends Shape<{
|
|
174
227
|
[K in keyof NestedShapes]: NestedShapes[K]["_plain"];
|
|
175
228
|
}, {
|
|
176
|
-
[K in keyof NestedShapes]: NestedShapes[K]["
|
|
229
|
+
[K in keyof NestedShapes]: NestedShapes[K]["_mutable"];
|
|
177
230
|
}, {
|
|
178
231
|
[K in keyof NestedShapes]: NestedShapes[K]["_placeholder"];
|
|
179
232
|
}> {
|
|
180
233
|
readonly _type: "doc";
|
|
181
234
|
readonly shapes: NestedShapes;
|
|
182
235
|
}
|
|
183
|
-
interface TextContainerShape extends Shape<string,
|
|
236
|
+
interface TextContainerShape extends Shape<string, TextRef, string> {
|
|
184
237
|
readonly _type: "text";
|
|
185
238
|
}
|
|
186
|
-
interface CounterContainerShape extends Shape<number,
|
|
239
|
+
interface CounterContainerShape extends Shape<number, CounterRef, number> {
|
|
187
240
|
readonly _type: "counter";
|
|
188
241
|
}
|
|
189
242
|
interface TreeContainerShape<NestedShape = ContainerOrValueShape> extends Shape<any, any, never[]> {
|
|
190
243
|
readonly _type: "tree";
|
|
191
244
|
readonly shape: NestedShape;
|
|
192
245
|
}
|
|
193
|
-
interface ListContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<NestedShape["_plain"][],
|
|
246
|
+
interface ListContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<NestedShape["_plain"][], ListRef<NestedShape>, never[]> {
|
|
194
247
|
readonly _type: "list";
|
|
195
248
|
readonly shape: NestedShape;
|
|
196
249
|
}
|
|
197
|
-
interface MovableListContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<NestedShape["_plain"][],
|
|
250
|
+
interface MovableListContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<NestedShape["_plain"][], MovableListRef<NestedShape>, never[]> {
|
|
198
251
|
readonly _type: "movableList";
|
|
199
252
|
readonly shape: NestedShape;
|
|
200
253
|
}
|
|
201
254
|
interface MapContainerShape<NestedShapes extends Record<string, ContainerOrValueShape> = Record<string, ContainerOrValueShape>> extends Shape<{
|
|
202
255
|
[K in keyof NestedShapes]: NestedShapes[K]["_plain"];
|
|
203
|
-
},
|
|
204
|
-
[K in keyof NestedShapes]: NestedShapes[K]["
|
|
256
|
+
}, MapRef<NestedShapes> & {
|
|
257
|
+
[K in keyof NestedShapes]: NestedShapes[K]["_mutable"];
|
|
205
258
|
}, {
|
|
206
259
|
[K in keyof NestedShapes]: NestedShapes[K]["_placeholder"];
|
|
207
260
|
}> {
|
|
208
261
|
readonly _type: "map";
|
|
209
262
|
readonly shapes: NestedShapes;
|
|
210
263
|
}
|
|
211
|
-
interface RecordContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<Record<string, NestedShape["_plain"]>,
|
|
264
|
+
interface RecordContainerShape<NestedShape extends ContainerOrValueShape = ContainerOrValueShape> extends Shape<Record<string, NestedShape["_plain"]>, RecordRef<NestedShape>, Record<string, never>> {
|
|
212
265
|
readonly _type: "record";
|
|
213
266
|
readonly shape: NestedShape;
|
|
214
267
|
}
|
|
@@ -242,7 +295,7 @@ interface Uint8ArrayValueShape extends Shape<Uint8Array, Uint8Array, Uint8Array>
|
|
|
242
295
|
interface ObjectValueShape<T extends Record<string, ValueShape> = Record<string, ValueShape>> extends Shape<{
|
|
243
296
|
[K in keyof T]: T[K]["_plain"];
|
|
244
297
|
}, {
|
|
245
|
-
[K in keyof T]: T[K]["
|
|
298
|
+
[K in keyof T]: T[K]["_mutable"];
|
|
246
299
|
}, {
|
|
247
300
|
[K in keyof T]: T[K]["_placeholder"];
|
|
248
301
|
}> {
|
|
@@ -250,17 +303,17 @@ interface ObjectValueShape<T extends Record<string, ValueShape> = Record<string,
|
|
|
250
303
|
readonly valueType: "object";
|
|
251
304
|
readonly shape: T;
|
|
252
305
|
}
|
|
253
|
-
interface RecordValueShape<T extends ValueShape = ValueShape> extends Shape<Record<string, T["_plain"]>, Record<string, T["
|
|
306
|
+
interface RecordValueShape<T extends ValueShape = ValueShape> extends Shape<Record<string, T["_plain"]>, Record<string, T["_mutable"]>, Record<string, never>> {
|
|
254
307
|
readonly _type: "value";
|
|
255
308
|
readonly valueType: "record";
|
|
256
309
|
readonly shape: T;
|
|
257
310
|
}
|
|
258
|
-
interface ArrayValueShape<T extends ValueShape = ValueShape> extends Shape<T["_plain"][], T["
|
|
311
|
+
interface ArrayValueShape<T extends ValueShape = ValueShape> extends Shape<T["_plain"][], T["_mutable"][], never[]> {
|
|
259
312
|
readonly _type: "value";
|
|
260
313
|
readonly valueType: "array";
|
|
261
314
|
readonly shape: T;
|
|
262
315
|
}
|
|
263
|
-
interface UnionValueShape<T extends ValueShape[] = ValueShape[]> extends Shape<T[number]["_plain"], T[number]["
|
|
316
|
+
interface UnionValueShape<T extends ValueShape[] = ValueShape[]> extends Shape<T[number]["_plain"], T[number]["_mutable"], T[number]["_placeholder"]> {
|
|
264
317
|
readonly _type: "value";
|
|
265
318
|
readonly valueType: "union";
|
|
266
319
|
readonly shapes: T;
|
|
@@ -278,7 +331,7 @@ interface UnionValueShape<T extends ValueShape[] = ValueShape[]> extends Shape<T
|
|
|
278
331
|
* @typeParam K - The discriminant key (e.g., "type")
|
|
279
332
|
* @typeParam T - A record mapping discriminant values to their object shapes
|
|
280
333
|
*/
|
|
281
|
-
interface DiscriminatedUnionValueShape<K extends string = string, T extends Record<string, ObjectValueShape> = Record<string, ObjectValueShape
|
|
334
|
+
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
335
|
readonly _type: "value";
|
|
283
336
|
readonly valueType: "discriminatedUnion";
|
|
284
337
|
readonly discriminantKey: K;
|
|
@@ -286,10 +339,10 @@ interface DiscriminatedUnionValueShape<K extends string = string, T extends Reco
|
|
|
286
339
|
}
|
|
287
340
|
type ValueShape = StringValueShape | NumberValueShape | BooleanValueShape | NullValueShape | UndefinedValueShape | Uint8ArrayValueShape | ObjectValueShape | RecordValueShape | ArrayValueShape | UnionValueShape | DiscriminatedUnionValueShape;
|
|
288
341
|
type ContainerOrValueShape = ContainerShape | ValueShape;
|
|
289
|
-
interface Shape<Plain,
|
|
342
|
+
interface Shape<Plain, Mutable, Placeholder = Plain> {
|
|
290
343
|
readonly _type: string;
|
|
291
344
|
readonly _plain: Plain;
|
|
292
|
-
readonly
|
|
345
|
+
readonly _mutable: Mutable;
|
|
293
346
|
readonly _placeholder: Placeholder;
|
|
294
347
|
}
|
|
295
348
|
/**
|
|
@@ -380,6 +433,59 @@ declare function overlayPlaceholder<Shape extends DocShape>(shape: Shape, crdtVa
|
|
|
380
433
|
*/
|
|
381
434
|
declare function mergeValue<Shape extends ContainerShape | ValueShape>(shape: Shape, crdtValue: Value, placeholderValue: Value): Value;
|
|
382
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Creates a proxy around a placeholder value (plain object/array) that mimics
|
|
438
|
+
* the behavior of TypedRef, specifically adding a .toJSON() method.
|
|
439
|
+
*
|
|
440
|
+
* This ensures consistent UX where users can call .toJSON() on document state
|
|
441
|
+
* regardless of whether it's loading (placeholder) or loaded (live ref).
|
|
442
|
+
*/
|
|
443
|
+
declare function createPlaceholderProxy<T extends object>(target: T): T;
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* A record of string keys to Loro values, used for presence data.
|
|
447
|
+
*/
|
|
448
|
+
type ObjectValue = Record<string, Value>;
|
|
449
|
+
/**
|
|
450
|
+
* Interface for presence management that can be implemented by different backends.
|
|
451
|
+
* This abstraction allows TypedPresence to work with any presence provider.
|
|
452
|
+
*/
|
|
453
|
+
interface PresenceInterface {
|
|
454
|
+
/**
|
|
455
|
+
* Set multiple presence values at once.
|
|
456
|
+
*/
|
|
457
|
+
set: (values: ObjectValue) => void;
|
|
458
|
+
/**
|
|
459
|
+
* Get a single presence value by key.
|
|
460
|
+
*/
|
|
461
|
+
get: (key: string) => Value;
|
|
462
|
+
/**
|
|
463
|
+
* The current peer's presence state.
|
|
464
|
+
*/
|
|
465
|
+
readonly self: ObjectValue;
|
|
466
|
+
/**
|
|
467
|
+
* Other peers' presence states, keyed by peer ID.
|
|
468
|
+
* Does NOT include self. Use this for iterating over remote peers.
|
|
469
|
+
*/
|
|
470
|
+
readonly peers: Map<string, ObjectValue>;
|
|
471
|
+
/**
|
|
472
|
+
* All peers' presence states, keyed by peer ID (includes self).
|
|
473
|
+
* @deprecated Use `peers` and `self` separately. This property is synthesized
|
|
474
|
+
* from `peers` and `self` for backward compatibility.
|
|
475
|
+
*/
|
|
476
|
+
readonly all: Record<string, ObjectValue>;
|
|
477
|
+
/**
|
|
478
|
+
* Set a single raw value by key (escape hatch for arbitrary keys).
|
|
479
|
+
*/
|
|
480
|
+
setRaw: (key: string, value: Value) => void;
|
|
481
|
+
/**
|
|
482
|
+
* Subscribe to presence changes.
|
|
483
|
+
* @param cb Callback that receives the aggregated presence values
|
|
484
|
+
* @returns Unsubscribe function
|
|
485
|
+
*/
|
|
486
|
+
subscribe: (cb: (values: ObjectValue) => void) => () => void;
|
|
487
|
+
}
|
|
488
|
+
|
|
383
489
|
/** biome-ignore-all lint/suspicious/noExplicitAny: JSON Patch values can be any type */
|
|
384
490
|
|
|
385
491
|
type JsonPatchAddOperation = {
|
|
@@ -462,10 +568,55 @@ declare class TypedDoc<Shape extends DocShape> {
|
|
|
462
568
|
}
|
|
463
569
|
declare function createTypedDoc<Shape extends DocShape>(shape: Shape, existingDoc?: LoroDoc): TypedDoc<Shape>;
|
|
464
570
|
|
|
571
|
+
/**
|
|
572
|
+
* A strongly-typed wrapper around a PresenceInterface.
|
|
573
|
+
* Provides type-safe access to presence data with automatic placeholder merging.
|
|
574
|
+
*
|
|
575
|
+
* @typeParam S - The shape of the presence data
|
|
576
|
+
*/
|
|
577
|
+
declare class TypedPresence<S extends ContainerShape | ValueShape> {
|
|
578
|
+
shape: S;
|
|
579
|
+
private presence;
|
|
580
|
+
private placeholder;
|
|
581
|
+
constructor(shape: S, presence: PresenceInterface);
|
|
582
|
+
/**
|
|
583
|
+
* Get the current peer's presence state with placeholder values merged in.
|
|
584
|
+
*/
|
|
585
|
+
get self(): Infer<S>;
|
|
586
|
+
/**
|
|
587
|
+
* Get other peers' presence states with placeholder values merged in.
|
|
588
|
+
* Does NOT include self. Use this for iterating over remote peers.
|
|
589
|
+
*/
|
|
590
|
+
get peers(): Map<string, Infer<S>>;
|
|
591
|
+
/**
|
|
592
|
+
* Get all peers' presence states with placeholder values merged in.
|
|
593
|
+
* @deprecated Use `peers` and `self` separately. This property is synthesized
|
|
594
|
+
* from `peers` and `self` for backward compatibility.
|
|
595
|
+
*/
|
|
596
|
+
get all(): Record<string, Infer<S>>;
|
|
597
|
+
/**
|
|
598
|
+
* Set presence values for the current peer.
|
|
599
|
+
*/
|
|
600
|
+
set(value: Partial<Infer<S>>): void;
|
|
601
|
+
/**
|
|
602
|
+
* Subscribe to presence changes.
|
|
603
|
+
* The callback is called immediately with the current state, then on each change.
|
|
604
|
+
*
|
|
605
|
+
* @param cb Callback that receives the typed presence state
|
|
606
|
+
* @returns Unsubscribe function
|
|
607
|
+
*/
|
|
608
|
+
subscribe(cb: (state: {
|
|
609
|
+
self: Infer<S>;
|
|
610
|
+
peers: Map<string, Infer<S>>;
|
|
611
|
+
/** @deprecated Use `peers` and `self` separately */
|
|
612
|
+
all: Record<string, Infer<S>>;
|
|
613
|
+
}) => void): () => void;
|
|
614
|
+
}
|
|
615
|
+
|
|
465
616
|
/**
|
|
466
617
|
* Validates placeholder against schema structure without using Zod
|
|
467
618
|
* Combines the functionality of createPlaceholderValidator and createValueValidator
|
|
468
619
|
*/
|
|
469
620
|
declare function validatePlaceholder<T extends DocShape>(placeholder: unknown, schema: T): Infer<T>;
|
|
470
621
|
|
|
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 };
|
|
622
|
+
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 };
|