@alteran/astro 0.3.2 → 0.3.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alteran/astro",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Astro integration for running a Cloudflare-hosted Bluesky PDS with Alteran.",
5
5
  "module": "index.js",
6
6
  "types": "index.d.ts",
package/src/db/repo.ts CHANGED
@@ -6,6 +6,7 @@ import { RepoManager } from '../services/repo-manager';
6
6
  import { createCommit, signCommit, commitCid, generateTid, serializeCommit } from '../lib/commit';
7
7
  import { CID } from 'multiformats/cid';
8
8
  import { resolveSecret } from '../lib/secrets';
9
+ import { encodeBlocksForCommit } from '../services/car';
9
10
 
10
11
  export async function getRoot(env: Env) {
11
12
  const db = drizzle(env.DB);
@@ -21,6 +22,9 @@ export async function bumpRoot(env: Env, prevMstRoot?: CID): Promise<{
21
22
  rev: string;
22
23
  ops: import('../lib/firehose/frames').RepoOp[];
23
24
  mstRoot: CID;
25
+ commitData: string;
26
+ sig: string;
27
+ blocks: string; // base64-encoded CAR
24
28
  }> {
25
29
  const db = drizzle(env.DB);
26
30
  const did = (await resolveSecret(env.PDS_DID)) ?? 'did:example:single-user';
@@ -89,7 +93,14 @@ export async function bumpRoot(env: Env, prevMstRoot?: CID): Promise<{
89
93
  // Append to commit log
90
94
  await appendCommit(env, cidString, rev, commitData, sigBase64);
91
95
 
92
- return { commitCid: cidString, rev, ops, mstRoot: mstRootCid };
96
+ // Encode blocks as CAR for firehose
97
+ const blocksBytes = await encodeBlocksForCommit(env, cid, mstRootCid, ops);
98
+ // Encode to base64 (workers-safe)
99
+ let blocksBase64 = '';
100
+ for (const b of blocksBytes) blocksBase64 += String.fromCharCode(b);
101
+ blocksBase64 = btoa(blocksBase64);
102
+
103
+ return { commitCid: cidString, rev, ops, mstRoot: mstRootCid, commitData, sig: sigBase64, blocks: blocksBase64 };
93
104
  }
94
105
 
95
106
  export async function appendCommit(env: Env, cid: string, rev: string, data: string, sig: string) {
@@ -28,7 +28,15 @@ export async function POST({ locals, request }: APIContext) {
28
28
 
29
29
  const repo = new RepoManager(env);
30
30
  const commit = await repo.createRecord(collection, record, rkey);
31
- await notifySequencer(env, { type: 'commit', did: env.PDS_DID ?? 'did:example:single-user', commitCid: commit.commitCid, rev: commit.rev, ops: commit.ops });
31
+ await notifySequencer(env, {
32
+ did: env.PDS_DID ?? 'did:example:single-user',
33
+ commitCid: commit.commitCid,
34
+ rev: commit.rev,
35
+ data: commit.commitData,
36
+ sig: commit.sig,
37
+ ops: commit.ops,
38
+ blocks: commit.blocks
39
+ });
32
40
 
33
41
  return new Response(JSON.stringify(commit), {
34
42
  headers: { 'Content-Type': 'application/json' },
@@ -28,7 +28,15 @@ export async function POST({ locals, request }: APIContext) {
28
28
 
29
29
  const repo = new RepoManager(env);
30
30
  const commit = await repo.deleteRecord(collection, rkey);
31
- await notifySequencer(env, { type: 'commit', did: env.PDS_DID ?? 'did:example:single-user', commitCid: commit.commitCid, rev: commit.rev, ops: commit.ops });
31
+ await notifySequencer(env, {
32
+ did: env.PDS_DID ?? 'did:example:single-user',
33
+ commitCid: commit.commitCid,
34
+ rev: commit.rev,
35
+ data: commit.commitData,
36
+ sig: commit.sig,
37
+ ops: commit.ops,
38
+ blocks: commit.blocks
39
+ });
32
40
 
33
41
  return new Response(JSON.stringify(commit), {
34
42
  headers: { 'Content-Type': 'application/json' },
@@ -28,7 +28,15 @@ export async function POST({ locals, request }: APIContext) {
28
28
 
29
29
  const repo = new RepoManager(env);
30
30
  const commit = await repo.putRecord(collection, rkey, record);
31
- await notifySequencer(env, { type: 'commit', did: env.PDS_DID ?? 'did:example:single-user', commitCid: commit.commitCid, rev: commit.rev, ops: commit.ops });
31
+ await notifySequencer(env, {
32
+ did: env.PDS_DID ?? 'did:example:single-user',
33
+ commitCid: commit.commitCid,
34
+ rev: commit.rev,
35
+ data: commit.commitData,
36
+ sig: commit.sig,
37
+ ops: commit.ops,
38
+ blocks: commit.blocks
39
+ });
32
40
 
33
41
  return new Response(JSON.stringify(commit), {
34
42
  headers: { 'Content-Type': 'application/json' },
@@ -98,6 +98,9 @@ export class RepoManager {
98
98
  commitCid: string;
99
99
  rev: string;
100
100
  ops: RepoOp[];
101
+ commitData: string;
102
+ sig: string;
103
+ blocks: string;
101
104
  }> {
102
105
  const key = rkey ?? generateTid();
103
106
  const { mst, recordCid, prevMstRoot } = await this.addRecord(collection, key, record);
@@ -107,9 +110,9 @@ export class RepoManager {
107
110
  await dalPutRecord(this.env, { uri, did: this.did, cid: recordCid.toString(), json: JSON.stringify(record) } as any);
108
111
 
109
112
  // Update repo root with signed commit and extract ops
110
- const { commitCid, rev, ops } = await bumpRoot(this.env, prevMstRoot ?? undefined);
113
+ const { commitCid, rev, ops, commitData, sig, blocks } = await bumpRoot(this.env, prevMstRoot ?? undefined);
111
114
 
112
- return { uri, cid: recordCid.toString(), commitCid, rev, ops };
115
+ return { uri, cid: recordCid.toString(), commitCid, rev, ops, commitData, sig, blocks };
113
116
  }
114
117
 
115
118
  /**
@@ -147,12 +150,15 @@ export class RepoManager {
147
150
  commitCid: string;
148
151
  rev: string;
149
152
  ops: RepoOp[];
153
+ commitData: string;
154
+ sig: string;
155
+ blocks: string;
150
156
  }> {
151
157
  const { mst, recordCid, prevMstRoot } = await this.updateRecord(collection, rkey, record);
152
158
  const uri = `at://${this.did}/${collection}/${rkey}`;
153
159
  await dalPutRecord(this.env, { uri, did: this.did, cid: recordCid.toString(), json: JSON.stringify(record) } as any);
154
- const { commitCid, rev, ops } = await bumpRoot(this.env, prevMstRoot ?? undefined);
155
- return { uri, cid: recordCid.toString(), commitCid, rev, ops };
160
+ const { commitCid, rev, ops, commitData, sig, blocks } = await bumpRoot(this.env, prevMstRoot ?? undefined);
161
+ return { uri, cid: recordCid.toString(), commitCid, rev, ops, commitData, sig, blocks };
156
162
  }
157
163
 
158
164
  /**
@@ -163,6 +169,9 @@ export class RepoManager {
163
169
  commitCid: string;
164
170
  rev: string;
165
171
  ops: RepoOp[];
172
+ commitData: string;
173
+ sig: string;
174
+ blocks: string;
166
175
  }> {
167
176
  const key = `${collection}/${rkey}`;
168
177
 
@@ -180,8 +189,8 @@ export class RepoManager {
180
189
  const uri = `at://${this.did}/${collection}/${rkey}`;
181
190
  await dalDeleteRecord(this.env, uri);
182
191
 
183
- const { commitCid, rev, ops } = await bumpRoot(this.env, prevMstRoot ?? undefined);
184
- return { uri, commitCid, rev, ops };
192
+ const { commitCid, rev, ops, commitData, sig, blocks } = await bumpRoot(this.env, prevMstRoot ?? undefined);
193
+ return { uri, commitCid, rev, ops, commitData, sig, blocks };
185
194
  }
186
195
 
187
196
  /**
@@ -79,8 +79,16 @@ export function createPdsFetchHandler(options?: CreatePdsFetchHandlerOptions): P
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.
82
+ // Best-effort: notify relays, but avoid doing so on relay-initiated endpoints
83
+ // to prevent feedback loops (describeServer/subscribeRepos).
82
84
  try {
83
- ctx.waitUntil(notifyRelaysIfNeeded(resolvedEnv as any, request.url));
85
+ const pathname = new URL(request.url).pathname;
86
+ const isRelayPath =
87
+ pathname === '/xrpc/com.atproto.server.describeServer' ||
88
+ pathname === '/xrpc/com.atproto.sync.subscribeRepos';
89
+ if (!isRelayPath) {
90
+ ctx.waitUntil(notifyRelaysIfNeeded(resolvedEnv as any, request.url));
91
+ }
84
92
  } catch (err) {
85
93
  // Never block on relay notification
86
94
  }
@@ -266,6 +266,16 @@ export class Sequencer {
266
266
  console.error('Failed to send info frame:', error);
267
267
  }
268
268
 
269
+ // Keep the connection alive to avoid intermediary idle timeouts (e.g., CF edge)
270
+ // Send a lightweight #info heartbeat every ~25s. Most clients ignore unknown #info
271
+ // messages; this is safe and keeps the socket active.
272
+ const keepalive = setInterval(() => {
273
+ try {
274
+ const ka = createInfoFrame('keepalive', 'ping');
275
+ ws.send(ka.toFramedBytes());
276
+ } catch {}
277
+ }, 25_000);
278
+
269
279
  // Set up event handlers
270
280
  ws.addEventListener('message', (evt) => {
271
281
  try {
@@ -280,10 +290,12 @@ export class Sequencer {
280
290
 
281
291
  ws.addEventListener('close', () => {
282
292
  this.clients.delete(id);
293
+ clearInterval(keepalive);
283
294
  });
284
295
 
285
296
  ws.addEventListener('error', () => {
286
297
  this.clients.delete(id);
298
+ clearInterval(keepalive);
287
299
  });
288
300
 
289
301
  // Replay buffered events if cursor provided