@alteran/astro 0.1.4 → 0.1.6

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 CHANGED
@@ -27,7 +27,7 @@ By default the integration injects all `/xrpc/*` ATProto routes, health/ready ch
27
27
  alteran({
28
28
  debugRoutes: process.env.NODE_ENV !== 'production',
29
29
  includeRootEndpoint: false,
30
- injectServerEntry: true,
30
+ injectServerEntry: true, // opt in if you don't maintain your own worker entrypoint
31
31
  });
32
32
  ```
33
33
 
@@ -35,11 +35,32 @@ The integration automatically:
35
35
  - Resolves all injected routes against the packaged runtime without requiring a Vite alias
36
36
  - Registers the middleware that applies structured logging and CORS enforcement
37
37
  - Injects all PDS HTTP endpoints into the host project
38
- - Sets `build.serverEntry` to the packaged Cloudflare worker (unless you opt out)
38
+ - Offers the packaged Cloudflare worker entrypoint when you enable `{ injectServerEntry: true }`
39
39
  - Publishes ambient env typings so `Env` and `App.Locals` are available from TypeScript
40
40
 
41
41
  When deploying, continue to configure Wrangler/D1/R2 secrets exactly as before—the integration does not change the runtime requirements.
42
42
 
43
+ ### Custom Worker Entrypoint
44
+
45
+ The integration no longer overrides `build.serverEntry` by default. If you need to export additional Durable Objects or otherwise customise the worker, keep your own entrypoint and compose Alteran's runtime helpers instead of copying the internal logic.
46
+
47
+ ```ts
48
+ // src/_worker.ts in your Astro project
49
+ import { createPdsFetchHandler, Sequencer } from '@alteran/astro/worker';
50
+
51
+ const fetch = createPdsFetchHandler();
52
+
53
+ export default { fetch };
54
+
55
+ // Re-export Sequencer so Wrangler can bind the Durable Object namespace
56
+ export { Sequencer };
57
+
58
+ // Export any additional Durable Objects after this line
59
+ export { MyDurableObject } from './worker/my-durable-object';
60
+ ```
61
+
62
+ Helpers like `onRequest`, `seed`, and `validateConfigOrThrow` are also exported from `@alteran/astro/worker` if you need to build more advanced wrappers (for example, to add request instrumentation before delegating to the PDS handler).
63
+
43
64
  To install dependencies:
44
65
 
45
66
  ```bash
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { readFileSync } from 'node:fs';
2
+ import { isAbsolute, relative } from 'node:path';
2
3
  import { fileURLToPath } from 'node:url';
3
4
 
4
5
  const CORE_ROUTES = [
@@ -37,7 +38,7 @@ const CORE_ROUTES = [
37
38
 
38
39
  const ROOT_ROUTE = {
39
40
  pattern: '/',
40
- entrypoint: './src/pages/index.ts',
41
+ entrypoint: './src/handlers/root.ts',
41
42
  };
42
43
 
43
44
  const DEBUG_ROUTES = [
@@ -56,7 +57,7 @@ export default function alteran(options = {}) {
56
57
  const {
57
58
  debugRoutes = false,
58
59
  includeRootEndpoint = false,
59
- injectServerEntry = true,
60
+ injectServerEntry = false,
60
61
  } = options;
61
62
 
62
63
  const middlewareEntrypoint = resolvePackagePath('./src/middleware.ts');
@@ -96,8 +97,21 @@ export default function alteran(options = {}) {
96
97
  order: 'pre',
97
98
  });
98
99
 
100
+ const srcDirUrl = config.srcDir ?? config.root;
101
+ const pagesDirUrl = config.pagesDir ?? new URL('./pages/', srcDirUrl);
102
+ const projectPagesDir = fileURLToPath(pagesDirUrl);
103
+
99
104
  for (const route of routes) {
100
- injectRoute({ pattern: route.pattern, entrypoint: resolvePackagePath(route.entrypoint) });
105
+ const entrypoint = resolvePackagePath(route.entrypoint);
106
+ const relativeToPages = relative(projectPagesDir, entrypoint);
107
+ const entrypointWithinPages =
108
+ relativeToPages === '' || (!relativeToPages.startsWith('..') && !isAbsolute(relativeToPages));
109
+
110
+ if (entrypointWithinPages) {
111
+ continue;
112
+ }
113
+
114
+ injectRoute({ pattern: route.pattern, entrypoint });
101
115
  }
102
116
  },
103
117
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alteran/astro",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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",
@@ -8,6 +8,10 @@
8
8
  ".": {
9
9
  "types": "./index.d.ts",
10
10
  "import": "./index.js"
11
+ },
12
+ "./worker": {
13
+ "types": "./types/worker.d.ts",
14
+ "import": "./src/worker/index.ts"
11
15
  }
12
16
  },
13
17
  "type": "module",
@@ -36,15 +40,13 @@
36
40
  "db:reset:local": "rm -rf .wrangler/state && rm -rf drizzle && bun run db:generate && bun run db:apply:local"
37
41
  },
