@alteran/astro 0.3.9 → 0.5.2
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/LICENSE +21 -0
- package/README.md +19 -30
- package/index.js +34 -28
- package/migrations/0007_bored_spitfire.sql +26 -0
- package/migrations/0008_furry_ozymandias.sql +2 -0
- package/migrations/meta/0007_snapshot.json +534 -0
- package/migrations/meta/0008_snapshot.json +548 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +10 -9
- package/src/app.ts +8 -4
- package/src/db/account.ts +25 -6
- package/src/db/dal.ts +34 -23
- package/src/db/repo.ts +35 -35
- package/src/db/schema.ts +5 -1
- package/src/db/seed.ts +5 -13
- package/src/entrypoints/server.ts +2 -22
- package/src/handlers/root.ts +4 -4
- package/src/lib/account-state.ts +156 -0
- package/src/lib/actor.ts +28 -12
- package/src/lib/appview/auth-policy.ts +66 -0
- package/src/lib/appview/did-resolver.ts +233 -0
- package/src/lib/appview/proxy.ts +221 -0
- package/src/lib/appview/service-config.ts +61 -0
- package/src/lib/appview/service-jwt.ts +93 -0
- package/src/lib/appview/types.ts +25 -0
- package/src/lib/appview.ts +5 -532
- package/src/lib/auth-errors.ts +24 -0
- package/src/lib/auth.ts +63 -15
- package/src/lib/blockstore-gc.ts +2 -1
- package/src/lib/cache.ts +30 -4
- package/src/lib/chat.ts +14 -8
- package/src/lib/commit.ts +26 -36
- package/src/lib/config.ts +26 -15
- package/src/lib/did-document.ts +32 -0
- package/src/lib/errors.ts +54 -0
- package/src/lib/feed.ts +18 -19
- package/src/lib/firehose/frames.ts +87 -47
- package/src/lib/firehose/validation.ts +3 -3
- package/src/lib/jwt.ts +85 -177
- package/src/lib/labeler.ts +43 -30
- package/src/lib/logger.ts +4 -0
- package/src/lib/mst/block-map.ts +172 -0
- package/src/lib/mst/blockstore.ts +56 -93
- package/src/lib/mst/index.ts +1 -0
- package/src/lib/mst/leaf.ts +25 -0
- package/src/lib/mst/mst.ts +81 -237
- package/src/lib/mst/serialize.ts +97 -0
- package/src/lib/mst/types.ts +21 -0
- package/src/lib/oauth/clients.ts +67 -0
- package/src/lib/oauth/dpop-errors.ts +15 -0
- package/src/lib/oauth/dpop.ts +150 -0
- package/src/lib/oauth/resource.ts +199 -0
- package/src/lib/oauth/store.ts +77 -0
- package/src/lib/preferences.ts +9 -34
- package/src/lib/refresh-session.ts +161 -0
- package/src/lib/relay.ts +10 -8
- package/src/lib/secrets.ts +6 -7
- package/src/lib/sequencer.ts +12 -3
- package/src/lib/service-auth.ts +184 -0
- package/src/lib/session-tokens.ts +28 -76
- package/src/lib/streaming-car.ts +3 -0
- package/src/lib/tracing.ts +4 -3
- package/src/lib/util.ts +65 -15
- package/src/middleware.ts +1 -1
- package/src/pages/.well-known/did.json.ts +27 -30
- package/src/pages/.well-known/oauth-authorization-server.ts +31 -0
- package/src/pages/.well-known/oauth-protected-resource.ts +22 -0
- package/src/pages/debug/record.ts +1 -1
- package/src/pages/debug/sequencer.ts +28 -0
- package/src/pages/oauth/authorize.ts +78 -0
- package/src/pages/oauth/consent.ts +80 -0
- package/src/pages/oauth/par.ts +121 -0
- package/src/pages/oauth/token.ts +158 -0
- package/src/pages/xrpc/[...nsid].ts +61 -0
- package/src/pages/xrpc/app.bsky.actor.getPreferences.ts +12 -13
- package/src/pages/xrpc/app.bsky.actor.putPreferences.ts +23 -23
- package/src/pages/xrpc/app.bsky.unspecced.getAgeAssuranceState.ts +9 -2
- package/src/pages/xrpc/chat.bsky.convo.getLog.ts +9 -2
- package/src/pages/xrpc/chat.bsky.convo.listConvos.ts +9 -2
- package/src/pages/xrpc/com.atproto.identity.getRecommendedDidCredentials.ts +43 -41
- package/src/pages/xrpc/com.atproto.identity.requestPlcOperationSignature.ts +10 -3
- package/src/pages/xrpc/com.atproto.identity.resolveHandle.ts +40 -9
- package/src/pages/xrpc/com.atproto.identity.signPlcOperation.ts +41 -29
- package/src/pages/xrpc/com.atproto.identity.submitPlcOperation.ts +20 -6
- package/src/pages/xrpc/com.atproto.identity.updateHandle.ts +1 -1
- package/src/pages/xrpc/com.atproto.repo.applyWrites.ts +101 -11
- package/src/pages/xrpc/com.atproto.repo.createRecord.ts +44 -14
- package/src/pages/xrpc/com.atproto.repo.deleteRecord.ts +41 -13
- package/src/pages/xrpc/com.atproto.repo.describeRepo.ts +2 -2
- package/src/pages/xrpc/com.atproto.repo.getRecord.ts +14 -1
- package/src/pages/xrpc/com.atproto.repo.listMissingBlobs.ts +14 -6
- package/src/pages/xrpc/com.atproto.repo.listRecords.ts +1 -1
- package/src/pages/xrpc/com.atproto.repo.putRecord.ts +42 -14
- package/src/pages/xrpc/com.atproto.repo.uploadBlob.ts +76 -15
- package/src/pages/xrpc/com.atproto.server.checkAccountStatus.ts +20 -8
- package/src/pages/xrpc/com.atproto.server.createSession.ts +31 -11
- package/src/pages/xrpc/com.atproto.server.describeServer.ts +1 -1
- package/src/pages/xrpc/com.atproto.server.getServiceAuth.ts +12 -5
- package/src/pages/xrpc/com.atproto.server.getSession.ts +22 -8
- package/src/pages/xrpc/com.atproto.server.refreshSession.ts +30 -72
- package/src/pages/xrpc/com.atproto.sync.getBlob.ts +71 -22
- package/src/pages/xrpc/com.atproto.sync.getCheckout.json.ts +1 -1
- package/src/pages/xrpc/com.atproto.sync.getCheckout.ts +1 -1
- package/src/pages/xrpc/com.atproto.sync.getHead.ts +7 -2
- package/src/pages/xrpc/com.atproto.sync.getLatestCommit.ts +1 -1
- package/src/pages/xrpc/com.atproto.sync.getRecord.ts +5 -27
- package/src/pages/xrpc/com.atproto.sync.getRepo.json.ts +1 -1
- package/src/pages/xrpc/com.atproto.sync.getRepo.ts +50 -5
- package/src/pages/xrpc/com.atproto.sync.getRepoStatus.ts +58 -0
- package/src/pages/xrpc/com.atproto.sync.listBlobs.ts +1 -1
- package/src/pages/xrpc/com.atproto.sync.listRepos.ts +5 -3
- package/src/services/car.ts +207 -55
- package/src/services/r2-blob-store.ts +1 -1
- package/src/services/repo/blockstore-ops.ts +29 -0
- package/src/services/repo/operations.ts +133 -0
- package/src/services/repo-manager.ts +202 -253
- package/src/worker/runtime.ts +53 -8
- package/src/worker/sequencer/broadcast.ts +91 -0
- package/src/worker/sequencer/cid-helpers.ts +39 -0
- package/src/worker/sequencer/payload.ts +84 -0
- package/src/worker/sequencer/types.ts +36 -0
- package/src/worker/sequencer/upgrade.ts +141 -0
- package/src/worker/sequencer.ts +263 -405
- package/types/env.d.ts +15 -3
- package/src/pages/xrpc/app.bsky.actor.getProfile.ts +0 -49
- package/src/pages/xrpc/app.bsky.actor.getProfiles.ts +0 -51
- package/src/pages/xrpc/app.bsky.feed.getActorFeeds.ts +0 -25
- package/src/pages/xrpc/app.bsky.feed.getAuthorFeed.ts +0 -42
- package/src/pages/xrpc/app.bsky.feed.getFeedGenerators.ts +0 -25
- package/src/pages/xrpc/app.bsky.feed.getPostThread.ts +0 -37
- package/src/pages/xrpc/app.bsky.feed.getPosts.ts +0 -26
- package/src/pages/xrpc/app.bsky.feed.getSuggestedFeeds.ts +0 -23
- package/src/pages/xrpc/app.bsky.feed.getTimeline.ts +0 -47
- package/src/pages/xrpc/app.bsky.graph.getFollowers.ts +0 -29
- package/src/pages/xrpc/app.bsky.graph.getFollows.ts +0 -29
- package/src/pages/xrpc/app.bsky.notification.getUnreadCount.ts +0 -20
- package/src/pages/xrpc/app.bsky.notification.listNotifications.ts +0 -27
- package/src/pages/xrpc/app.bsky.unspecced.getSuggestedFeeds.ts +0 -23
|
@@ -1,248 +1,295 @@
|
|
|
1
1
|
import { CID } from 'multiformats/cid';
|
|
2
2
|
import type { Env } from '../env';
|
|
3
|
-
import { MST, D1Blockstore,
|
|
3
|
+
import { MST, D1Blockstore, type BlockMap } from '../lib/mst';
|
|
4
4
|
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 * as dagCbor from '@ipld/dag-cbor';
|
|
9
|
-
import { cidForCbor } from '../lib/mst/util';
|
|
10
8
|
import { putRecord as dalPutRecord, deleteRecord as dalDeleteRecord } from '../db/dal';
|
|
11
9
|
import { bumpRoot } from '../db/repo';
|
|
12
10
|
import { generateTid } from '../lib/commit';
|
|
13
11
|
import { resolveSecret } from '../lib/secrets';
|
|
12
|
+
import { storeRecord, storeMstBlocks } from './repo/blockstore-ops';
|
|
13
|
+
import { extractOps as extractOpsImpl } from './repo/operations';
|
|
14
|
+
import { ServerMisconfigured } from '../lib/errors';
|
|
15
|
+
|
|
16
|
+
interface RecordMutation {
|
|
17
|
+
mst: MST;
|
|
18
|
+
recordCid: CID;
|
|
19
|
+
prevMstRoot: CID | null;
|
|
20
|
+
newMstBlocks: BlockMap;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface CommitResult {
|
|
24
|
+
uri: string;
|
|
25
|
+
cid: string;
|
|
26
|
+
commitCid: string;
|
|
27
|
+
rev: string;
|
|
28
|
+
ops: RepoOp[];
|
|
29
|
+
commitData: string;
|
|
30
|
+
sig: string;
|
|
31
|
+
blocks: string;
|
|
32
|
+
}
|
|
14
33
|
|
|
15
|
-
/**
|
|
16
|
-
* Repository Manager
|
|
17
|
-
* Manages MST-based repository operations
|
|
18
|
-
*/
|
|
19
34
|
export class RepoManager {
|
|
20
35
|
private blockstore: D1Blockstore;
|
|
21
|
-
private did: string;
|
|
22
36
|
|
|
23
37
|
constructor(private env: Env) {
|
|
24
38
|
this.blockstore = new D1Blockstore(env);
|
|
25
|
-
// Note: this.did will be set asynchronously, but it's only used in async methods
|
|
26
|
-
this.did = 'did:example:single-user'; // Default, will be resolved in async methods
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
private async getDid(): Promise<string> {
|
|
30
|
-
|
|
42
|
+
const did = await resolveSecret(this.env.PDS_DID);
|
|
43
|
+
if (!did) throw new ServerMisconfigured('PDS_DID is required');
|
|
44
|
+
return did;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
|
-
/**
|
|
34
|
-
* Get the current MST root
|
|
35
|
-
*/
|
|
36
47
|
async getRoot(): Promise<MST | null> {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
try {
|
|
49
|
+
const db = drizzle(this.env.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.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
|
+
}
|
|
43
71
|
|
|
44
|
-
|
|
72
|
+
const parsed = JSON.parse(String(commit.data));
|
|
73
|
+
const mstRoot = CID.parse(String(parsed.data));
|
|
45
74
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
75
|
+
console.log(
|
|
76
|
+
`[RepoManager] Loading MST root: ${mstRoot.toString()} from commit: ${row.commitCid}`,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return MST.load(this.blockstore, mstRoot);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error('[RepoManager] Error in getRoot:', error);
|
|
50
82
|
return null;
|
|
51
83
|
}
|
|
52
84
|
}
|
|
53
85
|
|
|
54
|
-
/**
|
|
55
|
-
* Get or create the MST root
|
|
56
|
-
*/
|
|
57
86
|
async getOrCreateRoot(): Promise<MST> {
|
|
58
87
|
const existing = await this.getRoot();
|
|
59
|
-
if (existing)
|
|
88
|
+
if (existing) {
|
|
89
|
+
const pointer = await existing.getPointer();
|
|
90
|
+
console.log(`[RepoManager] Loaded existing MST root: ${pointer.toString()}`);
|
|
91
|
+
return existing;
|
|
92
|
+
}
|
|
60
93
|
|
|
61
|
-
|
|
94
|
+
console.log('[RepoManager] Creating new empty MST');
|
|
62
95
|
const mst = await MST.create(this.blockstore, []);
|
|
96
|
+
await storeMstBlocks(this.blockstore, mst);
|
|
97
|
+
const pointer = await mst.getPointer();
|
|
98
|
+
console.log(`[RepoManager] Created new MST root: ${pointer.toString()}`);
|
|
63
99
|
return mst;
|
|
64
100
|
}
|
|
65
101
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
recordCid: CID;
|
|
72
|
-
prevMstRoot: CID | null;
|
|
73
|
-
}> {
|
|
102
|
+
async addRecord(
|
|
103
|
+
collection: string,
|
|
104
|
+
rkey: string,
|
|
105
|
+
record: unknown,
|
|
106
|
+
): Promise<RecordMutation> {
|
|
74
107
|
const key = `${collection}/${rkey}`;
|
|
75
|
-
|
|
76
|
-
// Get previous MST root for op extraction
|
|
77
108
|
const currentMst = await this.getOrCreateRoot();
|
|
78
109
|
const prevMstRoot = await currentMst.getPointer();
|
|
79
|
-
|
|
80
|
-
// Encode record and store in blockstore
|
|
81
|
-
const recordCid = await this.storeRecord(record);
|
|
82
|
-
|
|
83
|
-
// Add the new record
|
|
110
|
+
const recordCid = await storeRecord(this.blockstore, record);
|
|
84
111
|
const newMst = await currentMst.add(key, recordCid);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
await this.storeMstBlocks(newMst);
|
|
88
|
-
|
|
89
|
-
return { mst: newMst, recordCid, prevMstRoot };
|
|
112
|
+
const newMstBlocks = await storeMstBlocks(this.blockstore, newMst);
|
|
113
|
+
return { mst: newMst, recordCid, prevMstRoot, newMstBlocks };
|
|
90
114
|
}
|
|
91
115
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
cid: string;
|
|
98
|
-
commitCid: string;
|
|
99
|
-
rev: string;
|
|
100
|
-
ops: RepoOp[];
|
|
101
|
-
commitData: string;
|
|
102
|
-
sig: string;
|
|
103
|
-
blocks: string;
|
|
104
|
-
}> {
|
|
116
|
+
async createRecord(
|
|
117
|
+
collection: string,
|
|
118
|
+
record: unknown,
|
|
119
|
+
rkey?: string,
|
|
120
|
+
): Promise<CommitResult> {
|
|
105
121
|
const key = rkey ?? generateTid();
|
|
106
|
-
const { mst, recordCid, prevMstRoot } = await this.addRecord(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
const { mst, recordCid, prevMstRoot, newMstBlocks } = await this.addRecord(
|
|
123
|
+
collection,
|
|
124
|
+
key,
|
|
125
|
+
record,
|
|
126
|
+
);
|
|
111
127
|
|
|
112
|
-
|
|
113
|
-
const
|
|
128
|
+
const did = await this.getDid();
|
|
129
|
+
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
|
+
|
|
137
|
+
const currentRoot = await mst.getPointer();
|
|
138
|
+
const { commitCid, rev, ops, commitData, sig, blocks } = await bumpRoot(
|
|
139
|
+
this.env,
|
|
140
|
+
prevMstRoot ?? undefined,
|
|
141
|
+
currentRoot,
|
|
142
|
+
{ newMstBlocks: Array.from(newMstBlocks) },
|
|
143
|
+
);
|
|
114
144
|
|
|
115
145
|
return { uri, cid: recordCid.toString(), commitCid, rev, ops, commitData, sig, blocks };
|
|
116
146
|
}
|
|
117
147
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
recordCid: CID;
|
|
124
|
-
prevMstRoot: CID | null;
|
|
125
|
-
}> {
|
|
148
|
+
async updateRecord(
|
|
149
|
+
collection: string,
|
|
150
|
+
rkey: string,
|
|
151
|
+
record: unknown,
|
|
152
|
+
): Promise<RecordMutation> {
|
|
126
153
|
const key = `${collection}/${rkey}`;
|
|
127
|
-
|
|
128
|
-
// Get previous MST root for op extraction
|
|
129
154
|
const currentMst = await this.getOrCreateRoot();
|
|
130
155
|
const prevMstRoot = await currentMst.getPointer();
|
|
131
|
-
|
|
132
|
-
// Encode record and store in blockstore
|
|
133
|
-
const recordCid = await this.storeRecord(record);
|
|
134
|
-
|
|
135
|
-
// Update the record
|
|
156
|
+
const recordCid = await storeRecord(this.blockstore, record);
|
|
136
157
|
const newMst = await currentMst.update(key, recordCid);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
await this.storeMstBlocks(newMst);
|
|
140
|
-
|
|
141
|
-
return { mst: newMst, recordCid, prevMstRoot };
|
|
158
|
+
const newMstBlocks = await storeMstBlocks(this.blockstore, newMst);
|
|
159
|
+
return { mst: newMst, recordCid, prevMstRoot, newMstBlocks };
|
|
142
160
|
}
|
|
143
161
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
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
|
+
);
|
|
168
|
+
const did = await this.getDid();
|
|
169
|
+
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
|
+
});
|
|
176
|
+
const currentRoot = await mst.getPointer();
|
|
177
|
+
const { commitCid, rev, ops, commitData, sig, blocks } = await bumpRoot(
|
|
178
|
+
this.env,
|
|
179
|
+
prevMstRoot ?? undefined,
|
|
180
|
+
currentRoot,
|
|
181
|
+
{ newMstBlocks: Array.from(newMstBlocks) },
|
|
182
|
+
);
|
|
161
183
|
return { uri, cid: recordCid.toString(), commitCid, rev, ops, commitData, sig, blocks };
|
|
162
184
|
}
|
|
163
185
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
uri: string;
|
|
169
|
-
commitCid: string;
|
|
170
|
-
rev: string;
|
|
171
|
-
ops: RepoOp[];
|
|
172
|
-
commitData: string;
|
|
173
|
-
sig: string;
|
|
174
|
-
blocks: string;
|
|
175
|
-
}> {
|
|
186
|
+
async deleteRecord(
|
|
187
|
+
collection: string,
|
|
188
|
+
rkey: string,
|
|
189
|
+
): Promise<{ mst: MST; prevMstRoot: CID | null; uri: string; newMstBlocks: BlockMap }> {
|
|
176
190
|
const key = `${collection}/${rkey}`;
|
|
177
|
-
|
|
178
|
-
// Get previous MST root for op extraction
|
|
179
191
|
const currentMst = await this.getOrCreateRoot();
|
|
180
192
|
const prevMstRoot = await currentMst.getPointer();
|
|
181
|
-
|
|
182
|
-
// Delete the record
|
|
183
193
|
const newMst = await currentMst.delete(key);
|
|
194
|
+
const newMstBlocks = await storeMstBlocks(this.blockstore, newMst);
|
|
184
195
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// Delete from records table
|
|
189
|
-
const uri = `at://${this.did}/${collection}/${rkey}`;
|
|
196
|
+
const did = await this.getDid();
|
|
197
|
+
const uri = `at://${did}/${collection}/${rkey}`;
|
|
190
198
|
await dalDeleteRecord(this.env, uri);
|
|
191
199
|
|
|
192
|
-
|
|
193
|
-
return { uri, commitCid, rev, ops, commitData, sig, blocks };
|
|
200
|
+
return { mst: newMst, prevMstRoot, uri, newMstBlocks };
|
|
194
201
|
}
|
|
195
202
|
|
|
196
|
-
/**
|
|
197
|
-
* Get a record from the repository
|
|
198
|
-
*/
|
|
199
203
|
async getRecord(collection: string, rkey: string): Promise<unknown | null> {
|
|
200
204
|
const key = `${collection}/${rkey}`;
|
|
201
|
-
|
|
202
205
|
const currentMst = await this.getRoot();
|
|
203
|
-
if (!currentMst) return
|
|
206
|
+
if (!currentMst) return this.getRecordFromTable(collection, rkey);
|
|
204
207
|
|
|
205
208
|
const recordCid = await currentMst.get(key);
|
|
206
|
-
if (!recordCid) return
|
|
209
|
+
if (!recordCid) return this.getRecordFromTable(collection, rkey);
|
|
207
210
|
|
|
208
211
|
return this.blockstore.readObj(recordCid);
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
214
|
+
private async getRecordFromTable(collection: string, rkey: string): Promise<unknown | null> {
|
|
215
|
+
const did = await this.getDid();
|
|
216
|
+
const uri = `at://${did}/${collection}/${rkey}`;
|
|
217
|
+
|
|
218
|
+
const result = await this.env.DB.prepare(`SELECT json FROM record WHERE uri = ?`)
|
|
219
|
+
.bind(uri)
|
|
220
|
+
.first();
|
|
221
|
+
|
|
222
|
+
if (!result) return null;
|
|
223
|
+
try {
|
|
224
|
+
return JSON.parse(result.json as string);
|
|
225
|
+
} catch (parseError) {
|
|
226
|
+
console.warn('[RepoManager] Failed to parse record JSON:', parseError);
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async listRecords(
|
|
232
|
+
collection: string,
|
|
233
|
+
limit = 50,
|
|
234
|
+
cursor?: string,
|
|
235
|
+
): Promise<{ key: string; cid: CID }[]> {
|
|
215
236
|
const currentMst = await this.getRoot();
|
|
216
|
-
if (!currentMst) return
|
|
237
|
+
if (!currentMst) return this.listRecordsFromTable(collection, limit, cursor);
|
|
217
238
|
|
|
218
239
|
const prefix = `${collection}/`;
|
|
219
240
|
const leaves = await currentMst.listWithPrefix(prefix, limit);
|
|
220
241
|
|
|
221
|
-
|
|
222
|
-
.filter(leaf => !cursor || leaf.key > `${collection}/${cursor}`)
|
|
223
|
-
.map(leaf => ({
|
|
242
|
+
const results = leaves
|
|
243
|
+
.filter((leaf) => !cursor || leaf.key > `${collection}/${cursor}`)
|
|
244
|
+
.map((leaf) => ({
|
|
224
245
|
key: leaf.key.replace(prefix, ''),
|
|
225
246
|
cid: leaf.value,
|
|
226
247
|
}));
|
|
248
|
+
|
|
249
|
+
if (results.length === 0) return this.listRecordsFromTable(collection, limit, cursor);
|
|
250
|
+
return results;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private async listRecordsFromTable(
|
|
254
|
+
collection: string,
|
|
255
|
+
limit = 50,
|
|
256
|
+
cursor?: string,
|
|
257
|
+
): Promise<{ key: string; cid: CID }[]> {
|
|
258
|
+
const did = await this.getDid();
|
|
259
|
+
const prefix = `at://${did}/${collection}/`;
|
|
260
|
+
|
|
261
|
+
// D1's LIKE planner is flaky on long prefixes; use a >= / < range instead.
|
|
262
|
+
// URIs sort lexicographically so this scans only the collection's slice.
|
|
263
|
+
const rangeEnd =
|
|
264
|
+
prefix.slice(0, -1) +
|
|
265
|
+
String.fromCharCode(prefix.charCodeAt(prefix.length - 1) + 1);
|
|
266
|
+
|
|
267
|
+
const stmt = cursor
|
|
268
|
+
? this.env.DB.prepare(
|
|
269
|
+
`SELECT uri, cid FROM record WHERE uri >= ? AND uri < ? AND uri > ? ORDER BY uri LIMIT ?`,
|
|
270
|
+
).bind(prefix, rangeEnd, prefix + cursor, limit)
|
|
271
|
+
: this.env.DB.prepare(
|
|
272
|
+
`SELECT uri, cid FROM record WHERE uri >= ? AND uri < ? ORDER BY uri LIMIT ?`,
|
|
273
|
+
).bind(prefix, rangeEnd, limit);
|
|
274
|
+
|
|
275
|
+
const result = await stmt.all();
|
|
276
|
+
const rows = result.results as Array<{ uri: string; cid: string }>;
|
|
277
|
+
|
|
278
|
+
return rows.map((row) => ({
|
|
279
|
+
key: row.uri.replace(prefix, ''),
|
|
280
|
+
cid: CID.parse(row.cid),
|
|
281
|
+
}));
|
|
227
282
|
}
|
|
228
283
|
|
|
229
|
-
/**
|
|
230
|
-
* Update the repo root to point to the new MST
|
|
231
|
-
*/
|
|
232
284
|
async updateRoot(mst: MST, rev: number): Promise<void> {
|
|
233
285
|
const db = drizzle(this.env.DB);
|
|
234
286
|
const rootCid = await mst.getPointer();
|
|
235
287
|
const did = await this.getDid();
|
|
236
288
|
const revStr = String(rev);
|
|
237
289
|
|
|
238
|
-
// Use sql.raw with excluded to properly reference INSERT values
|
|
239
290
|
await db
|
|
240
291
|
.insert(repo_root)
|
|
241
|
-
.values({
|
|
242
|
-
did,
|
|
243
|
-
commitCid: rootCid.toString(),
|
|
244
|
-
rev: revStr,
|
|
245
|
-
})
|
|
292
|
+
.values({ did, commitCid: rootCid.toString(), rev: revStr })
|
|
246
293
|
.onConflictDoUpdate({
|
|
247
294
|
target: repo_root.did,
|
|
248
295
|
set: {
|
|
@@ -253,105 +300,7 @@ export class RepoManager {
|
|
|
253
300
|
.run();
|
|
254
301
|
}
|
|
255
302
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
* Compares old MST root with new MST root to identify create/update/delete operations
|
|
259
|
-
*/
|
|
260
|
-
async extractOps(prevRoot: CID | null, newRoot: CID): Promise<RepoOp[]> {
|
|
261
|
-
const ops: RepoOp[] = [];
|
|
262
|
-
|
|
263
|
-
// Load both trees
|
|
264
|
-
const newMst = await MST.load(this.blockstore, newRoot).getEntries();
|
|
265
|
-
const prevMst = prevRoot ? await MST.load(this.blockstore, prevRoot).getEntries() : [];
|
|
266
|
-
|
|
267
|
-
// Build maps for efficient lookup
|
|
268
|
-
const prevMap = new Map<string, CID>();
|
|
269
|
-
const newMap = new Map<string, CID>();
|
|
270
|
-
|
|
271
|
-
// Collect all leaves from previous tree
|
|
272
|
-
await this.collectLeaves(prevMst, prevMap);
|
|
273
|
-
|
|
274
|
-
// Collect all leaves from new tree
|
|
275
|
-
await this.collectLeaves(newMst, newMap);
|
|
276
|
-
|
|
277
|
-
// Find creates and updates
|
|
278
|
-
for (const [path, cid] of Array.from(newMap.entries())) {
|
|
279
|
-
const prevCid = prevMap.get(path);
|
|
280
|
-
if (!prevCid) {
|
|
281
|
-
// New key - create operation
|
|
282
|
-
ops.push({
|
|
283
|
-
action: 'create',
|
|
284
|
-
path,
|
|
285
|
-
cid,
|
|
286
|
-
});
|
|
287
|
-
} else if (!prevCid.equals(cid)) {
|
|
288
|
-
// Key exists but CID changed - update operation
|
|
289
|
-
ops.push({
|
|
290
|
-
action: 'update',
|
|
291
|
-
path,
|
|
292
|
-
cid,
|
|
293
|
-
prev: prevCid,
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Find deletes
|
|
299
|
-
for (const [path, prevCid] of Array.from(prevMap.entries())) {
|
|
300
|
-
if (!newMap.has(path)) {
|
|
301
|
-
// Key no longer exists - delete operation
|
|
302
|
-
ops.push({
|
|
303
|
-
action: 'delete',
|
|
304
|
-
path,
|
|
305
|
-
cid: null,
|
|
306
|
-
prev: prevCid,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Sort ops by path for deterministic ordering
|
|
312
|
-
ops.sort((a, b) => a.path.localeCompare(b.path));
|
|
313
|
-
|
|
314
|
-
return ops;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Recursively collect all leaves from MST entries into a map
|
|
319
|
-
*/
|
|
320
|
-
private async collectLeaves(entries: (MST | Leaf)[], map: Map<string, CID>): Promise<void> {
|
|
321
|
-
for (const entry of entries) {
|
|
322
|
-
if (entry.isLeaf()) {
|
|
323
|
-
map.set(entry.key, entry.value);
|
|
324
|
-
} else {
|
|
325
|
-
// Recursively collect from subtree
|
|
326
|
-
const subEntries = await entry.getEntries();
|
|
327
|
-
await this.collectLeaves(subEntries, map);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Store a record in the blockstore and return its CID
|
|
334
|
-
*/
|
|
335
|
-
private async storeRecord(record: unknown): Promise<CID> {
|
|
336
|
-
const bytes = dagCbor.encode(record);
|
|
337
|
-
const cid = await cidForCbor(record);
|
|
338
|
-
await this.blockstore.put(cid, bytes);
|
|
339
|
-
return cid;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Store all blocks from an MST to the blockstore
|
|
344
|
-
*/
|
|
345
|
-
private async storeMstBlocks(mst: MST): Promise<void> {
|
|
346
|
-
const { cid, bytes } = await mst.serialize();
|
|
347
|
-
await this.blockstore.put(cid, bytes);
|
|
348
|
-
|
|
349
|
-
// Recursively store child blocks
|
|
350
|
-
const entries = await mst.getEntries();
|
|
351
|
-
for (const entry of entries) {
|
|
352
|
-
if (entry.isTree()) {
|
|
353
|
-
await this.storeMstBlocks(entry);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
303
|
+
extractOps(prevRoot: CID | null, newRoot: CID): Promise<RepoOp[]> {
|
|
304
|
+
return extractOpsImpl(this.blockstore, prevRoot, newRoot);
|
|
356
305
|
}
|
|
357
306
|
}
|
package/src/worker/runtime.ts
CHANGED
|
@@ -75,7 +75,7 @@ export function createPdsFetchHandler(options?: CreatePdsFetchHandlerOptions): P
|
|
|
75
75
|
return new Response(null, { status: 204, headers }) as unknown as WorkersResponse;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
await seed(resolvedEnv.DB,
|
|
78
|
+
await seed(resolvedEnv.DB, resolvedEnv.PDS_DID as string);
|
|
79
79
|
|
|
80
80
|
// Fire-and-forget: let relays know this PDS exists and is reachable.
|
|
81
81
|
// Throttled per isolate and safe to call frequently.
|
|
@@ -89,15 +89,46 @@ export function createPdsFetchHandler(options?: CreatePdsFetchHandlerOptions): P
|
|
|
89
89
|
if (!isRelayPath) {
|
|
90
90
|
ctx.waitUntil(notifyRelaysIfNeeded(resolvedEnv as any, request.url));
|
|
91
91
|
}
|
|
92
|
-
} catch (
|
|
92
|
+
} catch (error) {
|
|
93
93
|
// Never block on relay notification
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
const url = new URL(request.url);
|
|
97
|
+
|
|
98
|
+
// Lightweight debug endpoint for Sequencer metrics
|
|
99
|
+
if (url.pathname === '/debug/sequencer' && request.method === 'GET') {
|
|
100
|
+
try {
|
|
101
|
+
if (!('SEQUENCER' in resolvedEnv) || !resolvedEnv.SEQUENCER) {
|
|
102
|
+
return new Response('Sequencer not configured', { status: 503 }) as unknown as WorkersResponse;
|
|
103
|
+
}
|
|
104
|
+
const id = (resolvedEnv as any).SEQUENCER.idFromName('default');
|
|
105
|
+
const stub = (resolvedEnv as any).SEQUENCER.get(id);
|
|
106
|
+
const proxyRequest = new Request(new URL('/metrics', request.url).toString(), { method: 'GET' });
|
|
107
|
+
const response = await stub.fetch(proxyRequest as any);
|
|
108
|
+
// Pass through JSON
|
|
109
|
+
const headers = new Headers(response.headers);
|
|
110
|
+
headers.set('Content-Type', 'application/json');
|
|
111
|
+
return new Response(await response.text(), { status: response.status, headers }) as unknown as WorkersResponse;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return new Response(JSON.stringify({ error: 'InternalError', message: 'Failed to fetch sequencer metrics' }), { status: 500, headers: { 'Content-Type': 'application/json' } }) as unknown as WorkersResponse;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
97
116
|
if (url.pathname === '/xrpc/com.atproto.sync.subscribeRepos') {
|
|
98
117
|
const upgrade = request.headers.get('upgrade');
|
|
99
118
|
if (upgrade !== 'websocket') {
|
|
100
|
-
|
|
119
|
+
try {
|
|
120
|
+
console.log(JSON.stringify({
|
|
121
|
+
level: 'warn',
|
|
122
|
+
type: 'ws_expected',
|
|
123
|
+
path: url.pathname,
|
|
124
|
+
method: request.method,
|
|
125
|
+
message: 'subscribeRepos requires WebSocket upgrade',
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
}));
|
|
128
|
+
} catch {
|
|
129
|
+
// Logging-only path; never block the response on log serialization.
|
|
130
|
+
}
|
|
131
|
+
return new Response('This endpoint requires a WebSocket (wss://) upgrade', { status: 426 }) as unknown as WorkersResponse;
|
|
101
132
|
}
|
|
102
133
|
if (!resolvedEnv.SEQUENCER) {
|
|
103
134
|
return new Response('Sequencer not configured', { status: 503 }) as unknown as WorkersResponse;
|
|
@@ -123,9 +154,21 @@ type AstroFetchHandler = (
|
|
|
123
154
|
let cachedFetchPromise: Promise<AstroFetchHandler> | undefined;
|
|
124
155
|
|
|
125
156
|
async function loadAstroFetchFromManifest(manifest: SSRManifest): Promise<AstroFetchHandler> {
|
|
126
|
-
const {
|
|
127
|
-
const
|
|
128
|
-
|
|
157
|
+
const { App } = await import('astro/app');
|
|
158
|
+
const { handle } = await import('@astrojs/cloudflare/handler');
|
|
159
|
+
const app = new App(manifest);
|
|
160
|
+
return async (
|
|
161
|
+
request: WorkersRequest,
|
|
162
|
+
env: Env,
|
|
163
|
+
ctx: ExecutionContext,
|
|
164
|
+
) =>
|
|
165
|
+
(await handle(
|
|
166
|
+
manifest,
|
|
167
|
+
app,
|
|
168
|
+
request as any,
|
|
169
|
+
env as any,
|
|
170
|
+
ctx as any,
|
|
171
|
+
)) as unknown as WorkersResponse;
|
|
129
172
|
}
|
|
130
173
|
|
|
131
174
|
async function getAstroFetch(options?: CreatePdsFetchHandlerOptions): Promise<AstroFetchHandler> {
|
|
@@ -135,8 +178,10 @@ async function getAstroFetch(options?: CreatePdsFetchHandlerOptions): Promise<As
|
|
|
135
178
|
|
|
136
179
|
if (!cachedFetchPromise) {
|
|
137
180
|
cachedFetchPromise = (async () => {
|
|
138
|
-
const
|
|
139
|
-
|
|
181
|
+
const moduleSpecifier = '@astrojs-manifest';
|
|
182
|
+
const mod = await import(/* @vite-ignore */ moduleSpecifier);
|
|
183
|
+
const manifest = (mod as any).manifest as SSRManifest;
|
|
184
|
+
return loadAstroFetchFromManifest(manifest);
|
|
140
185
|
})();
|
|
141
186
|
}
|
|
142
187
|
|