@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.
Files changed (75) hide show
  1. package/README.md +25 -25
  2. package/migrations/0010_eminent_klaw.sql +37 -0
  3. package/migrations/0011_chief_darwin.sql +31 -0
  4. package/migrations/0012_backfill_blob_usage.sql +39 -0
  5. package/migrations/meta/0010_snapshot.json +790 -0
  6. package/migrations/meta/0011_snapshot.json +813 -0
  7. package/migrations/meta/_journal.json +22 -1
  8. package/package.json +24 -41
  9. package/src/db/blob.ts +323 -0
  10. package/src/db/dal.ts +224 -78
  11. package/src/db/repo.ts +205 -25
  12. package/src/db/schema.ts +14 -5
  13. package/src/handlers/debug.ts +4 -3
  14. package/src/lib/appview/auth-policy.ts +7 -24
  15. package/src/lib/appview/proxy.ts +56 -23
  16. package/src/lib/appview/types.ts +1 -6
  17. package/src/lib/auth-scope.ts +399 -0
  18. package/src/lib/auth.ts +40 -39
  19. package/src/lib/commit.ts +37 -15
  20. package/src/lib/did-document.ts +4 -5
  21. package/src/lib/jwt.ts +3 -1
  22. package/src/lib/mime.ts +9 -0
  23. package/src/lib/oauth/observability.ts +53 -12
  24. package/src/lib/oauth/resource.ts +49 -0
  25. package/src/lib/preference-policy.ts +45 -0
  26. package/src/lib/preferences.ts +0 -4
  27. package/src/lib/public-host.ts +127 -0
  28. package/src/lib/ratelimit.ts +37 -12
  29. package/src/lib/relay.ts +7 -27
  30. package/src/lib/repo-write-blob-constraints.ts +141 -0
  31. package/src/lib/repo-write-data.ts +195 -0
  32. package/src/lib/repo-write-error.ts +46 -0
  33. package/src/lib/repo-write-validation.ts +463 -0
  34. package/src/lib/session-tokens.ts +22 -5
  35. package/src/lib/unsupported-routes.ts +32 -0
  36. package/src/lib/util.ts +57 -2
  37. package/src/pages/.well-known/atproto-did.ts +15 -3
  38. package/src/pages/.well-known/did.json.ts +13 -7
  39. package/src/pages/debug/db/bootstrap.ts +4 -3
  40. package/src/pages/debug/gc/blobs.ts +11 -8
  41. package/src/pages/debug/record.ts +11 -0
  42. package/src/pages/oauth/token.ts +78 -33
  43. package/src/pages/xrpc/[...nsid].ts +17 -9
  44. package/src/pages/xrpc/app.bsky.actor.getPreferences.ts +9 -3
  45. package/src/pages/xrpc/app.bsky.actor.putPreferences.ts +17 -4
  46. package/src/pages/xrpc/app.bsky.unspecced.getAgeAssuranceState.ts +4 -2
  47. package/src/pages/xrpc/chat.bsky.convo.getLog.ts +4 -2
  48. package/src/pages/xrpc/chat.bsky.convo.listConvos.ts +4 -2
  49. package/src/pages/xrpc/com.atproto.identity.getRecommendedDidCredentials.ts +10 -6
  50. package/src/pages/xrpc/com.atproto.identity.requestPlcOperationSignature.ts +4 -3
  51. package/src/pages/xrpc/com.atproto.identity.resolveHandle.ts +13 -5
  52. package/src/pages/xrpc/com.atproto.identity.signPlcOperation.ts +4 -2
  53. package/src/pages/xrpc/com.atproto.identity.submitPlcOperation.ts +4 -2
  54. package/src/pages/xrpc/com.atproto.identity.updateHandle.ts +12 -36
  55. package/src/pages/xrpc/com.atproto.repo.applyWrites.ts +90 -139
  56. package/src/pages/xrpc/com.atproto.repo.createRecord.ts +74 -47
  57. package/src/pages/xrpc/com.atproto.repo.deleteRecord.ts +119 -46
  58. package/src/pages/xrpc/com.atproto.repo.describeRepo.ts +21 -20
  59. package/src/pages/xrpc/com.atproto.repo.getRecord.ts +6 -1
  60. package/src/pages/xrpc/com.atproto.repo.listMissingBlobs.ts +4 -2
  61. package/src/pages/xrpc/com.atproto.repo.putRecord.ts +84 -47
  62. package/src/pages/xrpc/com.atproto.repo.uploadBlob.ts +199 -78
  63. package/src/pages/xrpc/com.atproto.server.checkAccountStatus.ts +4 -2
  64. package/src/pages/xrpc/com.atproto.server.getServiceAuth.ts +88 -21
  65. package/src/pages/xrpc/com.atproto.server.getSession.ts +3 -13
  66. package/src/pages/xrpc/com.atproto.sync.getBlob.ts +92 -74
  67. package/src/pages/xrpc/com.atproto.sync.listBlobs.ts +45 -23
  68. package/src/services/car.ts +13 -0
  69. package/src/services/repo/apply-prepared-writes.ts +185 -0
  70. package/src/services/repo/blob-refs.ts +48 -0
  71. package/src/services/repo/blockstore-ops.ts +59 -17
  72. package/src/services/repo/list-blobs.ts +43 -0
  73. package/src/services/repo-manager.ts +221 -78
  74. package/src/worker/runtime.ts +1 -1
  75. 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 { putRecord as dalPutRecord, deleteRecord as dalDeleteRecord } from '../db/dal';
