@loro-extended/change 1.0.0 → 1.0.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 (2) hide show
  1. package/README.md +58 -60
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -28,13 +28,13 @@ pnpm add @loro-extended/change loro-crdt
28
28
  ## Quick Start
29
29
 
30
30
  ```typescript
31
- import { createTypedDoc, Shape, batch, toJSON } from "@loro-extended/change";
31
+ import { createTypedDoc, Shape, change } from "@loro-extended/change";
32
32
 
33
33
  // Define your document schema
34
34
  const schema = Shape.doc({
35
35
  title: Shape.text().placeholder("My Todo List"),
36
36
  count: Shape.counter(),
37
- users: Shape.record(Shape.plain.object({
37
+ users: Shape.record(Shape.plain.struct({
38
38
  name: Shape.plain.string(),
39
39
  })),
40
40
  });
@@ -57,8 +57,8 @@ if ("alice" in doc.users) {
57
57
 
58
58
  // Batched mutations - commit together (optional, for performance)
59
59
  // Using functional helper (recommended)
60
- batch(doc, draft => {
61
- draft.title.insert(0, "Batch: ");
60
+ change(doc, draft => {
61
+ draft.title.insert(0, "Change: ");
62
62
  draft.count.increment(10);
63
63
  draft.users.set("bob", { name: "Bob" });
64
64
  });
@@ -66,7 +66,7 @@ batch(doc, draft => {
66
66
 
67
67
  // Get JSON snapshot using functional helper
68
68
  console.log(doc.toJSON());
69
- // { title: "Batch: 📝 Todo", count: 15, users: { alice: { name: "Alice" }, bob: { name: "Bob" } } }
69
+ // { title: "Change: 📝 Todo", count: 15, users: { alice: { name: "Alice" }, bob: { name: "Bob" } } }
70
70
  ```
71
71
 
72
72
  Note that this is even more useful in combination with `@loro-extended/react` (if your app uses React) and `@loro-extended/repo` for syncing between client/server or among peers.
