@atproto/space 0.0.0-spaces-alpha-20260818022935

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 (106) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE.txt +7 -0
  3. package/dist/commit.d.ts +25 -0
  4. package/dist/commit.d.ts.map +1 -0
  5. package/dist/commit.js +80 -0
  6. package/dist/commit.js.map +1 -0
  7. package/dist/credential.d.ts +77 -0
  8. package/dist/credential.d.ts.map +1 -0
  9. package/dist/credential.js +160 -0
  10. package/dist/credential.js.map +1 -0
  11. package/dist/dpop.d.ts +40 -0
  12. package/dist/dpop.d.ts.map +1 -0
  13. package/dist/dpop.js +96 -0
  14. package/dist/dpop.js.map +1 -0
  15. package/dist/error.d.ts +12 -0
  16. package/dist/error.d.ts.map +1 -0
  17. package/dist/error.js +21 -0
  18. package/dist/error.js.map +1 -0
  19. package/dist/index.d.ts +9 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +9 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/lthash.d.ts +18 -0
  24. package/dist/lthash.d.ts.map +1 -0
  25. package/dist/lthash.js +68 -0
  26. package/dist/lthash.js.map +1 -0
  27. package/dist/repo-commit.d.ts +51 -0
  28. package/dist/repo-commit.d.ts.map +1 -0
  29. package/dist/repo-commit.js +137 -0
  30. package/dist/repo-commit.js.map +1 -0
  31. package/dist/space-members.d.ts +27 -0
  32. package/dist/space-members.d.ts.map +1 -0
  33. package/dist/space-members.js +111 -0
  34. package/dist/space-members.js.map +1 -0
  35. package/dist/space-repo.d.ts +33 -0
  36. package/dist/space-repo.d.ts.map +1 -0
  37. package/dist/space-repo.js +136 -0
  38. package/dist/space-repo.js.map +1 -0
  39. package/dist/storage/index.d.ts +3 -0
  40. package/dist/storage/index.d.ts.map +1 -0
  41. package/dist/storage/index.js +19 -0
  42. package/dist/storage/index.js.map +1 -0
  43. package/dist/storage/memory-members-storage.d.ts +12 -0
  44. package/dist/storage/memory-members-storage.d.ts.map +1 -0
  45. package/dist/storage/memory-members-storage.js +36 -0
  46. package/dist/storage/memory-members-storage.js.map +1 -0
  47. package/dist/storage/memory-repo-storage.d.ts +17 -0
  48. package/dist/storage/memory-repo-storage.d.ts.map +1 -0
  49. package/dist/storage/memory-repo-storage.js +56 -0
  50. package/dist/storage/memory-repo-storage.js.map +1 -0
  51. package/dist/storage/types.d.ts +14 -0
  52. package/dist/storage/types.d.ts.map +1 -0
  53. package/dist/storage/types.js +3 -0
  54. package/dist/storage/types.js.map +1 -0
  55. package/dist/sync/car.d.ts +13 -0
  56. package/dist/sync/car.d.ts.map +1 -0
  57. package/dist/sync/car.js +185 -0
  58. package/dist/sync/car.js.map +1 -0
  59. package/dist/sync/consumer.d.ts +39 -0
  60. package/dist/sync/consumer.d.ts.map +1 -0
  61. package/dist/sync/consumer.js +89 -0
  62. package/dist/sync/consumer.js.map +1 -0
  63. package/dist/sync/index.d.ts +3 -0
  64. package/dist/sync/index.d.ts.map +1 -0
  65. package/dist/sync/index.js +3 -0
  66. package/dist/sync/index.js.map +1 -0
  67. package/dist/sync/provider.d.ts +25 -0
  68. package/dist/sync/provider.d.ts.map +1 -0
  69. package/dist/sync/provider.js +60 -0
  70. package/dist/sync/provider.js.map +1 -0
  71. package/dist/sync/repo-index.d.ts +19 -0
  72. package/dist/sync/repo-index.d.ts.map +1 -0
  73. package/dist/sync/repo-index.js +58 -0
  74. package/dist/sync/repo-index.js.map +1 -0
  75. package/dist/types.d.ts +49 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +35 -0
  78. package/dist/types.js.map +1 -0
  79. package/dist/util.d.ts +6 -0
  80. package/dist/util.d.ts.map +1 -0
  81. package/dist/util.js +14 -0
  82. package/dist/util.js.map +1 -0
  83. package/package.json +44 -0
  84. package/src/credential.ts +262 -0
  85. package/src/dpop.ts +166 -0
  86. package/src/error.ts +38 -0
  87. package/src/index.ts +8 -0
  88. package/src/lthash.ts +80 -0
  89. package/src/repo-commit.ts +171 -0
  90. package/src/sync/consumer.ts +149 -0
  91. package/src/sync/index.ts +2 -0
  92. package/src/sync/provider.ts +82 -0
  93. package/src/types.ts +62 -0
  94. package/src/util.ts +21 -0
  95. package/tests/credential.test.ts +323 -0
  96. package/tests/dpop.test.ts +234 -0
  97. package/tests/lthash.test.ts +97 -0
  98. package/tests/repo-commit.test.ts +281 -0
  99. package/tests/sync.test.ts +385 -0
  100. package/tsconfig.build.json +16 -0
  101. package/tsconfig.build.tsbuildinfo +1 -0
  102. package/tsconfig.json +7 -0
  103. package/tsconfig.test.json +11 -0
  104. package/tsconfig.test.tsbuildinfo +1 -0
  105. package/tsconfig.tests.tsbuildinfo +1 -0
  106. package/vitest.config.ts +5 -0
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RecordNotFoundError = exports.RecordAlreadyExistsError = exports.SpaceRepo = void 0;
4
+ const lex_cbor_1 = require("@atproto/lex-cbor");
5
+ const commit_js_1 = require("./commit.js");
6
+ const error_js_1 = require("./error.js");
7
+ const lthash_js_1 = require("./lthash.js");
8
+ const types_js_1 = require("./types.js");
9
+ const util_js_1 = require("./util.js");
10
+ class SpaceRepo {
11
+ constructor(params) {
12
+ this.storage = params.storage;
13
+ this.did = params.did;
14
+ this.setHash = params.setHash ?? new lthash_js_1.LtHash();
15
+ }
16
+ static create(storage, did) {
17
+ return new SpaceRepo({ storage, did });
18
+ }
19
+ static async load(storage, did) {
20
+ const stored = await storage.getSetHashState();
21
+ if (stored) {
22
+ return new SpaceRepo({ storage, did, setHash: new lthash_js_1.LtHash(stored) });
23
+ }
24
+ return SpaceRepo.recompute(storage, did);
25
+ }
26
+ static async loadOrCreate(storage, did) {
27
+ const stored = await storage.getSetHashState();
28
+ if (stored) {
29
+ return new SpaceRepo({ storage, did, setHash: new lthash_js_1.LtHash(stored) });
30
+ }
31
+ return new SpaceRepo({ storage, did });
32
+ }
33
+ static async recompute(storage, did) {
34
+ const setHash = new lthash_js_1.LtHash();
35
+ const collections = await storage.listCollections();
36
+ for (const collection of collections) {
37
+ const records = await storage.listRecords(collection);
38
+ for (const { rkey, record } of records) {
39
+ setHash.add(await (0, util_js_1.formatRecordElement)(collection, rkey, record));
40
+ }
41
+ }
42
+ return new SpaceRepo({ storage, did, setHash });
43
+ }
44
+ async formatCommit(writes) {
45
+ const ops = Array.isArray(writes) ? writes : [writes];
46
+ const prepared = [];
47
+ const newSetHash = new lthash_js_1.LtHash(this.setHash.toBytes());
48
+ for (const op of ops) {
49
+ if (op.action === types_js_1.WriteOpAction.Create) {
50
+ const existing = await this.storage.hasRecord(op.collection, op.rkey);
51
+ if (existing) {
52
+ throw new error_js_1.RecordAlreadyExistsError(op.collection, op.rkey);
53
+ }
54
+ const cid = await (0, lex_cbor_1.cidForLex)(op.record);
55
+ newSetHash.add(await (0, util_js_1.formatRecordElement)(op.collection, op.rkey, op.record));
56
+ prepared.push({
57
+ action: types_js_1.WriteOpAction.Create,
58
+ collection: op.collection,
59
+ rkey: op.rkey,
60
+ record: op.record,
61
+ cid,
62
+ });
63
+ }
64
+ else if (op.action === types_js_1.WriteOpAction.Update) {
65
+ const existing = await this.storage.getRecord(op.collection, op.rkey);
66
+ if (!existing) {
67
+ throw new error_js_1.RecordNotFoundError(op.collection, op.rkey);
68
+ }
69
+ newSetHash.remove(await (0, util_js_1.formatRecordElement)(op.collection, op.rkey, existing));
70
+ const cid = await (0, lex_cbor_1.cidForLex)(op.record);
71
+ newSetHash.add(await (0, util_js_1.formatRecordElement)(op.collection, op.rkey, op.record));
72
+ prepared.push({
73
+ action: types_js_1.WriteOpAction.Update,
74
+ collection: op.collection,
75
+ rkey: op.rkey,
76
+ record: op.record,
77
+ cid,
78
+ });
79
+ }
80
+ else if (op.action === types_js_1.WriteOpAction.Delete) {
81
+ const existing = await this.storage.getRecord(op.collection, op.rkey);
82
+ if (!existing) {
83
+ throw new error_js_1.RecordNotFoundError(op.collection, op.rkey);
84
+ }
85
+ newSetHash.remove(await (0, util_js_1.formatRecordElement)(op.collection, op.rkey, existing));
86
+ prepared.push({
87
+ action: types_js_1.WriteOpAction.Delete,
88
+ collection: op.collection,
89
+ rkey: op.rkey,
90
+ });
91
+ }
92
+ }
93
+ return {
94
+ writes: prepared,
95
+ setHash: newSetHash.toBytes(),
96
+ };
97
+ }
98
+ async applyCommit(commit) {
99
+ await this.storage.applyCommit(commit);
100
+ this.setHash = new lthash_js_1.LtHash(commit.setHash);
101
+ }
102
+ async applyWrites(writes) {
103
+ const commit = await this.formatCommit(writes);
104
+ await this.applyCommit(commit);
105
+ return commit;
106
+ }
107
+ // Reads
108
+ async getRecord(collection, rkey) {
109
+ return this.storage.getRecord(collection, rkey);
110
+ }
111
+ async listCollections() {
112
+ return this.storage.listCollections();
113
+ }
114
+ async listRecords(collection) {
115
+ return this.storage.listRecords(collection);
116
+ }
117
+ // Signed commits
118
+ async commit(space, keypair) {
119
+ return (0, commit_js_1.createCommit)(this.setHash, space, keypair);
120
+ }
121
+ // Integrity: the commit's hash matches our local set hash, and its MAC binds
122
+ // that hash to the commit context. Does not establish authenticity — use
123
+ // verifyCommitSig with the author's signing key for that.
124
+ verifyCommit(space, commit) {
125
+ return (commit.hash.equals(this.setHash.digest()) && (0, commit_js_1.verifyCommitMac)(space, commit));
126
+ }
127
+ // Authenticity: the signature over the commit context is valid for didKey.
128
+ verifyCommitSig(space, commit, didKey) {
129
+ return (0, commit_js_1.verifyCommitSig)(space, commit, didKey);
130
+ }
131
+ }
132
+ exports.SpaceRepo = SpaceRepo;
133
+ var error_js_2 = require("./error.js");
134
+ Object.defineProperty(exports, "RecordAlreadyExistsError", { enumerable: true, get: function () { return error_js_2.RecordAlreadyExistsError; } });
135
+ Object.defineProperty(exports, "RecordNotFoundError", { enumerable: true, get: function () { return error_js_2.RecordNotFoundError; } });
136
+ //# sourceMappingURL=space-repo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"space-repo.js","sourceRoot":"","sources":["../src/space-repo.ts"],"names":[],"mappings":"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","sourcesContent":["import { Keypair } from '@atproto/crypto'\nimport { cidForLex } from '@atproto/lex-cbor'\nimport { createCommit, verifyCommitMac, verifyCommitSig } from './commit.js'\nimport { RecordAlreadyExistsError, RecordNotFoundError } from './error.js'\nimport { LtHash } from './lthash.js'\nimport { SpaceRepoStorage } from './storage/index.js'\nimport {\n CommitData,\n PreparedWrite,\n RecordWriteOp,\n RepoRecord,\n SignedCommit,\n SpaceContext,\n WriteOpAction,\n} from './types.js'\nimport { formatRecordElement } from './util.js'\n\ntype Params = {\n storage: SpaceRepoStorage\n did: string\n setHash?: LtHash\n}\n\nexport class SpaceRepo {\n storage: SpaceRepoStorage\n did: string\n setHash: LtHash\n\n constructor(params: Params) {\n this.storage = params.storage\n this.did = params.did\n this.setHash = params.setHash ?? new LtHash()\n }\n\n static create(storage: SpaceRepoStorage, did: string): SpaceRepo {\n return new SpaceRepo({ storage, did })\n }\n\n static async load(\n storage: SpaceRepoStorage,\n did: string,\n ): Promise<SpaceRepo> {\n const stored = await storage.getSetHashState()\n if (stored) {\n return new SpaceRepo({ storage, did, setHash: new LtHash(stored) })\n }\n return SpaceRepo.recompute(storage, did)\n }\n\n static async loadOrCreate(\n storage: SpaceRepoStorage,\n did: string,\n ): Promise<SpaceRepo> {\n const stored = await storage.getSetHashState()\n if (stored) {\n return new SpaceRepo({ storage, did, setHash: new LtHash(stored) })\n }\n return new SpaceRepo({ storage, did })\n }\n\n static async recompute(\n storage: SpaceRepoStorage,\n did: string,\n ): Promise<SpaceRepo> {\n const setHash = new LtHash()\n const collections = await storage.listCollections()\n for (const collection of collections) {\n const records = await storage.listRecords(collection)\n for (const { rkey, record } of records) {\n setHash.add(await formatRecordElement(collection, rkey, record))\n }\n }\n return new SpaceRepo({ storage, did, setHash })\n }\n\n async formatCommit(\n writes: RecordWriteOp | RecordWriteOp[],\n ): Promise<CommitData> {\n const ops = Array.isArray(writes) ? writes : [writes]\n const prepared: PreparedWrite[] = []\n const newSetHash = new LtHash(this.setHash.toBytes())\n\n for (const op of ops) {\n if (op.action === WriteOpAction.Create) {\n const existing = await this.storage.hasRecord(op.collection, op.rkey)\n if (existing) {\n throw new RecordAlreadyExistsError(op.collection, op.rkey)\n }\n const cid = await cidForLex(op.record)\n newSetHash.add(\n await formatRecordElement(op.collection, op.rkey, op.record),\n )\n prepared.push({\n action: WriteOpAction.Create,\n collection: op.collection,\n rkey: op.rkey,\n record: op.record,\n cid,\n })\n } else if (op.action === WriteOpAction.Update) {\n const existing = await this.storage.getRecord(op.collection, op.rkey)\n if (!existing) {\n throw new RecordNotFoundError(op.collection, op.rkey)\n }\n newSetHash.remove(\n await formatRecordElement(op.collection, op.rkey, existing),\n )\n const cid = await cidForLex(op.record)\n newSetHash.add(\n await formatRecordElement(op.collection, op.rkey, op.record),\n )\n prepared.push({\n action: WriteOpAction.Update,\n collection: op.collection,\n rkey: op.rkey,\n record: op.record,\n cid,\n })\n } else if (op.action === WriteOpAction.Delete) {\n const existing = await this.storage.getRecord(op.collection, op.rkey)\n if (!existing) {\n throw new RecordNotFoundError(op.collection, op.rkey)\n }\n newSetHash.remove(\n await formatRecordElement(op.collection, op.rkey, existing),\n )\n prepared.push({\n action: WriteOpAction.Delete,\n collection: op.collection,\n rkey: op.rkey,\n })\n }\n }\n\n return {\n writes: prepared,\n setHash: newSetHash.toBytes(),\n }\n }\n\n async applyCommit(commit: CommitData): Promise<void> {\n await this.storage.applyCommit(commit)\n this.setHash = new LtHash(commit.setHash)\n }\n\n async applyWrites(\n writes: RecordWriteOp | RecordWriteOp[],\n ): Promise<CommitData> {\n const commit = await this.formatCommit(writes)\n await this.applyCommit(commit)\n return commit\n }\n\n // Reads\n\n async getRecord(\n collection: string,\n rkey: string,\n ): Promise<RepoRecord | null> {\n return this.storage.getRecord(collection, rkey)\n }\n\n async listCollections(): Promise<string[]> {\n return this.storage.listCollections()\n }\n\n async listRecords(\n collection: string,\n ): Promise<{ rkey: string; record: RepoRecord }[]> {\n return this.storage.listRecords(collection)\n }\n\n // Signed commits\n\n async commit(space: SpaceContext, keypair: Keypair): Promise<SignedCommit> {\n return createCommit(this.setHash, space, keypair)\n }\n\n // Integrity: the commit's hash matches our local set hash, and its MAC binds\n // that hash to the commit context. Does not establish authenticity — use\n // verifyCommitSig with the author's signing key for that.\n verifyCommit(space: SpaceContext, commit: SignedCommit): boolean {\n return (\n commit.hash.equals(this.setHash.digest()) && verifyCommitMac(space, commit)\n )\n }\n\n // Authenticity: the signature over the commit context is valid for didKey.\n verifyCommitSig(\n space: SpaceContext,\n commit: SignedCommit,\n didKey: string,\n ): Promise<boolean> {\n return verifyCommitSig(space, commit, didKey)\n }\n}\n\nexport { RecordAlreadyExistsError, RecordNotFoundError } from './error.js'\n"]}
@@ -0,0 +1,3 @@
1
+ export * from './types.js';
2
+ export * from './memory-repo-storage.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,0BAA0B,CAAA"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types.js"), exports);
18
+ __exportStar(require("./memory-repo-storage.js"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA0B;AAC1B,2DAAwC","sourcesContent":["export * from './types.js'\nexport * from './memory-repo-storage.js'\n"]}
@@ -0,0 +1,12 @@
1
+ import { MemberCommitData } from '../types.js';
2
+ import { SpaceMembersStorage } from './types.js';
3
+ export declare class MemoryMembersStorage implements SpaceMembersStorage {
4
+ private members;
5
+ private setHashState;
6
+ getMembers(): Promise<string[]>;
7
+ isMember(did: string): Promise<boolean>;
8
+ getSetHashState(): Promise<Buffer | null>;
9
+ applyCommit(commit: MemberCommitData): Promise<void>;
10
+ destroy(): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=memory-members-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-members-storage.d.ts","sourceRoot":"","sources":["../../src/storage/memory-members-storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAkB,MAAM,aAAa,CAAA;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAEhD,qBAAa,oBAAqB,YAAW,mBAAmB;IAC9D,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,YAAY,CAAsB;IAEpC,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAI/B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIvC,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIzC,WAAW,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAI/B"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryMembersStorage = void 0;
4
+ const types_js_1 = require("../types.js");
5
+ class MemoryMembersStorage {
6
+ constructor() {
7
+ this.members = new Set();
8
+ this.setHashState = null;
9
+ }
10
+ async getMembers() {
11
+ return [...this.members];
12
+ }
13
+ async isMember(did) {
14
+ return this.members.has(did);
15
+ }
16
+ async getSetHashState() {
17
+ return this.setHashState;
18
+ }
19
+ async applyCommit(commit) {
20
+ for (const op of commit.ops) {
21
+ if (op.action === types_js_1.MemberOpAction.Add) {
22
+ this.members.add(op.did);
23
+ }
24
+ else if (op.action === types_js_1.MemberOpAction.Remove) {
25
+ this.members.delete(op.did);
26
+ }
27
+ }
28
+ this.setHashState = Buffer.from(commit.setHash);
29
+ }
30
+ async destroy() {
31
+ this.members.clear();
32
+ this.setHashState = null;
33
+ }
34
+ }
35
+ exports.MemoryMembersStorage = MemoryMembersStorage;
36
+ //# sourceMappingURL=memory-members-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-members-storage.js","sourceRoot":"","sources":["../../src/storage/memory-members-storage.ts"],"names":[],"mappings":";;;AAAA,0CAA8D;AAG9D,MAAa,oBAAoB;IAAjC;QACU,YAAO,GAAG,IAAI,GAAG,EAAU,CAAA;QAC3B,iBAAY,GAAkB,IAAI,CAAA;IA6B5C,CAAC;IA3BC,KAAK,CAAC,UAAU;QACd,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,GAAW;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,OAAO,IAAI,CAAC,YAAY,CAAA;IAC1B,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAwB;QACxC,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;YAC5B,IAAI,EAAE,CAAC,MAAM,KAAK,yBAAc,CAAC,GAAG,EAAE,CAAC;gBACrC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;YAC1B,CAAC;iBAAM,IAAI,EAAE,CAAC,MAAM,KAAK,yBAAc,CAAC,MAAM,EAAE,CAAC;gBAC/C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;YAC7B,CAAC;QACH,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACjD,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;QACpB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;IAC1B,CAAC;CACF;AA/BD,oDA+BC","sourcesContent":["import { MemberCommitData, MemberOpAction } from '../types.js'\nimport { SpaceMembersStorage } from './types.js'\n\nexport class MemoryMembersStorage implements SpaceMembersStorage {\n private members = new Set<string>()\n private setHashState: Buffer | null = null\n\n async getMembers(): Promise<string[]> {\n return [...this.members]\n }\n\n async isMember(did: string): Promise<boolean> {\n return this.members.has(did)\n }\n\n async getSetHashState(): Promise<Buffer | null> {\n return this.setHashState\n }\n\n async applyCommit(commit: MemberCommitData): Promise<void> {\n for (const op of commit.ops) {\n if (op.action === MemberOpAction.Add) {\n this.members.add(op.did)\n } else if (op.action === MemberOpAction.Remove) {\n this.members.delete(op.did)\n }\n }\n this.setHashState = Buffer.from(commit.setHash)\n }\n\n async destroy(): Promise<void> {\n this.members.clear()\n this.setHashState = null\n }\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import { CommitData, RepoRecord } from '../types.js';
2
+ import { SpaceRepoStorage } from './types.js';
3
+ export declare class MemoryRepoStorage implements SpaceRepoStorage {
4
+ private data;
5
+ private setHashState;
6
+ getRecord(collection: string, rkey: string): Promise<RepoRecord | null>;
7
+ hasRecord(collection: string, rkey: string): Promise<boolean>;
8
+ listCollections(): Promise<string[]>;
9
+ listRecords(collection: string): Promise<{
10
+ rkey: string;
11
+ record: RepoRecord;
12
+ }[]>;
13
+ getSetHashState(): Promise<Buffer | null>;
14
+ applyCommit(commit: CommitData): Promise<void>;
15
+ destroy(): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=memory-repo-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-repo-storage.d.ts","sourceRoot":"","sources":["../../src/storage/memory-repo-storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAiB,MAAM,aAAa,CAAA;AAEnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAE7C,qBAAa,iBAAkB,YAAW,gBAAgB;IACxD,OAAO,CAAC,IAAI,CAAgC;IAC5C,OAAO,CAAC,YAAY,CAAsB;IAEpC,SAAS,CACb,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAIvB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7D,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAQpC,WAAW,CACf,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAE,EAAE,CAAC;IAW5C,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIzC,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAe9C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAI/B"}
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryRepoStorage = void 0;
4
+ const types_js_1 = require("../types.js");
5
+ const util_js_1 = require("../util.js");
6
+ class MemoryRepoStorage {
7
+ constructor() {
8
+ this.data = new Map();
9
+ this.setHashState = null;
10
+ }
11
+ async getRecord(collection, rkey) {
12
+ return this.data.get((0, util_js_1.formatDataKey)(collection, rkey)) ?? null;
13
+ }
14
+ async hasRecord(collection, rkey) {
15
+ return this.data.has((0, util_js_1.formatDataKey)(collection, rkey));
16
+ }
17
+ async listCollections() {
18
+ const collections = new Set();
19
+ for (const key of this.data.keys()) {
20
+ collections.add((0, util_js_1.parseDataKey)(key).collection);
21
+ }
22
+ return [...collections];
23
+ }
24
+ async listRecords(collection) {
25
+ const results = [];
26
+ for (const [key, record] of this.data) {
27
+ const parsed = (0, util_js_1.parseDataKey)(key);
28
+ if (parsed.collection === collection) {
29
+ results.push({ rkey: parsed.rkey, record });
30
+ }
31
+ }
32
+ return results;
33
+ }
34
+ async getSetHashState() {
35
+ return this.setHashState;
36
+ }
37
+ async applyCommit(commit) {
38
+ for (const write of commit.writes) {
39
+ const key = (0, util_js_1.formatDataKey)(write.collection, write.rkey);
40
+ if (write.action === types_js_1.WriteOpAction.Create ||
41
+ write.action === types_js_1.WriteOpAction.Update) {
42
+ this.data.set(key, write.record);
43
+ }
44
+ else if (write.action === types_js_1.WriteOpAction.Delete) {
45
+ this.data.delete(key);
46
+ }
47
+ }
48
+ this.setHashState = Buffer.from(commit.setHash);
49
+ }
50
+ async destroy() {
51
+ this.data.clear();
52
+ this.setHashState = null;
53
+ }
54
+ }
55
+ exports.MemoryRepoStorage = MemoryRepoStorage;
56
+ //# sourceMappingURL=memory-repo-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-repo-storage.js","sourceRoot":"","sources":["../../src/storage/memory-repo-storage.ts"],"names":[],"mappings":";;;AAAA,0CAAmE;AACnE,wCAAwD;AAGxD,MAAa,iBAAiB;IAA9B;QACU,SAAI,GAAG,IAAI,GAAG,EAAsB,CAAA;QACpC,iBAAY,GAAkB,IAAI,CAAA;IAyD5C,CAAC;IAvDC,KAAK,CAAC,SAAS,CACb,UAAkB,EAClB,IAAY;QAEZ,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAA,uBAAa,EAAC,UAAU,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI,CAAA;IAC/D,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,UAAkB,EAAE,IAAY;QAC9C,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAA,uBAAa,EAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAA;IACvD,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAA;QACrC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACnC,WAAW,CAAC,GAAG,CAAC,IAAA,sBAAY,EAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAA;QAC/C,CAAC;QACD,OAAO,CAAC,GAAG,WAAW,CAAC,CAAA;IACzB,CAAC;IAED,KAAK,CAAC,WAAW,CACf,UAAkB;QAElB,MAAM,OAAO,GAA2C,EAAE,CAAA;QAC1D,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACtC,MAAM,MAAM,GAAG,IAAA,sBAAY,EAAC,GAAG,CAAC,CAAA;YAChC,IAAI,MAAM,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;gBACrC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;YAC7C,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,OAAO,IAAI,CAAC,YAAY,CAAA;IAC1B,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,MAAkB;QAClC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,IAAA,uBAAa,EAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;YACvD,IACE,KAAK,CAAC,MAAM,KAAK,wBAAa,CAAC,MAAM;gBACrC,KAAK,CAAC,MAAM,KAAK,wBAAa,CAAC,MAAM,EACrC,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;YAClC,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,KAAK,wBAAa,CAAC,MAAM,EAAE,CAAC;gBACjD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACvB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACjD,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;QACjB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;IAC1B,CAAC;CACF;AA3DD,8CA2DC","sourcesContent":["import { CommitData, RepoRecord, WriteOpAction } from '../types.js'\nimport { formatDataKey, parseDataKey } from '../util.js'\nimport { SpaceRepoStorage } from './types.js'\n\nexport class MemoryRepoStorage implements SpaceRepoStorage {\n private data = new Map<string, RepoRecord>()\n private setHashState: Buffer | null = null\n\n async getRecord(\n collection: string,\n rkey: string,\n ): Promise<RepoRecord | null> {\n return this.data.get(formatDataKey(collection, rkey)) ?? null\n }\n\n async hasRecord(collection: string, rkey: string): Promise<boolean> {\n return this.data.has(formatDataKey(collection, rkey))\n }\n\n async listCollections(): Promise<string[]> {\n const collections = new Set<string>()\n for (const key of this.data.keys()) {\n collections.add(parseDataKey(key).collection)\n }\n return [...collections]\n }\n\n async listRecords(\n collection: string,\n ): Promise<{ rkey: string; record: RepoRecord }[]> {\n const results: { rkey: string; record: RepoRecord }[] = []\n for (const [key, record] of this.data) {\n const parsed = parseDataKey(key)\n if (parsed.collection === collection) {\n results.push({ rkey: parsed.rkey, record })\n }\n }\n return results\n }\n\n async getSetHashState(): Promise<Buffer | null> {\n return this.setHashState\n }\n\n async applyCommit(commit: CommitData): Promise<void> {\n for (const write of commit.writes) {\n const key = formatDataKey(write.collection, write.rkey)\n if (\n write.action === WriteOpAction.Create ||\n write.action === WriteOpAction.Update\n ) {\n this.data.set(key, write.record)\n } else if (write.action === WriteOpAction.Delete) {\n this.data.delete(key)\n }\n }\n this.setHashState = Buffer.from(commit.setHash)\n }\n\n async destroy(): Promise<void> {\n this.data.clear()\n this.setHashState = null\n }\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import { CommitData, RepoRecord } from '../types.js';
2
+ export interface SpaceRepoStorage {
3
+ getRecord(collection: string, rkey: string): Promise<RepoRecord | null>;
4
+ hasRecord(collection: string, rkey: string): Promise<boolean>;
5
+ listCollections(): Promise<string[]>;
6
+ listRecords(collection: string): Promise<{
7
+ rkey: string;
8
+ record: RepoRecord;
9
+ }[]>;
10
+ getSetHashState(): Promise<Buffer | null>;
11
+ applyCommit(commit: CommitData): Promise<void>;
12
+ destroy(): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEpD,MAAM,WAAW,gBAAgB;IAE/B,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IACvE,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7D,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACpC,WAAW,CACT,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAE,EAAE,CAAC,CAAA;IAGlD,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAGzC,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAG9C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"","sourcesContent":["import { CommitData, RepoRecord } from '../types.js'\n\nexport interface SpaceRepoStorage {\n // Record reads\n getRecord(collection: string, rkey: string): Promise<RepoRecord | null>\n hasRecord(collection: string, rkey: string): Promise<boolean>\n listCollections(): Promise<string[]>\n listRecords(\n collection: string,\n ): Promise<{ rkey: string; record: RepoRecord }[]>\n\n // Set hash\n getSetHashState(): Promise<Buffer | null>\n\n // Atomic commit\n applyCommit(commit: CommitData): Promise<void>\n\n // Lifecycle\n destroy(): Promise<void>\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import { Cid } from '@atproto/lex-data';
2
+ export type CarBlock = {
3
+ cid: Cid;
4
+ bytes: Uint8Array;
5
+ };
6
+ export declare const encodeCarHeader: (roots: Cid[]) => Uint8Array;
7
+ export declare const encodeCarBlock: (block: CarBlock) => Uint8Array;
8
+ export declare function writeCarStream(roots: Cid[], blocks: AsyncIterable<CarBlock> | Iterable<CarBlock>): AsyncIterable<Uint8Array>;
9
+ export declare const readCarStream: (car: AsyncIterable<Uint8Array> | Iterable<Uint8Array>) => Promise<{
10
+ roots: Cid[];
11
+ blocks: AsyncGenerator<CarBlock>;
12
+ }>;
13
+ //# sourceMappingURL=car.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"car.d.ts","sourceRoot":"","sources":["../../src/sync/car.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAoB,MAAM,mBAAmB,CAAA;AAGzD,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,GAAG,CAAA;IACR,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAKD,eAAO,MAAM,eAAe,UAAW,GAAG,EAAE,KAAG,UAG9C,CAAA;AAGD,eAAO,MAAM,cAAc,UAAW,QAAQ,KAAG,UAOhD,CAAA;AAED,wBAAuB,cAAc,CACnC,KAAK,EAAE,GAAG,EAAE,EACZ,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,GACnD,aAAa,CAAC,UAAU,CAAC,CAK3B;AAGD,eAAO,MAAM,aAAa,QACnB,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,KACpD,OAAO,CAAC;IAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAA;CAAE,CAgC5D,CAAA"}
@@ -0,0 +1,185 @@
1
+ import { decode, encode } from '@atproto/lex-cbor';
2
+ import { decodeCid, ifCid } from '@atproto/lex-data';
3
+ import { RepoVerificationError } from '../error.js';
4
+ // A minimal CAR v1 reader/writer. `@atproto/repo` has its own, but it writes
5
+ // single-root files and carries the MST machinery with it; we need two roots.
6
+ export const encodeCarHeader = (roots) => {
7
+ const header = encode({ version: 1, roots });
8
+ return concat(encodeVarint(header.byteLength), header);
9
+ };
10
+ // varint(len(cid) + len(bytes)) || cid || bytes
11
+ export const encodeCarBlock = (block) => {
12
+ const cidBytes = block.cid.bytes;
13
+ return concat(encodeVarint(cidBytes.byteLength + block.bytes.byteLength), cidBytes, block.bytes);
14
+ };
15
+ export async function* writeCarStream(roots, blocks) {
16
+ yield encodeCarHeader(roots);
17
+ for await (const block of blocks) {
18
+ yield encodeCarBlock(block);
19
+ }
20
+ }
21
+ // Blocks are not verified against their cids; verifyRepoCar does that.
22
+ export const readCarStream = async (car) => {
23
+ const reader = new ByteReader(car);
24
+ const headerSize = await reader.readVarint();
25
+ if (headerSize === null) {
26
+ throw new RepoVerificationError('could not read CAR header');
27
+ }
28
+ let header;
29
+ try {
30
+ header = decode(await reader.readExactly(headerSize));
31
+ }
32
+ catch (err) {
33
+ throw new RepoVerificationError(`could not decode CAR header: ${err instanceof Error ? err.message : String(err)}`);
34
+ }
35
+ if (header === null || typeof header !== 'object') {
36
+ throw new RepoVerificationError('malformed CAR header');
37
+ }
38
+ const { version, roots } = header;
39
+ if (version !== 1) {
40
+ throw new RepoVerificationError(`unsupported CAR version: ${version}`);
41
+ }
42
+ if (!Array.isArray(roots)) {
43
+ throw new RepoVerificationError('CAR header is missing roots');
44
+ }
45
+ const parsedRoots = roots.map((root) => {
46
+ const cid = ifCid(root);
47
+ if (!cid)
48
+ throw new RepoVerificationError('invalid root cid in CAR header');
49
+ return cid;
50
+ });
51
+ return { roots: parsedRoots, blocks: readCarBlocks(reader) };
52
+ };
53
+ async function* readCarBlocks(reader) {
54
+ while (true) {
55
+ const blockSize = await reader.readVarint();
56
+ if (blockSize === null)
57
+ return;
58
+ const framed = await reader.readExactly(blockSize);
59
+ if (framed.byteLength <= CID_BYTE_LENGTH) {
60
+ throw new RepoVerificationError('truncated block in CAR');
61
+ }
62
+ let cid;
63
+ try {
64
+ cid = decodeCid(framed.subarray(0, CID_BYTE_LENGTH));
65
+ }
66
+ catch (err) {
67
+ throw new RepoVerificationError(`invalid block cid in CAR: ${err instanceof Error ? err.message : String(err)}`);
68
+ }
69
+ yield { cid, bytes: framed.subarray(CID_BYTE_LENGTH) };
70
+ }
71
+ }
72
+ const CID_BYTE_LENGTH = 36;
73
+ class ByteReader {
74
+ constructor(stream) {
75
+ this.chunks = [];
76
+ this.buffered = 0;
77
+ this.done = false;
78
+ this.iterator =
79
+ Symbol.asyncIterator in stream
80
+ ? stream[Symbol.asyncIterator]()
81
+ : stream[Symbol.iterator]();
82
+ }
83
+ // Returns null at a clean end of stream.
84
+ async readVarint() {
85
+ let result = 0;
86
+ for (let shift = 0; shift <= 49; shift += 7) {
87
+ const byte = await this.readByte();
88
+ if (byte === null) {
89
+ if (shift === 0)
90
+ return null;
91
+ throw new RepoVerificationError('truncated varint in CAR');
92
+ }
93
+ result += (byte & 0x7f) * 2 ** shift;
94
+ if ((byte & 0x80) === 0)
95
+ return result;
96
+ }
97
+ throw new RepoVerificationError('varint in CAR is too large');
98
+ }
99
+ async readExactly(size) {
100
+ await this.fill(size);
101
+ if (this.buffered < size) {
102
+ throw new RepoVerificationError('unexpected end of CAR');
103
+ }
104
+ if (size === 0)
105
+ return new Uint8Array();
106
+ const first = this.chunks[0];
107
+ if (first.byteLength >= size) {
108
+ this.chunks[0] = first.subarray(size);
109
+ if (this.chunks[0].byteLength === 0)
110
+ this.chunks.shift();
111
+ this.buffered -= size;
112
+ return first.subarray(0, size);
113
+ }
114
+ const out = new Uint8Array(size);
115
+ let offset = 0;
116
+ while (offset < size) {
117
+ const chunk = this.chunks[0];
118
+ const take = Math.min(chunk.byteLength, size - offset);
119
+ out.set(chunk.subarray(0, take), offset);
120
+ offset += take;
121
+ if (take === chunk.byteLength) {
122
+ this.chunks.shift();
123
+ }
124
+ else {
125
+ this.chunks[0] = chunk.subarray(take);
126
+ }
127
+ this.buffered -= take;
128
+ }
129
+ return out;
130
+ }
131
+ async readByte() {
132
+ await this.fill(1);
133
+ if (this.buffered === 0)
134
+ return null;
135
+ const first = this.chunks[0];
136
+ const byte = first[0];
137
+ if (first.byteLength === 1) {
138
+ this.chunks.shift();
139
+ }
140
+ else {
141
+ this.chunks[0] = first.subarray(1);
142
+ }
143
+ this.buffered -= 1;
144
+ return byte;
145
+ }
146
+ async fill(needed) {
147
+ while (this.buffered < needed && !this.done) {
148
+ const next = await this.iterator.next();
149
+ if (next.done) {
150
+ this.done = true;
151
+ return;
152
+ }
153
+ if (next.value.byteLength > 0) {
154
+ this.chunks.push(next.value);
155
+ this.buffered += next.value.byteLength;
156
+ }
157
+ }
158
+ }
159
+ }
160
+ const encodeVarint = (value) => {
161
+ if (!Number.isSafeInteger(value) || value < 0) {
162
+ throw new Error(`cannot encode varint: ${value}`);
163
+ }
164
+ const out = [];
165
+ let rest = value;
166
+ while (rest >= 0x80) {
167
+ out.push((rest & 0x7f) | 0x80);
168
+ rest = Math.floor(rest / 128);
169
+ }
170
+ out.push(rest);
171
+ return new Uint8Array(out);
172
+ };
173
+ const concat = (...parts) => {
174
+ let size = 0;
175
+ for (const part of parts)
176
+ size += part.byteLength;
177
+ const out = new Uint8Array(size);
178
+ let offset = 0;
179
+ for (const part of parts) {
180
+ out.set(part, offset);
181
+ offset += part.byteLength;
182
+ }
183
+ return out;
184
+ };
185
+ //# sourceMappingURL=car.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"car.js","sourceRoot":"","sources":["../../src/sync/car.ts"],"names":[],"mappings":"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","sourcesContent":["import { decode, encode } from '@atproto/lex-cbor'\nimport { Cid, decodeCid, ifCid } from '@atproto/lex-data'\nimport { RepoVerificationError } from '../error.js'\n\nexport type CarBlock = {\n cid: Cid\n bytes: Uint8Array\n}\n\n// A minimal CAR v1 reader/writer. `@atproto/repo` has its own, but it writes\n// single-root files and carries the MST machinery with it; we need two roots.\n\nexport const encodeCarHeader = (roots: Cid[]): Uint8Array => {\n const header = encode({ version: 1, roots })\n return concat(encodeVarint(header.byteLength), header)\n}\n\n// varint(len(cid) + len(bytes)) || cid || bytes\nexport const encodeCarBlock = (block: CarBlock): Uint8Array => {\n const cidBytes = block.cid.bytes\n return concat(\n encodeVarint(cidBytes.byteLength + block.bytes.byteLength),\n cidBytes,\n block.bytes,\n )\n}\n\nexport async function* writeCarStream(\n roots: Cid[],\n blocks: AsyncIterable<CarBlock> | Iterable<CarBlock>,\n): AsyncIterable<Uint8Array> {\n yield encodeCarHeader(roots)\n for await (const block of blocks) {\n yield encodeCarBlock(block)\n }\n}\n\n// Blocks are not verified against their cids; verifyRepoCar does that.\nexport const readCarStream = async (\n car: AsyncIterable<Uint8Array> | Iterable<Uint8Array>,\n): Promise<{ roots: Cid[]; blocks: AsyncGenerator<CarBlock> }> => {\n const reader = new ByteReader(car)\n const headerSize = await reader.readVarint()\n if (headerSize === null) {\n throw new RepoVerificationError('could not read CAR header')\n }\n\n let header: unknown\n try {\n header = decode(await reader.readExactly(headerSize))\n } catch (err) {\n throw new RepoVerificationError(\n `could not decode CAR header: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n if (header === null || typeof header !== 'object') {\n throw new RepoVerificationError('malformed CAR header')\n }\n const { version, roots } = header as { version?: unknown; roots?: unknown }\n if (version !== 1) {\n throw new RepoVerificationError(`unsupported CAR version: ${version}`)\n }\n if (!Array.isArray(roots)) {\n throw new RepoVerificationError('CAR header is missing roots')\n }\n const parsedRoots = roots.map((root) => {\n const cid = ifCid(root)\n if (!cid) throw new RepoVerificationError('invalid root cid in CAR header')\n return cid\n })\n\n return { roots: parsedRoots, blocks: readCarBlocks(reader) }\n}\n\nasync function* readCarBlocks(reader: ByteReader): AsyncGenerator<CarBlock> {\n while (true) {\n const blockSize = await reader.readVarint()\n if (blockSize === null) return\n const framed = await reader.readExactly(blockSize)\n if (framed.byteLength <= CID_BYTE_LENGTH) {\n throw new RepoVerificationError('truncated block in CAR')\n }\n let cid: Cid\n try {\n cid = decodeCid(framed.subarray(0, CID_BYTE_LENGTH))\n } catch (err) {\n throw new RepoVerificationError(\n `invalid block cid in CAR: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n yield { cid, bytes: framed.subarray(CID_BYTE_LENGTH) }\n }\n}\n\nconst CID_BYTE_LENGTH = 36\n\nclass ByteReader {\n private readonly iterator: AsyncIterator<Uint8Array> | Iterator<Uint8Array>\n private chunks: Uint8Array[] = []\n private buffered = 0\n private done = false\n\n constructor(stream: AsyncIterable<Uint8Array> | Iterable<Uint8Array>) {\n this.iterator =\n Symbol.asyncIterator in stream\n ? stream[Symbol.asyncIterator]()\n : stream[Symbol.iterator]()\n }\n\n // Returns null at a clean end of stream.\n async readVarint(): Promise<number | null> {\n let result = 0\n for (let shift = 0; shift <= 49; shift += 7) {\n const byte = await this.readByte()\n if (byte === null) {\n if (shift === 0) return null\n throw new RepoVerificationError('truncated varint in CAR')\n }\n result += (byte & 0x7f) * 2 ** shift\n if ((byte & 0x80) === 0) return result\n }\n throw new RepoVerificationError('varint in CAR is too large')\n }\n\n async readExactly(size: number): Promise<Uint8Array> {\n await this.fill(size)\n if (this.buffered < size) {\n throw new RepoVerificationError('unexpected end of CAR')\n }\n if (size === 0) return new Uint8Array()\n\n const first = this.chunks[0]\n if (first.byteLength >= size) {\n this.chunks[0] = first.subarray(size)\n if (this.chunks[0].byteLength === 0) this.chunks.shift()\n this.buffered -= size\n return first.subarray(0, size)\n }\n\n const out = new Uint8Array(size)\n let offset = 0\n while (offset < size) {\n const chunk = this.chunks[0]\n const take = Math.min(chunk.byteLength, size - offset)\n out.set(chunk.subarray(0, take), offset)\n offset += take\n if (take === chunk.byteLength) {\n this.chunks.shift()\n } else {\n this.chunks[0] = chunk.subarray(take)\n }\n this.buffered -= take\n }\n return out\n }\n\n private async readByte(): Promise<number | null> {\n await this.fill(1)\n if (this.buffered === 0) return null\n const first = this.chunks[0]\n const byte = first[0]\n if (first.byteLength === 1) {\n this.chunks.shift()\n } else {\n this.chunks[0] = first.subarray(1)\n }\n this.buffered -= 1\n return byte\n }\n\n private async fill(needed: number): Promise<void> {\n while (this.buffered < needed && !this.done) {\n const next = await this.iterator.next()\n if (next.done) {\n this.done = true\n return\n }\n if (next.value.byteLength > 0) {\n this.chunks.push(next.value)\n this.buffered += next.value.byteLength\n }\n }\n }\n}\n\nconst encodeVarint = (value: number): Uint8Array => {\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new Error(`cannot encode varint: ${value}`)\n }\n const out: number[] = []\n let rest = value\n while (rest >= 0x80) {\n out.push((rest & 0x7f) | 0x80)\n rest = Math.floor(rest / 128)\n }\n out.push(rest)\n return new Uint8Array(out)\n}\n\nconst concat = (...parts: Uint8Array[]): Uint8Array => {\n let size = 0\n for (const part of parts) size += part.byteLength\n const out = new Uint8Array(size)\n let offset = 0\n for (const part of parts) {\n out.set(part, offset)\n offset += part.byteLength\n }\n return out\n}\n"]}
@@ -0,0 +1,39 @@
1
+ import type { Cid } from '@atproto/lex-data';
2
+ import { RepoCommit } from '../repo-commit.js';
3
+ import { type RepoIndex, type SignedCommit, type SpaceRecord } from '../types.js';
4
+ export type VerifyRepoParams = {
5
+ space: string;
6
+ author: string;
7
+ didKey: string;
8
+ expectValues?: boolean;
9
+ };
10
+ export type VerifiedRecord = {
11
+ collection: string;
12
+ rkey: string;
13
+ cid: Cid;
14
+ record: SpaceRecord;
15
+ };
16
+ export type VerifiedRepo = {
17
+ commit: SignedCommit;
18
+ index: RepoIndex;
19
+ repo: RepoCommit;
20
+ records: AsyncGenerator<VerifiedRecord>;
21
+ };
22
+ /**
23
+ * Verify a serialized repo, streaming out its records.
24
+ *
25
+ * The three stages follow the CAR's layout, so nothing needs buffering: the
26
+ * commit, then the index against the commit's hash (which authenticates every
27
+ * path/cid pair without reading a record), then each block against its index
28
+ * entry. That last stage runs as `records` is drained, so it must be consumed to
29
+ * know the repo was complete — {@link verifyRepoCarFull} does that.
30
+ */
31
+ export declare const verifyRepoCar: (car: AsyncIterable<Uint8Array> | Iterable<Uint8Array>, params: VerifyRepoParams) => Promise<VerifiedRepo>;
32
+ /**
33
+ * Verify a serialized repo and collect its records. Prefer {@link verifyRepoCar}
34
+ * for large repos, where streaming avoids holding every record at once.
35
+ */
36
+ export declare const verifyRepoCarFull: (car: AsyncIterable<Uint8Array> | Iterable<Uint8Array>, params: VerifyRepoParams) => Promise<Omit<VerifiedRepo, 'records'> & {
37
+ records: VerifiedRecord[];
38
+ }>;
39
+ //# sourceMappingURL=consumer.d.ts.map