38
42
  "devDependencies": {
39
- "@astrojs/cloudflare": "^11.0.4",
43
+ "@astrojs/cloudflare": "^12.6.9",
40
44
  "@atproto/api": "^0.17.0",
41
45
  "@cloudflare/vite-plugin": "^1.13.8",
42
- "alchemy": "^0.70.2",
43
46
  "@types/bun": "latest",
44
- "astro": "^4.16.9",
45
47
  "drizzle-kit": "^0.31.5",
46
48
  "miniflare": "3",
47
- "vite": "^7.1.8",
49
+ "vite": "^6.3.6",
48
50
  "wrangler": "^4.40.3"
49
51
  },
50
52
  "peerDependencies": {
@@ -55,6 +57,7 @@
55
57
  "@ipld/car": "^5.4.2",
56
58
  "@ipld/dag-cbor": "^9.2.5",
57
59
  "@noble/hashes": "^2.0.1",
60
+ "astro": "^5.14.1",
58
61
  "astro-icon": "^1.1.5",
59
62
  "dotenv": "^17.2.3",
60
63
  "drizzle-orm": "^0.44.6",
package/src/_worker.ts CHANGED
@@ -1,44 +1,7 @@
1
- import { handle } from 'astro/internal/handler';
2
- import { onRequest } from './middleware';
3
- import { seed } from './db/seed';
4
- import { validateConfigOrThrow } from './lib/config';
5
- import type { Env } from './env';
1
+ import { createPdsFetchHandler } from './worker/runtime';
6
2
 
7
- export default {
8
- async fetch(request: Request, env: Env, ctx: ExecutionContext) {
9
- // Validate configuration on startup (fail fast if invalid)
10
- try {
11
- validateConfigOrThrow(env);
12
- } catch (error) {
13
- return new Response(
14
- JSON.stringify({
15
- error: 'ConfigurationError',
16
- message: error instanceof Error ? error.message : 'Invalid configuration',
17
- }),
18
- {
19
- status: 500,
20
- headers: { 'Content-Type': 'application/json' },
21
- }
22
- );
23
- }
3
+ const fetch = createPdsFetchHandler();
24
4
 
25
- await seed(env.DB, env.PDS_DID ?? 'did:example:single-user');
5
+ export default { fetch };
26
6
 
27
- const url = new URL(request.url);
28
- if (url.pathname === '/xrpc/com.atproto.sync.subscribeRepos') {
29
- const upgrade = request.headers.get('upgrade');
30
- if (upgrade !== 'websocket') return new Response('Expected websocket', { status: 426 });
31
- if (!env.SEQUENCER) return new Response('Sequencer not configured', { status: 503 });
32
-
33
- const id = env.SEQUENCER.idFromName('default');
34
- const stub = env.SEQUENCER.get(id);
35
- return stub.fetch(request as any);
36
- }
37
-
38
- const locals: any = { runtime: { env, ctx, request } };
39
- return await onRequest(locals as any, async () => await handle(locals as any));
40
- },
41
- };
42
-
43
- // Export Durable Object(s)
44
7
  export { Sequencer } from './worker/sequencer';
@@ -1,5 +1,108 @@
1
1
  import type { APIContext } from 'astro';
2
2
 
3
- export async function GET(_ctx: APIContext) {
4
- return new Response('Alteran is alive', { status: 200 });
3
+ const HTML_TEMPLATE = (
4
+ handle,
5
+ did,
6
+ ) => `<!DOCTYPE html>
7
+ <html lang="en">
8
+ <head>
9
+ <meta charset="utf-8" />
10
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
11
+ <title>Alteran PDS</title>
12
+ <style>
13
+ :root {
14
+ color-scheme: light dark;
15
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
16
+ }
17
+ body {
18
+ margin: 0;
19
+ min-height: 100vh;
20
+ display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ background: radial-gradient(circle at top, #f5f7ff, #e2e8f0);
24
+ }
25
+ .card {
26
+ background: rgba(255, 255, 255, 0.92);
27
+ backdrop-filter: blur(6px);
28
+ padding: 2.5rem;
29
+ border-radius: 1rem;
30
+ box-shadow: 0 25px 45px rgba(15, 23, 42, 0.18);
31
+ text-align: center;
32
+ max-width: 28rem;
33
+ }
34
+ h1 {
35
+ margin: 0 0 1.5rem;
36
+ font-size: clamp(2rem, 4vw, 2.75rem);
37
+ letter-spacing: -0.02em;
38
+ color: #111827;
39
+ }
40
+ p {
41
+ margin: 0.75rem 0;
42
+ color: #334155;
43
+ line-height: 1.55;
44
+ }
45
+ .pill {
46
+ display: inline-block;
47
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
48
+ font-size: 0.95rem;
49
+ padding: 0.35rem 0.75rem;
50
+ border-radius: 999px;
51
+ background: rgba(59, 130, 246, 0.13);
52
+ color: #1d4ed8;
53
+ }
54
+ a {
55
+ display: inline-flex;
56
+ align-items: center;
57
+ gap: 0.5rem;
58
+ color: #2563eb;
59
+ text-decoration: none;
60
+ font-weight: 600;
61
+ margin-top: 1.5rem;
62
+ }
63
+ a:hover {
64
+ text-decoration: underline;
65
+ }
66
+ svg {
67
+ width: 1.25rem;
68
+ height: 1.25rem;
69
+ }
70
+ </style>
71
+ </head>
72
+ <body>
73
+ <div class="card">
74
+ <h1>Alteran</h1>
75
+ <p>This single-user ATProto Personal Data Server runs on Cloudflare Workers.</p>
76
+ <p>
77
+ <strong>Handle:</strong>
78
+ <span class="pill">${handle}</span>
79
+ </p>
80
+ <p>
81
+ <strong>DID:</strong>
82
+ <span class="pill">${did}</span>
83
+ </p>
84
+ <a href="https://github.com/alteran-dev/alteran" target="_blank" rel="noopener noreferrer">
85
+ <svg viewBox="0 0 24 24" role="img" aria-hidden="true" focusable="false">
86
+ <path
87
+ fill="currentColor"
88
+ d="M12 .5a12 12 0 0 0-3.79 23.4c.6.11.82-.26.82-.58v-2.02c-3.34.73-4.04-1.61-4.04-1.61-.55-1.4-1.35-1.77-1.35-1.77-1.1-.75.08-.74.08-.74 1.22.09 1.87 1.26 1.87 1.26 1.08 1.85 2.83 1.32 3.52 1.01.11-.79.42-1.32.76-1.62-2.67-.3-5.47-1.34-5.47-5.98 0-1.32.47-2.39 1.25-3.24-.13-.3-.54-1.52.12-3.17 0 0 1.01-.32 3.3 1.24a11.5 11.5 0 0 1 6 0c2.29-1.56 3.3-1.24 3.3-1.24.66 1.65.25 2.87.12 3.17.78.85 1.25 1.92 1.25 3.24 0 4.66-2.8 5.68-5.48 5.97.43.37.81 1.09.81 2.2v3.26c0 .32.22.7.83.58A12 12 0 0 0 12 .5"
89
+ />
90
+ </svg>
91
+ Source Code
92
+ </a>
93
+ </div>
94
+ </body>
95
+ </html>`;
96
+
97
+ export async function GET({ locals }: APIContext) {
98
+ const { env } = locals.runtime ?? {};
99
+ const handle = env?.PDS_HANDLE ?? 'unknown.handle';
100
+ const did = env?.PDS_DID ?? 'did:plc:unknown';
101
+
102
+ return new Response(HTML_TEMPLATE(handle, did), {
103
+ status: 200,
104
+ headers: {
105
+ 'Content-Type': 'text/html; charset=utf-8',
106
+ },
107
+ });
5
108
  }
@@ -0,0 +1,6 @@
1
+ export { createPdsFetchHandler } from './runtime';
2
+ export type { PdsFetchHandler } from './runtime';
3
+ export { Sequencer } from './sequencer';
4
+ export { onRequest } from '../middleware';
5
+ export { seed } from '../db/seed';
6
+ export { validateConfigOrThrow } from '../lib/config';
@@ -0,0 +1,63 @@
1
+ import { handle } from 'astro/internal/handler';
2
+ import { onRequest } from '../middleware';
3
+ import { seed } from '../db/seed';
4
+ import { validateConfigOrThrow } from '../lib/config';
5
+ import type { Env } from '../env';
6
+ import type {
7
+ ExecutionContext,
8
+ Request as WorkersRequest,
9
+ Response as WorkersResponse,
10
+ } from '@cloudflare/workers-types';
11
+
12
+ export type PdsFetchHandler = (
13
+ request: WorkersRequest,
14
+ env: Env,
15
+ ctx: ExecutionContext
16
+ ) => Promise<WorkersResponse>;
17
+
18
+ /**
19
+ * Returns the Alteran PDS Worker fetch handler so downstream apps can
20
+ * compose it inside their own Cloudflare Worker entrypoint.
21
+ */
22
+ export function createPdsFetchHandler(): PdsFetchHandler {
23
+ return async function fetch(request: WorkersRequest, env: Env, ctx: ExecutionContext) {
24
+ try {
25
+ validateConfigOrThrow(env);
26
+ } catch (error) {
27
+ return new Response(
28
+ JSON.stringify({
29
+ error: 'ConfigurationError',
30
+ message: error instanceof Error ? error.message : 'Invalid configuration',
31
+ }),
32
+ {
33
+ status: 500,
34
+ headers: { 'Content-Type': 'application/json' },
35
+ }
36
+ ) as unknown as WorkersResponse;
37
+ }
38
+
39
+ await seed(env.DB, env.PDS_DID ?? 'did:example:single-user');
40
+
41
+ const url = new URL(request.url);
42
+ if (url.pathname === '/xrpc/com.atproto.sync.subscribeRepos') {
43
+ const upgrade = request.headers.get('upgrade');
44
+ if (upgrade !== 'websocket') {
45
+ return new Response('Expected websocket', { status: 426 }) as unknown as WorkersResponse;
46
+ }
47
+ if (!env.SEQUENCER) {
48
+ return new Response('Sequencer not configured', { status: 503 }) as unknown as WorkersResponse;
49
+ }
50
+
51
+ const id = env.SEQUENCER.idFromName('default');
52
+ const stub = env.SEQUENCER.get(id);
53
+ return (await stub.fetch(request as any)) as unknown as WorkersResponse;
54
+ }
55
+
56
+ const locals: any = { runtime: { env, ctx, request } };
57
+ return (await onRequest(locals as any, async () => await handle(locals as any))) as unknown as WorkersResponse;
58
+ };
59
+ }
60
+
61
+ export { onRequest };
62
+ export { seed };
63
+ export { validateConfigOrThrow };
@@ -0,0 +1,5 @@
1
+ export { createPdsFetchHandler, type PdsFetchHandler } from '../src/worker/runtime';
2
+ export { Sequencer } from '../src/worker/sequencer';
3
+ export { onRequest } from '../src/middleware';
4
+ export { seed } from '../src/db/seed';
5
+ export { validateConfigOrThrow } from '../src/lib/config';
@@ -1,57 +0,0 @@
1
- ---
2
- import { Icon } from 'astro-icon/components';
3
- ---
4
-
5
- <html lang="en">
6
- <head>
7
- <meta charset="utf-8" />
8
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
9
- <meta name="viewport" content="width=device-width" />
10
- <meta name="generator" content={Astro.generator} />
11
- <title>Alteran PDS</title>
12
- <style>
13
- body {
14
- font-family: sans-serif;
15
- display: flex;
16
- justify-content: center;
17
- align-items: center;
18
- height: 100vh;
19
- background-color: #f0f2f5;
20
- }
21
- .card {
22
- background-color: white;
23
- padding: 2rem;
24
- border-radius: 0.5rem;
25
- box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1);
26
- text-align: center;
27
- }
28
- h1 {
29
- margin-top: 0;
30
- }
31
- .did, .handle {
32
- font-family: monospace;
33
- background-color: #eee;
34
- padding: 0.25rem 0.5rem;
35
- border-radius: 0.25rem;
36
- }
37
- </style>
38
- </head>
39
- <body>
40
- <div class="card">
41
- <h1>Alteran</h1>
42
- <p>This is a single-user ATProto Personal Data Server running on Cloudflare.</p>
43
- <p>
44
- <strong>Handle:</strong> <span class="handle">{import.meta.env.PDS_HANDLE}</span>
45
- </p>
46
- <p>
47
- <strong>DID:</strong> <span class="did">{import.meta.env.PDS_DID}</span>
48
- </p>
49
- <p>
50
- <a href="https://github.com/alteran-dev/alteran" target="_blank" rel="noopener noreferrer">
51
- <Icon name="simple-icons:github" />
52
- Source Code
53
- </a>
54
- </p>
55
- </div>
56
- </body>
57
- </html>
@@ -1,2 +0,0 @@
1
- export { GET } from '../handlers/root';
2
-