@atproto/repo 0.0.1 → 0.2.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.
Files changed (104) hide show
  1. package/bench/mst.bench.ts +7 -4
  2. package/bench/repo.bench.ts +25 -16
  3. package/dist/block-map.d.ts +27 -0
  4. package/dist/data-diff.d.ts +36 -0
  5. package/dist/error.d.ts +20 -0
  6. package/dist/index.d.ts +4 -1
  7. package/dist/index.js +22870 -12456
  8. package/dist/index.js.map +4 -4
  9. package/dist/mst/diff.d.ts +4 -33
  10. package/dist/mst/mst.d.ts +73 -31
  11. package/dist/mst/util.d.ts +13 -5
  12. package/dist/parse.d.ts +16 -0
  13. package/dist/readable-repo.d.ts +23 -0
  14. package/dist/repo.d.ts +19 -31
  15. package/dist/src/block-map.d.ts +23 -0
  16. package/dist/src/blockstore/persistent-blockstore.d.ts +12 -0
  17. package/dist/src/cid-set.d.ts +14 -0
  18. package/dist/src/collection.d.ts +22 -0
  19. package/dist/src/data-diff.d.ts +34 -0
  20. package/dist/src/error.d.ts +21 -0
  21. package/dist/src/index.d.ts +7 -0
  22. package/dist/src/logger.d.ts +2 -0
  23. package/dist/src/mst/diff.d.ts +33 -0
  24. package/dist/src/mst/index.d.ts +4 -0
  25. package/dist/src/mst/mst.d.ts +106 -0
  26. package/dist/src/mst/util.d.ts +9 -0
  27. package/dist/src/mst/walker.d.ts +22 -0
  28. package/dist/src/parse.d.ts +11 -0
  29. package/dist/src/readable-repo.d.ts +25 -0
  30. package/dist/src/repo.d.ts +39 -0
  31. package/dist/src/storage/error.d.ts +22 -0
  32. package/dist/src/storage/index.d.ts +1 -0
  33. package/dist/src/storage/memory-blobstore.d.ts +1 -0
  34. package/dist/src/storage/memory-blockstore.d.ts +28 -0
  35. package/dist/src/storage/readable-blockstore.d.ts +21 -0
  36. package/dist/src/storage/repo-storage.d.ts +18 -0
  37. package/dist/src/storage/sync-storage.d.ts +15 -0
  38. package/dist/src/storage/types.d.ts +12 -0
  39. package/dist/src/storage/util.d.ts +17 -0
  40. package/dist/src/structure.d.ts +39 -0
  41. package/dist/src/sync/consumer.d.ts +19 -0
  42. package/dist/src/sync/index.d.ts +2 -0
  43. package/dist/src/sync/producer.d.ts +13 -0
  44. package/dist/src/sync/provider.d.ts +11 -0
  45. package/dist/src/types.d.ts +368 -0
  46. package/dist/src/util.d.ts +13 -0
  47. package/dist/src/verify.d.ts +5 -0
  48. package/dist/storage/index.d.ts +4 -0
  49. package/dist/storage/memory-blockstore.d.ts +29 -0
  50. package/dist/storage/readable-blockstore.d.ts +24 -0
  51. package/dist/storage/repo-storage.d.ts +19 -0
  52. package/dist/storage/sync-storage.d.ts +15 -0
  53. package/dist/storage/types.d.ts +4 -0
  54. package/dist/sync/consumer.d.ts +19 -0
  55. package/dist/sync/index.d.ts +2 -0
  56. package/dist/sync/provider.d.ts +9 -0
  57. package/dist/tsconfig.build.tsbuildinfo +1 -0
  58. package/dist/types.d.ts +137 -331
  59. package/dist/util.d.ts +35 -12
  60. package/dist/verify.d.ts +31 -4
  61. package/jest.bench.config.js +2 -1
  62. package/package.json +13 -6
  63. package/src/block-map.ts +103 -0
  64. package/src/cid-set.ts +1 -2
  65. package/src/data-diff.ts +117 -0
  66. package/src/error.ts +31 -0
  67. package/src/index.ts +4 -1
  68. package/src/mst/diff.ts +120 -90
  69. package/src/mst/mst.ts +179 -187
  70. package/src/mst/util.ts +54 -31
  71. package/src/parse.ts +44 -0
  72. package/src/readable-repo.ts +75 -0
  73. package/src/repo.ts +145 -244
  74. package/src/storage/index.ts +4 -0
  75. package/src/storage/memory-blockstore.ts +133 -0
  76. package/src/storage/readable-blockstore.ts +56 -0
  77. package/src/storage/repo-storage.ts +43 -0
  78. package/src/storage/sync-storage.ts +35 -0
  79. package/src/storage/types.ts +4 -0
  80. package/src/sync/consumer.ts +140 -0
  81. package/src/sync/index.ts +2 -0
  82. package/src/sync/provider.ts +91 -0
  83. package/src/types.ts +110 -73
  84. package/src/util.ts +258 -56
  85. package/src/verify.ts +248 -42
  86. package/tests/_util.ts +132 -97
  87. package/tests/mst.test.ts +269 -122
  88. package/tests/rebase.test.ts +37 -0
  89. package/tests/repo.test.ts +48 -50
  90. package/tests/sync/checkout.test.ts +75 -0
  91. package/tests/sync/diff.test.ts +92 -0
  92. package/tests/sync/narrow.test.ts +149 -0
  93. package/tests/util.test.ts +21 -0
  94. package/tsconfig.build.tsbuildinfo +1 -1
  95. package/tsconfig.json +2 -1
  96. package/src/blockstore/index.ts +0 -2
  97. package/src/blockstore/ipld-store.ts +0 -103
  98. package/src/blockstore/memory-blockstore.ts +0 -49
  99. package/src/sync.ts +0 -38
  100. package/tests/sync.test.ts +0 -129
  101. /package/dist/{blockstore → src/blockstore}/index.d.ts +0 -0
  102. /package/dist/{blockstore → src/blockstore}/ipld-store.d.ts +0 -0
  103. /package/dist/{blockstore → src/blockstore}/memory-blockstore.d.ts +0 -0
  104. /package/dist/{sync.d.ts → src/sync.d.ts} +0 -0
