Skip to content

Commit 58b2dc9

Browse files
committed
Remove maps and create insert function
1 parent 86922f7 commit 58b2dc9

File tree

9 files changed

+179
-300
lines changed

9 files changed

+179
-300
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ const orbit = await createOrbitDB({ ipfs })
4949

5050
const db = await orbitdb.open({ type: "nested" });
5151

52-
await db.put({ a: { b: 1, c: 2 } });
53-
await db.put({ d: 3 });
52+
await db.insert({ a: { b: 1 }, d: 3 });
53+
await db.insert("a", { c: 2 });
5454

5555
const all = await db.all(); // { a: { b: 1, c: 2}, d: 3 }
5656

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"@orbitdb/core": "^3.0.2",
3030
"@orbitdb/ordered-keyvalue-db": "^1.3.5",
3131
"helia": "^5.4.2",
32-
"it-all": "^3.0.9"
32+
"it-all": "^3.0.9",
33+
"ts-deepmerge": "^7.0.3"
3334
},
3435
"devDependencies": {
3536
"@chainsafe/libp2p-gossipsub": "^14.1.1",

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/nested.ts

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,21 @@ import type {
1010
InternalDatabase,
1111
} from "@orbitdb/core";
1212
import type { HeliaLibp2p } from "helia";
13-
import type { NestedKey, NestedValue, PossiblyNestedValue } from "./types";
14-
import { flatten, isSubkey, joinKey, splitKey, toNested } from "./utils.js";
13+
import type {
14+
NestedKey,
15+
NestedValue,
16+
NestedValueWithUndefined,
17+
PossiblyNestedValue,
18+
} from "./types";
19+
import {
20+
flatten,
21+
isNestedValue,
22+
isSubkey,
23+
joinKey,
24+
removeUndefineds,
25+
splitKey,
26+
toNested,
27+
} from "./utils.js";
1528
import type { Libp2p } from "libp2p";
1629
import type { ServiceMap } from "@libp2p/interface";
1730

@@ -66,16 +79,16 @@ const Nested =
6679
onUpdate,
6780
});
6881

69-
const { put, set, putNested, setNested, get, del, iterator, all } =
70-
NestedApi({ database });
82+
const { put, set, insert, get, del, iterator, all } = NestedApi({
83+
database,
84+
});
7185

