@alteran/astro 0.1.14 → 0.3.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.
Files changed (62) hide show
  1. package/README.md +25 -0
  2. package/index.js +2 -4
  3. package/migrations/0006_adorable_spectrum.sql +11 -0
  4. package/migrations/meta/0006_snapshot.json +429 -0
  5. package/migrations/meta/_journal.json +7 -0
  6. package/package.json +6 -3
  7. package/src/db/account.ts +145 -0
  8. package/src/db/dal.ts +27 -9
  9. package/src/db/repo.ts +9 -8
  10. package/src/db/schema.ts +29 -11
  11. package/src/lib/actor.ts +133 -0
  12. package/src/lib/appview.ts +508 -0
  13. package/src/lib/auth.ts +22 -2
  14. package/src/lib/blob-refs.ts +9 -13
  15. package/src/lib/chat.ts +238 -0
  16. package/src/lib/config.ts +15 -7
  17. package/src/lib/feed.ts +165 -0
  18. package/src/lib/jwt.ts +135 -44
  19. package/src/lib/labeler.ts +91 -0
  20. package/src/lib/mst/blockstore.ts +98 -14
  21. package/src/lib/password.ts +40 -0
  22. package/src/lib/preferences.ts +73 -0
  23. package/src/lib/relay.ts +101 -0
  24. package/src/lib/secrets.ts +3 -0
  25. package/src/lib/session-tokens.ts +202 -0
  26. package/src/lib/token-cleanup.ts +3 -12
  27. package/src/lib/util.ts +17 -2
  28. package/src/middleware.ts +20 -21
  29. package/src/pages/.well-known/did.json.ts +45 -32
  30. package/src/pages/xrpc/app.bsky.actor.getPreferences.ts +23 -0
  31. package/src/pages/xrpc/app.bsky.actor.getProfile.ts +34 -0
  32. package/src/pages/xrpc/app.bsky.actor.getProfiles.ts +42 -0
  33. package/src/pages/xrpc/app.bsky.actor.putPreferences.ts +36 -0
  34. package/src/pages/xrpc/app.bsky.feed.getAuthorFeed.ts +42 -0
  35. package/src/pages/xrpc/app.bsky.feed.getPostThread.ts +37 -0
  36. package/src/pages/xrpc/app.bsky.feed.getPosts.ts +26 -0
  37. package/src/pages/xrpc/app.bsky.feed.getTimeline.ts +35 -0
  38. package/src/pages/xrpc/app.bsky.graph.getFollowers.ts +29 -0
  39. package/src/pages/xrpc/app.bsky.graph.getFollows.ts +29 -0
  40. package/src/pages/xrpc/app.bsky.labeler.getServices.ts +29 -0
  41. package/src/pages/xrpc/app.bsky.notification.getUnreadCount.ts +20 -0
  42. package/src/pages/xrpc/app.bsky.notification.listNotifications.ts +27 -0
  43. package/src/pages/xrpc/app.bsky.unspecced.getAgeAssuranceState.ts +19 -0
  44. package/src/pages/xrpc/app.bsky.unspecced.getConfig.ts +15 -0
  45. package/src/pages/xrpc/chat.bsky.convo.getLog.ts +26 -0
  46. package/src/pages/xrpc/chat.bsky.convo.listConvos.ts +37 -0
  47. package/src/pages/xrpc/com.atproto.identity.getRecommendedDidCredentials.ts +64 -66
  48. package/src/pages/xrpc/com.atproto.identity.requestPlcOperationSignature.ts +24 -0
  49. package/src/pages/xrpc/com.atproto.identity.signPlcOperation.ts +127 -0
  50. package/src/pages/xrpc/com.atproto.identity.submitPlcOperation.ts +91 -0
  51. package/src/pages/xrpc/com.atproto.repo.uploadBlob.ts +6 -2
  52. package/src/pages/xrpc/com.atproto.server.createSession.ts +36 -8
  53. package/src/pages/xrpc/com.atproto.server.describeServer.ts +37 -4
  54. package/src/pages/xrpc/com.atproto.server.getServiceAuth.ts +64 -0
  55. package/src/pages/xrpc/com.atproto.server.refreshSession.ts +55 -32
  56. package/src/services/repo-manager.ts +15 -6
  57. package/src/worker/runtime.ts +9 -0
  58. package/types/env.d.ts +9 -0
  59. package/src/pages/xrpc/com.atproto.repo.importRepo.ts +0 -142
  60. package/src/pages/xrpc/com.atproto.server.activateAccount.ts +0 -53
  61. package/src/pages/xrpc/com.atproto.server.createAccount.ts +0 -99
  62. package/src/pages/xrpc/com.atproto.server.deactivateAccount.ts +0 -53
