-
50c0083: # PlainValueRef: Reactive subscriptions for plain values
Plain value properties (from
Shape.plain.*) now returnPlainValueRef<T>instead of raw values. This enables reactive subscriptions viauseValue()andsubscribe().value(ref)- Get current value from PlainValueRef, TypedRef, or TypedDocuseValue(doc.meta.title)- Now works with plain value propertiessubscribe(doc.meta.title, cb)- Now works with plain value properties
Plain value property access now returns
PlainValueRef<T>instead ofT:// Before const title: string = doc.meta.title; // After const title: PlainValueRef<string> = doc.meta.title; const titleValue: string = value(doc.meta.title);
Strict equality comparisons become TypeScript errors (guiding correct usage):
// Before (worked) if (doc.meta.title === "foo") { ... } // After (type error - use value()) if (value(doc.meta.title) === "foo") { ... }
Template literals, string concatenation, and JSON serialization work transparently:
console.log(`Title: ${doc.meta.title}`); // Works via valueOf() JSON.stringify(doc.meta.title); // Works via toJSON()
doc.meta.title = "new value"; // Still works
-
29853c3: # Breaking: Major API Simplification
This release introduces significant breaking changes to simplify the loro-extended API. The changes consolidate mutation patterns, simplify native Loro access, and remove redundant APIs.
Handle.change()removed - Usechange(handle.doc, fn)insteadloro()now returns native types directly - No more.docor.containerindirectionext(ref).change()removed - Usechange(ref, fn)insteadgetLoroDoc()removed - Useloro(doc)insteadloro(ref).docremoved - Useext(ref).docinsteadloro(ref).containerremoved - Useloro(ref)directly
The
Handle.change()method has been removed from@loro-extended/repoto narrow its focus as a handle. Use thechange()functional helper instead.Before:
handle.change((draft) => { draft.title.insert(0, "Hello"); draft.count.increment(5); });
After:
import { change } from "@loro-extended/change"; change(handle.doc, (draft) => { draft.title.insert(0, "Hello"); draft.count.increment(5); });
The
loro()function now returns native Loro types directly, without the.docor.containerindirection.Before:
// For TypedDoc const loroDoc = loro(doc).doc; const frontiers = loro(doc).doc.frontiers(); loro(doc).doc.subscribe(callback); loro(doc).doc.import(bytes); // For TypedRef const loroText = loro(textRef).container; const loroList = loro(listRef).container;
After:
// For TypedDoc - loro() returns LoroDoc directly const loroDoc = loro(doc); const frontiers = loro(doc).frontiers(); loro(doc).subscribe(callback); loro(doc).import(bytes); // For TypedRef - loro() returns the container directly const loroText = loro(textRef); // Returns LoroText const loroList = loro(listRef); // Returns LoroList
The
change()method has been deprecated from theloro()namespace for refs. Use thechange()functional helper instead.Before:
loro(ref).change((draft) => { // mutations });
After:
import { change } from "@loro-extended/change"; change(ref, (draft) => { // mutations });
The
getLoroDoc()function has been removed. Useloro(doc)directly.Before:
import { getLoroDoc } from "@loro-extended/change"; const loroDoc = getLoroDoc(typedDoc);
After:
import { loro } from "@loro-extended/change"; const loroDoc = loro(typedDoc);
To get the underlying
LoroDocfrom a ref, useext(ref).docinstead ofloro(ref).doc. This belongs onext()because loro's native containers don't point back to their LoroDoc.Before:
const loroDoc = loro(textRef).doc;
After:
import { ext } from "@loro-extended/change"; const loroDoc = ext(textRef).doc;
-
Update imports:
// Add these imports where needed import { change, loro, ext } from "@loro-extended/change";
-
Replace
handle.change(fn)withchange(handle.doc, fn):# Find all usages grep -r "handle\.change(" --include="*.ts" --include="*.tsx"
-
Replace
loro(x).docwithloro(x):# Find all usages grep -r "loro(.*).doc" --include="*.ts" --include="*.tsx"
-
Replace
loro(ref).containerwithloro(ref):# Find all usages grep -r "loro(.*).container" --include="*.ts" --include="*.tsx"
-
Replace
getLoroDoc(x)withloro(x):# Find all usages grep -r "getLoroDoc(" --include="*.ts" --include="*.tsx"
-
Replace
loro(ref).docwithext(ref).doc:# For refs (not docs), use ext() to access the LoroDoc # Before: loro(textRef).doc # After: ext(textRef).doc
Old Pattern New Pattern handle.change(fn)change(handle.doc, fn)loro(doc).docloro(doc)loro(doc).doc.frontiers()loro(doc).frontiers()loro(doc).doc.subscribe(cb)loro(doc).subscribe(cb)loro(doc).doc.import(bytes)loro(doc).import(bytes)loro(doc).doc.export(opts)loro(doc).export(opts)loro(ref).containerloro(ref)loro(ref).docext(ref).docgetLoroDoc(doc)loro(doc)ext(ref).change(fn)change(ref, fn)
The
change(doc, fn)functional helper is the canonical way to mutate documents:import { change } from "@loro-extended/change"; // Mutate a TypedDoc change(doc, (draft) => { draft.title.insert(0, "Hello"); draft.count.increment(5); draft.items.push("new item"); }); // Mutate via a Handle change(handle.doc, (draft) => { draft.title.insert(0, "Hello"); });
Note:
ext(doc).change(fn)is also available for method-chaining scenarios, butchange(doc, fn)is preferred.Use
loro()to access native Loro types:import { loro } from "@loro-extended/change"; // Get LoroDoc from TypedDoc const loroDoc = loro(doc); const frontiers = loro(doc).frontiers(); const version = loro(doc).version(); // Get native containers from refs const loroText: LoroText = loro(doc.title); const loroList: LoroList = loro(doc.items); const loroCounter: LoroCounter = loro(doc.count);
Use
ext()for loro-extended-specific features:import { ext } from "@loro-extended/change"; // Document-level features ext(doc).fork(); // Fork the TypedDoc ext(doc).forkAt(frontiers); // Fork TypedDoc at specific version ext(doc).shallowForkAt(frontiers); // Shallow fork of TypedDoc ext(doc).initialize(); // Initialize metadata ext(doc).applyPatch(patch); // Apply JSON patch ext(doc).docShape; // Get the schema ext(doc).rawValue; // Get raw JSON value, no overlay or diff ext(doc).mergeable; // Check mergeable flag // Ref-level features ext(ref).doc; // Get LoroDoc from any ref ext(listRef).pushContainer(c); // Push container to list ext(listRef).insertContainer(i, c); // Insert container at index ext(mapRef).setContainer(key, c); // Set container on map // Subscriptions via subscribe() functional helper subscribe(doc, callback); // Subscribe to all document changes subscribe(doc, (p) => p.config.theme, callback); // Subscribe to specific path subscribe(ref, callback); // Subscribe to container changes // Or use loro() for native Loro subscription access loro(doc).subscribe(callback); // Native LoroDoc subscription
These changes simplify the API by:
- Consolidating mutation patterns - One canonical way to mutate:
change(doc, fn) - Removing indirection -
loro()returns native types directly, no.docor.container - Clear separation -
loro()for native Loro access,ext()for loro-extended features - Reducing cognitive load - Fewer ways to do the same thing
The previous API had multiple ways to mutate documents (
handle.change(),ext(doc).change(),change(doc, fn)) and required extra property access to get native types (loro(doc).doc). The new API is more consistent and easier to learn.
-
a3f151f: feat: Simplified React API with doc-first design
This release simplifies the React API by making the document the primary interface:
New API:
// Get doc directly (no Handle intermediary) const doc = useDocument(docId, schema); // Subscribe to values (returns value directly) const title = useValue(doc.title); // string const snapshot = useValue(doc); // Infer<D> // Placeholder access (rare) const placeholder = usePlaceholder(doc.title); // Mutate directly doc.title.insert(0, "Hello"); // Sync/network access (rare) import { sync } from "@loro-extended/repo"; sync(doc).peerId; await sync(doc).waitForSync(); sync(doc).presence.setSelf({ status: "online" });
Key Changes:
repo.get()now returnsDoc<D>directly (TypedDoc with sync capabilities)repo.get()now caches documents and throws on schema mismatchuseDocument(docId, schema)is the primary React hookuseValue(ref)returns value directly (not wrapped in object)usePlaceholder(ref)for placeholder accesssync(doc)provides access to peerId, readyStates, waitForSync, ephemeral storessyncandhasSyncare now re-exported from@loro-extended/react
Deprecations:
useHandle— useuseDocumentinsteaduseDoc(handle)— useuseValue(doc)for snapshotsuseRefValue— useuseValueinstead (returns value directly)Handletype — still exported but deprecatedrepo.getHandle()— userepo.get()instead
Migration:
// Before const handle = useHandle(docId, schema); const snapshot = useDoc(handle); const { value, placeholder } = useRefValue(handle.doc.title); handle.doc.title.insert(0, "Hello"); // After const doc = useDocument(docId, schema); const snapshot = useValue(doc); const title = useValue(doc.title); const placeholder = usePlaceholder(doc.title); doc.title.insert(0, "Hello");
-
5039c52: Add
useDocIdFromHashhook for syncing document ID with URL hashThis hook enables shareable URLs where the hash contains the document ID (e.g.,
https://app.example.com/#doc-abc123).Features:
- Uses
useSyncExternalStorefor concurrent mode safety - SSR-safe with server snapshot support
- Automatically writes hash on mount if empty
- Caches generated default ID across renders
Also exports pure utility functions
parseHash()andgetDocIdFromHash()for testing and custom implementations.import { useDocIdFromHash, useDocument } from "@loro-extended/react"; import { generateUUID } from "@loro-extended/repo"; function App() { const docId = useDocIdFromHash(() => generateUUID()); const doc = useDocument(docId, MySchema); // ... }
- Uses
- Updated dependencies [f90c7f7]
- Updated dependencies [50c0083]
- Updated dependencies [a3f151f]
- Updated dependencies [29853c3]
- Updated dependencies [5039c52]
- @loro-extended/repo@6.0.0-beta.0
- @loro-extended/hooks-core@6.0.0-beta.0
- @loro-extended/hooks-core@5.4.2
- @loro-extended/repo@5.4.2
- @loro-extended/hooks-core@5.4.1
- @loro-extended/repo@5.4.1
-
cab74a3: Externalize
loro-crdtfrom bundle output to fix Bun compatibilityAdded
external: ["loro-crdt"]to tsup configs for all core packages. This preventsloro-crdtfrom being bundled into the dist output, allowing bundlers like Bun to resolve it separately and handle WASM initialization correctly.This fixes the
examples/todo-minimalexample which uses Bun's bundler and was failing due to top-level await issues whenloro-crdtwas bundled inline. -
Updated dependencies [b2614e6]
-
Updated dependencies [cab74a3]
-
Updated dependencies [a532f43]
- @loro-extended/repo@5.4.0
- @loro-extended/hooks-core@5.4.0
-
de27b84: Add automatic cursor restoration and namespace-based undo
- Cursor restoration now works automatically when using
useCollaborativeTextwithuseUndoManager - Cursor position is stored with container ID in
onPush, restored to correct element inonPop - Add namespace support to scope undo/redo to specific groups of fields
- Namespaces use
LoroDoc.setNextCommitOrigin()andUndoManager.excludeOriginPrefixes - Add
cursorRestorationconfig option toRepoProvider(default: true)
// Namespace-based undo const { undo: undoHeader } = useUndoManager(handle, "header") const { undo: undoBody } = useUndoManager(handle, "body") // Assign fields to namespaces <CollaborativeInput textRef={titleRef} undoNamespace="header" /> <CollaborativeTextarea textRef={descriptionRef} undoNamespace="body" />
- When
undoNamespace="header"is set, changes calldoc.setNextCommitOrigin("loro-extended:ns:header")before commit - The "header" UndoManager has
excludeOriginPrefixes: ["loro-extended:ns:body", ...]to ignore other namespaces - Cursor position is stored with the container ID of the focused element
- On undo, the cursor is restored to the element matching the stored container ID
Apps using manual cursor tracking via
getCursors/setCursorscan remove that code - it's now automatic. To opt-out:<RepoProvider config={{ cursorRestoration: false }}>
- Cursor restoration now works automatically when using
-
8fffae6: Add
useRefValuehook for fine-grained subscriptions to typed refsThe new
useRefValuehook subscribes to a single typed ref (TextRef, ListRef, CounterRef, etc.) and returns its current value. This provides:- No prop drilling - Components only need the ref, not value + placeholder
- Automatic placeholder - Extracts placeholder from
Shape.text().placeholder() - Fine-grained subscriptions - Only re-renders when this specific container changes
- Type-safe - Return type is inferred from the ref type
Example usage:
import { useRefValue, type TextRef } from "@loro-extended/react"; function ControlledInput({ textRef }: { textRef: TextRef }) { // No need to pass value or placeholder as props! const { value, placeholder } = useRefValue(textRef); return ( <input value={value} placeholder={placeholder} onChange={(e) => textRef.update(e.target.value)} /> ); }
This is particularly useful for building controlled inputs without the prop drilling required when using
useDocat the parent level. -
Updated dependencies [c97a468]
-
Updated dependencies [5a87c2b]
-
Updated dependencies [de27b84]
-
Updated dependencies [790e1eb]
-
Updated dependencies [de27b84]
-
Updated dependencies [8fffae6]
-
Updated dependencies [8fffae6]
- @loro-extended/repo@5.3.0
- @loro-extended/hooks-core@5.3.0
- Updated dependencies [6048f48]
- @loro-extended/repo@5.2.0
- @loro-extended/hooks-core@5.2.0
- @loro-extended/hooks-core@5.1.0
- @loro-extended/repo@5.1.0
- Updated dependencies [f254aa2]
- @loro-extended/repo@5.0.0
- @loro-extended/hooks-core@5.0.0
- Updated dependencies [14b9193]
- Updated dependencies [37cdd5e]
- Updated dependencies [c3e5d1f]
- @loro-extended/repo@4.0.0
- @loro-extended/hooks-core@4.0.0
- Updated dependencies [d893fe9]
- Updated dependencies [786b8b1]
- Updated dependencies [8061a20]
- Updated dependencies [cf064fa]
- Updated dependencies [1b2a3a4]
- Updated dependencies [702871b]
- Updated dependencies [27cdfb7]
- @loro-extended/repo@3.0.0
- @loro-extended/hooks-core@3.0.0
-
977922e: # Unified Ephemeral Store System v2
This release implements a major refactor of the ephemeral (presence) store system, providing a unified API for managing ephemeral data across documents.
TypedDocHandleremoved - UseHandleorHandleWithEphemeralsinsteadUntypedDocHandleremoved - UseHandlewithShape.any()for untyped documentsusePresence(handle)deprecated - UseuseEphemeral(handle.presence)insteadhandle.presence.set(value)changed - Usehandle.presence.setSelf(value)insteadhandle.presence.allremoved - Use{ self, peers }fromuseEphemeral()or accesshandle.presence.selfandhandle.presence.peersdirectly
Shape.map()deprecated - UseShape.struct()for CRDT container structsShape.plain.object()deprecated - UseShape.plain.struct()for plain value structs
The third argument to
repo.get()now expects anEphemeralDeclarationsobject:// Before const handle = repo.get(docId, DocSchema, PresenceSchema); // After const handle = repo.get(docId, DocSchema, { presence: PresenceSchema });
All handle types are now unified into a single
Handle<D, E>class:docis always aTypedDoc<D>(useShape.any()for untyped)- Ephemeral stores are accessed as properties via the declarations
- Full sync infrastructure (readyStates, waitUntilReady, etc.)
You can now declare multiple ephemeral stores per document for bandwidth isolation:
const handle = repo.get(docId, DocSchema, { mouse: MouseShape, // High-frequency updates profile: ProfileShape, // Low-frequency updates }); handle.mouse.setSelf({ x: 100, y: 200 }); handle.profile.setSelf({ name: "Alice" });
New unified interface for ephemeral stores:
interface TypedEphemeral<T> { // Core API set(key: string, value: T): void; get(key: string): T | undefined; getAll(): Map<string, T>; delete(key: string): void; // Convenience API for per-peer pattern readonly self: T | undefined; setSelf(value: T): void; readonly peers: Map<string, T>; // Subscription subscribe(cb: (event) => void): () => void; // Escape hatch readonly raw: EphemeralStore; }
Libraries can register their own ephemeral stores for network sync:
const externalStore = new LibraryEphemeralStore(); handle.addEphemeral("library-data", externalStore);
New hook for subscribing to ephemeral store changes:
const { self, peers } = useEphemeral(handle.presence);
// Before const MessageSchema = Shape.map({ id: Shape.plain.string(), content: Shape.text(), }); const PresenceSchema = Shape.plain.object({ name: Shape.plain.string(), }); // After const MessageSchema = Shape.struct({ id: Shape.plain.string(), content: Shape.text(), }); const PresenceSchema = Shape.plain.struct({ name: Shape.plain.string(), }); const EphemeralDeclarations = { presence: PresenceSchema, };
// Before const handle = repo.get(docId, DocSchema, PresenceSchema); const { self, peers } = usePresence(handle); handle.presence.set({ name: "Alice" }); // After const handle = repo.get(docId, DocSchema, { presence: PresenceSchema }); const { self, peers } = useEphemeral(handle.presence); handle.presence.setSelf({ name: "Alice" });
// Before import { TypedDocHandle } from "@loro-extended/repo"; const handle = new TypedDocHandle(untypedHandle, DocSchema, PresenceSchema); // After import { HandleWithEphemerals } from "@loro-extended/repo"; const handle = repo.get(docId, DocSchema, { presence: PresenceSchema });
- Updated dependencies [686006d]
- Updated dependencies [ccdca91]
- Updated dependencies [ae0ed28]
- Updated dependencies [a901004]
- Updated dependencies [977922e]
- @loro-extended/repo@2.0.0
- @loro-extended/hooks-core@2.0.0
- Updated dependencies [4896d83]
- @loro-extended/repo@1.1.0
- @loro-extended/hooks-core@1.1.0
- Updated dependencies [f982d45]
- @loro-extended/repo@1.0.1
- @loro-extended/hooks-core@1.0.1
-
db55b58: ## Breaking Change: New Handle-First Hooks API
This release introduces a completely new hooks API that provides better separation of concerns, improved type safety, and more predictable behavior.
// Get a stable handle (never re-renders) const handle = useHandle(docId, docSchema); // or with presence const handle = useHandle(docId, docSchema, presenceSchema); // Subscribe to document changes (reactive) const doc = useDoc(handle); // or with selector for fine-grained updates const title = useDoc(handle, (d) => d.title); // Subscribe to presence changes (reactive) const { self, peers } = usePresence(handle); // Mutate via handle handle.change((d) => { d.title = "new"; }); handle.presence.set({ cursor: { x: 10, y: 20 } });
Before:
const [doc, changeDoc, handle] = useDocument(docId, schema); changeDoc((d) => { d.title = "new"; }); const { peers, self, setSelf } = usePresence(docId, PresenceSchema); setSelf({ cursor: { x: 10, y: 20 } });
After:
const handle = useHandle(docId, schema, PresenceSchema); const doc = useDoc(handle); const { self, peers } = usePresence(handle); handle.change((d) => { d.title = "new"; }); handle.presence.set({ cursor: { x: 10, y: 20 } });
The following hooks have been removed:
useDocument- UseuseHandle+useDocinsteaduseUntypedDocument- Userepo.get(docId)for untyped accessuseUntypedPresence- UseuseHandlewith a presence schemauseDocHandleState,useDocChanger,useTypedDocState,useTypedDocChanger,useRawLoroDoc,useUntypedDocChanger
- Stable handle reference -
useHandlereturns a stable reference that never changes, preventing unnecessary re-renders - Separation of concerns - Document access and mutations are clearly separated
- Fine-grained reactivity - Use selectors with
useDocto only re-render when specific data changes - Unified presence - Presence is now tied to the handle, making it easier to manage
- Better TypeScript support - Improved type inference throughout
-
5d8cfdb: # Grand Unified API v3: Proxy-based TypedDoc with $ namespace
This release transforms the
@loro-extended/changeAPI to provide a cleaner, more intuitive interface for working with typed Loro documents.TypedDoc is now a Proxy that allows direct access to schema properties:
// Before (old API) doc.value.title.insert(0, "Hello") doc.value.count.increment(5) doc.batch(draft => { ... }) doc.loroDoc // After (new API) doc.title.insert(0, "Hello") doc.count.increment(5) batch(doc, draft => { ... }) getLoroDoc(doc)
All internal meta-operations can be accessed via the
$property:doc.$.batch(fn)- Batch multiple mutations into a single transactiondoc.$.change(fn)- Deprecated alias forbatch()doc.$.rawValue- Get raw CRDT state without placeholdersdoc.$.loroDoc- Access underlying LoroDoc
Schema properties are accessed directly on the doc object:
// Direct mutations - commit immediately doc.title.insert(0, "Hello"); doc.count.increment(5); doc.users.set("alice", { name: "Alice" }); // Check existence doc.users.has("alice"); // true "alice" in doc.users; // true (via Proxy has trap)
-
Replace
doc.value.withdoc.:doc.value.title→doc.titledoc.value.count→doc.count
-
Replace
doc.meta-operations withbatch()andgetLoroDoc()(preferred), or if needed, you can reach into internal properties:doc.batch()→doc.$.batch()doc.change()→doc.$.change()(deprecated, use$.batch())doc.rawValue→doc.$.rawValuedoc.loroDoc→doc.$.loroDoc
- Updated
TypedDocHandleto use new API internally - Updated
useDochook types to useInfer<D>instead ofDeepReadonly<Infer<D>>
- Updated dependencies [5d8cfdb]
- Updated dependencies [db55b58]
- Updated dependencies [dafd365]
- @loro-extended/hooks-core@1.0.0
- @loro-extended/repo@1.0.0
- @loro-extended/hooks-core@0.9.1
- @loro-extended/repo@0.9.1
-
702af3c: Renamed internal DraftNode classes to TypedRef for clarity:
DraftNode→TypedRefDraftNodeParams→TypedRefParamsDraftDoc→DocRefMapDraftNode→MapRefListDraftNode→ListRefListDraftNodeBase→ListRefBaseRecordDraftNode→RecordRefTextDraftNode→TextRefCounterDraftNode→CounterRefMovableListDraftNode→MovableListRefTreeDraftNode→TreeRef
Added
Mutable<T>type alias (replacesDraft<T>).Draft<T>is now deprecated but still exported for backward compatibility.Added
InferMutableType<T>type alias (replacesInferDraftType<T>).InferDraftType<T>is now deprecated but still exported for backward compatibility.The
draft-nodes/directory is nowtyped-refs/.The
Shapeinterface now uses_mutableinstead of_draftfor the mutable type parameter.Added consistent readonly enforcement to all TypedRef mutation methods:
TextRef:insert,delete,update,mark,unmark,applyDeltaCounterRef:increment,decrementTreeRef:createNode,move,delete
-
Updated dependencies [9ba361d]
-
Updated dependencies [10b8a07]
-
Updated dependencies [d9ea24e]
-
Updated dependencies [702af3c]
- @loro-extended/repo@0.9.0
- @loro-extended/hooks-core@0.9.0
- a6d3fc8: Need to publish hooks-core
- Updated dependencies [a6d3fc8]
- @loro-extended/hooks-core@0.8.1
- @loro-extended/repo@0.8.1
-
1a80326: Remove use of emptyState and required emptyState params for TypedDoc and useDocument. Instead, you can optionally annotate your Shape schema with
.placeholder()values if you need a placeholder when the underlying LoroDoc has no value. A placeholder is like a default value, but stops existing as soon as the property is mutated. -
907cdce: Remove
emptyStateparameter from TypedPresence and usePresence. Instead, use.placeholder()annotations on your schema to define default values.The
emptyStateparameter has been removed from:TypedPresenceconstructorDocHandle.presence()methodusePresencehook
Before:
const PresenceSchema = Shape.plain.object({ name: Shape.plain.string(), cursor: Shape.plain.object({ x: Shape.plain.number(), y: Shape.plain.number(), }), }); const EmptyPresence = { name: "Anonymous", cursor: { x: 0, y: 0 }, }; // Usage const presence = handle.presence(PresenceSchema, EmptyPresence); const { self } = usePresence(docId, PresenceSchema, EmptyPresence);
After:
const PresenceSchema = Shape.plain.object({ name: Shape.plain.string().placeholder("Anonymous"), cursor: Shape.plain.object({ x: Shape.plain.number(), // default 0 y: Shape.plain.number(), // default 0 }), }); // Usage - no emptyState needed! const presence = handle.presence(PresenceSchema); const { self } = usePresence(docId, PresenceSchema);
Placeholder values are automatically derived from the schema. Use
.placeholder()on individual shapes to customize default values. Shapes without explicit.placeholder()use sensible defaults:Shape.plain.string()→""Shape.plain.number()→0Shape.plain.boolean()→falseShape.plain.object({...})→ recursively derived from nested shapesShape.plain.record(...)→{}Shape.plain.array(...)→[]
- Updated dependencies [907cdce]
- @loro-extended/repo@0.8.0
- @loro-extended/hooks-core@0.2.0
- Updated dependencies [a26a6c2]
- Updated dependencies [0879e51]
- @loro-extended/repo@0.7.0
- b9da0e9: Prevent empty state in useDocument or TypedDoc where empty state includes invalid state--for example, in
RecordorListShape types. The type system previously implied you could pre-populate a list or record with empty state. This is not the case--empty state is not merged in for shape types that do not have pre-defined keys.
- Updated dependencies [c67e26c]
- Updated dependencies [76a18ba]
- @loro-extended/repo@0.6.0
- Updated dependencies [9b291dc]
- Updated dependencies [204fda2]
- @loro-extended/repo@0.5.0
- Accurate and fast presence updates
- Updated dependencies
- @loro-extended/repo@0.4.0
- 6d95249: Consistent ReadyState and additional tests
- Updated dependencies [6d95249]
- @loro-extended/repo@0.3.0
- Release 0.2.0
- Updated dependencies
- @loro-extended/repo@0.2.0