@alteran/astro 0.7.6 → 0.8.1
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/README.md +25 -25
- package/migrations/0010_eminent_klaw.sql +37 -0
- package/migrations/0011_chief_darwin.sql +31 -0
- package/migrations/0012_backfill_blob_usage.sql +39 -0
- package/migrations/meta/0010_snapshot.json +790 -0
- package/migrations/meta/0011_snapshot.json +813 -0
- package/migrations/meta/_journal.json +22 -1
- package/package.json +24 -41
- package/src/db/blob.ts +323 -0
- package/src/db/dal.ts +224 -78
- package/src/db/repo.ts +205 -25
- package/src/db/schema.ts +14 -5
- package/src/handlers/debug.ts +4 -3
- package/src/lib/appview/auth-policy.ts +7 -24
- package/src/lib/appview/proxy.ts +56 -23
- package/src/lib/appview/types.ts +1 -6
- package/src/lib/auth-scope.ts +399 -0
- package/src/lib/auth.ts +40 -39
- package/src/lib/commit.ts +37 -15
- package/src/lib/did-document.ts +4 -5
- package/src/lib/jwt.ts +3 -1
- package/src/lib/mime.ts +9 -0
- package/src/lib/oauth/observability.ts +53 -12
- package/src/lib/oauth/resource.ts +49 -0
- package/src/lib/preference-policy.ts +45 -0
- package/src/lib/preferences.ts +0 -4
- package/src/lib/public-host.ts +127 -0
- package/src/lib/ratelimit.ts +37 -12
- package/src/lib/relay.ts +7 -27
- package/src/lib/repo-write-blob-constraints.ts +141 -0
- package/src/lib/repo-write-data.ts +195 -0
- package/src/lib/repo-write-error.ts +46 -0
- package/src/lib/repo-write-validation.ts +463 -0
- package/src/lib/session-tokens.ts +22 -5
- package/src/lib/unsupported-routes.ts +32 -0
- package/src/lib/util.ts +57 -2
- package/src/pages/.well-known/atproto-did.ts +15 -3
- package/src/pages/.well-known/did.json.ts +13 -7
- package/src/pages/debug/db/bootstrap.ts +4 -3
- package/src/pages/debug/gc/blobs.ts +11 -8
- package/src/pages/debug/record.ts +11 -0
- package/src/pages/oauth/token.ts +78 -33
- package/src/pages/xrpc/[...nsid].ts +17 -9
- package/src/pages/xrpc/app.bsky.actor.getPreferences.ts +9 -3
- package/src/pages/xrpc/app.bsky.actor.putPreferences.ts +17 -4
- package/src/pages/xrpc/app.bsky.unspecced.getAgeAssuranceState.ts +4 -2
- package/src/pages/xrpc/chat.bsky.convo.getLog.ts +4 -2
- package/src/pages/xrpc/chat.bsky.convo.listConvos.ts +4 -2
- package/src/pages/xrpc/com.atproto.identity.getRecommendedDidCredentials.ts +10 -6
- package/src/pages/xrpc/com.atproto.identity.requestPlcOperationSignature.ts +4 -3
- package/src/pages/xrpc/com.atproto.identity.resolveHandle.ts +13 -5
- package/src/pages/xrpc/com.atproto.identity.signPlcOperation.ts +4 -2
- package/src/pages/xrpc/com.atproto.identity.submitPlcOperation.ts +4 -2
- package/src/pages/xrpc/com.atproto.identity.updateHandle.ts +12 -36
- package/src/pages/xrpc/com.atproto.repo.applyWrites.ts +90 -139
- package/src/pages/xrpc/com.atproto.repo.createRecord.ts +74 -47
- package/src/pages/xrpc/com.atproto.repo.deleteRecord.ts +119 -46
- package/src/pages/xrpc/com.atproto.repo.describeRepo.ts +21 -20
- package/src/pages/xrpc/com.atproto.repo.getRecord.ts +6 -1
- package/src/pages/xrpc/com.atproto.repo.listMissingBlobs.ts +4 -2
- package/src/pages/xrpc/com.atproto.repo.putRecord.ts +84 -47
- package/src/pages/xrpc/com.atproto.repo.uploadBlob.ts +199 -78
- package/src/pages/xrpc/com.atproto.server.checkAccountStatus.ts +4 -2
- package/src/pages/xrpc/com.atproto.server.getServiceAuth.ts +88 -21
- package/src/pages/xrpc/com.atproto.server.getSession.ts +3 -13
- package/src/pages/xrpc/com.atproto.sync.getBlob.ts +92 -74
- package/src/pages/xrpc/com.atproto.sync.listBlobs.ts +45 -23
- package/src/services/car.ts +13 -0
- package/src/services/repo/apply-prepared-writes.ts +185 -0
- package/src/services/repo/blob-refs.ts +48 -0
- package/src/services/repo/blockstore-ops.ts +59 -17
- package/src/services/repo/list-blobs.ts +43 -0
- package/src/services/repo-manager.ts +221 -78
- package/src/worker/runtime.ts +1 -1
- package/src/worker/sequencer/upgrade.ts +4 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Env } from '../../env';
|
|
2
|
+
|
|
3
|
+
type ListBlobCidsParams = {
|
|
4
|
+
did: string;
|
|
5
|
+
since?: string;
|
|
6
|
+
cursor?: string;
|
|
7
|
+
limit: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export async function listBlobCids(
|
|
11
|
+
env: Env,
|
|
12
|
+
{ did, since, cursor, limit }: ListBlobCidsParams,
|
|
13
|
+
): Promise<{ cids: string[]; cursor?: string }> {
|
|
14
|
+
const pageSize = limit + 1;
|
|
15
|
+
const clauses = ['did = ?'];
|
|
16
|
+
const values: Array<string | number> = [did];
|
|
17
|
+
if (since) {
|
|
18
|
+
clauses.push('repo_rev > ?');
|
|
19
|
+
values.push(since);
|
|
20
|
+
}
|
|
21
|
+
if (cursor) {
|
|
22
|
+
clauses.push('cid > ?');
|
|
23
|
+
values.push(cursor);
|
|
24
|
+
}
|
|
25
|
+
values.push(pageSize);
|
|
26
|
+
|
|
27
|
+
const result = await env.ALTERAN_DB.prepare(
|
|
28
|
+
`SELECT DISTINCT cid
|
|
29
|
+
FROM blob_usage
|
|
30
|
+
WHERE ${clauses.join(' AND ')}
|
|
31
|
+
ORDER BY cid
|
|
32
|
+
LIMIT ?`,
|
|
33
|
+
)
|
|
34
|
+
.bind(...values)
|
|
35
|
+
.all<{ cid: string }>();
|
|
36
|
+
|
|
37
|
+
const rows = result.results ?? [];
|
|
38
|
+
const page = rows.slice(0, limit).map((row) => row.cid);
|
|
39
|
+
return {
|
|
40
|
+
cids: page,
|
|
41
|
+
...(page.length > 0 ? { cursor: page[page.length - 1] } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -5,18 +5,38 @@ import { drizzle } from 'drizzle-orm/d1';
|
|
|
5
5
|
import { repo_root } from '../db/schema';
|
|
6
6
|
import { eq, sql } from 'drizzle-orm';
|
|
7
7
|
import type { RepoOp } from '../lib/firehose/frames';
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
putRecordStatements,
|
|
10
|
+
repairRecordBlobUsageForCurrentRecord,
|
|
11
|
+
setRecordBlobUsageStatements,
|
|
12
|
+
getRecordBlobKeys,
|
|
13
|
+
type BlobKeyRef,
|
|
14
|
+
} from '../db/dal';
|
|
15
|
+
import { assertRepoHead, bumpRoot, RepoBlobNotFoundError, RepoCommitConflictError } from '../db/repo';
|
|
10
16
|
import { generateTid } from '../lib/commit';
|
|
11
17
|
import { resolveSecret } from '../lib/secrets';
|
|
12
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
collectUnstoredMstBlocks,
|
|
20
|
+
encodeRecordBlock,
|
|
21
|
+
cidForRecord,
|
|
22
|
+
type EncodedBlock,
|
|
23
|
+
} from './repo/blockstore-ops';
|
|
13
24
|
import { extractOps as extractOpsImpl } from './repo/operations';
|
|
14
25
|
import { ServerMisconfigured } from '../lib/errors';
|
|
26
|
+
import { RepoWriteError } from '../lib/repo-write-error';
|
|
27
|
+
import type { PreparedWrite } from '../lib/repo-write-validation';
|
|
28
|
+
import { assertBlobKeysAvailable, resolveRecordBlobKeys } from './repo/blob-refs';
|
|
29
|
+
import {
|
|
30
|
+
applyPreparedWritesToRepo,
|
|
31
|
+
type BatchCommitResult,
|
|
32
|
+
} from './repo/apply-prepared-writes';
|
|
15
33
|
|
|
16
34
|
interface RecordMutation {
|
|
17
35
|
mst: MST;
|
|
18
36
|
recordCid: CID;
|
|
37
|
+
recordBlock: EncodedBlock;
|
|
19
38
|
prevMstRoot: CID | null;
|
|
39
|
+
expectedCommitCid: string | null;
|
|
20
40
|
newMstBlocks: BlockMap;
|
|
21
41
|
}
|
|
22
42
|
|
|
@@ -29,8 +49,23 @@ interface CommitResult {
|
|
|
29
49
|
commitData: string;
|
|
30
50
|
sig: string;
|
|
31
51
|
blocks: string;
|
|
52
|
+
dereferencedBlobKeys: BlobKeyRef[];
|
|
32
53
|
}
|
|
33
54
|
|
|
55
|
+
interface NoopRecordResult {
|
|
56
|
+
uri: string;
|
|
57
|
+
cid: string;
|
|
58
|
+
ops: RepoOp[];
|
|
59
|
+
dereferencedBlobKeys?: undefined;
|
|
60
|
+
commitCid?: undefined;
|
|
61
|
+
rev?: undefined;
|
|
62
|
+
commitData?: undefined;
|
|
63
|
+
sig?: undefined;
|
|
64
|
+
blocks?: undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type RecordWriteResult = CommitResult | NoopRecordResult;
|
|
68
|
+
|
|
34
69
|
export class RepoManager {
|
|
35
70
|
private blockstore: D1Blockstore;
|
|
36
71
|
|
|
@@ -44,59 +79,61 @@ export class RepoManager {
|
|
|
44
79
|
return did;
|
|
45
80
|
}
|
|
46
81
|
|
|
47
|
-
async
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const did = await this.getDid();
|
|
51
|
-
|
|
52
|
-
const rows = await db
|
|
53
|
-
.select()
|
|
54
|
-
.from(repo_root)
|
|
55
|
-
.where(eq(repo_root.did, did))
|
|
56
|
-
.limit(1);
|
|
57
|
-
|
|
58
|
-
const row = rows[0];
|
|
59
|
-
if (!row) return null;
|
|
60
|
-
|
|
61
|
-
const commit = await this.env.ALTERAN_DB.prepare(
|
|
62
|
-
`SELECT data FROM commit_log WHERE cid = ? LIMIT 1`,
|
|
63
|
-
)
|
|
64
|
-
.bind(row.commitCid)
|
|
65
|
-
.first();
|
|
66
|
-
|
|
67
|
-
if (!commit) {
|
|
68
|
-
console.error(`[RepoManager] No commit found for CID: ${row.commitCid}`);
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
82
|
+
private async getRootSnapshot(): Promise<{ mst: MST; commitCid: string } | null> {
|
|
83
|
+
const db = drizzle(this.env.ALTERAN_DB);
|
|
84
|
+
const did = await this.getDid();
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
const rows = await db
|
|
87
|
+
.select()
|
|
88
|
+
.from(repo_root)
|
|
89
|
+
.where(eq(repo_root.did, did))
|
|
90
|
+
.limit(1);
|
|
74
91
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
);
|
|
92
|
+
const row = rows[0];
|
|
93
|
+
if (!row) return null;
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
const commit = await this.env.ALTERAN_DB.prepare(
|
|
96
|
+
`SELECT data FROM commit_log WHERE cid = ? LIMIT 1`,
|
|
97
|
+
)
|
|
98
|
+
.bind(row.commitCid)
|
|
99
|
+
.first();
|
|
100
|
+
|
|
101
|
+
if (!commit) {
|
|
102
|
+
throw new Error(`repo root points at missing commit ${row.commitCid}`);
|
|
83
103
|
}
|
|
104
|
+
|
|
105
|
+
const parsed = JSON.parse(String(commit.data));
|
|
106
|
+
const mstRoot = CID.parse(String(parsed.data));
|
|
107
|
+
|
|
108
|
+
console.log(
|
|
109
|
+
`[RepoManager] Loading MST root: ${mstRoot.toString()} from commit: ${row.commitCid}`,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return { mst: await MST.load(this.blockstore, mstRoot), commitCid: row.commitCid };
|
|
84
113
|
}
|
|
85
114
|
|
|
86
|
-
async
|
|
87
|
-
|
|
115
|
+
async getRoot(): Promise<MST | null> {
|
|
116
|
+
return (await this.getRootSnapshot())?.mst ?? null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private async getOrCreateRootSnapshot(): Promise<{ mst: MST; commitCid: string | null }> {
|
|
120
|
+
const existing = await this.getRootSnapshot();
|
|
88
121
|
if (existing) {
|
|
89
|
-
const pointer = await existing.getPointer();
|
|
122
|
+
const pointer = await existing.mst.getPointer();
|
|
90
123
|
console.log(`[RepoManager] Loaded existing MST root: ${pointer.toString()}`);
|
|
91
124
|
return existing;
|
|
92
125
|
}
|
|
93
126
|
|
|
94
127
|
console.log('[RepoManager] Creating new empty MST');
|
|
95
128
|
const mst = await MST.create(this.blockstore, []);
|
|
96
|
-
await storeMstBlocks(this.blockstore, mst);
|
|
97
129
|
const pointer = await mst.getPointer();
|
|
98
130
|
console.log(`[RepoManager] Created new MST root: ${pointer.toString()}`);
|
|
99
|
-
return mst;
|
|
131
|
+
return { mst, commitCid: null };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async getOrCreateRoot(): Promise<MST> {
|
|
135
|
+
const snapshot = await this.getOrCreateRootSnapshot();
|
|
136
|
+
return snapshot.mst;
|
|
100
137
|
}
|
|
101
138
|
|
|
102
139
|
async addRecord(
|
|
@@ -105,44 +142,70 @@ export class RepoManager {
|
|
|
105
142
|
record: unknown,
|
|
106
143
|
): Promise<RecordMutation> {
|
|
107
144
|
const key = `${collection}/${rkey}`;
|
|
108
|
-
const currentMst = await this.
|
|
109
|
-
const prevMstRoot = await currentMst.getPointer();
|
|
110
|
-
const
|
|
145
|
+
const { mst: currentMst, commitCid: expectedCommitCid } = await this.getOrCreateRootSnapshot();
|
|
146
|
+
const prevMstRoot = expectedCommitCid === null ? null : await currentMst.getPointer();
|
|
147
|
+
const recordBlock = await encodeRecordBlock(record);
|
|
148
|
+
const [recordCid] = recordBlock;
|
|
111
149
|
const newMst = await currentMst.add(key, recordCid);
|
|
112
|
-
const newMstBlocks = await
|
|
113
|
-
return { mst: newMst, recordCid, prevMstRoot, newMstBlocks };
|
|
150
|
+
const newMstBlocks = await collectUnstoredMstBlocks(newMst);
|
|
151
|
+
return { mst: newMst, recordCid, recordBlock, prevMstRoot, expectedCommitCid, newMstBlocks };
|
|
114
152
|
}
|
|
115
153
|
|
|
116
154
|
async createRecord(
|
|
117
155
|
collection: string,
|
|
118
156
|
record: unknown,
|
|
119
157
|
rkey?: string,
|
|
158
|
+
blobKeys?: string[],
|
|
159
|
+
expectedCommitCid?: string | null,
|
|
120
160
|
): Promise<CommitResult> {
|
|
121
161
|
const key = rkey ?? generateTid();
|
|
122
|
-
const { mst, recordCid, prevMstRoot, newMstBlocks } = await this.addRecord(
|
|
162
|
+
const { mst, recordCid, recordBlock, prevMstRoot, expectedCommitCid: rootCommitCid, newMstBlocks } = await this.addRecord(
|
|
123
163
|
collection,
|
|
124
164
|
key,
|
|
125
165
|
record,
|
|
126
166
|
);
|
|
127
167
|
|
|
128
168
|
const did = await this.getDid();
|
|
169
|
+
const effectiveBlobKeys = blobKeys ?? await resolveRecordBlobKeys(this.env, did, record);
|
|
129
170
|
const uri = `at://${did}/${collection}/${key}`;
|
|
130
|
-
await dalPutRecord(this.env, {
|
|
131
|
-
uri,
|
|
132
|
-
did,
|
|
133
|
-
cid: recordCid.toString(),
|
|
134
|
-
json: JSON.stringify(record),
|
|
135
|
-
});
|
|
136
171
|
|
|
137
172
|
const currentRoot = await mst.getPointer();
|
|
173
|
+
const commitGuard = expectedCommitCid === undefined ? rootCommitCid : expectedCommitCid;
|
|
174
|
+
const opsForCommit: RepoOp[] = [{ action: 'create', path: `${collection}/${key}`, cid: recordCid }];
|
|
175
|
+
await assertBlobKeysAvailable(this.env, did, effectiveBlobKeys);
|
|
138
176
|
const { commitCid, rev, ops, commitData, sig, blocks } = await bumpRoot(
|
|
139
177
|
this.env,
|
|
140
178
|
prevMstRoot ?? undefined,
|
|
141
179
|
currentRoot,
|
|
142
|
-
{
|
|
180
|
+
{
|
|
181
|
+
ops: opsForCommit,
|
|
182
|
+
newMstBlocks: Array.from(newMstBlocks),
|
|
183
|
+
newRecordBlocks: [recordBlock],
|
|
184
|
+
expectedCommitCid: commitGuard,
|
|
185
|
+
requiredBlobKeys: effectiveBlobKeys,
|
|
186
|
+
sideEffectStatements: (guard) => [
|
|
187
|
+
...putRecordStatements(this.env, {
|
|
188
|
+
uri,
|
|
189
|
+
did,
|
|
190
|
+
cid: recordCid.toString(),
|
|
191
|
+
json: JSON.stringify(record),
|
|
192
|
+
}, guard),
|
|
193
|
+
...setRecordBlobUsageStatements(this.env, did, uri, effectiveBlobKeys, guard),
|
|
194
|
+
],
|
|
195
|
+
},
|
|
143
196
|
);
|
|
144
197
|
|
|
145
|
-
return {
|
|
198
|
+
return {
|
|
199
|
+
uri,
|
|
200
|
+
cid: recordCid.toString(),
|
|
201
|
+
commitCid,
|
|
202
|
+
rev,
|
|
203
|
+
ops,
|
|
204
|
+
commitData,
|
|
205
|
+
sig,
|
|
206
|
+
blocks,
|
|
207
|
+
dereferencedBlobKeys: [],
|
|
208
|
+
};
|
|
146
209
|
}
|
|
147
210
|
|
|
148
211
|
async updateRecord(
|
|
@@ -151,53 +214,133 @@ export class RepoManager {
|
|
|
151
214
|
record: unknown,
|
|
152
215
|
): Promise<RecordMutation> {
|
|
153
216
|
const key = `${collection}/${rkey}`;
|
|
154
|
-
const currentMst = await this.
|
|
155
|
-
const prevMstRoot = await currentMst.getPointer();
|
|
156
|
-
const
|
|
217
|
+
const { mst: currentMst, commitCid: expectedCommitCid } = await this.getOrCreateRootSnapshot();
|
|
218
|
+
const prevMstRoot = expectedCommitCid === null ? null : await currentMst.getPointer();
|
|
219
|
+
const recordBlock = await encodeRecordBlock(record);
|
|
220
|
+
const [recordCid] = recordBlock;
|
|
157
221
|
const newMst = await currentMst.update(key, recordCid);
|
|
158
|
-
const newMstBlocks = await
|
|
159
|
-
return { mst: newMst, recordCid, prevMstRoot, newMstBlocks };
|
|
222
|
+
const newMstBlocks = await collectUnstoredMstBlocks(newMst);
|
|
223
|
+
return { mst: newMst, recordCid, recordBlock, prevMstRoot, expectedCommitCid, newMstBlocks };
|
|
160
224
|
}
|
|
161
225
|
|
|
162
|
-
async putRecord(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
226
|
+
async putRecord(
|
|
227
|
+
collection: string,
|
|
228
|
+
rkey: string,
|
|
229
|
+
record: unknown,
|
|
230
|
+
blobKeys?: string[],
|
|
231
|
+
expectedCommitCid?: string | null,
|
|
232
|
+
): Promise<RecordWriteResult> {
|
|
233
|
+
const key = `${collection}/${rkey}`;
|
|
234
|
+
const { mst: currentMst, commitCid: rootCommitCid } = await this.getOrCreateRootSnapshot();
|
|
235
|
+
const prevMstRoot = rootCommitCid === null ? null : await currentMst.getPointer();
|
|
236
|
+
const existingCid = await currentMst.get(key);
|
|
237
|
+
const recordCid = await cidForRecord(record);
|
|
168
238
|
const did = await this.getDid();
|
|
239
|
+
const effectiveBlobKeys = blobKeys ?? await resolveRecordBlobKeys(this.env, did, record);
|
|
169
240
|
const uri = `at://${did}/${collection}/${rkey}`;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
241
|
+
const commitGuard = expectedCommitCid === undefined ? rootCommitCid : expectedCommitCid;
|
|
242
|
+
if (existingCid?.toString() === recordCid.toString()) {
|
|
243
|
+
if (typeof commitGuard !== 'string') {
|
|
244
|
+
await assertRepoHead(this.env, did, commitGuard);
|
|
245
|
+
} else {
|
|
246
|
+
const repairResult = await repairRecordBlobUsageForCurrentRecord(
|
|
247
|
+
this.env,
|
|
248
|
+
did,
|
|
249
|
+
uri,
|
|
250
|
+
recordCid.toString(),
|
|
251
|
+
effectiveBlobKeys,
|
|
252
|
+
commitGuard,
|
|
253
|
+
);
|
|
254
|
+
if (repairResult.tag === 'blobNotFound') throw new RepoBlobNotFoundError();
|
|
255
|
+
if (repairResult.tag === 'conflict') throw new RepoCommitConflictError();
|
|
256
|
+
}
|
|
257
|
+
return { uri, cid: recordCid.toString(), ops: [] };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const previousBlobKeys = await getRecordBlobKeys(this.env, did, uri);
|
|
261
|
+
const recordBlock = await encodeRecordBlock(record);
|
|
262
|
+
const mst = existingCid
|
|
263
|
+
? await currentMst.update(key, recordCid)
|
|
264
|
+
: await currentMst.add(key, recordCid);
|
|
265
|
+
const newMstBlocks = await collectUnstoredMstBlocks(mst);
|
|
176
266
|
const currentRoot = await mst.getPointer();
|
|
267
|
+
const opsForCommit: RepoOp[] = existingCid
|
|
268
|
+
? [{ action: 'update', path: key, cid: recordCid, prev: existingCid }]
|
|
269
|
+
: [{ action: 'create', path: key, cid: recordCid }];
|
|
270
|
+
await assertBlobKeysAvailable(this.env, did, effectiveBlobKeys);
|
|
177
271
|
const { commitCid, rev, ops, commitData, sig, blocks } = await bumpRoot(
|
|
178
272
|
this.env,
|
|
179
273
|
prevMstRoot ?? undefined,
|
|
180
274
|
currentRoot,
|
|
181
|
-
{
|
|
275
|
+
{
|
|
276
|
+
ops: opsForCommit,
|
|
277
|
+
newMstBlocks: Array.from(newMstBlocks),
|
|
278
|
+
newRecordBlocks: [recordBlock],
|
|
279
|
+
expectedCommitCid: commitGuard,
|
|
280
|
+
requiredBlobKeys: effectiveBlobKeys,
|
|
281
|
+
sideEffectStatements: (guard) => [
|
|
282
|
+
...putRecordStatements(this.env, {
|
|
283
|
+
uri,
|
|
284
|
+
did,
|
|
285
|
+
cid: recordCid.toString(),
|
|
286
|
+
json: JSON.stringify(record),
|
|
287
|
+
}, guard),
|
|
288
|
+
...setRecordBlobUsageStatements(this.env, did, uri, effectiveBlobKeys, guard),
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
);
|
|
292
|
+
return {
|
|
293
|
+
uri,
|
|
294
|
+
cid: recordCid.toString(),
|
|
295
|
+
commitCid,
|
|
296
|
+
rev,
|
|
297
|
+
ops,
|
|
298
|
+
commitData,
|
|
299
|
+
sig,
|
|
300
|
+
blocks,
|
|
301
|
+
dereferencedBlobKeys: previousBlobKeys
|
|
302
|
+
.filter((key) => !effectiveBlobKeys.includes(key))
|
|
303
|
+
.map((key) => ({ did, key })),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async getRecordCid(collection: string, rkey: string): Promise<CID | null> {
|
|
308
|
+
const currentMst = await this.getRoot();
|
|
309
|
+
if (!currentMst) return null;
|
|
310
|
+
return currentMst.get(`${collection}/${rkey}`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async applyPreparedWrites(
|
|
314
|
+
writes: PreparedWrite[],
|
|
315
|
+
expectedCommitCid?: string | null,
|
|
316
|
+
): Promise<BatchCommitResult> {
|
|
317
|
+
const did = await this.getDid();
|
|
318
|
+
const snapshot = await this.getOrCreateRootSnapshot();
|
|
319
|
+
return applyPreparedWritesToRepo(
|
|
320
|
+
this.env,
|
|
321
|
+
did,
|
|
322
|
+
snapshot.mst,
|
|
323
|
+
writes,
|
|
324
|
+
expectedCommitCid === undefined ? snapshot.commitCid : expectedCommitCid,
|
|
182
325
|
);
|
|
183
|
-
return { uri, cid: recordCid.toString(), commitCid, rev, ops, commitData, sig, blocks };
|
|
184
326
|
}
|
|
185
327
|
|
|
186
328
|
async deleteRecord(
|
|
187
329
|
collection: string,
|
|
188
330
|
rkey: string,
|
|
189
|
-
): Promise<{ mst: MST; prevMstRoot: CID | null; uri: string; newMstBlocks: BlockMap }> {
|
|
331
|
+
): Promise<{ mst: MST; prevMstRoot: CID | null; uri: string; newMstBlocks: BlockMap; currentCid: CID }> {
|
|
190
332
|
const key = `${collection}/${rkey}`;
|
|
191
333
|
const currentMst = await this.getOrCreateRoot();
|
|
192
334
|
const prevMstRoot = await currentMst.getPointer();
|
|
335
|
+
const currentCid = await currentMst.get(key);
|
|
336
|
+
if (!currentCid) throw new RepoWriteError('InvalidRequest', 'record does not exist');
|
|
193
337
|
const newMst = await currentMst.delete(key);
|
|
194
|
-
const newMstBlocks = await
|
|
338
|
+
const newMstBlocks = await collectUnstoredMstBlocks(newMst);
|
|
195
339
|
|
|
196
340
|
const did = await this.getDid();
|
|
197
341
|
const uri = `at://${did}/${collection}/${rkey}`;
|
|
198
|
-
await dalDeleteRecord(this.env, uri);
|
|
199
342
|
|
|
200
|
-
return { mst: newMst, prevMstRoot, uri, newMstBlocks };
|
|
343
|
+
return { mst: newMst, prevMstRoot, uri, newMstBlocks, currentCid };
|
|
201
344
|
}
|
|
202
345
|
|
|
203
346
|
async getRecord(collection: string, rkey: string): Promise<unknown | null> {
|
package/src/worker/runtime.ts
CHANGED
|
@@ -166,7 +166,7 @@ export function normalizePdsRequestForAstro(request: WorkersRequest): WorkersReq
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
const headerRecord: Record<string, string> = {};
|
|
169
|
-
request.headers.forEach((value, key) => {
|
|
169
|
+
request.headers.forEach((value: string, key: string) => {
|
|
170
170
|
headerRecord[key] = value;
|
|
171
171
|
});
|
|
172
172
|
headerRecord.origin = url.origin;
|
|
@@ -30,6 +30,8 @@ export type UpgradeContext = {
|
|
|
30
30
|
readonly onClient: (id: string, cursor: number, server: HibernatableSocket) => void;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
type WebSocketResponseInit = ResponseInit & { readonly webSocket: WebSocket };
|
|
34
|
+
|
|
33
35
|
// Reject NaN / negative / non-integer cursors at the boundary. parseInt('abc')
|
|
34
36
|
// yields NaN, which would silently bypass `cursor > nextSeq - 1` (all NaN
|
|
35
37
|
// comparisons are false) and get persisted into the attachment.
|
|
@@ -137,5 +139,6 @@ export function handleUpgrade(
|
|
|
137
139
|
function buildUpgradeResponse(client: WebSocket, requestedProtocol: string | undefined): Response {
|
|
138
140
|
const headers = new Headers();
|
|
139
141
|
if (requestedProtocol) headers.set('Sec-WebSocket-Protocol', requestedProtocol);
|
|
140
|
-
|
|
142
|
+
const init: WebSocketResponseInit = { status: 101, webSocket: client, headers };
|
|
143
|
+
return new Response(null, init);
|
|
141
144
|
}
|