package/README.md CHANGED
@@ -414,6 +414,31 @@ npm install -g wscat
414
414
  wscat -c "ws://localhost:4321/xrpc/com.atproto.sync.subscribeRepos"
415
415
  ```
416
416
 
417
+ ### Publish to a Relay (Bluesky)
418
+
419
+ Relays discover PDSes via `com.atproto.sync.requestCrawl`. Your deployment will automatically notify relays on the first request it handles (and at most every 12 hours per isolate).
420
+
421
+ - Set your public hostname (bare domain, no protocol):
422
+ ```
423
+ PDS_HOSTNAME=your-pds.example.com
424
+ ```
425
+
426
+ - Optional: choose relays to notify (CSV of hostnames). Defaults to `bsky.network`.
427
+ ```
428
+ PDS_RELAY_HOSTS=bsky.network
429
+ ```
430
+
431
+ - To trigger manually from your machine:
432
+ ```bash
433
+ curl -X POST "https://bsky.network/xrpc/com.atproto.sync.requestCrawl" \
434
+ -H "Content-Type: application/json" \
435
+ -d '{"hostname":"your-pds.example.com"}'
436
+ ```
437
+
438
+ Notes:
439
+ - Use only the hostname in `hostname` (no `https://`).
440
+ - Ensure your PDS is publicly reachable over HTTPS/WSS and that DID documents resolve to this hostname.
441
+
417
442
  ### Test XRPC Endpoints