9
- import { bumpRoot } from '../db/repo';
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 { storeRecord, storeMstBlocks } from './repo/blockstore-ops';
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 getRoot(): Promise<MST | null> {
48
- try {
49
- const db = drizzle(this.env.ALTERAN_DB);
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
- const parsed = JSON.parse(String(commit.data));
73
- const mstRoot = CID.parse(String(parsed.data));
86
+ const rows = await db
87
+ .select()
88
+ .from(repo_root)
89
+ .where(eq(repo_root.did, did))
90
+ .limit(1);
74
91
 
75
- console.log(
76
- `[RepoManager] Loading MST root: ${mstRoot.toString()} from commit: ${row.commitCid}`,
77
- );
92
+ const row = rows[0];
93
+ if (!row) return null;
78
94
 
79
- return MST.load(this.blockstore, mstRoot);
80
- } catch (error) {
81
- console.error('[RepoManager] Error in getRoot:', error);
82
- return null;
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 getOrCreateRoot(): Promise<MST> {
87
- const existing = await this.getRoot();
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.getOrCreateRoot();
109
- const prevMstRoot = await currentMst.getPointer();
110
- const recordCid = await storeRecord(this.blockstore, record);
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 storeMstBlocks(this.blockstore, newMst);
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
- { newMstBlocks: Array.from(newMstBlocks) },
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 { uri, cid: recordCid.toString(), commitCid, rev, ops, commitData, sig, blocks };
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.getOrCreateRoot();
155
- const prevMstRoot = await currentMst.getPointer();
156
- const recordCid = await storeRecord(this.blockstore, record);
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 storeMstBlocks(this.blockstore, newMst);
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(collection: string, rkey: string, record: unknown): Promise<CommitResult> {
163
- const { mst, recordCid, prevMstRoot, newMstBlocks } = await this.updateRecord(
164
- collection,
165
- rkey,
166
- record,
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
- await dalPutRecord(this.env, {
171
- uri,
172
- did,
173
- cid: recordCid.toString(),
174
- json: JSON.stringify(record),
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
- { newMstBlocks: Array.from(newMstBlocks) },
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 storeMstBlocks(this.blockstore, newMst);
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> {
@@ -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
- return new Response(null, { status: 101, webSocket: client, headers });
142
+ const init: WebSocketResponseInit = { status: 101, webSocket: client, headers };
143
+ return new Response(null, init);
141
144
  }