package/src/types.ts CHANGED
@@ -1,101 +1,138 @@
1
1
  import { z } from 'zod'
2
- import { BlockWriter } from '@ipld/car/writer'
3
- import { def as common } from '@atproto/common'
2
+ import { schema as common, def as commonDef } from '@atproto/common'
4
3
  import { CID } from 'multiformats'
5
- import { DataDiff } from './mst'
4
+ import BlockMap from './block-map'
5
+ import { RepoRecord } from '@atproto/lexicon'
6
6
 
7
- const repoMeta = z.object({
7
+ // Repo nodes
8
+ // ---------------
9
+
10
+ const unsignedCommit = z.object({
8
11
  did: z.string(),
9
12
  version: z.number(),
10
- datastore: z.string(),
11
- })
12
- export type RepoMeta = z.infer<typeof repoMeta>
13
-
14
- const repoRoot = z.object({
15
- meta: common.cid,
16
13
  prev: common.cid.nullable(),
17
- auth_token: common.cid.nullable(),
18
14
  data: common.cid,
19
15
  })
20
- export type RepoRoot = z.infer<typeof repoRoot>
16
+ export type UnsignedCommit = z.infer<typeof unsignedCommit> & { sig?: never }
21
17
 
22
18
  const commit = z.object({
23
- root: common.cid,
19
+ did: z.string(),
20
+ version: z.number(),
21
+ prev: common.cid.nullable(),
22
+ data: common.cid,
24
23
  sig: common.bytes,
25
24
  })
26
25
  export type Commit = z.infer<typeof commit>
27
26
 
28
- export const cidCreateOp = z.object({
29
- action: z.literal('create'),
30
- collection: z.string(),
31
- rkey: z.string(),
32
- cid: common.cid,
33
- })
34
- export type CidCreateOp = z.infer<typeof cidCreateOp>
27
+ export const schema = {
28
+ ...common,
29
+ commit,
30
+ }
35
31
 
36
- export const cidUpdateOp = z.object({
37
- action: z.literal('update'),
38
- collection: z.string(),
39
- rkey: z.string(),
40
- cid: common.cid,
41
- })
42
- export type CidUpdateOp = z.infer<typeof cidUpdateOp>
32
+ export const def = {
33
+ ...commonDef,
34
+ commit: {
35
+ name: 'commit',
36
+ schema: schema.commit,
37
+ },
38
+ }
43
39
 
44
- export const deleteOp = z.object({
45
- action: z.literal('delete'),
46
- collection: z.string(),
47
- rkey: z.string(),
48
- })
49
- export type DeleteOp = z.infer<typeof deleteOp>
40
+ // Repo Operations
41
+ // ---------------
50
42
 
51
- export const cidWriteOp = z.union([cidCreateOp, cidUpdateOp, deleteOp])
52
- export type CidWriteOp = z.infer<typeof cidWriteOp>
43
+ export enum WriteOpAction {
44
+ Create = 'create',
45
+ Update = 'update',
46
+ Delete = 'delete',
47
+ }
53
48
 
54
- export const recordCreateOp = z.object({
55
- action: z.literal('create'),
56
- collection: z.string(),
57
- rkey: z.string(),
58
- value: z.any(),
59
- })
60
- export type RecordCreateOp = z.infer<typeof recordCreateOp>
49
+ export type RecordCreateOp = {
50
+ action: WriteOpAction.Create
51
+ collection: string
52
+ rkey: string
53
+ record: RepoRecord
54
+ }
61
55
 
62
- export const recordUpdateOp = z.object({
63
- action: z.literal('update'),
64
- collection: z.string(),
65
- rkey: z.string(),
66
- value: z.any(),
67
- })
68
- export type RecordUpdateOp = z.infer<typeof recordUpdateOp>
56
+ export type RecordUpdateOp = {
57
+ action: WriteOpAction.Update
58
+ collection: string
59
+ rkey: string
60
+ record: RepoRecord
61
+ }
69
62
 
70
- export const recordWriteOp = z.union([recordCreateOp, recordUpdateOp, deleteOp])
71
- export type RecordWriteOp = z.infer<typeof recordWriteOp>
63
+ export type RecordDeleteOp = {
64
+ action: WriteOpAction.Delete
65
+ collection: string
66
+ rkey: string
67
+ }
72
68
 
73
- export const def = {
74
- ...common,
75
- repoMeta,
76
- repoRoot,
77
- commit,
78
- cidWriteOp,
79
- recordWriteOp,
69
+ export type RecordWriteOp = RecordCreateOp | RecordUpdateOp | RecordDeleteOp
70
+
71
+ export type RecordCreateDescript = RecordCreateOp & {
72
+ cid: CID
80
73
  }
81
74
 
82
- export interface CarStreamable {
83
- writeToCarStream(car: BlockWriter): Promise<void>
75
+ export type RecordUpdateDescript = RecordUpdateOp & {
76
+ prev: CID
77
+ cid: CID
84
78
  }
85
79
 
86
- export type DataValue = {
87
- key: string
88
- value: CID
80
+ export type RecordDeleteDescript = RecordDeleteOp & {
81
+ cid: CID
82
+ }
83
+
84
+ export type RecordWriteDescript =
85
+ | RecordCreateDescript
86
+ | RecordUpdateDescript
87
+ | RecordDeleteDescript
88
+
89
+ export type WriteLog = RecordWriteDescript[][]
90
+
91
+ // Updates/Commits
92
+ // ---------------
93
+
94
+ export type CommitBlockData = {
95
+ commit: CID
96
+ blocks: BlockMap
97
+ }
98
+
99
+ export type CommitData = CommitBlockData & {
100
+ prev: CID | null
101
+ }
102
+
103
+ export type RebaseData = {
104
+ commit: CID
105
+ rebased: CID
106
+ blocks: BlockMap
107
+ preservedCids: CID[]
108
+ }
109
+
110
+ export type CommitCidData = {
111
+ commit: CID
112
+ prev: CID | null
113
+ cids: CID[]
114
+ }
115
+
116
+ export type RepoUpdate = CommitData & {
117
+ ops: RecordWriteOp[]
118
+ }
119
+
120
+ export type CollectionContents = Record<string, RepoRecord>
121
+ export type RepoContents = Record<string, CollectionContents>
122
+
123
+ export type RepoRecordWithCid = { cid: CID; value: RepoRecord }
124
+ export type CollectionContentsWithCids = Record<string, RepoRecordWithCid>
125
+ export type RepoContentsWithCids = Record<string, CollectionContentsWithCids>
126
+
127
+ export type DatastoreContents = Record<string, CID>
128
+
129
+ export type RecordPath = {
130
+ collection: string
131
+ rkey: string
89
132
  }
90
133
 
91
- export interface DataStore {
92
- add(key: string, value: CID): Promise<DataStore>
93
- update(key: string, value: CID): Promise<DataStore>
94
- delete(key: string): Promise<DataStore>
95
- get(key: string): Promise<CID | null>
96
- list(count: number, after?: string, before?: string): Promise<DataValue[]>
97
- listWithPrefix(prefix: string, count?: number): Promise<DataValue[]>
98
- diff(other: DataStore): Promise<DataDiff>
99
- stage(): Promise<CID>
100
- writeToCarStream(car: BlockWriter): Promise<void>
134
+ export type RecordClaim = {
135
+ collection: string
136
+ rkey: string
137
+ record: RepoRecord | null
101
138
  }
package/src/util.ts CHANGED
@@ -1,88 +1,290 @@
1
1
  import { CID } from 'multiformats/cid'
2
- import * as auth from '@atproto/auth'
2
+ import * as cbor from '@ipld/dag-cbor'
3
+ import { CarReader } from '@ipld/car/reader'
4
+ import { BlockWriter, CarWriter } from '@ipld/car/writer'
5
+ import { Block as CarBlock } from '@ipld/car/api'
6
+ import {
7
+ streamToBuffer,
8
+ verifyCidForBytes,
9
+ cborDecode,
10
+ check,
11
+ schema,
12
+ cidForCbor,
13
+ byteIterableToStream,
14
+ } from '@atproto/common'
15
+ import { ipldToLex, lexToIpld, LexValue, RepoRecord } from '@atproto/lexicon'
16
+
17
+ import * as crypto from '@atproto/crypto'
3
18
  import Repo from './repo'
4
- import { DataDiff, MST } from './mst'
5
- import { IpldStore } from './blockstore'
6
- import { DataStore, RecordWriteOp, def } from './types'
7
-
8
- export const ucanForOperation = async (
9
- prevData: DataStore,
10
- newData: DataStore,
11
- rootDid: string,
12
- authStore: auth.AuthStore,
13
- ): Promise<string> => {
14
- const diff = await prevData.diff(newData)
15
- const neededCaps = diff.neededCapabilities(rootDid)
16
- const ucanForOp = await authStore.createUcanForCaps(rootDid, neededCaps, 30)
17
- return auth.encodeUcan(ucanForOp)
18
- }
19
-
20
- export const getCommitPath = async (
21
- blockstore: IpldStore,
22
- earliest: CID | null,
23
- latest: CID,
24
- ): Promise<CID[] | null> => {
25
- let curr: CID | null = latest
26
- const path: CID[] = []
27
- while (curr !== null) {
28
- path.push(curr)
29
- const commit = await blockstore.get(curr, def.commit)
30
- if (earliest && curr.equals(earliest)) {
31
- return path.reverse()
32
- }
33
- const root = await blockstore.get(commit.root, def.repoRoot)
34
- if (!earliest && root.prev === null) {
35
- return path.reverse()
19
+ import { MST } from './mst'
20
+ import DataDiff from './data-diff'
21
+ import { RepoStorage } from './storage'
22
+ import {
23
+ Commit,
24
+ RecordCreateDescript,
25
+ RecordDeleteDescript,
26
+ RecordPath,
27
+ RecordUpdateDescript,
28
+ RecordWriteDescript,
29
+ UnsignedCommit,
30
+ WriteLog,
31
+ WriteOpAction,
32
+ } from './types'
33
+ import BlockMap from './block-map'
34
+ import { MissingBlocksError } from './error'
35
+ import * as parse from './parse'
36
+ import { Keypair } from '@atproto/crypto'
37
+ import { Readable } from 'stream'
38
+
39
+ export async function* verifyIncomingCarBlocks(
40
+ car: AsyncIterable<CarBlock>,
41
+ ): AsyncIterable<CarBlock> {
42
+ for await (const block of car) {
43
+ await verifyCidForBytes(block.cid, block.bytes)
44
+ yield block
45
+ }
46
+ }
47
+
48
+ // we have to turn the car writer output into a stream in order to properly handle errors
49
+ export function writeCarStream(
50
+ root: CID | null,
51
+ fn: (car: BlockWriter) => Promise<void>,
52
+ ): Readable {
53
+ const { writer, out } =
54
+ root !== null ? CarWriter.create(root) : CarWriter.create()
55
+
56
+ const stream = byteIterableToStream(out)
57
+ fn(writer)
58
+ .catch((err) => {
59
+ stream.destroy(err)
60
+ })
61
+ .finally(() => writer.close())
62
+ return stream
63
+ }
64
+
65
+ export async function* writeCar(
66
+ root: CID | null,
67
+ fn: (car: BlockWriter) => Promise<void>,
68
+ ): AsyncIterable<Uint8Array> {
69
+ const stream = writeCarStream(root, fn)
70
+ for await (const chunk of stream) {
71
+ yield chunk
72
+ }
73
+ }
74
+
75
+ export const blocksToCarStream = (
76
+ root: CID | null,
77
+ blocks: BlockMap,
78
+ ): AsyncIterable<Uint8Array> => {
79
+ return writeCar(root, async (writer) => {
80
+ for (const entry of blocks.entries()) {
81
+ await writer.put(entry)
36
82
  }
37
- curr = root.prev
83
+ })
84
+ }
85
+
86
+ export const blocksToCarFile = (
87
+ root: CID | null,
88
+ blocks: BlockMap,
89
+ ): Promise<Uint8Array> => {
90
+ const carStream = blocksToCarStream(root, blocks)
91
+ return streamToBuffer(carStream)
92
+ }
93
+
94
+ export const readCar = async (
95
+ bytes: Uint8Array,
96
+ ): Promise<{ roots: CID[]; blocks: BlockMap }> => {
97
+ const car = await CarReader.fromBytes(bytes)
98
+ const roots = await car.getRoots()
99
+ const blocks = new BlockMap()
100
+ for await (const block of verifyIncomingCarBlocks(car.blocks())) {
101
+ await blocks.set(block.cid, block.bytes)
102
+ }
103
+ return {
104
+ roots,
105
+ blocks,
38
106
  }
39
- return null
40
107
  }
41
108
 
42
- export const getWriteOpLog = async (
43
- blockstore: IpldStore,
44
- earliest: CID | null,
109
+ export const readCarWithRoot = async (
110
+ bytes: Uint8Array,
111
+ ): Promise<{ root: CID; blocks: BlockMap }> => {
112
+ const { roots, blocks } = await readCar(bytes)
113
+ if (roots.length !== 1) {
114
+ throw new Error(`Expected one root, got ${roots.length}`)
115
+ }
116
+ const root = roots[0]
117
+ return {
118
+ root,
119
+ blocks,
120
+ }
121
+ }
122
+
123
+ export const getWriteLog = async (
124
+ storage: RepoStorage,
45
125
  latest: CID,
46
- ): Promise<RecordWriteOp[][]> => {
47
- const commits = await getCommitPath(blockstore, earliest, latest)
126
+ earliest: CID | null,
127
+ ): Promise<WriteLog> => {
128
+ const commits = await storage.getCommitPath(latest, earliest)
48
129
  if (!commits) throw new Error('Could not find shared history')
49
- const heads = await Promise.all(commits.map((c) => Repo.load(blockstore, c)))
130
+ const heads = await Promise.all(commits.map((c) => Repo.load(storage, c)))
50
131
  // Turn commit path into list of diffs
51
- let prev: DataStore = await MST.create(blockstore) // Empty
132
+ let prev = await MST.create(storage) // Empty
52
133
  const msts = heads.map((h) => h.data)
53
134
  const diffs: DataDiff[] = []
54
135
  for (const mst of msts) {
55
- diffs.push(await prev.diff(mst))
136
+ diffs.push(await DataDiff.of(mst, prev))
56
137
  prev = mst
57
138
  }
139
+ const fullDiff = collapseDiffs(diffs)
140
+ const diffBlocks = await storage.getBlocks(fullDiff.newCidList())
141
+ if (diffBlocks.missing.length > 0) {
142
+ throw new MissingBlocksError('write op log', diffBlocks.missing)
143
+ }
58
144
  // Map MST diffs to write ops
59
- return Promise.all(diffs.map((diff) => diffToWriteOps(blockstore, diff)))
145
+ return Promise.all(
146
+ diffs.map((diff) => diffToWriteDescripts(diff, diffBlocks.blocks)),
147
+ )
60
148
  }
61
149
 
62
- export const diffToWriteOps = (
63
- blockstore: IpldStore,
150
+ export const diffToWriteDescripts = (
64
151
  diff: DataDiff,
65
- ): Promise<RecordWriteOp[]> => {
152
+ blocks: BlockMap,
153
+ ): Promise<RecordWriteDescript[]> => {
66
154
  return Promise.all([
67
155
  ...diff.addList().map(async (add) => {
68
- const { collection, rkey } = parseRecordKey(add.key)
69
- const value = await blockstore.getUnchecked(add.cid)
70
- return { action: 'create' as const, collection, rkey, value }
156
+ const { collection, rkey } = parseDataKey(add.key)
157
+ const value = await parse.getAndParseRecord(blocks, add.cid)
158
+ return {
159
+ action: WriteOpAction.Create,
160
+ collection,
161
+ rkey,
162
+ cid: add.cid,
163
+ record: value.record,
164
+ } as RecordCreateDescript
71
165
  }),
72
166
  ...diff.updateList().map(async (upd) => {
73
- const { collection, rkey } = parseRecordKey(upd.key)
74
- const value = await blockstore.getUnchecked(upd.cid)
75
- return { action: 'update' as const, collection, rkey, value }
167
+ const { collection, rkey } = parseDataKey(upd.key)
168
+ const value = await parse.getAndParseRecord(blocks, upd.cid)
169
+ return {
170
+ action: WriteOpAction.Update,
171
+ collection,
172
+ rkey,
173
+ cid: upd.cid,
174
+ prev: upd.prev,
175
+ record: value.record,
176
+ } as RecordUpdateDescript
76
177
  }),
77
178
  ...diff.deleteList().map((del) => {
78
- const { collection, rkey } = parseRecordKey(del.key)
79
- return { action: 'delete' as const, collection, rkey }
179
+ const { collection, rkey } = parseDataKey(del.key)
180
+ return {
181
+ action: WriteOpAction.Delete,
182
+ collection,
183
+ rkey,
184
+ cid: del.cid,
185
+ } as RecordDeleteDescript
80
186
  }),
81
187
  ])
82
188
  }
83
189
 
84
- export const parseRecordKey = (key: string) => {
190
+ export const collapseWriteLog = (log: WriteLog): RecordWriteDescript[] => {
191
+ const creates: Record<string, RecordCreateDescript> = {}
192
+ const updates: Record<string, RecordUpdateDescript> = {}
193
+ const deletes: Record<string, RecordDeleteDescript> = {}
194
+ for (const commit of log) {
195
+ for (const op of commit) {
196
+ const key = op.collection + '/' + op.rkey
197
+ if (op.action === WriteOpAction.Create) {
198
+ const del = deletes[key]
199
+ if (del) {
200
+ if (del.cid !== op.cid) {
201
+ updates[key] = {
202
+ ...op,
203
+ action: WriteOpAction.Update,
204
+ prev: del.cid,
205
+ }
206
+ }
207
+ delete deletes[key]
208
+ } else {
209
+ creates[key] = op
210
+ }
211
+ } else if (op.action === WriteOpAction.Update) {
212
+ updates[key] = op
213
+ delete creates[key]
214
+ delete deletes[key]
215
+ } else if (op.action === WriteOpAction.Delete) {
216
+ if (creates[key]) {
217
+ delete creates[key]
218
+ } else {
219
+ delete updates[key]
220
+ deletes[key] = op
221
+ }
222
+ } else {
223
+ throw new Error(`unknown action: ${op}`)
224
+ }
225
+ }
226
+ }
227
+ return [
228
+ ...Object.values(creates),
229
+ ...Object.values(updates),
230
+ ...Object.values(deletes),
231
+ ]
232
+ }
233
+
234
+ export const collapseDiffs = (diffs: DataDiff[]): DataDiff => {
235
+ return diffs.reduce((acc, cur) => {
236
+ acc.addDiff(cur)
237
+ return acc
238
+ }, new DataDiff())
239
+ }
240
+
241
+ export const parseDataKey = (key: string): RecordPath => {
85
242
  const parts = key.split('/')
86
243
  if (parts.length !== 2) throw new Error(`Invalid record key: ${key}`)
87
244
  return { collection: parts[0], rkey: parts[1] }
88
245
  }
246
+
247
+ export const formatDataKey = (collection: string, rkey: string): string => {
248
+ return collection + '/' + rkey
249
+ }
250
+
251
+ export const metaEqual = (a: Commit, b: Commit): boolean => {
252
+ return a.did === b.did && a.version === b.version
253
+ }
254
+
255
+ export const signCommit = async (
256
+ unsigned: UnsignedCommit,
257
+ keypair: Keypair,
258
+ ): Promise<Commit> => {
259
+ const encoded = cbor.encode(unsigned)
260
+ const sig = await keypair.sign(encoded)
261
+ return {
262
+ ...unsigned,
263
+ sig,
264
+ }
265
+ }
266
+
267
+ export const verifyCommitSig = async (
268
+ commit: Commit,
269
+ didKey: string,
270
+ ): Promise<boolean> => {
271
+ const { sig, ...rest } = commit
272
+ const encoded = cbor.encode(rest)
273
+ return crypto.verifySignature(didKey, encoded, sig)
274
+ }
275
+
276
+ export const cborToLex = (val: Uint8Array): LexValue => {
277
+ return ipldToLex(cborDecode(val))
278
+ }
279
+
280
+ export const cborToLexRecord = (val: Uint8Array): RepoRecord => {
281
+ const parsed = cborToLex(val)
282
+ if (!check.is(parsed, schema.map)) {
283
+ throw new Error('lexicon records be a json object')
284
+ }
285
+ return parsed
286
+ }
287
+
288
+ export const cidForRecord = async (val: LexValue) => {
289
+ return cidForCbor(lexToIpld(val))
290
+ }