@storacha/md-merge 0.2.0 → 0.3.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/dist/codec.d.ts +18 -0
- package/dist/codec.js +108 -0
- package/dist/crdt/codec.d.ts +33 -0
- package/dist/crdt/codec.js +69 -0
- package/dist/crdt/rga.d.ts +53 -0
- package/dist/crdt/rga.js +118 -0
- package/dist/diff.d.ts +11 -0
- package/dist/diff.js +214 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +41 -0
- package/dist/parse.d.ts +9 -0
- package/dist/parse.js +22 -0
- package/dist/rga-tree.d.ts +32 -0
- package/dist/rga-tree.js +218 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +2 -0
- package/package.json +1 -1
- package/src/codec.ts +55 -118
- package/src/crdt/codec.ts +116 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAG-CBOR serialization for plain RGA instances.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { encode, decode } from 'multiformats/block'
|
|
6
|
+
import { sha256 } from 'multiformats/hashes/sha2'
|
|
7
|
+
import * as cbor from '@ipld/dag-cbor'
|
|
8
|
+
import { RGA, type RGANodeId, type RGAEvent, type EventComparator } from './rga.js'
|
|
9
|
+
|
|
10
|
+
/** Recursively strip `undefined` values (not IPLD-compatible). */
|
|
11
|
+
export function stripUndefined(obj: unknown): unknown {
|
|
12
|
+
if (obj === null || obj === undefined) return null
|
|
13
|
+
if (Array.isArray(obj)) return obj.map(stripUndefined)
|
|
14
|
+
if (typeof obj === 'object') {
|
|
15
|
+
const clean: Record<string, unknown> = {}
|
|
16
|
+
for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {
|
|
17
|
+
if (v !== undefined) clean[k] = stripUndefined(v)
|
|
18
|
+
}
|
|
19
|
+
return clean
|
|
20
|
+
}
|
|
21
|
+
return obj
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---- Serializable shapes ----
|
|
25
|
+
|
|
26
|
+
export interface SerializedNodeId {
|
|
27
|
+
uuid: string
|
|
28
|
+
event: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SerializedRGANode<V = unknown> {
|
|
32
|
+
id: SerializedNodeId
|
|
33
|
+
value: V
|
|
34
|
+
afterId: SerializedNodeId | null
|
|
35
|
+
tombstone: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SerializedRGA<V = unknown> {
|
|
39
|
+
nodes: SerializedRGANode<V>[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---- Helpers ----
|
|
43
|
+
|
|
44
|
+
export function serializeNodeId<E extends RGAEvent>(id: RGANodeId<E>): SerializedNodeId {
|
|
45
|
+
return { uuid: id.uuid, event: id.event.toString() }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function deserializeNodeId<E extends RGAEvent>(
|
|
49
|
+
raw: SerializedNodeId,
|
|
50
|
+
parseEvent: (s: string) => E,
|
|
51
|
+
): RGANodeId<E> {
|
|
52
|
+
return { uuid: raw.uuid, event: parseEvent(raw.event) }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---- Serialize / Deserialize ----
|
|
56
|
+
|
|
57
|
+
export function serializeRGA<T, E extends RGAEvent>(
|
|
58
|
+
rga: RGA<T, E>,
|
|
59
|
+
serializeValue: (v: T) => unknown = (v) => v,
|
|
60
|
+
): SerializedRGA {
|
|
61
|
+
const nodes: SerializedRGANode[] = []
|
|
62
|
+
for (const node of rga.nodes.values()) {
|
|
63
|
+
nodes.push({
|
|
64
|
+
id: serializeNodeId(node.id),
|
|
65
|
+
value: serializeValue(node.value),
|
|
66
|
+
afterId: node.afterId ? serializeNodeId(node.afterId) : null,
|
|
67
|
+
tombstone: node.tombstone,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
return { nodes }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function deserializeRGA<T, E extends RGAEvent>(
|
|
74
|
+
raw: SerializedRGA,
|
|
75
|
+
parseEvent: (s: string) => E,
|
|
76
|
+
deserializeValue: (v: unknown) => T,
|
|
77
|
+
fingerprintFn: (value: T) => string,
|
|
78
|
+
compareEvents: EventComparator<E>,
|
|
79
|
+
): RGA<T, E> {
|
|
80
|
+
const rga = new RGA<T, E>(fingerprintFn, compareEvents)
|
|
81
|
+
for (const rawNode of raw.nodes) {
|
|
82
|
+
const id = deserializeNodeId(rawNode.id, parseEvent)
|
|
83
|
+
const afterId = rawNode.afterId ? deserializeNodeId(rawNode.afterId, parseEvent) : undefined
|
|
84
|
+
const value = deserializeValue(rawNode.value)
|
|
85
|
+
const key = `${id.uuid}:${id.event.toString()}`
|
|
86
|
+
rga.nodes.set(key, { id, value, afterId, tombstone: rawNode.tombstone })
|
|
87
|
+
}
|
|
88
|
+
return rga
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---- Public API: encode/decode to DAG-CBOR blocks ----
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Encode a plain RGA as a DAG-CBOR block.
|
|
95
|
+
*/
|
|
96
|
+
export async function encodeRGA<T, E extends RGAEvent>(
|
|
97
|
+
rga: RGA<T, E>,
|
|
98
|
+
serializeValue: (v: T) => unknown = (v) => v,
|
|
99
|
+
) {
|
|
100
|
+
const value = stripUndefined(serializeRGA(rga, serializeValue))
|
|
101
|
+
return encode({ value, codec: cbor, hasher: sha256 })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Decode a plain RGA from a DAG-CBOR block.
|
|
106
|
+
*/
|
|
107
|
+
export async function decodeRGA<T, E extends RGAEvent>(
|
|
108
|
+
block: { bytes: Uint8Array },
|
|
109
|
+
parseEvent: (s: string) => E,
|
|
110
|
+
deserializeValue: (v: unknown) => T,
|
|
111
|
+
fingerprintFn: (value: T) => string,
|
|
112
|
+
compareEvents: EventComparator<E>,
|
|
113
|
+
): Promise<RGA<T, E>> {
|
|
114
|
+
const decoded = await decode({ bytes: block.bytes, codec: cbor, hasher: sha256 })
|
|
115
|
+
return deserializeRGA(decoded.value as SerializedRGA, parseEvent, deserializeValue, fingerprintFn, compareEvents)
|
|
116
|
+
}
|