7286
return {
7387
...database,
7488
type,
7589
put,
7690
set,
77-
putNested,
78-
setNested,
91+
insert,
7992
get,
8093
del,
8194
iterator,
@@ -93,12 +106,12 @@ export const NestedApi = ({ database }: { database: InternalDatabase }) => {
93106
value: DagCborEncodable,
94107
): Promise<string> => {
95108
const joinedKey = typeof key === "string" ? key : joinKey(key);
96-
return addOperation({ op: "PUT", key: joinedKey, value });
109+
return await addOperation({ op: "PUT", key: joinedKey, value });
97110
};
98111

99112
const del = async (key: NestedKey): Promise<string> => {
100113
const joinedKey = typeof key === "string" ? key : joinKey(key);
101-
return addOperation({ op: "DEL", key: joinedKey, value: null });
114+
return await addOperation({ op: "DEL", key: joinedKey, value: null });
102115
};
103116

104117
const get = async (
@@ -123,25 +136,31 @@ export const NestedApi = ({ database }: { database: InternalDatabase }) => {
123136
return nested;
124137
};
125138

126-
type PutNestedFunction = {
127-
(object: NestedValue): Promise<string[]>;
128-
(key: string, object: NestedValue): Promise<string[]>;
139+
type InsertFunction = {
140+
(object: NestedValueWithUndefined): Promise<string>;
141+
(key: string, object: NestedValueWithUndefined): Promise<string>;
129142
};
130143

131-
const putNested: PutNestedFunction = async (
144+
const insert: InsertFunction = async (
132145
keyOrObject,
133-
object?: NestedValue | undefined,
134-
): Promise<string[]> => {
135-
let flattenedEntries: { key: string; value: DagCborEncodable }[];
146+
object?: NestedValueWithUndefined | undefined,
147+
): Promise<string> => {
136148
if (typeof keyOrObject === "string") {
137-
flattenedEntries = flatten(object!).map((entry) => ({
138-
key: `${keyOrObject}/${entry.key}`,
139-
value: entry.value,
140-
}));
149+
const joinedRootKey =
150+
typeof keyOrObject === "string" ? keyOrObject : joinKey(keyOrObject);
151+
return await addOperation({
152+
op: "INSERT",
153+
key: joinedRootKey,
154+
value: removeUndefineds(object!),
155+
});
141156
} else {
142-
flattenedEntries = flatten(keyOrObject);
157+
console.log({ keyOrObject, val: removeUndefineds(keyOrObject) });
158+
return await addOperation({
159+
op: "INSERT",
160+
key: null,
161+
value: removeUndefineds(keyOrObject),
162+
});
143163
}
144-
return await Promise.all(flattenedEntries.map((e) => put(e.key, e.value)));
145164
};
146165

147166
const iterator = async function* ({
@@ -157,20 +176,48 @@ export const NestedApi = ({ database }: { database: InternalDatabase }) => {
157176
> {
158177
const keys: { [key: string]: true } = {};
159178
let count = 0;
179+
160180
const keyExists = (key: string) => {
161181
return !!keys[key] || Object.keys(keys).find((k) => isSubkey(key, k));
162182
};
183+
184+
function* processEntry({
185+
key,
186+
value,
187+
hash,
188+
}: {
189+
key: string;
190+
value: DagCborEncodable | undefined;
191+
hash: string;
192+
}) {
193+
if (keyExists(key)) return;
194+
if (value === undefined) return;
195+
keys[key] = true;
196+
count++;
197+
yield { key, value, hash };
198+
}
199+
163200
for await (const entry of log.traverse()) {
164201
const { op, key, value } = entry.payload;
202+
203+
if (op === "INSERT") {
204+
if (typeof value !== "object" || !isNestedValue(value)) continue;
205+
const flattenedEntries = flatten(value).map((entry) => ({
206+
key: key === null ? entry.key : `${key}/${entry.key}`,
207+
value: entry.value,
208+
}));
209+
const hash = entry.hash;
210+
for (const flat of flattenedEntries) {
211+
yield* processEntry({ key: flat.key, value: flat.value, hash });
212+
}
213+
}
214+
165215
if (typeof key !== "string") continue;
166216

167-
if (op === "PUT" && !keyExists(key)) {
168-
if (value === undefined) continue;
169-
keys[key] = true;
170-
count++;
217+
if (op === "PUT") {
171218
const hash = entry.hash;
172-
yield { key, value, hash };
173-
} else if (op === "DEL" && !keyExists(key)) {
219+
yield* processEntry({ key, value, hash });
220+
} else if (op === "DEL") {
174221
keys[key] = true;
175222
}
176223
if (amount !== undefined && count >= amount) {
@@ -192,8 +239,7 @@ export const NestedApi = ({ database }: { database: InternalDatabase }) => {
192239
set: put,
193240
del,
194241
get,
195-
putNested,
196-
setNested: putNested,
242+
insert,
197243
iterator,
198244
all,
199245
};

src/types.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ export type RecursivePartial<T> = {
66

77
export type NestedKey = string | string[];
88

9-
export type PossiblyNestedValue = DagCborEncodable | NestedValue;
10-
export type NestedValue = {
11-
[key: string]: DagCborEncodable | NestedValue;
9+
export type PossiblyNestedValue<T = DagCborEncodable> = T | NestedValue<T>;
10+
export type NestedValue<T = DagCborEncodable> = {
11+
[key: string]: T | NestedValue<T>;
1212
};
13+
export type NestedValueWithUndefined = NestedValue<
14+
DagCborEncodable | undefined
15+
>;

src/utils.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import type { DagCborEncodable } from "@orbitdb/core";
22

3-
import type { NestedKey, NestedValue, PossiblyNestedValue } from "./types";
3+
import type {
4+
NestedKey,
5+
NestedValue,
6+
NestedValueWithUndefined,
7+
PossiblyNestedValue,
8+
} from "./types";
9+
import { merge } from "ts-deepmerge";
410

511
export const splitKey = (key: string): string[] => key.split("/");
612
export const joinKey = (key: string[]): string => key.join("/");
@@ -48,9 +54,7 @@ export const isSisterKey = (key1: NestedKey, key2: NestedKey): boolean => {
4854
return true;
4955
};
5056

51-
export const isNestedValueObject = (
52-
x: PossiblyNestedValue,
53-
): x is NestedValue => {
57+
export const isNestedValue = (x: PossiblyNestedValue): x is NestedValue => {
5458
return (
5559
typeof x === "object" &&
5660
!Array.isArray(x) &&
@@ -66,21 +70,21 @@ export const isNestedKey = (x: unknown): x is NestedKey => {
6670
);
6771
};
6872

69-
export const flatten = (
70-
x: NestedValue,
71-
): { key: string; value: DagCborEncodable }[] => {
72-
const flattened: { key: string; value: DagCborEncodable }[] = [];
73+
export const flatten = <T = DagCborEncodable>(
74+
x: NestedValue<T>,
75+
): { key: string; value: T }[] => {
76+
const flattened: { key: string; value: T }[] = [];
7377

7478
const recursiveFlatten = (
75-
x: PossiblyNestedValue,
79+
x: PossiblyNestedValue<T>,
7680
rootKey: string[],
7781
): void => {
7882
if (typeof x === "object" && !Array.isArray(x) && x !== null) {
7983
for (const [key, value] of Object.entries(x)) {
8084
recursiveFlatten(value, [...rootKey, key]);
8185
}
8286
} else {
83-
flattened.push({ key: rootKey.join("/"), value: x });
87+
flattened.push({ key: rootKey.join("/"), value: x as T });
8488
}
8589
};
8690

@@ -100,7 +104,24 @@ export const toNested = (
100104
root = root[c] as NestedValue;
101105
}
102106
const finalKeyComponent = keyComponents.pop();
103-
if (finalKeyComponent) root[finalKeyComponent] = value;
107+
if (finalKeyComponent) {
108+
const existingValue = root[finalKeyComponent];
109+
if (isNestedValue(value) && isNestedValue(existingValue)) {
110+
root[finalKeyComponent] = merge(existingValue, value);
111+
} else {
112+
root[finalKeyComponent] = value;
113+
}
114+
}
104115
}
105116
return nested;
106117
};
118+
119+
export const removeUndefineds = (x: NestedValueWithUndefined): NestedValue => {
120+
const components = flatten(x);
121+
return toNested(
122+
components.filter(({ value }) => value !== undefined) as {
123+
key: string;
124+
value: DagCborEncodable;
125+
}[],
126+
);
127+
};

test/nested.spec.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -181,17 +181,6 @@ describe("Nested Database", () => {
181181
expect(actual).to.be.undefined();
182182
});
183183

184-
it("remove a nested value", async () => {
185-
await db.put(["a/b"], 1);
186-
await db.put("a/c", 2);
187-
188-
await db.del("a/b");
189-
await db.del(["a", "c"]);
190-
191-
const actual = await db.all();
192-
expect(actual).to.deep.equal({ a: {} });
193-
});
194-
195184
it("add a nested value - list syntax", async () => {
196185
await db.put(["a", "b"], 1);
197186
await db.put(["a", "c"], 2);
@@ -220,22 +209,39 @@ describe("Nested Database", () => {
220209
expect(actual).to.deep.equal({ a: 3 });
221210
});
222211

223-
it("put nested without key", async () => {
224-
await db.put({ a: { b: 1, c: 2 } });
212+
it("insert nested without key", async () => {
213+
await db.insert({ a: { b: 1, c: 2 } });
225214

226215
const actual = await db.all();
227216
expect(actual).to.deep.equal({ a: { b: 1, c: 2 } });
228217
});
229218

230-
it("put nested value merges with previous values", async () => {
231-
await db.put("a", { b: 2, c: 3 });
232-
await db.put("a", { b: 1 });
219+
it("insert nested value merges with previous values in `all()`", async () => {
220+
await db.insert("a", { b: 2, c: 3 });
221+
await db.insert("a", { b: 1 });
233222

234223
const actual = await db.all();
235224

236225
expect(actual).to.deep.equal({ a: { b: 1, c: 3 } });
237226
});
238227

228+
it("insert nested value merges with previous values in `get()`", async () => {
229+
await db.insert("a", { b: 2, c: 3 });
230+
await db.insert("a", { b: 1 });
231+
232+
const actual = await db.get("a");
233+
234+
expect(actual).to.deep.equal({ b: 1, c: 3 });
235+
});
236+
237+
it("insert nested value containing undefined", async () => {
238+
await db.insert("a", { b: 2, c: undefined });
239+
240+
const actual = await db.all();
241+
242+
expect(actual).to.deep.equal({ a: { b: 2 } });
243+
});
244+
239245
it.skip("add positioned nested value after put", async () => {
240246
await db.put("a", { b: 2, c: 3 });
241247
// await db.put("a/d", 1, 1);
@@ -370,7 +376,7 @@ describe("Nested Database", () => {
370376
expect(actual).to.deep.equal(ref);
371377
});
372378

373-
it("move a value twice", async () => {
379+
it.skip("move a value twice", async () => {
374380
await fillKeys(db, 3);
375381
// await db.move("key2", 0);
376382
// await db.move("key2", 1);

0 commit comments

Comments
 (0)