@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.
@@ -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
+ }