@alteran/astro 0.1.0
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 +558 -0
- package/index.d.ts +12 -0
- package/index.js +129 -0
- package/package.json +75 -0
- package/src/_worker.ts +44 -0
- package/src/app.ts +10 -0
- package/src/db/client.ts +7 -0
- package/src/db/dal.ts +97 -0
- package/src/db/repo.ts +135 -0
- package/src/db/schema.ts +89 -0
- package/src/db/seed.ts +14 -0
- package/src/env.d.ts +4 -0
- package/src/handlers/debug.ts +34 -0
- package/src/handlers/health.ts +6 -0
- package/src/handlers/ready.ts +14 -0
- package/src/handlers/root.ts +5 -0
- package/src/handlers/wellknown.ts +7 -0
- package/src/handlers/xrpc.repo.core.ts +57 -0
- package/src/handlers/xrpc.server.createSession.ts +25 -0
- package/src/handlers/xrpc.server.refreshSession.ts +43 -0
- package/src/lib/auth.ts +20 -0
- package/src/lib/blockstore-gc.ts +197 -0
- package/src/lib/cache.ts +236 -0
- package/src/lib/car-reader.ts +157 -0
- package/src/lib/commit-log-pruning.ts +76 -0
- package/src/lib/commit.ts +162 -0
- package/src/lib/config.ts +208 -0
- package/src/lib/errors.ts +142 -0
- package/src/lib/firehose/frames.ts +229 -0
- package/src/lib/firehose/parse.ts +82 -0
- package/src/lib/firehose/validation.ts +9 -0
- package/src/lib/handle.ts +90 -0
- package/src/lib/jwt.ts +150 -0
- package/src/lib/logger.ts +73 -0
- package/src/lib/metrics.ts +194 -0
- package/src/lib/mst/blockstore.ts +105 -0
- package/src/lib/mst/index.ts +3 -0
- package/src/lib/mst/mst.ts +643 -0
- package/src/lib/mst/util.ts +86 -0
- package/src/lib/ratelimit.ts +34 -0
- package/src/lib/sequencer.ts +10 -0
- package/src/lib/streaming-car.ts +137 -0
- package/src/lib/token-cleanup.ts +38 -0
- package/src/lib/tracing.ts +136 -0
- package/src/lib/util.ts +55 -0
- package/src/middleware.ts +102 -0
- package/src/pages/.well-known/atproto-did.ts +7 -0
- package/src/pages/.well-known/did.json.ts +76 -0
- package/src/pages/debug/blob/[...key].ts +27 -0
- package/src/pages/debug/db/bootstrap.ts +23 -0
- package/src/pages/debug/db/commits.ts +20 -0
- package/src/pages/debug/gc/blobs.ts +16 -0
- package/src/pages/debug/record.ts +33 -0
- package/src/pages/health.ts +68 -0
- package/src/pages/index.astro +57 -0
- package/src/pages/index.ts +2 -0
- package/src/pages/ready.ts +16 -0
- package/src/pages/xrpc/com.atproto.identity.resolveHandle.ts +38 -0
- package/src/pages/xrpc/com.atproto.identity.updateHandle.ts +45 -0
- package/src/pages/xrpc/com.atproto.repo.applyWrites.ts +73 -0
- package/src/pages/xrpc/com.atproto.repo.createRecord.ts +36 -0
- package/src/pages/xrpc/com.atproto.repo.deleteRecord.ts +36 -0
- package/src/pages/xrpc/com.atproto.repo.describeRepo.ts +51 -0
- package/src/pages/xrpc/com.atproto.repo.getRecord.ts +25 -0
- package/src/pages/xrpc/com.atproto.repo.listRecords.ts +57 -0
- package/src/pages/xrpc/com.atproto.repo.putRecord.ts +36 -0
- package/src/pages/xrpc/com.atproto.repo.uploadBlob.ts +53 -0
- package/src/pages/xrpc/com.atproto.server.createSession.ts +92 -0
- package/src/pages/xrpc/com.atproto.server.deleteSession.ts +25 -0
- package/src/pages/xrpc/com.atproto.server.describeServer.ts +17 -0
- package/src/pages/xrpc/com.atproto.server.getSession.ts +46 -0
- package/src/pages/xrpc/com.atproto.server.refreshSession.ts +67 -0
- package/src/pages/xrpc/com.atproto.sync.getBlocks.json.ts +16 -0
- package/src/pages/xrpc/com.atproto.sync.getBlocks.ts +56 -0
- package/src/pages/xrpc/com.atproto.sync.getCheckout.json.ts +20 -0
- package/src/pages/xrpc/com.atproto.sync.getCheckout.ts +43 -0
- package/src/pages/xrpc/com.atproto.sync.getHead.ts +11 -0
- package/src/pages/xrpc/com.atproto.sync.getLatestCommit.ts +42 -0
- package/src/pages/xrpc/com.atproto.sync.getRecord.ts +63 -0
- package/src/pages/xrpc/com.atproto.sync.getRepo.json.ts +20 -0
- package/src/pages/xrpc/com.atproto.sync.getRepo.range.ts +34 -0
- package/src/pages/xrpc/com.atproto.sync.getRepo.ts +17 -0
- package/src/pages/xrpc/com.atproto.sync.listBlobs.ts +53 -0
- package/src/pages/xrpc/com.atproto.sync.listRepos.ts +31 -0
- package/src/services/car.ts +249 -0
- package/src/services/r2-blob-store.ts +87 -0
- package/src/services/repo-manager.ts +339 -0
- package/src/shims/astro-internal-handler.d.ts +4 -0
- package/src/worker/sequencer.ts +563 -0
- package/types/env.d.ts +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
# Alteran
|
|
2
|
+
|
|
3
|
+
## Astro Integration
|
|
4
|
+
|
|
5
|
+
This repository now ships an Astro integration that turns any Cloudflare Worker-backed Astro app into a single-user ATProto Personal Data Server. Install the package (or link it locally), then add the integration to your `astro.config.mjs`:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @alteran/astro
|
|
9
|
+
# or
|
|
10
|
+
bun add @alteran/astro
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { defineConfig } from 'astro/config';
|
|
15
|
+
import cloudflare from '@astrojs/cloudflare';
|
|
16
|
+
import alteran from '@alteran/astro';
|
|
17
|
+
|
|
18
|
+
export default defineConfig({
|
|
19
|
+
adapter: cloudflare({ mode: 'advanced' }),
|
|
20
|
+
integrations: [alteran()],
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
By default the integration injects all `/xrpc/*` ATProto routes, health/ready checks, and the Cloudflare Worker entrypoint that wires `locals.runtime`. Optional flags let you expose the `/debug/*` utilities or keep your own homepage:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
alteran({
|
|
28
|
+
debugRoutes: process.env.NODE_ENV !== 'production',
|
|
29
|
+
includeRootEndpoint: false,
|
|
30
|
+
injectServerEntry: true,
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The integration automatically:
|
|
35
|
+
- Adds a Vite alias of `@alteran/*` that points to the package runtime
|
|
36
|
+
- Registers the middleware that applies structured logging and CORS enforcement
|
|
37
|
+
- Injects all PDS HTTP endpoints into the host project
|
|
38
|
+
- Sets `build.serverEntry` to the packaged Cloudflare worker (unless you opt out)
|
|
39
|
+
- Publishes ambient env typings so `Env` and `App.Locals` are available from TypeScript
|
|
40
|
+
|
|
41
|
+
When deploying, continue to configure Wrangler/D1/R2 secrets exactly as before—the integration does not change the runtime requirements.
|
|
42
|
+
|
|
43
|
+
To install dependencies:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
bun install
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Dev server (Vite dev):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
bun run dev
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Cloudflare local dev (optional):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bunx wrangler dev --local
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Build and deploy:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bun run build
|
|
65
|
+
bun run deploy
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Health endpoints: `GET /health` and `GET /ready` return `200 ok`.
|
|
69
|
+
|
|
70
|
+
Auth (JWT)
|
|
71
|
+
- `POST /xrpc/com.atproto.server.createSession` returns `accessJwt` and `refreshJwt` (HS256).
|
|
72
|
+
- `POST /xrpc/com.atproto.server.refreshSession` expects `Authorization: Bearer <refreshJwt>` and issues a new pair.
|
|
73
|
+
- Use `Authorization: Bearer <accessJwt>` on write routes.
|
|
74
|
+
- Secrets to set (Wrangler secrets or local bindings):
|
|
75
|
+
- `USER_PASSWORD` (dev login password)
|
|
76
|
+
- `ACCESS_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET` (HMAC keys)
|
|
77
|
+
- `PDS_DID`, `PDS_HANDLE`
|
|
78
|
+
|
|
79
|
+
Rate limiting & limits
|
|
80
|
+
- Per‑IP rate limit (best‑effort, D1‑backed): set `PDS_RATE_LIMIT_PER_MIN` (default writes=60/min, blobs=30/min). Responses include `x-ratelimit-*` headers.
|
|
81
|
+
- JSON body size cap via `PDS_MAX_JSON_BYTES` (default 65536/64 KiB).
|
|
82
|
+
- CORS: allow `*` by default in dev. In production, set `PDS_CORS_ORIGIN` to a CSV of allowed origins (e.g., `https://example.com,https://app.example.com`). Requests with an `Origin` not in this set are denied at the CORS layer (no wildcard fallback).
|
|
83
|
+
|
|
84
|
+
This project was created using `bun init` in bun v1.2.22 and configured for Cloudflare Workers with Vite and `@cloudflare/vite-plugin`.
|
|
85
|
+
|
|
86
|
+
## Database Migrations
|
|
87
|
+
|
|
88
|
+
This project uses Drizzle Kit for database schema management and migrations.
|
|
89
|
+
|
|
90
|
+
### Migration Workflow
|
|
91
|
+
|
|
92
|
+
1. **Modify Schema**: Edit [`src/db/schema.ts`](src/db/schema.ts:1) to add/modify tables or indexes
|
|
93
|
+
2. **Generate Migration**: Run `bun run db:generate` to create a new migration file in `drizzle/`
|
|
94
|
+
3. **Review Migration**: Check the generated SQL in `drizzle/XXXX_*.sql`
|
|
95
|
+
4. **Apply Locally**: Run `bun run db:apply:local` to apply to local D1 database
|
|
96
|
+
5. **Apply to Production**: Run `wrangler d1 migrations apply pds --remote` after deployment
|
|
97
|
+
|
|
98
|
+
### Migration Versioning
|
|
99
|
+
|
|
100
|
+
- Migrations are versioned sequentially (0000, 0001, 0002, etc.)
|
|
101
|
+
- Each migration is tracked in `drizzle/meta/_journal.json`
|
|
102
|
+
- Migrations are applied in order and cannot be skipped
|
|
103
|
+
- Applied migrations are recorded in D1's `_cf_KV` table
|
|
104
|
+
|
|
105
|
+
### Rollback Procedures
|
|
106
|
+
|
|
107
|
+
**Important**: D1 does not support automatic rollbacks. To rollback:
|
|
108
|
+
|
|
109
|
+
1. Create a new migration that reverses the changes
|
|
110
|
+
2. Test thoroughly in local/staging environment
|
|
111
|
+
3. Apply the rollback migration to production
|
|
112
|
+
|
|
113
|
+
Example rollback migration:
|
|
114
|
+
```sql
|
|
115
|
+
-- Rollback: Remove index added in 0002
|
|
116
|
+
DROP INDEX IF EXISTS `record_cid_idx`;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Data Retention & Pruning
|
|
120
|
+
|
|
121
|
+
**Commit Log**: Stores full commit history for firehose and sync
|
|
122
|
+
- Default retention: Last 10,000 commits
|
|
123
|
+
- Pruning: Use [`pruneOldCommits()`](src/lib/commit-log-pruning.ts:19) utility
|
|
124
|
+
- Older commits can be safely removed as current state is in MST
|
|
125
|
+
|
|
126
|
+
**Blockstore**: Stores MST nodes (Merkle Search Tree blocks)
|
|
127
|
+
- Retention: Blocks referenced by recent commits
|
|
128
|
+
- GC: Use [`pruneOrphanedBlocks()`](src/lib/blockstore-gc.ts:127) utility
|
|
129
|
+
- Orphaned blocks (not in recent commits) can be removed
|
|
130
|
+
|
|
131
|
+
**Token Revocation**: Stores revoked JWT tokens
|
|
132
|
+
- Automatic cleanup: Expired tokens removed lazily (1% of requests)
|
|
133
|
+
- Manual cleanup: Use token cleanup utility
|
|
134
|
+
## Configuration Management
|
|
135
|
+
|
|
136
|
+
### Environment Setup
|
|
137
|
+
|
|
138
|
+
This PDS supports multiple environments (dev, staging, production) with separate configurations.
|
|
139
|
+
|
|
140
|
+
**Deploy to specific environment:**
|
|
141
|
+
```bash
|
|
142
|
+
# Development
|
|
143
|
+
wrangler deploy --env dev
|
|
144
|
+
|
|
145
|
+
# Staging
|
|
146
|
+
wrangler deploy --env staging
|
|
147
|
+
|
|
148
|
+
# Production
|
|
149
|
+
wrangler deploy --env production
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Required Secrets
|
|
153
|
+
|
|
154
|
+
Set these secrets for each environment using `wrangler secret put <NAME> --env <environment>`:
|
|
155
|
+
|
|
156
|
+
| Secret | Description | Example |
|
|
157
|
+
|--------|-------------|---------|
|
|
158
|
+
| `PDS_DID` | Your DID identifier | `did:plc:abc123` or `did:web:example.com` |
|
|
159
|
+
| `PDS_HANDLE` | Your handle | `user.bsky.social` |
|
|
160
|
+
| `USER_PASSWORD` | Login password | Strong password |
|
|
161
|
+
| `ACCESS_TOKEN_SECRET` | JWT access token secret | Random 32+ char string |
|
|
162
|
+
| `REFRESH_TOKEN_SECRET` | JWT refresh token secret | Random 32+ char string |
|
|
163
|
+
| `REPO_SIGNING_KEY` | Ed25519 signing key (base64) | From `generate-signing-key.ts` |
|
|
164
|
+
|
|
165
|
+
**Generate secrets:**
|
|
166
|
+
```bash
|
|
167
|
+
# Generate signing key
|
|
168
|
+
bun run scripts/generate-signing-key.ts
|
|
169
|
+
|
|
170
|
+
# Set secrets (example for production)
|
|
171
|
+
wrangler secret put PDS_DID --env production
|
|
172
|
+
wrangler secret put PDS_HANDLE --env production
|
|
173
|
+
wrangler secret put USER_PASSWORD --env production
|
|
174
|
+
wrangler secret put ACCESS_TOKEN_SECRET --env production
|
|
175
|
+
wrangler secret put REFRESH_TOKEN_SECRET --env production
|
|
176
|
+
wrangler secret put REPO_SIGNING_KEY --env production
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Optional Configuration
|
|
180
|
+
|
|
181
|
+
These can be set as environment variables in [`wrangler.jsonc`](wrangler.jsonc:1) or as secrets:
|
|
182
|
+
|
|
183
|
+
| Variable | Default | Description |
|
|
184
|
+
|----------|---------|-------------|
|
|
185
|
+
| `PDS_ALLOWED_MIME` | `image/jpeg,image/png,...` | Comma-separated MIME types |
|
|
186
|
+
| `PDS_MAX_BLOB_SIZE` | `5242880` (5MB) | Max blob size in bytes |
|
|
187
|
+
| `PDS_MAX_JSON_BYTES` | `65536` (64KB) | Max JSON body size |
|
|
188
|
+
| `PDS_RATE_LIMIT_PER_MIN` | `60` | Write requests per minute |
|
|
189
|
+
| `PDS_CORS_ORIGIN` | `*` (dev), specific (prod) | Allowed CORS origins |
|
|
190
|
+
| `PDS_SEQ_WINDOW` | `512` | Firehose sequence window |
|
|
191
|
+
| `PDS_HOSTNAME` | - | Public hostname |
|
|
192
|
+
| `PDS_ACCESS_TTL_SEC` | `3600` (1 hour) | Access token TTL |
|
|
193
|
+
| `PDS_REFRESH_TTL_SEC` | `2592000` (30 days) | Refresh token TTL |
|
|
194
|
+
|
|
195
|
+
### Configuration Validation
|
|
196
|
+
|
|
197
|
+
The PDS validates configuration on startup and will fail fast if required secrets are missing:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// Automatic validation in src/_worker.ts
|
|
201
|
+
validateConfigOrThrow(env);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Validation checks:**
|
|
205
|
+
- All required secrets are present
|
|
206
|
+
- CORS is not wildcard in production
|
|
207
|
+
- DID format is valid
|
|
208
|
+
- Handle format is valid
|
|
209
|
+
- Numeric values are positive
|
|
210
|
+
|
|
211
|
+
### Environment-Specific Settings
|
|
212
|
+
|
|
213
|
+
See [`wrangler.jsonc`](wrangler.jsonc:40) for environment-specific configurations:
|
|
214
|
+
|
|
215
|
+
- **Development**: Relaxed CORS, larger blob limits, local D1/R2
|
|
216
|
+
- **Staging**: Production-like settings, separate D1/R2 instances
|
|
217
|
+
- **Production**: Strict CORS, production D1/R2, observability enabled
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
Debugging & storage
|
|
221
|
+
- D1 schema/migrations: generated with Drizzle Kit into `drizzle/`. Generate with `bunx drizzle-kit generate`.
|
|
222
|
+
- Apply schema locally: `bunx wrangler d1 migrations apply pds --local` (requires dev DB named `pds`).
|
|
223
|
+
- Bootstrap route (alt): `POST /debug/db/bootstrap` creates a minimal `record` table.
|
|
224
|
+
- Insert a test record: `POST /debug/record` with `{ "uri": "at://did:example/app.bsky.feed.post/123", "json": {"msg":"hi"} }`.
|
|
225
|
+
- Get a record: `GET /debug/record?uri=at://did:example/app.bsky.feed.post/123`.
|
|
226
|
+
- R2 test: `PUT /debug/blob/<key>` and `GET /debug/blob/<key>`.
|
|
227
|
+
- Run GC: `POST /debug/gc/blobs` removes R2 objects with no references
|
|
228
|
+
|
|
229
|
+
XRPC surface
|
|
230
|
+
- `GET /xrpc/com.atproto.server.describeServer`
|
|
231
|
+
- `POST /xrpc/com.atproto.server.createSession` (returns JWTs)
|
|
232
|
+
- `POST /xrpc/com.atproto.server.refreshSession`
|
|
233
|
+
- `GET /xrpc/com.atproto.repo.getRecord?uri=...` (reads from D1 `record` table) or `repo+collection+rkey`
|
|
234
|
+
- `POST /xrpc/com.atproto.repo.createRecord` (auth required)
|
|
235
|
+
- `POST /xrpc/com.atproto.repo.putRecord` (auth required)
|
|
236
|
+
- `POST /xrpc/com.atproto.repo.deleteRecord` (auth required)
|
|
237
|
+
- `POST /xrpc/com.atproto.repo.uploadBlob` (auth + MIME allowlist)
|
|
238
|
+
- Stores blob metadata in `blob` table (`cid`=sha256 b64url, `mime`, `size`)
|
|
239
|
+
- Blob references inside records tracked by R2 key; deleting a record drops usage and GC can reclaim orphaned objects
|
|
240
|
+
|
|
241
|
+
Sync (minimal JSON variants)
|
|
242
|
+
- `GET /xrpc/com.atproto.sync.getHead` → `{ root, rev }`
|
|
243
|
+
- `GET /xrpc/com.atproto.sync.getRepo.json?did=<did>` → `{ did, head, rev, records: [{uri,cid,value}] }`
|
|
244
|
+
- `GET /xrpc/com.atproto.sync.getCheckout.json?did=<did>` → same as above
|
|
245
|
+
- `GET /xrpc/com.atproto.sync.getBlocks.json?cids=<cid1,cid2>` → `{ blocks: [{cid,value}] }`
|
|
246
|
+
|
|
247
|
+
Sync (CAR v1)
|
|
248
|
+
- `GET /xrpc/com.atproto.sync.getRepo?did=<did>` → `application/vnd.ipld.car` snapshot
|
|
249
|
+
- `GET /xrpc/com.atproto.sync.getCheckout?did=<did>` → same as above
|
|
250
|
+
- `GET /xrpc/com.atproto.sync.getBlocks?cids=<cid1,cid2>` → `application/vnd.ipld.car` with requested blocks
|
|
251
|
+
- Blocks are DAG-CBOR encoded; CIDs are CIDv1 (dag-cbor + sha2-256)
|
|
252
|
+
|
|
253
|
+
Firehose (WebSocket)
|
|
254
|
+
- `GET /xrpc/com.atproto.sync.subscribeRepos` upgrades to WebSocket.
|
|
255
|
+
- On writes, the worker POSTs a small commit frame to the `Sequencer` Durable Object, which broadcasts to all subscribers.
|
|
256
|
+
- Frames (subject to change):
|
|
257
|
+
- `{"type":"hello","now":<ms>}` once on connect
|
|
258
|
+
- `{"type":"commit","did":"...","commitCid":"...","rev":<n>,"ts":<ms>}` on each write
|
|
259
|
+
|
|
260
|
+
Blob storage
|
|
261
|
+
- Keys are content-addressed: `blobs/by-cid/<sha256-b64url>`; upload response `$link` equals this key.
|
|
262
|
+
- Allowed MIME types via `PDS_ALLOWED_MIME` (CSV). Size limit via `PDS_MAX_BLOB_SIZE` (bytes).
|
|
263
|
+
|
|
264
|
+
Secrets & config (Wrangler)
|
|
265
|
+
- Required:
|
|
266
|
+
- `PDS_DID`, `PDS_HANDLE`, `USER_PASSWORD`
|
|
267
|
+
- `ACCESS_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`
|
|
268
|
+
- Optional:
|
|
269
|
+
- `PDS_ALLOWED_MIME`, `PDS_MAX_BLOB_SIZE`, `PDS_MAX_JSON_BYTES`, `PDS_RATE_LIMIT_PER_MIN`, `PDS_CORS_ORIGIN`
|
|
270
|
+
- Durable Objects: ensure binding for `Sequencer` exists and migration tag added (see `wrangler.jsonc`).
|
|
271
|
+
|
|
272
|
+
Identity (DID)
|
|
273
|
+
- This single‑user PDS uses `did:web`.
|
|
274
|
+
- Host `/.well-known/atproto-did` on your production domain with the DID value.
|
|
275
|
+
- Set `PDS_DID` and `PDS_HANDLE` secrets to match your deployment.
|
|
276
|
+
|
|
277
|
+
## P0 Implementation - Core Protocol Compliance ✅
|
|
278
|
+
|
|
279
|
+
This PDS now implements full AT Protocol core compliance with:
|
|
280
|
+
|
|
281
|
+
### MST (Merkle Search Tree)
|
|
282
|
+
- ✅ Sorted, deterministic tree structure
|
|
283
|
+
- ✅ Automatic rebalancing (~4 fanout)
|
|
284
|
+
- ✅ Prefix compression for efficiency
|
|
285
|
+
- ✅ D1 blockstore integration
|
|
286
|
+
|
|
287
|
+
### Signed Commits
|
|
288
|
+
- ✅ Ed25519 cryptographic signatures
|
|
289
|
+
- ✅ AT Protocol v3 commit structure
|
|
290
|
+
- ✅ TID-based revisions
|
|
291
|
+
- ✅ Commit chain tracking
|
|
292
|
+
|
|
293
|
+
### Firehose
|
|
294
|
+
- ✅ WebSocket-based event stream
|
|
295
|
+
- ✅ CBOR-encoded frames (#info, #commit, #identity, #account)
|
|
296
|
+
- ✅ Cursor-based replay
|
|
297
|
+
- ✅ Backpressure handling
|
|
298
|
+
- ✅ Durable Object coordination
|
|
299
|
+
|
|
300
|
+
### XRPC Endpoints
|
|
301
|
+
- ✅ Server: getSession, deleteSession
|
|
302
|
+
- ✅ Repo: listRecords, describeRepo, applyWrites
|
|
303
|
+
- ✅ Sync: listBlobs, getRecord, listRepos, getLatestCommit
|
|
304
|
+
- ✅ Identity: resolveHandle, updateHandle
|
|
305
|
+
|
|
306
|
+
## Setup Instructions
|
|
307
|
+
|
|
308
|
+
### 1. Generate Signing Key
|
|
309
|
+
```bash
|
|
310
|
+
bun run scripts/generate-signing-key.ts
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### 2. Configure Secrets
|
|
314
|
+
|
|
315
|
+
**Required Secrets:**
|
|
316
|
+
```bash
|
|
317
|
+
wrangler secret put REPO_SIGNING_KEY # From step 1
|
|
318
|
+
wrangler secret put PDS_DID # Your DID
|
|
319
|
+
wrangler secret put PDS_HANDLE # Your handle
|
|
320
|
+
wrangler secret put USER_PASSWORD # Login password
|
|
321
|
+
wrangler secret put ACCESS_TOKEN_SECRET
|
|
322
|
+
wrangler secret put REFRESH_TOKEN_SECRET
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**For Local Development (.dev.vars):**
|
|
326
|
+
```env
|
|
327
|
+
PDS_DID=did:plc:your-did-here
|
|
328
|
+
PDS_HANDLE=your-handle.bsky.social
|
|
329
|
+
REPO_SIGNING_KEY=<base64-key-from-step-1>
|
|
330
|
+
USER_PASSWORD=your-password
|
|
331
|
+
ACCESS_TOKEN_SECRET=your-access-secret
|
|
332
|
+
REFRESH_TOKEN_SECRET=your-refresh-secret
|
|
333
|
+
PDS_SEQ_WINDOW=512
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### 3. Run Database Migration
|
|
337
|
+
```bash
|
|
338
|
+
bun run db:generate
|
|
339
|
+
bun run db:apply:local
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### 4. Run Tests
|
|
343
|
+
```bash
|
|
344
|
+
bun test tests/mst.test.ts
|
|
345
|
+
bun test tests/commit.test.ts
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### 5. Start Development
|
|
349
|
+
```bash
|
|
350
|
+
bun run dev
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Testing the Implementation
|
|
354
|
+
|
|
355
|
+
### Test Firehose
|
|
356
|
+
```bash
|
|
357
|
+
npm install -g wscat
|
|
358
|
+
wscat -c "ws://localhost:4321/xrpc/com.atproto.sync.subscribeRepos"
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Test XRPC Endpoints
|
|
362
|
+
```bash
|
|
363
|
+
# Get session
|
|
364
|
+
curl http://localhost:4321/xrpc/com.atproto.server.getSession
|
|
365
|
+
|
|
366
|
+
# Describe repo
|
|
367
|
+
curl "http://localhost:4321/xrpc/com.atproto.repo.describeRepo?repo=did:example:single-user"
|
|
368
|
+
|
|
369
|
+
# List records
|
|
370
|
+
curl "http://localhost:4321/xrpc/com.atproto.repo.listRecords?repo=did:example:single-user&collection=app.bsky.feed.post"
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Documentation
|
|
374
|
+
|
|
375
|
+
- [`P0_COMPLETE.md`](P0_COMPLETE.md) - Full P0 implementation details
|
|
376
|
+
- [`P0_IMPLEMENTATION_SUMMARY.md`](P0_IMPLEMENTATION_SUMMARY.md) - Technical summary
|
|
377
|
+
- [`PROGRESS.md`](PROGRESS.md) - Development progress notes
|
|
378
|
+
|
|
379
|
+
Repo signing key (REQUIRED)
|
|
380
|
+
- Generate an Ed25519 signing key: `bun run scripts/generate-signing-key.ts`
|
|
381
|
+
- Store as `REPO_SIGNING_KEY` secret (base64-encoded private key)
|
|
382
|
+
|
|
383
|
+
## P1 Implementation - Production Readiness 🚀
|
|
384
|
+
|
|
385
|
+
This PDS now includes production-grade features for security, observability, and reliability:
|
|
386
|
+
|
|
387
|
+
### Authentication Hardening
|
|
388
|
+
- ✅ **Single-use refresh tokens** with JTI tracking
|
|
389
|
+
- ✅ **Token rotation** on every refresh
|
|
390
|
+
- ✅ **Automatic token cleanup** (lazy cleanup on 1% of requests)
|
|
391
|
+
- ✅ **Account lockout** after 5 failed login attempts (15-minute lockout)
|
|
392
|
+
- ✅ **EdDSA (Ed25519) JWT signing** support (in addition to HS256)
|
|
393
|
+
- ✅ **Proper JWT claims**: `sub`, `aud`, `iat`, `exp`, `jti`, `scope`
|
|
394
|
+
- ✅ **Production CORS validation** (no wildcard in production)
|
|
395
|
+
|
|
396
|
+
### Error Handling
|
|
397
|
+
- ✅ **XRPC error hierarchy** with AT Protocol error codes
|
|
398
|
+
- ✅ **Consistent error responses** with user-friendly messages
|
|
399
|
+
- ✅ **Error categorization** (client vs server errors)
|
|
400
|
+
- ✅ **Request ID tracking** in all error responses
|
|
401
|
+
|
|
402
|
+
### Observability
|
|
403
|
+
- ✅ **Structured JSON logging** with levels (debug, info, warn, error)
|
|
404
|
+
- ✅ **Request ID tracking** in all logs and response headers
|
|
405
|
+
- ✅ **Enhanced health checks** for D1 and R2 dependencies
|
|
406
|
+
- ✅ **Performance metrics** in request logs (duration, status)
|
|
407
|
+
|
|
408
|
+
### Additional Configuration
|
|
409
|
+
|
|
410
|
+
**JWT Configuration:**
|
|
411
|
+
```bash
|
|
412
|
+
# Algorithm selection (HS256 or EdDSA)
|
|
413
|
+
PDS_HOSTNAME=your-pds.example.com
|
|
414
|
+
PDS_ACCESS_TTL_SEC=3600 # 1 hour
|
|
415
|
+
PDS_REFRESH_TTL_SEC=2592000 # 30 days
|
|
416
|
+
JWT_ALGORITHM=HS256 # or EdDSA
|
|
417
|
+
|
|
418
|
+
# For EdDSA (optional)
|
|
419
|
+
JWT_ED25519_PRIVATE_KEY=<base64-encoded-key>
|
|
420
|
+
JWT_ED25519_PUBLIC_KEY=<base64-encoded-key>
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**CORS Configuration:**
|
|
424
|
+
```bash
|
|
425
|
+
# Comma-separated list of allowed origins (no wildcard in production)
|
|
426
|
+
PDS_CORS_ORIGIN=https://app.example.com,https://admin.example.com
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Logging & Monitoring
|
|
430
|
+
|
|
431
|
+
**View logs in development:**
|
|
432
|
+
```bash
|
|
433
|
+
wrangler tail --format=pretty
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**View logs in production:**
|
|
437
|
+
```bash
|
|
438
|
+
wrangler tail --env production --format=json
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Configure Logpush (production):**
|
|
442
|
+
1. Set up Logpush in Cloudflare dashboard
|
|
443
|
+
2. Send logs to your preferred service (Datadog, Splunk, S3, etc.)
|
|
444
|
+
3. Filter by `requestId` for request tracing
|
|
445
|
+
|
|
446
|
+
**Log format:**
|
|
447
|
+
```json
|
|
448
|
+
{
|
|
449
|
+
"level": "info",
|
|
450
|
+
"type": "request",
|
|
451
|
+
"requestId": "uuid",
|
|
452
|
+
"method": "POST",
|
|
453
|
+
"path": "/xrpc/com.atproto.repo.createRecord",
|
|
454
|
+
"status": 200,
|
|
455
|
+
"duration": 45,
|
|
456
|
+
"timestamp": "2025-10-02T22:00:00.000Z"
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Health Check
|
|
461
|
+
|
|
462
|
+
**Endpoint:** `GET /health`
|
|
463
|
+
|
|
464
|
+
**Response:**
|
|
465
|
+
```json
|
|
466
|
+
{
|
|
467
|
+
"status": "healthy",
|
|
468
|
+
"timestamp": "2025-10-02T22:00:00.000Z",
|
|
469
|
+
"checks": {
|
|
470
|
+
"database": { "status": "ok" },
|
|
471
|
+
"storage": { "status": "ok" }
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Returns `503` if any dependency is unhealthy.
|
|
477
|
+
|
|
478
|
+
### Security Best Practices
|
|
479
|
+
|
|
480
|
+
1. **Never use wildcard CORS in production** - Set explicit origins in `PDS_CORS_ORIGIN`
|
|
481
|
+
2. **Use strong secrets** - Generate cryptographically secure values for all secrets
|
|
482
|
+
3. **Enable EdDSA signing** - More secure than HS256 for production
|
|
483
|
+
4. **Monitor failed login attempts** - Check logs for suspicious activity
|
|
484
|
+
5. **Set appropriate token TTLs** - Balance security and user experience
|
|
485
|
+
|
|
486
|
+
### Documentation
|
|
487
|
+
|
|
488
|
+
- [`P1_IMPLEMENTATION_SUMMARY.md`](P1_IMPLEMENTATION_SUMMARY.md) - Full P1 implementation details
|
|
489
|
+
- [`P1.md`](P1.md) - P1 task breakdown and requirements
|
|
490
|
+
|
|
491
|
+
## P3 Implementation - Optimization & Interoperability 🚀
|
|
492
|
+
|
|
493
|
+
This PDS now includes optimization for Cloudflare Workers and interoperability features:
|
|
494
|
+
|
|
495
|
+
### Cloudflare Workers Optimization
|
|
496
|
+
- ✅ **Streaming CAR encoding** for memory efficiency (< 128MB)
|
|
497
|
+
- ✅ **Edge caching** for DID documents and static assets
|
|
498
|
+
- ✅ **Performance tests** verifying CPU and memory constraints
|
|
499
|
+
- ✅ **Memory-efficient operations** for large repositories
|
|
500
|
+
|
|
501
|
+
### Blob Storage Enhancement
|
|
502
|
+
- ✅ **Blob quota tracking** per DID (default: 10GB)
|
|
503
|
+
- ✅ **Quota enforcement** on upload
|
|
504
|
+
- ✅ **Reference counting** for garbage collection
|
|
505
|
+
- ✅ **Deduplication** by content-addressed storage
|
|
506
|
+
|
|
507
|
+
### Identity Enhancement
|
|
508
|
+
- ✅ **DID document generation** at `/.well-known/did.json`
|
|
509
|
+
- ✅ **Handle validation** and normalization
|
|
510
|
+
- ✅ **Service endpoints** in DID document
|
|
511
|
+
- ✅ **Edge caching** for identity documents
|
|
512
|
+
|
|
513
|
+
### Interoperability Testing
|
|
514
|
+
- ✅ **Federation test stubs** for PDS-to-PDS sync
|
|
515
|
+
- ✅ **Compliance test stubs** for AT Protocol
|
|
516
|
+
- ✅ **Protocol version** documentation
|
|
517
|
+
- ✅ **Lexicon validation** framework
|
|
518
|
+
|
|
519
|
+
### Configuration
|
|
520
|
+
|
|
521
|
+
**Blob Quota:**
|
|
522
|
+
```bash
|
|
523
|
+
PDS_BLOB_QUOTA_BYTES=10737418240 # Default: 10GB
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**Caching (automatic):**
|
|
527
|
+
- DID documents: 1 hour TTL, 24 hour stale-while-revalidate
|
|
528
|
+
- Records: 1 minute TTL, 5 minute stale-while-revalidate
|
|
529
|
+
- Repo snapshots: 5 minute TTL, 1 hour stale-while-revalidate
|
|
530
|
+
|
|
531
|
+
### Testing
|
|
532
|
+
|
|
533
|
+
```bash
|
|
534
|
+
# Performance tests
|
|
535
|
+
bun test tests/performance.test.ts
|
|
536
|
+
|
|
537
|
+
# Memory tests
|
|
538
|
+
bun test tests/memory.test.ts
|
|
539
|
+
|
|
540
|
+
# Blob tests
|
|
541
|
+
bun test tests/blob.test.ts
|
|
542
|
+
|
|
543
|
+
# Identity tests
|
|
544
|
+
bun test tests/identity.test.ts
|
|
545
|
+
|
|
546
|
+
# Federation tests
|
|
547
|
+
bun test tests/federation.test.ts
|
|
548
|
+
|
|
549
|
+
# Compliance tests
|
|
550
|
+
bun test tests/compliance.test.ts
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### Documentation
|
|
554
|
+
|
|
555
|
+
- [`P3_IMPLEMENTATION_SUMMARY.md`](P3_IMPLEMENTATION_SUMMARY.md) - Full P3 implementation details
|
|
556
|
+
- [`P3.md`](P3.md) - P3 task breakdown and requirements
|
|
557
|
+
|
|
558
|
+
- Used for signing all repository commits
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AstroIntegration } from 'astro';
|
|
2
|
+
import type { Env, PdsLocals } from './types/env';
|
|
3
|
+
|
|
4
|
+
export interface PdsIntegrationOptions {
|
|
5
|
+
debugRoutes?: boolean;
|
|
6
|
+
includeRootEndpoint?: boolean;
|
|
7
|
+
injectServerEntry?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function alteran(options?: PdsIntegrationOptions): AstroIntegration;
|
|
11
|
+
|
|
12
|
+
export type { Env, PdsLocals };
|
package/index.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
const CORE_ROUTES = [
|
|
5
|
+
{ pattern: '/.well-known/atproto-did', entrypoint: './src/pages/.well-known/atproto-did.ts' },
|
|
6
|
+
{ pattern: '/.well-known/did.json', entrypoint: './src/pages/.well-known/did.json.ts' },
|
|
7
|
+
{ pattern: '/health', entrypoint: './src/pages/health.ts' },
|
|
8
|
+
{ pattern: '/ready', entrypoint: './src/pages/ready.ts' },
|
|
9
|
+
{ pattern: '/xrpc/com.atproto.identity.resolveHandle', entrypoint: './src/pages/xrpc/com.atproto.identity.resolveHandle.ts' },
|
|
10
|
+
{ pattern: '/xrpc/com.atproto.identity.updateHandle', entrypoint: './src/pages/xrpc/com.atproto.identity.updateHandle.ts' },
|
|
11
|
+
{ pattern: '/xrpc/com.atproto.repo.applyWrites', entrypoint: './src/pages/xrpc/com.atproto.repo.applyWrites.ts' },
|
|
12
|
+
{ pattern: '/xrpc/com.atproto.repo.createRecord', entrypoint: './src/pages/xrpc/com.atproto.repo.createRecord.ts' },
|
|
13
|
+
{ pattern: '/xrpc/com.atproto.repo.deleteRecord', entrypoint: './src/pages/xrpc/com.atproto.repo.deleteRecord.ts' },
|
|
14
|
+
{ pattern: '/xrpc/com.atproto.repo.describeRepo', entrypoint: './src/pages/xrpc/com.atproto.repo.describeRepo.ts' },
|
|
15
|
+
{ pattern: '/xrpc/com.atproto.repo.getRecord', entrypoint: './src/pages/xrpc/com.atproto.repo.getRecord.ts' },
|
|
16
|
+
{ pattern: '/xrpc/com.atproto.repo.listRecords', entrypoint: './src/pages/xrpc/com.atproto.repo.listRecords.ts' },
|
|
17
|
+
{ pattern: '/xrpc/com.atproto.repo.putRecord', entrypoint: './src/pages/xrpc/com.atproto.repo.putRecord.ts' },
|
|
18
|
+
{ pattern: '/xrpc/com.atproto.repo.uploadBlob', entrypoint: './src/pages/xrpc/com.atproto.repo.uploadBlob.ts' },
|
|
19
|
+
{ pattern: '/xrpc/com.atproto.server.createSession', entrypoint: './src/pages/xrpc/com.atproto.server.createSession.ts' },
|
|
20
|
+
{ pattern: '/xrpc/com.atproto.server.deleteSession', entrypoint: './src/pages/xrpc/com.atproto.server.deleteSession.ts' },
|
|
21
|
+
{ pattern: '/xrpc/com.atproto.server.describeServer', entrypoint: './src/pages/xrpc/com.atproto.server.describeServer.ts' },
|
|
22
|
+
{ pattern: '/xrpc/com.atproto.server.getSession', entrypoint: './src/pages/xrpc/com.atproto.server.getSession.ts' },
|
|
23
|
+
{ pattern: '/xrpc/com.atproto.server.refreshSession', entrypoint: './src/pages/xrpc/com.atproto.server.refreshSession.ts' },
|
|
24
|
+
{ pattern: '/xrpc/com.atproto.sync.getBlocks', entrypoint: './src/pages/xrpc/com.atproto.sync.getBlocks.ts' },
|
|
25
|
+
{ pattern: '/xrpc/com.atproto.sync.getBlocks.json', entrypoint: './src/pages/xrpc/com.atproto.sync.getBlocks.json.ts' },
|
|
26
|
+
{ pattern: '/xrpc/com.atproto.sync.getCheckout', entrypoint: './src/pages/xrpc/com.atproto.sync.getCheckout.ts' },
|
|
27
|
+
{ pattern: '/xrpc/com.atproto.sync.getCheckout.json', entrypoint: './src/pages/xrpc/com.atproto.sync.getCheckout.json.ts' },
|
|
28
|
+
{ pattern: '/xrpc/com.atproto.sync.getHead', entrypoint: './src/pages/xrpc/com.atproto.sync.getHead.ts' },
|
|
29
|
+
{ pattern: '/xrpc/com.atproto.sync.getLatestCommit', entrypoint: './src/pages/xrpc/com.atproto.sync.getLatestCommit.ts' },
|
|
30
|
+
{ pattern: '/xrpc/com.atproto.sync.getRecord', entrypoint: './src/pages/xrpc/com.atproto.sync.getRecord.ts' },
|
|
31
|
+
{ pattern: '/xrpc/com.atproto.sync.getRepo', entrypoint: './src/pages/xrpc/com.atproto.sync.getRepo.ts' },
|
|
32
|
+
{ pattern: '/xrpc/com.atproto.sync.getRepo.json', entrypoint: './src/pages/xrpc/com.atproto.sync.getRepo.json.ts' },
|
|
33
|
+
{ pattern: '/xrpc/com.atproto.sync.getRepo.range', entrypoint: './src/pages/xrpc/com.atproto.sync.getRepo.range.ts' },
|
|
34
|
+
{ pattern: '/xrpc/com.atproto.sync.listBlobs', entrypoint: './src/pages/xrpc/com.atproto.sync.listBlobs.ts' },
|
|
35
|
+
{ pattern: '/xrpc/com.atproto.sync.listRepos', entrypoint: './src/pages/xrpc/com.atproto.sync.listRepos.ts' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const ROOT_ROUTE = {
|
|
39
|
+
pattern: '/',
|
|
40
|
+
entrypoint: './src/pages/index.ts',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const DEBUG_ROUTES = [
|
|
44
|
+
{ pattern: '/debug/blob/[...key]', entrypoint: './src/pages/debug/blob/[...key].ts' },
|
|
45
|
+
{ pattern: '/debug/db/bootstrap', entrypoint: './src/pages/debug/db/bootstrap.ts' },
|
|
46
|
+
{ pattern: '/debug/db/commits', entrypoint: './src/pages/debug/db/commits.ts' },
|
|
47
|
+
{ pattern: '/debug/gc/blobs', entrypoint: './src/pages/debug/gc/blobs.ts' },
|
|
48
|
+
{ pattern: '/debug/record', entrypoint: './src/pages/debug/record.ts' },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const pkgRoot = new URL('.', import.meta.url);
|
|
52
|
+
|
|
53
|
+
const resolvePackagePath = (relative) => fileURLToPath(new URL(relative, pkgRoot));
|
|
54
|
+
|
|
55
|
+
export default function alteran(options = {}) {
|
|
56
|
+
const {
|
|
57
|
+
debugRoutes = false,
|
|
58
|
+
includeRootEndpoint = false,
|
|
59
|
+
injectServerEntry = true,
|
|
60
|
+
} = options;
|
|
61
|
+
|
|
62
|
+
const aliasTarget = resolvePackagePath('./src');
|
|
63
|
+
const middlewareEntrypoint = resolvePackagePath('./src/middleware.ts');
|
|
64
|
+
const serverEntrypoint = resolvePackagePath('./src/_worker.ts');
|
|
65
|
+
|
|
66
|
+
const routes = CORE_ROUTES.slice();
|
|
67
|
+
if (includeRootEndpoint) {
|
|
68
|
+
routes.unshift(ROOT_ROUTE);
|
|
69
|
+
}
|
|
70
|
+
if (debugRoutes) {
|
|
71
|
+
routes.push(...DEBUG_ROUTES);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
name: 'alteran',
|
|
76
|
+
hooks: {
|
|
77
|
+
'astro:config:setup'({ config, updateConfig, addMiddleware, injectRoute, logger }) {
|
|
78
|
+
if (config.output !== 'server') {
|
|
79
|
+
updateConfig({ output: 'server' });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
updateConfig({
|
|
83
|
+
vite: {
|
|
84
|
+
resolve: {
|
|
85
|
+
alias: {
|
|
86
|
+
'@alteran': aliasTarget,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (injectServerEntry) {
|
|
93
|
+
if (config.build?.serverEntry && config.build.serverEntry !== serverEntrypoint) {
|
|
94
|
+
logger.info(
|
|
95
|
+
'[alteran] Overriding existing build.serverEntry with the packaged worker entry. Pass { injectServerEntry: false } to opt out.'
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
updateConfig({
|
|
99
|
+
build: {
|
|
100
|
+
serverEntry: serverEntrypoint,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
addMiddleware({
|
|
106
|
+
entrypoint: middlewareEntrypoint,
|
|
107
|
+
order: 'pre',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
for (const route of routes) {
|
|
111
|
+
injectRoute({ pattern: route.pattern, entrypoint: resolvePackagePath(route.entrypoint) });
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
'astro:config:done'({ config, injectTypes, logger }) {
|
|
116
|
+
const envTypesPath = resolvePackagePath('./types/env.d.ts');
|
|
117
|
+
const envTypes = readFileSync(envTypesPath, 'utf-8');
|
|
118
|
+
injectTypes({ filename: 'astro-cloudflare-pds.d.ts', content: envTypes });
|
|
119
|
+
|
|
120
|
+
const adapterName = config.adapter?.name ?? 'unknown adapter';
|
|
121
|
+
if (!adapterName.toLowerCase().includes('cloudflare')) {
|
|
122
|
+
logger.warn(
|
|
123
|
+
`[alteran] Expected a Cloudflare adapter. Found "${adapterName}". The PDS worker relies on Cloudflare runtime bindings.`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|