work-in-progress atproto PDS
typescript
atproto
pds
atcute
1import { TestBsky } from './bsky.ts';
2import { BSKY_PORT, PDS_PORT, PLC_PORT } from './const.ts';
3import { TestPlc } from './plc.ts';
4
5// polyfill for Node.js 22
6if (typeof AsyncDisposableStack === 'undefined') {
7 globalThis.AsyncDisposableStack = class ADS implements AsyncDisposableStack {
8 #disposed = false;
9 #stack: (() => Promise<void>)[] = [];
10
11 readonly [Symbol.toStringTag] = 'AsyncDisposableStack';
12
13 get disposed() {
14 return this.#disposed;
15 }
16
17 use<T extends AsyncDisposable | Disposable | null | undefined>(value: T): T {
18 if (this.#disposed) {
19 throw new ReferenceError('AsyncDisposableStack already disposed');
20 }
21 if (value != null) {
22 if (Symbol.asyncDispose in (value as object)) {
23 this.#stack.push(async () => {
24 await (value as AsyncDisposable)[Symbol.asyncDispose]();
25 });
26 } else if (Symbol.dispose in (value as object)) {
27 this.#stack.push(async () => (value as Disposable)[Symbol.dispose]());
28 }
29 }
30 return value;
31 }
32
33 adopt<T>(value: T, onDisposeAsync: (value: T) => PromiseLike<void> | void): T {
34 if (this.#disposed) {
35 throw new ReferenceError('AsyncDisposableStack already disposed');
36 }
37 this.#stack.push(async () => {
38 await onDisposeAsync(value);
39 });
40 return value;
41 }
42
43 defer(onDisposeAsync: () => PromiseLike<void> | void): void {
44 if (this.#disposed) {
45 throw new ReferenceError('AsyncDisposableStack already disposed');
46 }
47 this.#stack.push(async () => {
48 await onDisposeAsync();
49 });
50 }
51
52 move(): AsyncDisposableStack {
53 if (this.#disposed) {
54 throw new ReferenceError('AsyncDisposableStack already disposed');
55 }
56 const cloned = new ADS();
57 cloned.#stack = this.#stack;
58 this.#stack = [];
59 this.#disposed = true;
60 return cloned;
61 }
62
63 async disposeAsync(): Promise<void> {
64 if (this.#disposed) {
65 return;
66 }
67 this.#disposed = true;
68 let error: unknown;
69 while (this.#stack.length > 0) {
70 const dispose = this.#stack.pop()!;
71 try {
72 await dispose();
73 } catch (e) {
74 error ??= e;
75 }
76 }
77 if (error) {
78 throw error;
79 }
80 }
81
82 [Symbol.asyncDispose]() {
83 return this.disposeAsync();
84 }
85 };
86}
87
88const run = async () => {
89 console.log(`
90┌──────────────────────────────────┐
91│ danaus dev-env (PLC + bsky) │
92└──────────────────────────────────┘
93`);
94
95 const dbPostgresUrl = process.env.DB_POSTGRES_URL;
96 if (!dbPostgresUrl) {
97 throw new Error('DB_POSTGRES_URL is required');
98 }
99
100 await using stack = new AsyncDisposableStack();
101
102 const plc = await TestPlc.create({
103 port: PLC_PORT,
104 dbPostgresUrl: dbPostgresUrl,
105 dbPostgresSchema: 'plc',
106 });
107 stack.use(plc);
108
109 const bsky = await TestBsky.create({
110 plcUrl: plc.url,
111 repoProvider: `ws://localhost:${PDS_PORT}`,
112 pdsPort: PDS_PORT,
113 port: BSKY_PORT,
114 dbPostgresUrl: dbPostgresUrl,
115 dbPostgresSchema: 'bsky',
116 redisHost: process.env.REDIS_HOST ?? 'localhost',
117 privateKey: '3f916c70dc69e4c5e83877f013325b11ecac31742e6a42f5c4fb240d0703d9d5',
118 });
119 stack.use(bsky);
120
121 console.log(`👤 PLC server http://localhost:${plc.port}`);
122 console.log(`🌅 AppView http://localhost:${bsky.port}`);
123 console.log(`🌅 AppView DID ${bsky.serverDid}`);
124
125 const shutdown = async () => {
126 console.log('\nshutting down...');
127 await stack.disposeAsync();
128 process.exit(0);
129 };
130
131 process.on('SIGINT', shutdown);
132 process.on('SIGTERM', shutdown);
133
134 // keep process alive
135 await new Promise(() => {});
136};
137
138run().catch((err) => {
139 console.error('fatal error:', err);
140 process.exit(1);
141});