@@ -88,8 +88,8 @@ const blogSchema = Shape.doc({
88
88
  // Lists for ordered data
89
89
  tags: Shape.list(Shape.plain.string()), // List of strings
90
90
 
91
- // Maps for structured data
92
- metadata: Shape.map({
91
+ // Structs for structured data with fixed keys
92
+ metadata: Shape.struct({
93
93
  author: Shape.plain.string(), // Plain values (POJOs)
94
94
  publishedAt: Shape.plain.string(), // ISO date string
95
95
  featured: Shape.plain.boolean(),
@@ -97,7 +97,7 @@ const blogSchema = Shape.doc({
97
97
 
98
98
  // Movable lists for reorderable content
99
99
  sections: Shape.movableList(
100
- Shape.map({
100
+ Shape.struct({
101
101
  heading: Shape.text(), // Collaborative headings
102
102
  content: Shape.text(), // Collaborative content
103
103
  order: Shape.plain.number(), // Plain metadata
@@ -106,7 +106,7 @@ const blogSchema = Shape.doc({
106
106
  });
107
107
  ```
108
108
 
109
- **NOTE:** Use `Shape.*` for collaborative containers and `Shape.plain.*` for plain values. Only put plain values inside Loro containers - a Loro container inside a plain JS object or array won't work.
109
+ **NOTE:** Use `Shape.*` for collaborative containers and `Shape.plain.*` for plain values. Only put plain values inside Loro containers - a Loro container inside a plain JS struct or array won't work.
110
110
 
111
111
  ### Empty State Overlay
112
112
 
@@ -118,13 +118,13 @@ const blogSchemaWithDefaults = Shape.doc({
118
118
  title: Shape.text().placeholder("Untitled Document"),
119
119
  viewCount: Shape.counter(), // defaults to 0
120
120
  tags: Shape.list(Shape.plain.string()), // defaults to []
121
- metadata: Shape.map({
121
+ metadata: Shape.struct({
122
122
  author: Shape.plain.string().placeholder("Anonymous"),
123
123
  publishedAt: Shape.plain.string(), // defaults to ""
124
124
  featured: Shape.plain.boolean(), // defaults to false
125
125
  }),
126
126
  sections: Shape.movableList(
127
- Shape.map({
127
+ Shape.struct({
128
128
  heading: Shape.text(),
129
129
  content: Shape.text(),
130
130
  order: Shape.plain.number(),
@@ -139,7 +139,7 @@ console.log(doc.toJSON());
139
139
  // { title: "Untitled Document", viewCount: 0, ... }
140
140
 
141
141
  // After changes, CRDT values take priority over empty state
142
- batch(doc, (draft) => {
142
+ change(doc, (draft) => {
143
143
  draft.title.insert(0, "My Blog Post");
144
144
  draft.viewCount.increment(10);
145
145
  });
@@ -160,12 +160,12 @@ doc.viewCount.increment(1);
160
160
  doc.tags.push("typescript");
161
161
  ```
162
162
 
163
- For batched operations (better performance, atomic undo), use `batch()`:
163
+ For batched operations (better performance, atomic undo), use `change()`:
164
164
 
165
165
  ```typescript
166
- import { batch, toJSON } from "@loro-extended/change";
166
+ import { change } from "@loro-extended/change";
167
167
 
168
- batch(doc, (draft) => {
168
+ change(doc, (draft) => {
169
169
  // Text operations
170
170
  draft.title.insert(0, "📝");
171
171
  draft.title.delete(5, 3);
@@ -179,7 +179,7 @@ batch(doc, (draft) => {
179
179
  draft.tags.insert(0, "loro");
180
180
  draft.tags.delete(1, 1);
181
181
 
182
- // Map operations (POJO values)
182
+ // Struct operations (POJO values)
183
183
  draft.metadata.set("author", "John Doe");
184
184
  draft.metadata.delete("featured");
185
185
 
@@ -193,21 +193,21 @@ batch(doc, (draft) => {
193
193
  });
194
194
 
195
195
  // All changes are committed atomically as one transaction
196
- // batch() returns the doc for chaining
196
+ // change() returns the doc for chaining
197
197
  console.log(doc.toJSON()); // Updated document state
198
198
  ```
199
199
 
200
- ### When to Use `batch()` vs Direct Mutations
200
+ ### When to Use `change()` vs Direct Mutations
201
201
 
202
202
  | Use Case | Approach |
203
203
  |----------|----------|
204
204
  | Single mutation | Direct: `doc.count.increment(1)` |
205
- | Multiple related mutations | Batched: `batch(doc, d => { ... })` |
206
- | Atomic undo/redo | Batched: `batch(doc, d => { ... })` |
207
- | Performance-critical bulk updates | Batched: `batch(doc, d => { ... })` |
205
+ | Multiple related mutations | Batched: `change(doc, d => { ... })` |
206
+ | Atomic undo/redo | Batched: `change(doc, d => { ... })` |
207
+ | Performance-critical bulk updates | Batched: `change(doc, d => { ... })` |
208
208
  | Simple reads + writes | Direct: `doc.users.set(...)` |
209
209
 
210
- > **Note:** The `$.change()` and `$.batch()` methods are available as an escape hatch, but the functional `batch()` helper is recommended for cleaner code.
210
+ > **Note:** The `$.change()` method is available as an escape hatch, but the functional `change()` helper is recommended for cleaner code.
211
211
 
212
212
  ## Advanced Usage
213
213
 
@@ -219,19 +219,19 @@ For type-safe tagged unions (like different message types or presence states), u
219
219
  import { Shape, mergeValue } from "@loro-extended/change";
220
220
 
221
221
  // Define variant shapes - each must have the discriminant key
222
- const ClientPresenceShape = Shape.plain.object({
222
+ const ClientPresenceShape = Shape.plain.struct({
223
223
  type: Shape.plain.string("client"), // Literal type for discrimination
224
224
  name: Shape.plain.string(),
225
- input: Shape.plain.object({
225
+ input: Shape.plain.struct({
226
226
  force: Shape.plain.number(),
227
227
  angle: Shape.plain.number(),
228
228
  }),
229
229
  });
230
230
 
231
- const ServerPresenceShape = Shape.plain.object({
231
+ const ServerPresenceShape = Shape.plain.struct({
232
232
  type: Shape.plain.string("server"), // Literal type for discrimination
233
233
  cars: Shape.plain.record(
234
- Shape.plain.object({
234
+ Shape.plain.struct({
235
235
  x: Shape.plain.number(),
236
236
  y: Shape.plain.number(),
237
237
  })
@@ -287,11 +287,11 @@ Handle complex nested documents with ease:
287
287
 
288
288
  ```typescript
289
289
  const complexSchema = Shape.doc({
290
- article: Shape.map({
290
+ article: Shape.struct({
291
291
  title: Shape.text(),
292
- metadata: Shape.map({
292
+ metadata: Shape.struct({
293
293
  views: Shape.counter(),
294
- author: Shape.map({
294
+ author: Shape.struct({
295
295
  name: Shape.plain.string(),
296
296
  email: Shape.plain.string(),
297
297
  }),
@@ -314,7 +314,7 @@ const emptyState = {
314
314
 
315
315
  const doc = createTypedDoc(complexSchema);
316
316
 
317
- batch(doc, (draft) => {
317
+ change(doc, (draft) => {
318
318
  draft.article.title.insert(0, "Deep Nesting Example");
319
319
  draft.article.metadata.views.increment(5);
320
320
  draft.article.metadata.author.name = "Alice"; // plain string update is captured and applied after closure
@@ -322,20 +322,20 @@ batch(doc, (draft) => {
322
322
  });
323
323
  ```
324
324
 
325
- ### Map Operations
325
+ ### Struct Operations
326
326
 
327
- For map containers, use the standard map methods:
327
+ For struct containers (fixed-key objects), use direct property access:
328
328
 
329
329
  ```typescript
330
330
  const schema = Shape.doc({
331
- settings: Shape.map({
331
+ settings: Shape.struct({
332
332
  theme: Shape.plain.string(),
333
333
  collapsed: Shape.plain.boolean(),
334
334
  width: Shape.plain.number(),
335
335
  }),
336
336
  });
337
337
 
338
- batch(doc, (draft) => {
338
+ change(doc, (draft) => {
339
339
  // Set individual values
340
340
  draft.settings.theme = "dark";
341
341
  draft.settings.collapsed = true;
@@ -350,11 +350,11 @@ Create lists containing CRDT containers for collaborative nested structures:
350
350
  ```typescript
351
351
  const collaborativeSchema = Shape.doc({
352
352
  articles: Shape.list(
353
- Shape.map({
353
+ Shape.struct({
354
354
  title: Shape.text(), // Collaborative title
355
355
  content: Shape.text(), // Collaborative content
356
356
  tags: Shape.list(Shape.plain.string()), // Collaborative tag list
357
- metadata: Shape.plain.object({
357
+ metadata: Shape.plain.struct({
358
358
  // Static metadata
359
359
  authorId: Shape.plain.string(),
360
360
  publishedAt: Shape.plain.string(),
@@ -363,7 +363,7 @@ const collaborativeSchema = Shape.doc({
363
363
  ),
364
364
  });
365
365
 
366
- batch(doc, (draft) => {
366
+ change(doc, (draft) => {
367
367
  // Push creates and configures nested containers automatically
368
368
  draft.articles.push({
369
369
  title: "Collaborative Article",
@@ -410,30 +410,28 @@ const doc = new TypedDoc(schema);
410
410
 
411
411
  These functional helpers provide a cleaner API and are the recommended way to work with TypedDoc:
412
412
 
413
- #### `batch(doc, mutator)`
413
+ #### `change(doc, mutator)`
414
414
 
415
415
  Batches multiple mutations into a single transaction. Returns the doc for chaining.
416
416
 
417
417
  ```typescript
418
- import { batch } from "@loro-extended/change";
418
+ import { change } from "@loro-extended/change";
419
419
 
420
- batch(doc, (draft) => {
420
+ change(doc, (draft) => {
421
421
  draft.title.insert(0, "Hello");
422
422
  draft.count.increment(5);
423
423
  });
424
424
 
425
- // Chainable - batch returns the doc
426
- batch(doc, d => d.count.increment(1)).count.increment(2);
425
+ // Chainable - change returns the doc
426
+ change(doc, d => d.count.increment(1)).count.increment(2);
427
427
  ```
428
428
 
429
- #### `toJSON(doc)`
429
+ #### `doc.toJSON()`
430
430
 
431
431
  Returns the full plain JavaScript object representation of the document.
432
432
 
433
433
  ```typescript
434
- import { toJSON } from "@loro-extended/change";
435
-
436
- const snapshot = toJSON(doc);
434
+ const snapshot = doc.toJSON();
437
435
  // { title: "Hello", count: 5, ... }
438
436
  ```
439
437
 
@@ -452,12 +450,12 @@ loroDoc.subscribe((event) => console.log("Changed:", event));
452
450
 
453
451
  The `$` namespace provides access to meta-operations. While functional helpers are recommended, the `$` namespace is available for advanced use cases:
454
452
 
455
- #### `doc.$.batch(mutator)`
453
+ #### `doc.$.change(mutator)`
456
454
 
457
- Same as `batch(doc, mutator)`.
455
+ Same as `change(doc, mutator)`.
458
456
 
459
457
  ```typescript
460
- doc.$.batch((draft) => {
458
+ doc.$.change((draft) => {
461
459
  // Make changes to draft - all commit together
462
460
  });
463
461
  ```
@@ -481,7 +479,7 @@ const schema = Shape.doc({
481
479
  - `Shape.counter()` - Collaborative increment/decrement counters
482
480
  - `Shape.list(itemSchema)` - Collaborative ordered lists
483
481
  - `Shape.movableList(itemSchema)` - Collaborative reorderable lists
484
- - `Shape.map(shape)` - Collaborative key-value maps with fixed keys
482
+ - `Shape.struct(shape)` - Collaborative structs with fixed keys (uses LoroMap internally)
485
483
  - `Shape.record(valueSchema)` - Collaborative key-value maps with dynamic string keys
486
484
  - `Shape.tree(shape)` - Collaborative hierarchical tree structures (Note: incomplete implementation)
487
485
 
@@ -493,7 +491,7 @@ const schema = Shape.doc({
493
491
  - `Shape.plain.null()` - Null values
494
492
  - `Shape.plain.undefined()` - Undefined values
495
493
  - `Shape.plain.uint8Array()` - Binary data values
496
- - `Shape.plain.object(shape)` - Object values with fixed keys
494
+ - `Shape.plain.struct(shape)` - Struct values with fixed keys
497
495
  - `Shape.plain.record(valueShape)` - Object values with dynamic string keys
498
496
  - `Shape.plain.array(itemShape)` - Array values
499
497
  - `Shape.plain.union(shapes)` - Union of value types (e.g., `string | null`)
@@ -522,7 +520,7 @@ doc.users.has("alice"); // true
522
520
  "alice" in doc.users; // true
523
521
  ```
524
522
 
525
- For batched mutations, use `$.batch()` instead.
523
+ For batched mutations, use `$.change()` instead.
526
524
 
527
525
  #### `doc.$.toJSON()`
528
526
 
@@ -611,7 +609,7 @@ draft.todos.forEach((todo, index) => {
611
609
  **Important**: Methods like `find()` and `filter()` return **mutable draft objects** that you can modify directly:
612
610
 
613
611
  ```typescript
614
- batch(doc, (draft) => {
612
+ change(doc, (draft) => {
615
613
  // Find and mutate pattern - very common!
616
614
  const todo = draft.todos.find((t) => t.id === "123");
617
615
  if (todo) {
@@ -627,7 +625,7 @@ batch(doc, (draft) => {
627
625
  });
628
626
  ```
629
627
 
630
- This dual interface ensures predicates work with current data (including previous mutations in the same `batch()` block) while returned objects remain mutable.
628
+ This dual interface ensures predicates work with current data (including previous mutations in the same `change()` block) while returned objects remain mutable.
631
629
 
632
630
  ### Movable List Operations
633
631
 
@@ -690,7 +688,7 @@ interface TodoDoc {
690
688
  const todoSchema = Shape.doc({
691
689
  title: Shape.text(),
692
690
  todos: Shape.list(
693
- Shape.plain.object({
691
+ Shape.plain.struct({
694
692
  id: Shape.plain.string(),
695
693
  text: Shape.plain.string(),
696
694
  done: Shape.plain.boolean(),
@@ -699,10 +697,10 @@ const todoSchema = Shape.doc({
699
697
  });
700
698
 
701
699
  // TypeScript will ensure the schema produces the correct type
702
- const doc = new TypedDoc(todoSchema);
700
+ const doc = createTypedDoc(todoSchema);
703
701
 
704
702
  // Mutations are type-safe
705
- batch(doc, (draft) => {
703
+ change(doc, (draft) => {
706
704
  draft.title.insert(0, "Hello"); // ✅ Valid - TypeScript knows this is LoroText
707
705
  draft.todos.push({
708
706
  // ✅ Valid - TypeScript knows the expected shape
@@ -754,8 +752,8 @@ The `TypedPresence` class provides type-safe access to ephemeral presence data w
754
752
  import { TypedPresence, Shape } from "@loro-extended/change";
755
753
 
756
754
  // Define a presence schema with placeholders
757
- const PresenceSchema = Shape.plain.object({
758
- cursor: Shape.plain.object({
755
+ const PresenceSchema = Shape.plain.struct({
756
+ cursor: Shape.plain.struct({
759
757
  x: Shape.plain.number(),
760
758
  y: Shape.plain.number(),
761
759
  }),
@@ -806,7 +804,7 @@ This is typically provided by `UntypedDocHandle.presence` in `@loro-extended/rep
806
804
 
807
805
  ## Performance Considerations
808
806
 
809
- - All changes within a `batch()` call are batched into a single transaction
807
+ - All changes within a `change()` call are batched into a single transaction
810
808
  - Empty state overlay is computed on-demand, not stored
811
809
  - Container creation is lazy - containers are only created when accessed
812
810
  - Type validation occurs at development time, not runtime
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loro-extended/change",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A schema-driven, type-safe wrapper for Loro CRDT that provides natural JavaScript syntax for collaborative data mutations",
5
5
  "author": "Duane Johnson",
6
6
  "license": "MIT",