Skip to content

Commit bc30a92

Browse files
committed
fix: ensure is assigned
1 parent 744021f commit bc30a92

File tree

3 files changed

+48
-29
lines changed

3 files changed

+48
-29
lines changed

packages/core/src/core/mirror.ts

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -817,12 +817,10 @@ export class Mirror<S extends SchemaType> {
817817
this.registerContainerWithRegistry(container.id, fieldSchema);
818818

819819
// Inject $cid for root maps into pending state immediately
820-
if (fieldSchema && isLoroMapSchema(fieldSchema) && pendingState) {
820+
if (pendingState && container.kind() === "Map") {
821821
const rootObj = pendingState as Record<string, unknown>;
822822
const child = rootObj[keyStr];
823-
if (isObject(child)) {
824-
child[CID_KEY] = container.id;
825-
}
823+
this.stampCid(child, container.id);
826824
}
827825

828826
// Apply direct changes to the container
@@ -870,13 +868,7 @@ export class Mirror<S extends SchemaType> {
870868
value,
871869
);
872870
// Stamp $cid into the pendingState value for child maps
873-
if (
874-
schema &&
875-
isLoroMapSchema(schema) &&
876-
isObject(value)
877-
) {
878-
value[CID_KEY] = inserted.id;
879-
}
871+
this.stampCid(value, inserted.id);
880872
} else if (kind === "delete") {
881873
map.delete(key as string);
882874
} else {
@@ -974,13 +966,7 @@ export class Mirror<S extends SchemaType> {
974966
this.registerContainer(newContainer.id, schema);
975967
this.initializeContainer(newContainer, schema, value);
976968
// Stamp $cid into pending state when replacing with a map container
977-
if (
978-
schema &&
979-
isLoroMapSchema(schema) &&
980-
isObject(value)
981-
) {
982-
value[CID_KEY] = newContainer.id;
983-
}
969+
this.stampCid(value, newContainer.id);
984970
} else {
985971
throw new Error();
986972
}
@@ -1407,8 +1393,8 @@ export class Mirror<S extends SchemaType> {
14071393

14081394
this.initializeContainer(insertedContainer, schema, value);
14091395
// Stamp $cid for child maps directly on the provided value (pending state)
1410-
if (schema && isLoroMapSchema(schema) && isObject(value)) {
1411-
value[CID_KEY] = insertedContainer.id;
1396+
if (insertedContainer.kind() === "Map") {
1397+
this.stampCid(value, insertedContainer.id);
14121398
}
14131399
return insertedContainer;
14141400
}
@@ -1549,8 +1535,8 @@ export class Mirror<S extends SchemaType> {
15491535

15501536
this.initializeContainer(insertedContainer, schema, value);
15511537
// Stamp $cid for list item maps directly on the provided value (pending state)
1552-
if (schema && isLoroMapSchema(schema) && isObject(value)) {
1553-
value[CID_KEY] = insertedContainer.id;
1538+
if (insertedContainer.kind() === "Map") {
1539+
this.stampCid(value, insertedContainer.id);
15541540
}
15551541
return insertedContainer;
15561542
}
@@ -1806,16 +1792,13 @@ export class Mirror<S extends SchemaType> {
18061792
if (kind === "Map") {
18071793
const m = c as LoroMap;
18081794
const obj: JSONObject = {};
1795+
obj[CID_KEY] = String(c.id);
18091796
for (const k of m.keys()) {
18101797
const v = m.get(k);
18111798
obj[k] = isContainer(v)
18121799
? this.containerToStateJson(v)
18131800
: (v as JSONValue);
18141801
}
1815-
const schema = this.getContainerSchema(c.id);
1816-
if (schema && isLoroMapSchema(schema)) {
1817-
obj[CID_KEY] = c.id;
1818-
}
18191802
return obj;
18201803
} else if (kind === "List" || kind === "MovableList") {
18211804
const arr: JSONValue[] = [];
@@ -1945,6 +1928,11 @@ export class Mirror<S extends SchemaType> {
19451928
});
19461929
}
19471930

1931+
private stampCid(target: unknown, cid: ContainerID) {
1932+
if (!isObject(target)) return;
1933+
(target as Record<string, unknown>)[CID_KEY] = cid;
1934+
}
1935+
19481936
private getSchemaForMapKey(
19491937
schema:
19501938
| LoroMapSchema<Record<string, SchemaType>>
@@ -2035,6 +2023,12 @@ export function toNormalizedJson(doc: LoroDoc) {
20352023
return normalizeTreeNodes(v.toJSON()) as unknown as typeof v;
20362024
}
20372025

2026+
if (isContainer(v) && v.kind() === "Map") {
2027+
const obj = (v as LoroMap).getShallowValue();
2028+
obj[CID_KEY] = v.id;
2029+
return obj;
2030+
}
2031+
20382032
return v;
20392033
});
20402034
}

packages/core/tests/cid.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,4 +484,29 @@ describe("$cid: state injection and write ignoring (always-on for LoroMap)", ()
484484
]);
485485
expect(typeof st.tree[0].data[CID_KEY]).toBe("string");
486486
});
487+
488+
it("$cid is assigned to LoroMap inside LoroList even with schema", async () => {
489+
const doc = new LoroDoc();
490+
doc.getList("list").pushContainer(new LoroMap());
491+
const m = new Mirror({
492+
doc,
493+
schema: schema({
494+
list: schema.LoroList(
495+
schema.LoroMap({ title: schema.String() }),
496+
),
497+
}),
498+
});
499+
console.log(m.getState());
500+
const id = (m.getState() as any)["list"][0].$cid;
501+
expect(typeof id === "string").toBeTruthy();
502+
});
503+
504+
it("$cid is assigned to LoroMap inside LoroList even without schema", async () => {
505+
const doc = new LoroDoc();
506+
doc.getList("list").pushContainer(new LoroMap());
507+
const m = new Mirror({ doc });
508+
console.log(m.getState());
509+
const id = (m.getState() as any)["list"][0].$cid;
510+
expect(typeof id === "string").toBeTruthy();
511+
});
487512
});

packages/core/tests/null-in-map-consistency.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe("setState consistency with null fields in LoroMap", () => {
3838
});
3939

4040
// Sanity: mirror picks up the null from doc (ignoring $cid, which is app-only)
41-
expect(stripCid(mirror.getState())).toEqual(toNormalizedJson(doc));
41+
expect(mirror.getState()).toEqual(toNormalizedJson(doc));
4242
console.log(JSON.stringify(doc.toJSON(), null, 2));
4343

4444
// Update another field (unrelated) to force a diff run
@@ -50,7 +50,7 @@ describe("setState consistency with null fields in LoroMap", () => {
5050
}).not.toThrow();
5151

5252
// State remains in sync with doc (ignoring $cid)
53-
expect(stripCid(mirror.getState())).toEqual(toNormalizedJson(doc));
53+
expect(mirror.getState()).toEqual(toNormalizedJson(doc));
5454
// And the original null is preserved
5555
expect((mirror.getState() as any).m.nested).toBeNull();
5656
});
@@ -83,7 +83,7 @@ describe("setState consistency with null fields in LoroMap", () => {
8383
expect(() => {
8484
mirror.setState((s) => s);
8585
}).not.toThrow();
86-
expect(stripCid(mirror.getState())).toEqual(toNormalizedJson(doc));
86+
expect(mirror.getState()).toEqual(toNormalizedJson(doc));
8787
expect((mirror.getState() as any).root.list[0].child).toBeNull();
8888
});
8989
});

0 commit comments

Comments
 (0)