418
443
  ```bash
419
444
  # Get session
package/index.js CHANGED
@@ -8,23 +8,21 @@ const CORE_ROUTES = [
8
8
  { pattern: '/health', entrypoint: './src/pages/health.ts' },
9
9
  { pattern: '/ready', entrypoint: './src/pages/ready.ts' },
10
10
  { pattern: '/xrpc/com.atproto.identity.getRecommendedDidCredentials', entrypoint: './src/pages/xrpc/com.atproto.identity.getRecommendedDidCredentials.ts' },
11
+ { pattern: '/xrpc/com.atproto.identity.requestPlcOperationSignature', entrypoint: './src/pages/xrpc/com.atproto.identity.requestPlcOperationSignature.ts' },
11
12
  { pattern: '/xrpc/com.atproto.identity.resolveHandle', entrypoint: './src/pages/xrpc/com.atproto.identity.resolveHandle.ts' },
13
+ { pattern: '/xrpc/com.atproto.identity.submitPlcOperation', entrypoint: './src/pages/xrpc/com.atproto.identity.submitPlcOperation.ts' },
12
14
  { pattern: '/xrpc/com.atproto.identity.updateHandle', entrypoint: './src/pages/xrpc/com.atproto.identity.updateHandle.ts' },
13
15
  { pattern: '/xrpc/com.atproto.repo.applyWrites', entrypoint: './src/pages/xrpc/com.atproto.repo.applyWrites.ts' },
14
16
  { pattern: '/xrpc/com.atproto.repo.createRecord', entrypoint: './src/pages/xrpc/com.atproto.repo.createRecord.ts' },
15
17
  { pattern: '/xrpc/com.atproto.repo.deleteRecord', entrypoint: './src/pages/xrpc/com.atproto.repo.deleteRecord.ts' },
16
18
  { pattern: '/xrpc/com.atproto.repo.describeRepo', entrypoint: './src/pages/xrpc/com.atproto.repo.describeRepo.ts' },
17
19
  { pattern: '/xrpc/com.atproto.repo.getRecord', entrypoint: './src/pages/xrpc/com.atproto.repo.getRecord.ts' },
18
- { pattern: '/xrpc/com.atproto.repo.importRepo', entrypoint: './src/pages/xrpc/com.atproto.repo.importRepo.ts' },
19
20
  { pattern: '/xrpc/com.atproto.repo.listMissingBlobs', entrypoint: './src/pages/xrpc/com.atproto.repo.listMissingBlobs.ts' },
20
21
  { pattern: '/xrpc/com.atproto.repo.listRecords', entrypoint: './src/pages/xrpc/com.atproto.repo.listRecords.ts' },
21
22
  { pattern: '/xrpc/com.atproto.repo.putRecord', entrypoint: './src/pages/xrpc/com.atproto.repo.putRecord.ts' },
22
23
  { pattern: '/xrpc/com.atproto.repo.uploadBlob', entrypoint: './src/pages/xrpc/com.atproto.repo.uploadBlob.ts' },
23
- { pattern: '/xrpc/com.atproto.server.activateAccount', entrypoint: './src/pages/xrpc/com.atproto.server.activateAccount.ts' },
24
24
  { pattern: '/xrpc/com.atproto.server.checkAccountStatus', entrypoint: './src/pages/xrpc/com.atproto.server.checkAccountStatus.ts' },
25
- { pattern: '/xrpc/com.atproto.server.createAccount', entrypoint: './src/pages/xrpc/com.atproto.server.createAccount.ts' },
26
25
  { pattern: '/xrpc/com.atproto.server.createSession', entrypoint: './src/pages/xrpc/com.atproto.server.createSession.ts' },
27
- { pattern: '/xrpc/com.atproto.server.deactivateAccount', entrypoint: './src/pages/xrpc/com.atproto.server.deactivateAccount.ts' },
28
26
  { pattern: '/xrpc/com.atproto.server.deleteSession', entrypoint: './src/pages/xrpc/com.atproto.server.deleteSession.ts' },
29
27
  { pattern: '/xrpc/com.atproto.server.describeServer', entrypoint: './src/pages/xrpc/com.atproto.server.describeServer.ts' },
30
28
  { pattern: '/xrpc/com.atproto.server.getSession', entrypoint: './src/pages/xrpc/com.atproto.server.getSession.ts' },
@@ -0,0 +1,11 @@
1
+ PRAGMA foreign_keys=OFF;--> statement-breakpoint
2
+ CREATE TABLE `__new_repo_root` (
3
+ `did` text PRIMARY KEY NOT NULL,
4
+ `commit_cid` text NOT NULL,
5
+ `rev` text NOT NULL
6
+ );
7
+ --> statement-breakpoint
8
+ INSERT INTO `__new_repo_root`("did", "commit_cid", "rev") SELECT "did", "commit_cid", "rev" FROM `repo_root`;--> statement-breakpoint
9
+ DROP TABLE `repo_root`;--> statement-breakpoint
10
+ ALTER TABLE `__new_repo_root` RENAME TO `repo_root`;--> statement-breakpoint
11
+ PRAGMA foreign_keys=ON;
@@ -0,0 +1,429 @@
1
+ {
2
+ "version": "6",
3
+ "dialect": "sqlite",
4
+ "id": "08df0598-155a-48d1-ae5f-dd88eed67f51",
5
+ "prevId": "cfdb4a96-9b33-41d2-9733-a44d61806a55",
6
+ "tables": {
7
+ "account_state": {
8
+ "name": "account_state",
9
+ "columns": {
10
+ "did": {
11
+ "name": "did",
12
+ "type": "text",
13
+ "primaryKey": true,
14
+ "notNull": true,
15
+ "autoincrement": false
16
+ },
17
+ "active": {
18
+ "name": "active",
19
+ "type": "integer",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "autoincrement": false,
23
+ "default": false
24
+ },
25
+ "created_at": {
26
+ "name": "created_at",
27
+ "type": "integer",
28
+ "primaryKey": false,
29
+ "notNull": true,
30
+ "autoincrement": false
31
+ }
32
+ },
33
+ "indexes": {},
34
+ "foreignKeys": {},
35
+ "compositePrimaryKeys": {},
36
+ "uniqueConstraints": {},
37
+ "checkConstraints": {}
38
+ },
39
+ "blob_quota": {
40
+ "name": "blob_quota",
41
+ "columns": {
42
+ "did": {
43
+ "name": "did",
44
+ "type": "text",
45
+ "primaryKey": true,
46
+ "notNull": true,
47
+ "autoincrement": false
48
+ },
49
+ "total_bytes": {
50
+ "name": "total_bytes",
51
+ "type": "integer",
52
+ "primaryKey": false,
53
+ "notNull": true,
54
+ "autoincrement": false,
55
+ "default": 0
56
+ },
57
+ "blob_count": {
58
+ "name": "blob_count",
59
+ "type": "integer",
60
+ "primaryKey": false,
61
+ "notNull": true,
62
+ "autoincrement": false,
63
+ "default": 0
64
+ },
65
+ "updated_at": {
66
+ "name": "updated_at",
67
+ "type": "integer",
68
+ "primaryKey": false,
69
+ "notNull": true,
70
+ "autoincrement": false
71
+ }
72
+ },
73
+ "indexes": {},
74
+ "foreignKeys": {},
75
+ "compositePrimaryKeys": {},
76
+ "uniqueConstraints": {},
77
+ "checkConstraints": {}
78
+ },
79
+ "blob": {
80
+ "name": "blob",
81
+ "columns": {
82
+ "cid": {
83
+ "name": "cid",
84
+ "type": "text",
85
+ "primaryKey": true,
86
+ "notNull": true,
87
+ "autoincrement": false
88
+ },
89
+ "did": {
90
+ "name": "did",
91
+ "type": "text",
92
+ "primaryKey": false,
93
+ "notNull": true,
94
+ "autoincrement": false
95
+ },
96
+ "key": {
97
+ "name": "key",
98
+ "type": "text",
99
+ "primaryKey": false,
100
+ "notNull": true,
101
+ "autoincrement": false
102
+ },
103
+ "mime": {
104
+ "name": "mime",
105
+ "type": "text",
106
+ "primaryKey": false,
107
+ "notNull": true,
108
+ "autoincrement": false
109
+ },
110
+ "size": {
111
+ "name": "size",
112
+ "type": "integer",
113
+ "primaryKey": false,
114
+ "notNull": true,
115
+ "autoincrement": false
116
+ }
117
+ },
118
+ "indexes": {},
119
+ "foreignKeys": {},
120
+ "compositePrimaryKeys": {},
121
+ "uniqueConstraints": {},
122
+ "checkConstraints": {}
123
+ },
124
+ "blob_usage": {
125
+ "name": "blob_usage",
126
+ "columns": {
127
+ "record_uri": {
128
+ "name": "record_uri",
129
+ "type": "text",
130
+ "primaryKey": false,
131
+ "notNull": true,
132
+ "autoincrement": false
133
+ },
134
+ "key": {
135
+ "name": "key",
136
+ "type": "text",
137
+ "primaryKey": false,
138
+ "notNull": true,
139
+ "autoincrement": false
140
+ }
141
+ },
142
+ "indexes": {
143
+ "blob_usage_record_uri_idx": {
144
+ "name": "blob_usage_record_uri_idx",
145
+ "columns": [
146
+ "record_uri"
147
+ ],
148
+ "isUnique": false
149
+ }
150
+ },
151
+ "foreignKeys": {},
152
+ "compositePrimaryKeys": {
153
+ "blob_usage_record_uri_key_pk": {
154
+ "columns": [
155
+ "record_uri",
156
+ "key"
157
+ ],
158
+ "name": "blob_usage_record_uri_key_pk"
159
+ }
160
+ },
161
+ "uniqueConstraints": {},
162
+ "checkConstraints": {}
163
+ },
164
+ "blockstore": {
165
+ "name": "blockstore",
166
+ "columns": {
167
+ "cid": {
168
+ "name": "cid",
169
+ "type": "text",
170
+ "primaryKey": true,
171
+ "notNull": true,
172
+ "autoincrement": false
173
+ },
174
+ "bytes": {
175
+ "name": "bytes",
176
+ "type": "text",
177
+ "primaryKey": false,
178
+ "notNull": false,
179
+ "autoincrement": false
180
+ }
181
+ },
182
+ "indexes": {},
183
+ "foreignKeys": {},
184
+ "compositePrimaryKeys": {},
185
+ "uniqueConstraints": {},
186
+ "checkConstraints": {}
187
+ },
188
+ "commit_log": {
189
+ "name": "commit_log",
190
+ "columns": {
191
+ "seq": {
192
+ "name": "seq",
193
+ "type": "integer",
194
+ "primaryKey": true,
195
+ "notNull": true,
196
+ "autoincrement": false
197
+ },
198
+ "cid": {
199
+ "name": "cid",
200
+ "type": "text",
201
+ "primaryKey": false,
202
+ "notNull": true,
203
+ "autoincrement": false
204
+ },
205
+ "rev": {
206
+ "name": "rev",
207
+ "type": "text",
208
+ "primaryKey": false,
209
+ "notNull": true,
210
+ "autoincrement": false
211
+ },
212
+ "data": {
213
+ "name": "data",
214
+ "type": "text",
215
+ "primaryKey": false,
216
+ "notNull": true,
217
+ "autoincrement": false
218
+ },
219
+ "sig": {
220
+ "name": "sig",
221
+ "type": "text",
222
+ "primaryKey": false,
223
+ "notNull": true,
224
+ "autoincrement": false
225
+ },
226
+ "ts": {
227
+ "name": "ts",
228
+ "type": "integer",
229
+ "primaryKey": false,
230
+ "notNull": true,
231
+ "autoincrement": false
232
+ }
233
+ },
234
+ "indexes": {
235
+ "commit_log_seq_idx": {
236
+ "name": "commit_log_seq_idx",
237
+ "columns": [
238
+ "seq"
239
+ ],
240
+ "isUnique": false
241
+ }
242
+ },
243
+ "foreignKeys": {},
244
+ "compositePrimaryKeys": {},
245
+ "uniqueConstraints": {},
246
+ "checkConstraints": {}
247
+ },
248
+ "login_attempts": {
249
+ "name": "login_attempts",
250
+ "columns": {
251
+ "ip": {
252
+ "name": "ip",
253
+ "type": "text",
254
+ "primaryKey": true,
255
+ "notNull": true,
256
+ "autoincrement": false
257
+ },
258
+ "attempts": {
259
+ "name": "attempts",
260
+ "type": "integer",
261
+ "primaryKey": false,
262
+ "notNull": true,
263
+ "autoincrement": false,
264
+ "default": 0
265
+ },
266
+ "locked_until": {
267
+ "name": "locked_until",
268
+ "type": "integer",
269
+ "primaryKey": false,
270
+ "notNull": false,
271
+ "autoincrement": false
272
+ },
273
+ "last_attempt": {
274
+ "name": "last_attempt",
275
+ "type": "integer",
276
+ "primaryKey": false,
277
+ "notNull": true,
278
+ "autoincrement": false
279
+ }
280
+ },
281
+ "indexes": {},
282
+ "foreignKeys": {},
283
+ "compositePrimaryKeys": {},
284
+ "uniqueConstraints": {},
285
+ "checkConstraints": {}
286
+ },
287
+ "record": {
288
+ "name": "record",
289
+ "columns": {
290
+ "uri": {
291
+ "name": "uri",
292
+ "type": "text",
293
+ "primaryKey": true,
294
+ "notNull": true,
295
+ "autoincrement": false
296
+ },
297
+ "did": {
298
+ "name": "did",
299
+ "type": "text",
300
+ "primaryKey": false,
301
+ "notNull": true,
302
+ "autoincrement": false
303
+ },
304
+ "cid": {
305
+ "name": "cid",
306
+ "type": "text",
307
+ "primaryKey": false,
308
+ "notNull": true,
309
+ "autoincrement": false
310
+ },
311
+ "json": {
312
+ "name": "json",
313
+ "type": "text",
314
+ "primaryKey": false,
315
+ "notNull": true,
316
+ "autoincrement": false
317
+ },
318
+ "created_at": {
319
+ "name": "created_at",
320
+ "type": "integer",
321
+ "primaryKey": false,
322
+ "notNull": false,
323
+ "autoincrement": false,
324
+ "default": 0
325
+ }
326
+ },
327
+ "indexes": {
328
+ "record_did_idx": {
329
+ "name": "record_did_idx",
330
+ "columns": [
331
+ "did"
332
+ ],
333
+ "isUnique": false
334
+ },
335
+ "record_cid_idx": {
336
+ "name": "record_cid_idx",
337
+ "columns": [
338
+ "cid"
339
+ ],
340
+ "isUnique": false
341
+ }
342
+ },
343
+ "foreignKeys": {},
344
+ "compositePrimaryKeys": {},
345
+ "uniqueConstraints": {},
346
+ "checkConstraints": {}
347
+ },
348
+ "repo_root": {
349
+ "name": "repo_root",
350
+ "columns": {
351
+ "did": {
352
+ "name": "did",
353
+ "type": "text",
354
+ "primaryKey": true,
355
+ "notNull": true,
356
+ "autoincrement": false
357
+ },
358
+ "commit_cid": {
359
+ "name": "commit_cid",
360
+ "type": "text",
361
+ "primaryKey": false,
362
+ "notNull": true,
363
+ "autoincrement": false
364
+ },
365
+ "rev": {
366
+ "name": "rev",
367
+ "type": "text",
368
+ "primaryKey": false,
369
+ "notNull": true,
370
+ "autoincrement": false
371
+ }
372
+ },
373
+ "indexes": {},
374
+ "foreignKeys": {},
375
+ "compositePrimaryKeys": {},
376
+ "uniqueConstraints": {},
377
+ "checkConstraints": {}
378
+ },
379
+ "token_revocation": {
380
+ "name": "token_revocation",
381
+ "columns": {
382
+ "jti": {
383
+ "name": "jti",
384
+ "type": "text",
385
+ "primaryKey": true,
386
+ "notNull": true,
387
+ "autoincrement": false
388
+ },
389
+ "exp": {
390
+ "name": "exp",
391
+ "type": "integer",
392
+ "primaryKey": false,
393
+ "notNull": true,
394
+ "autoincrement": false
395
+ },
396
+ "revoked_at": {
397
+ "name": "revoked_at",
398
+ "type": "integer",
399
+ "primaryKey": false,
400
+ "notNull": true,
401
+ "autoincrement": false
402
+ }
403
+ },
404
+ "indexes": {
405
+ "token_revocation_exp_idx": {
406
+ "name": "token_revocation_exp_idx",
407
+ "columns": [
408
+ "exp"
409
+ ],
410
+ "isUnique": false
411
+ }
412
+ },
413
+ "foreignKeys": {},
414
+ "compositePrimaryKeys": {},
415
+ "uniqueConstraints": {},
416
+ "checkConstraints": {}
417
+ }
418
+ },
419
+ "views": {},
420
+ "enums": {},
421
+ "_meta": {
422
+ "schemas": {},
423
+ "tables": {},
424
+ "columns": {}
425
+ },
426
+ "internal": {
427
+ "indexes": {}
428
+ }
429
+ }
@@ -43,6 +43,13 @@
43
43
  "when": 1759515076154,
44
44
  "tag": "0005_odd_bishop",
45
45
  "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 6,
49
+ "version": "6",
50
+ "when": 1759532836051,
51
+ "tag": "0006_adorable_spectrum",
52
+ "breakpoints": true
46
53
  }
47
54
  ]
48
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alteran/astro",
3
- "version": "0.1.14",
3
+ "version": "0.3.0",
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",
@@ -38,7 +38,8 @@
38
38
  "db:apply:local": "bunx wrangler d1 migrations apply pds --local",
39
39
  "db:apply:local:direct": "wrangler d1 migrations apply pds --local",
40
40
  "db:reset:local": "rm -rf .wrangler/state && rm -rf drizzle && bun run db:generate && bun run db:apply:local",
41
- "secrets:setup": "bun run scripts/setup-secrets.ts"
41
+ "secrets:setup": "bun run scripts/setup-secrets.ts",
42
+ "relay:request-crawl": "bun run scripts/request-crawl.ts"
42
43
  },
43
44
  "devDependencies": {
44
45
  "@astrojs/cloudflare": "^12.6.9",
@@ -54,6 +55,9 @@
54
55
  "typescript": "^5"
55
56
  },
56
57
  "dependencies": {
58
+ "@atproto/crypto": "^0.4.4",
59
+ "@cloudflare/workers-types": "^4.20251001.0",
60
+ "@did-plc/lib": "^0.0.4",
57
61
  "@iconify-json/simple-icons": "^1.2.53",
58
62
  "@ipld/car": "^5.4.2",
59
63
  "@ipld/dag-cbor": "^9.2.5",
@@ -62,7 +66,6 @@
62
66
  "astro-icon": "^1.1.5",
63
67
  "dotenv": "^17.2.3",
64
68
  "drizzle-orm": "^0.44.6",
65
- "@cloudflare/workers-types": "^4.20251001.0",
66
69
  "get-port": "^7.1.0",
67
70
  "hono": "^4.9.9",
68
71
  "multiformats": "^13.4.1",
@@ -0,0 +1,145 @@
1
+ import { eq, or, lt } from 'drizzle-orm';
2
+ import { getDb } from './client';
3
+ import { account, refresh_token_store, secret } from './schema';
4
+ import type { Env } from '../env';
5
+ import { normalizeHandle } from '../lib/handle';
6
+
7
+ const NOW = () => Math.floor(Date.now());
8
+
9
+ export type AccountRow = typeof account.$inferSelect;
10
+ export type RefreshTokenRow = typeof refresh_token_store.$inferSelect;
11
+
12
+ function normalizeIdentifier(identifier: string): { did: string | null; handle: string | null } {
13
+ if (!identifier) return { did: null, handle: null };
14
+ const trimmed = identifier.trim();
15
+ if (trimmed.startsWith('did:')) {
16
+ return { did: trimmed, handle: null };
17
+ }
18
+ return { did: null, handle: normalizeHandle(trimmed) };
19
+ }
20
+
21
+ export async function getAccountByIdentifier(env: Env, identifier: string): Promise<AccountRow | null> {
22
+ const db = getDb(env);
23
+ const ident = normalizeIdentifier(identifier);
24
+ const clauses = [] as any[];
25
+ if (ident.did) clauses.push(eq(account.did, ident.did));
26
+ if (ident.handle) clauses.push(eq(account.handle, ident.handle));
27
+ if (clauses.length === 0) return null;
28
+ const where = clauses.length === 1 ? clauses[0] : or(...clauses);
29
+ return db.select().from(account).where(where).get();
30
+ }
31
+
32
+ export async function createAccount(env: Env, data: {
33
+ did: string;
34
+ handle: string;
35
+ passwordScrypt: string | null;
36
+ email?: string | null;
37
+ }): Promise<void> {
38
+ const db = getDb(env);
39
+ const now = NOW();
40
+ await db
41
+ .insert(account)
42
+ .values({
43
+ did: data.did,
44
+ handle: normalizeHandle(data.handle),
45
+ passwordScrypt: data.passwordScrypt ?? null,
46
+ email: data.email ?? null,
47
+ createdAt: now,
48
+ updatedAt: now,
49
+ })
50
+ .onConflictDoUpdate({
51
+ target: account.did,
52
+ set: {
53
+ handle: normalizeHandle(data.handle),
54
+ passwordScrypt: data.passwordScrypt ?? null,
55
+ email: data.email ?? null,
56
+ updatedAt: now,
57
+ },
58
+ });
59
+ }
60
+
61
+ export async function updateAccountPassword(env: Env, did: string, passwordScrypt: string): Promise<void> {
62
+ const db = getDb(env);
63
+ await db
64
+ .update(account)
65
+ .set({ passwordScrypt, updatedAt: NOW() })
66
+ .where(eq(account.did, did))
67
+ .run();
68
+ }
69
+
70
+ export async function storeRefreshToken(env: Env, data: {
71
+ id: string;
72
+ did: string;
73
+ expiresAt: number; // epoch seconds
74
+ appPasswordName?: string | null;
75
+ }): Promise<void> {
76
+ const db = getDb(env);
77
+ await db
78
+ .insert(refresh_token_store)
79
+ .values({
80
+ id: data.id,
81
+ did: data.did,
82
+ expiresAt: data.expiresAt,
83
+ appPasswordName: data.appPasswordName ?? null,
84
+ nextId: null,
85
+ })
86
+ .onConflictDoUpdate({
87
+ target: refresh_token_store.id,
88
+ set: {
89
+ did: data.did,
90
+ expiresAt: data.expiresAt,
91
+ appPasswordName: data.appPasswordName ?? null,
92
+ nextId: null,
93
+ },
94
+ });
95
+ }
96
+
97
+ export async function markRefreshTokenRotated(env: Env, id: string, nextId: string, graceExpiresAt: number): Promise<void> {
98
+ const db = getDb(env);
99
+ await db
100
+ .update(refresh_token_store)
101
+ .set({ nextId, expiresAt: graceExpiresAt })
102
+ .where(eq(refresh_token_store.id, id))
103
+ .run();
104
+ }
105
+
106
+ export async function getRefreshToken(env: Env, id: string): Promise<RefreshTokenRow | null> {
107
+ const db = getDb(env);
108
+ return db.select().from(refresh_token_store).where(eq(refresh_token_store.id, id)).get();
109
+ }
110
+
111
+ export async function deleteRefreshToken(env: Env, id: string): Promise<void> {
112
+ const db = getDb(env);
113
+ await db.delete(refresh_token_store).where(eq(refresh_token_store.id, id)).run();
114
+ }
115
+
116
+ export async function cleanupExpiredRefreshTokens(env: Env, now: number): Promise<number> {
117
+ const db = getDb(env);
118
+ const res = await db.delete(refresh_token_store).where(lt(refresh_token_store.expiresAt, now)).run();
119
+ return res.meta.changes ?? 0;
120
+ }
121
+
122
+ export async function getSecret(env: Env, key: string): Promise<string | null> {
123
+ const db = getDb(env);
124
+ const row = await db.select().from(secret).where(eq(secret.key, key)).get();
125
+ return row?.value ?? null;
126
+ }
127
+
128
+ export async function setSecret(env: Env, key: string, value: string): Promise<void> {
129
+ const db = getDb(env);
130
+ await db
131
+ .insert(secret)
132
+ .values({ key, value, updatedAt: NOW() })
133
+ .onConflictDoUpdate({
134
+ target: secret.key,
135
+ set: { value, updatedAt: NOW() },
136
+ });
137
+ }
138
+
139
+ export async function getOrCreateSecret(env: Env, key: string, factory: () => Promise<string>): Promise<string> {
140
+ const existing = await getSecret(env, key);
141
+ if (existing) return existing;
142
+ const value = await factory();
143
+ await setSecret(env, key, value);
144
+ return value;
145
+ }