@hatk/hatk 0.0.1-alpha.40 → 0.0.1-alpha.42

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 (89) hide show
  1. package/dist/cli.js +16 -553
  2. package/dist/database/adapters/sqlite.d.ts.map +1 -1
  3. package/dist/database/adapters/sqlite.js +2 -1
  4. package/dist/database/db.d.ts +23 -0
  5. package/dist/database/db.d.ts.map +1 -1
  6. package/dist/database/db.js +81 -4
  7. package/dist/dev-entry.d.ts.map +1 -1
  8. package/dist/dev-entry.js +2 -1
  9. package/dist/hooks.d.ts +24 -3
  10. package/dist/hooks.d.ts.map +1 -1
  11. package/dist/hooks.js +34 -7
  12. package/dist/indexer.d.ts +2 -0
  13. package/dist/indexer.d.ts.map +1 -1
  14. package/dist/indexer.js +17 -0
  15. package/dist/labels.d.ts +2 -0
  16. package/dist/labels.d.ts.map +1 -1
  17. package/dist/labels.js +5 -0
  18. package/dist/lexicon-resolve.d.ts.map +1 -1
  19. package/dist/lexicon-resolve.js +27 -112
  20. package/dist/lexicons/com/atproto/label/defs.json +75 -0
  21. package/dist/lexicons/com/atproto/moderation/defs.json +30 -0
  22. package/dist/lexicons/com/atproto/repo/strongRef.json +24 -0
  23. package/dist/lexicons/dev/hatk/createRecord.json +40 -0
  24. package/dist/lexicons/dev/hatk/createReport.json +48 -0
  25. package/dist/lexicons/dev/hatk/deleteRecord.json +25 -0
  26. package/dist/lexicons/dev/hatk/describeCollections.json +41 -0
  27. package/dist/lexicons/dev/hatk/describeFeeds.json +29 -0
  28. package/dist/lexicons/dev/hatk/describeLabels.json +31 -0
  29. package/dist/lexicons/dev/hatk/getFeed.json +30 -0
  30. package/dist/lexicons/dev/hatk/getPreferences.json +19 -0
  31. package/dist/lexicons/dev/hatk/getRecord.json +26 -0
  32. package/dist/lexicons/dev/hatk/getRecords.json +32 -0
  33. package/dist/lexicons/dev/hatk/putPreference.json +28 -0
  34. package/dist/lexicons/dev/hatk/putRecord.json +41 -0
  35. package/dist/lexicons/dev/hatk/searchRecords.json +32 -0
  36. package/dist/lexicons/dev/hatk/uploadBlob.json +23 -0
  37. package/dist/main.js +2 -1
  38. package/dist/oauth/server.d.ts.map +1 -1
  39. package/dist/oauth/server.js +3 -2
  40. package/dist/pds-proxy.d.ts.map +1 -1
  41. package/dist/pds-proxy.js +15 -0
  42. package/dist/server-init.d.ts.map +1 -1
  43. package/dist/server-init.js +3 -2
  44. package/dist/server.d.ts.map +1 -1
  45. package/dist/server.js +91 -13
  46. package/dist/templates/feed.tpl +14 -0
  47. package/dist/templates/hook.tpl +5 -0
  48. package/dist/templates/label.tpl +15 -0
  49. package/dist/templates/og.tpl +17 -0
  50. package/dist/templates/seed.tpl +11 -0
  51. package/dist/templates/setup.tpl +5 -0
  52. package/dist/templates/test-feed.tpl +19 -0
  53. package/dist/templates/test-xrpc.tpl +19 -0
  54. package/dist/templates/xrpc.tpl +41 -0
  55. package/dist/xrpc.d.ts +14 -0
  56. package/dist/xrpc.d.ts.map +1 -1
  57. package/dist/xrpc.js +27 -0
  58. package/package.json +3 -2
  59. package/public/admin.html +133 -0
  60. package/dist/cloudflare/container.d.ts +0 -73
  61. package/dist/cloudflare/container.d.ts.map +0 -1
  62. package/dist/cloudflare/container.js +0 -232
  63. package/dist/cloudflare/hooks.d.ts +0 -33
  64. package/dist/cloudflare/hooks.d.ts.map +0 -1
  65. package/dist/cloudflare/hooks.js +0 -40
  66. package/dist/cloudflare/init.d.ts +0 -27
  67. package/dist/cloudflare/init.d.ts.map +0 -1
  68. package/dist/cloudflare/init.js +0 -103
  69. package/dist/cloudflare/worker.d.ts +0 -27
  70. package/dist/cloudflare/worker.d.ts.map +0 -1
  71. package/dist/cloudflare/worker.js +0 -54
  72. package/dist/database/adapters/d1.d.ts +0 -56
  73. package/dist/database/adapters/d1.d.ts.map +0 -1
  74. package/dist/database/adapters/d1.js +0 -108
  75. package/dist/db.d.ts +0 -134
  76. package/dist/db.d.ts.map +0 -1
  77. package/dist/db.js +0 -1327
  78. package/dist/fts.d.ts +0 -20
  79. package/dist/fts.d.ts.map +0 -1
  80. package/dist/fts.js +0 -767
  81. package/dist/oauth/hooks.d.ts +0 -10
  82. package/dist/oauth/hooks.d.ts.map +0 -1
  83. package/dist/oauth/hooks.js +0 -40
  84. package/dist/schema.d.ts +0 -59
  85. package/dist/schema.d.ts.map +0 -1
  86. package/dist/schema.js +0 -387
  87. package/dist/test-browser.d.ts +0 -14
  88. package/dist/test-browser.d.ts.map +0 -1
  89. package/dist/test-browser.js +0 -26
@@ -1,73 +0,0 @@
1
- /**
2
- * Cloudflare Container entry point for hatk.
3
- *
4
- * Runs as a long-lived Node.js process alongside the Worker. Handles the
5
- * firehose indexer and backfill loop — the Worker delegates resync requests
6
- * here via RPC (Cloudflare Container service bindings).
7
- *
8
- * No HTTP server — all communication is via the Container RPC interface.
9
- */
10
- export interface Env {
11
- /** Cloudflare D1 database binding */
12
- DB: D1Database;
13
- HATK_RELAY: string;
14
- HATK_PLC: string;
15
- HATK_OAUTH_ISSUER?: string;
16
- HATK_OAUTH_SCOPES?: string;
17
- HATK_ADMINS?: string;
18
- HATK_COLLECTIONS?: string;
19
- HATK_BACKFILL_PARALLELISM?: string;
20
- HATK_BACKFILL_FETCH_TIMEOUT?: string;
21
- HATK_BACKFILL_MAX_RETRIES?: string;
22
- HATK_BACKFILL_FULL_NETWORK?: string;
23
- HATK_BACKFILL_REPOS?: string;
24
- HATK_BACKFILL_SIGNAL_COLLECTIONS?: string;
25
- }
26
- interface D1Database {
27
- prepare(sql: string): any;
28
- batch<T = unknown>(statements: any[]): Promise<any[]>;
29
- exec(sql: string): Promise<any>;
30
- }
31
- /**
32
- * Resync a single DID by triggering auto-backfill.
33
- * Called by the Worker via Container service binding RPC.
34
- */
35
- declare function resync(did: string): Promise<void>;
36
- /**
37
- * Trigger a full re-enumeration backfill of all repos.
38
- * Called by the Worker via Container service binding RPC.
39
- */
40
- declare function resyncAll(): Promise<void>;
41
- /**
42
- * Return basic status info about the Container.
43
- * Called by the Worker for health checks / admin UI.
44
- */
45
- declare function getStatus(): {
46
- initialized: boolean;
47
- collections: string[];
48
- uptimeMs: number;
49
- };
50
- /**
51
- * Cloudflare Container entry point.
52
- *
53
- * Containers expose RPC methods that the Worker can call via the service binding.
54
- * The Container also handles fetch requests routed from the Worker, but for hatk
55
- * all Worker-to-Container communication uses the RPC methods above.
56
- */
57
- declare const _default: {
58
- /**
59
- * Container startup — called when the Container is first instantiated.
60
- * Initializes the database, starts the firehose, and begins backfill.
61
- */
62
- start(env: Env): Promise<void>;
63
- /**
64
- * Handle fetch requests forwarded from the Worker.
65
- * The Container doesn't serve HTTP — return 404 for any direct requests.
66
- */
67
- fetch(request: Request, env: Env): Promise<Response>;
68
- resync: typeof resync;
69
- resyncAll: typeof resyncAll;
70
- getStatus: typeof getStatus;
71
- };
72
- export default _default;
73
- //# sourceMappingURL=container.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../src/cloudflare/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA0BH,MAAM,WAAW,GAAG;IAClB,qCAAqC;IACrC,EAAE,EAAE,UAAU,CAAA;IAGd,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAGzB,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,2BAA2B,CAAC,EAAE,MAAM,CAAA;IACpC,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,0BAA0B,CAAC,EAAE,MAAM,CAAA;IACnC,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,gCAAgC,CAAC,EAAE,MAAM,CAAA;CAC1C;AAGD,UAAU,UAAU;IAClB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;IACzB,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACrD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;CAChC;AAoLD;;;GAGG;AACH,iBAAe,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhD;AAED;;;GAGG;AACH,iBAAe,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAExC;AAED;;;GAGG;AACH,iBAAS,SAAS,IAAI;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAMtF;AAID;;;;;;GAMG;;IAED;;;OAGG;eACc,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpC;;;OAGG;mBACkB,OAAO,OAAO,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;;;;;AAb5D,wBA0BC"}
@@ -1,232 +0,0 @@
1
- /**
2
- * Cloudflare Container entry point for hatk.
3
- *
4
- * Runs as a long-lived Node.js process alongside the Worker. Handles the
5
- * firehose indexer and backfill loop — the Worker delegates resync requests
6
- * here via RPC (Cloudflare Container service bindings).
7
- *
8
- * No HTTP server — all communication is via the Container RPC interface.
9
- */
10
- import { D1Adapter } from "../database/adapters/d1.js";
11
- import { initDatabase, getCursor, migrateSchema } from "../database/db.js";
12
- import { storeLexicons, discoverCollections, buildSchemas } from "../database/schema.js";
13
- import { discoverViews } from "../views.js";
14
- import { getDialect } from "../database/dialect.js";
15
- import { setSearchPort } from "../database/fts.js";
16
- import { rebuildAllIndexes } from "../database/fts.js";
17
- import { registerCoreHandlers } from "../server.js";
18
- import { configureRelay } from "../xrpc.js";
19
- import { startIndexer, triggerAutoBackfill } from "../indexer.js";
20
- import { runBackfill } from "../backfill.js";
21
- import { relayHttpUrl } from "../config.js";
22
- import { validateLexicons } from '@bigmoves/lexicon';
23
- import { log } from "../logger.js";
24
- // ---------- Container state ----------
25
- let initialized = false;
26
- let initPromise = null;
27
- let collections = [];
28
- let collectionSet = new Set();
29
- let backfillOpts = null;
30
- let startedAt = 0;
31
- /**
32
- * One-time initialization. Mirrors main.ts startup minus the HTTP server.
33
- */
34
- async function initialize(env) {
35
- startedAt = Date.now();
36
- // 1. Parse config from env vars
37
- const relay = env.HATK_RELAY || 'wss://bsky.network';
38
- const plc = env.HATK_PLC || 'https://plc.directory';
39
- configureRelay(relay);
40
- const admins = env.HATK_ADMINS ? env.HATK_ADMINS.split(',').map((s) => s.trim()) : [];
41
- // 2. Load lexicons — injected at build time via virtual module
42
- let lexicons;
43
- try {
44
- // @ts-expect-error — virtual module generated at build time
45
- const lexiconModule = await import('virtual:hatk-lexicons');
46
- lexicons = new Map(Object.entries(lexiconModule.default));
47
- }
48
- catch {
49
- lexicons = new Map();
50
- }
51
- const lexiconErrors = validateLexicons([...lexicons.values()]);
52
- if (lexiconErrors) {
53
- for (const [nsid, errors] of Object.entries(lexiconErrors)) {
54
- for (const err of errors) {
55
- console.error(`[container] Invalid lexicon ${nsid}: ${err}`);
56
- }
57
- }
58
- throw new Error('Invalid lexicons — check build output');
59
- }
60
- storeLexicons(lexicons);
61
- // 3. Determine collections
62
- collections = env.HATK_COLLECTIONS
63
- ? env.HATK_COLLECTIONS.split(',').map((s) => s.trim())
64
- : discoverCollections(lexicons);
65
- collectionSet = new Set(collections);
66
- if (collections.length === 0) {
67
- log('[container] No record collections found — running in indexer-only mode');
68
- }
69
- log(`[container] Loaded config: ${collections.length} collections`);
70
- // 4. Build schemas and init D1
71
- discoverViews();
72
- const engineDialect = getDialect('d1');
73
- const { schemas, ddlStatements } = buildSchemas(lexicons, collections, engineDialect);
74
- const adapter = new D1Adapter();
75
- adapter.initWithBinding(env.DB);
76
- setSearchPort(null); // D1 uses SQLite FTS natively
77
- await initDatabase(adapter, ':memory:', schemas, ddlStatements);
78
- // Auto-migrate schema if lexicons changed
79
- const migrationChanges = await migrateSchema(schemas);
80
- if (migrationChanges.length > 0) {
81
- log(`[container] Applied ${migrationChanges.length} schema migration(s)`);
82
- }
83
- // 5. Init server directory handlers (feeds, labels, hooks, xrpc, setup)
84
- // In Containers, we load these via the bundled virtual module like the Worker.
85
- // The server/ directory scanning won't work in a Container since there's no filesystem
86
- // layout matching the dev project. For now, register core handlers only.
87
- // When build tooling (Task 7) bundles server handlers, they'll be imported here.
88
- const oauthConfig = env.HATK_OAUTH_ISSUER
89
- ? {
90
- issuer: env.HATK_OAUTH_ISSUER,
91
- scopes: env.HATK_OAUTH_SCOPES ? env.HATK_OAUTH_SCOPES.split(',').map((s) => s.trim()) : ['read', 'write'],
92
- clients: [],
93
- }
94
- : null;
95
- registerCoreHandlers(collections, oauthConfig);
96
- // 6. Parse backfill config
97
- const backfillConfig = {
98
- fullNetwork: env.HATK_BACKFILL_FULL_NETWORK === 'true',
99
- parallelism: env.HATK_BACKFILL_PARALLELISM ? parseInt(env.HATK_BACKFILL_PARALLELISM, 10) : 10,
100
- fetchTimeout: env.HATK_BACKFILL_FETCH_TIMEOUT ? parseInt(env.HATK_BACKFILL_FETCH_TIMEOUT, 10) : 30,
101
- maxRetries: env.HATK_BACKFILL_MAX_RETRIES ? parseInt(env.HATK_BACKFILL_MAX_RETRIES, 10) : 5,
102
- repos: env.HATK_BACKFILL_REPOS ? env.HATK_BACKFILL_REPOS.split(',').map((s) => s.trim()) : undefined,
103
- signalCollections: env.HATK_BACKFILL_SIGNAL_COLLECTIONS
104
- ? env.HATK_BACKFILL_SIGNAL_COLLECTIONS.split(',').map((s) => s.trim())
105
- : undefined,
106
- };
107
- backfillOpts = {
108
- pdsUrl: relayHttpUrl(relay),
109
- plcUrl: plc,
110
- collections: collectionSet,
111
- config: backfillConfig,
112
- };
113
- // 7. Start firehose indexer
114
- const cursor = await getCursor('relay');
115
- startIndexer({
116
- relayUrl: relay,
117
- collections: collectionSet,
118
- signalCollections: backfillConfig.signalCollections ? new Set(backfillConfig.signalCollections) : undefined,
119
- pinnedRepos: backfillConfig.repos ? new Set(backfillConfig.repos) : undefined,
120
- cursor,
121
- fetchTimeout: backfillConfig.fetchTimeout,
122
- maxRetries: backfillConfig.maxRetries,
123
- parallelism: backfillConfig.parallelism,
124
- });
125
- log('[container] Firehose indexer started');
126
- // 8. Run backfill in background
127
- runBackfillAndRestart();
128
- initialized = true;
129
- log('[container] Initialization complete');
130
- }
131
- /**
132
- * Run backfill, rebuild FTS indexes, and restart the process if records
133
- * were imported (to reclaim memory from CAR parsing). Mirrors main.ts behavior.
134
- */
135
- function runBackfillAndRestart() {
136
- if (!backfillOpts)
137
- return;
138
- runBackfill(backfillOpts)
139
- .then(async (recordCount) => {
140
- log('[container] Backfill complete, building FTS indexes...');
141
- await rebuildAllIndexes(collections);
142
- log('[container] FTS indexes ready');
143
- return recordCount;
144
- })
145
- .then((recordCount) => {
146
- if (recordCount > 0) {
147
- log('[container] Restarting to reclaim memory...');
148
- process.exit(1);
149
- }
150
- })
151
- .catch((err) => {
152
- console.error('[container] Backfill error:', err.message);
153
- });
154
- }
155
- /**
156
- * Ensure initialization has completed. Uses a shared promise so concurrent
157
- * RPC calls don't trigger multiple inits.
158
- */
159
- function ensureInit(env) {
160
- if (initialized)
161
- return Promise.resolve();
162
- if (!initPromise) {
163
- initPromise = initialize(env).catch((err) => {
164
- initPromise = null;
165
- throw err;
166
- });
167
- }
168
- return initPromise;
169
- }
170
- // ---------- RPC methods ----------
171
- /**
172
- * Resync a single DID by triggering auto-backfill.
173
- * Called by the Worker via Container service binding RPC.
174
- */
175
- async function resync(did) {
176
- await triggerAutoBackfill(did);
177
- }
178
- /**
179
- * Trigger a full re-enumeration backfill of all repos.
180
- * Called by the Worker via Container service binding RPC.
181
- */
182
- async function resyncAll() {
183
- runBackfillAndRestart();
184
- }
185
- /**
186
- * Return basic status info about the Container.
187
- * Called by the Worker for health checks / admin UI.
188
- */
189
- function getStatus() {
190
- return {
191
- initialized,
192
- collections,
193
- uptimeMs: startedAt > 0 ? Date.now() - startedAt : 0,
194
- };
195
- }
196
- // ---------- Container export ----------
197
- /**
198
- * Cloudflare Container entry point.
199
- *
200
- * Containers expose RPC methods that the Worker can call via the service binding.
201
- * The Container also handles fetch requests routed from the Worker, but for hatk
202
- * all Worker-to-Container communication uses the RPC methods above.
203
- */
204
- export default {
205
- /**
206
- * Container startup — called when the Container is first instantiated.
207
- * Initializes the database, starts the firehose, and begins backfill.
208
- */
209
- async start(env) {
210
- await ensureInit(env);
211
- },
212
- /**
213
- * Handle fetch requests forwarded from the Worker.
214
- * The Container doesn't serve HTTP — return 404 for any direct requests.
215
- */
216
- async fetch(request, env) {
217
- await ensureInit(env);
218
- return new Response(JSON.stringify({ error: 'Container does not serve HTTP requests' }), {
219
- status: 404,
220
- headers: { 'Content-Type': 'application/json' },
221
- });
222
- },
223
- // RPC methods exposed to the Worker via service binding
224
- resync,
225
- resyncAll,
226
- getStatus,
227
- };
228
- // Graceful shutdown
229
- process.on('SIGTERM', () => {
230
- log('[container] Received SIGTERM, shutting down...');
231
- process.exit(0);
232
- });
@@ -1,33 +0,0 @@
1
- /**
2
- * SvelteKit handle hook for Cloudflare Workers.
3
- *
4
- * Use with @sveltejs/adapter-cloudflare. Lazily initializes hatk on the first
5
- * request using the D1 binding from platform.env, then intercepts hatk API
6
- * routes before SvelteKit processes them.
7
- *
8
- * @example
9
- * ```ts
10
- * // app/hooks.server.ts
11
- * import { createHandle } from '@hatk/hatk/cloudflare/hooks'
12
- * export const handle = createHandle()
13
- * ```
14
- */
15
- type MaybePromise<T> = T | Promise<T>;
16
- /** Minimal SvelteKit Handle type to avoid depending on @sveltejs/kit. */
17
- type Handle = (input: {
18
- event: {
19
- request: Request;
20
- url: URL;
21
- platform?: {
22
- env?: Record<string, unknown>;
23
- };
24
- };
25
- resolve: (event: any) => MaybePromise<Response>;
26
- }) => MaybePromise<Response>;
27
- /**
28
- * Create a SvelteKit `handle` function that initializes hatk with D1
29
- * and intercepts API routes.
30
- */
31
- export declare function createHandle(): Handle;
32
- export {};
33
- //# sourceMappingURL=hooks.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/cloudflare/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAErC,yEAAyE;AACzE,KAAK,MAAM,GAAG,CAAC,KAAK,EAAE;IACpB,KAAK,EAAE;QACL,OAAO,EAAE,OAAO,CAAA;QAChB,GAAG,EAAE,GAAG,CAAA;QACR,QAAQ,CAAC,EAAE;YAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;SAAE,CAAA;KAC7C,CAAA;IACD,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,YAAY,CAAC,QAAQ,CAAC,CAAA;CAChD,KAAK,YAAY,CAAC,QAAQ,CAAC,CAAA;AAE5B;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAsBrC"}
@@ -1,40 +0,0 @@
1
- /**
2
- * SvelteKit handle hook for Cloudflare Workers.
3
- *
4
- * Use with @sveltejs/adapter-cloudflare. Lazily initializes hatk on the first
5
- * request using the D1 binding from platform.env, then intercepts hatk API
6
- * routes before SvelteKit processes them.
7
- *
8
- * @example
9
- * ```ts
10
- * // app/hooks.server.ts
11
- * import { createHandle } from '@hatk/hatk/cloudflare/hooks'
12
- * export const handle = createHandle()
13
- * ```
14
- */
15
- import { ensureInit, getHandler } from "./init.js";
16
- import { isHatkRoute } from "../adapter.js";
17
- /**
18
- * Create a SvelteKit `handle` function that initializes hatk with D1
19
- * and intercepts API routes.
20
- */
21
- export function createHandle() {
22
- return async ({ event, resolve }) => {
23
- const env = event.platform?.env;
24
- if (!env || !env.DB) {
25
- // Not running on Cloudflare (e.g. dev mode) — pass through
26
- return resolve(event);
27
- }
28
- // Lazy init hatk with the D1 binding
29
- await ensureInit(env);
30
- // hatk API routes
31
- if (isHatkRoute(event.url.pathname)) {
32
- const handler = getHandler();
33
- if (handler) {
34
- return handler(event.request);
35
- }
36
- }
37
- // Everything else → SvelteKit
38
- return resolve(event);
39
- };
40
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Shared Cloudflare initialization logic used by both the standalone Worker
3
- * entry and the SvelteKit handle hook.
4
- */
5
- export interface CloudflareEnv {
6
- DB: D1Database;
7
- HATK_RELAY?: string;
8
- HATK_PLC?: string;
9
- HATK_OAUTH_ISSUER?: string;
10
- HATK_OAUTH_SCOPES?: string;
11
- HATK_ADMINS?: string;
12
- HATK_COLLECTIONS?: string;
13
- [key: string]: unknown;
14
- }
15
- interface D1Database {
16
- prepare(sql: string): any;
17
- batch<T = unknown>(statements: any[]): Promise<any[]>;
18
- exec(sql: string): Promise<any>;
19
- }
20
- /**
21
- * Ensure initialization has completed. Concurrent calls share the same promise.
22
- */
23
- export declare function ensureInit(env: CloudflareEnv): Promise<void>;
24
- /** Get the hatk request handler (only valid after init). */
25
- export declare function getHandler(): ((request: Request) => Promise<Response>) | null;
26
- export {};
27
- //# sourceMappingURL=init.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cloudflare/init.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,UAAU,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,UAAU,UAAU;IAClB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;IACzB,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACrD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;CAChC;AAoFD;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAS5D;AAED,4DAA4D;AAC5D,wBAAgB,UAAU,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAE7E"}
@@ -1,103 +0,0 @@
1
- /**
2
- * Shared Cloudflare initialization logic used by both the standalone Worker
3
- * entry and the SvelteKit handle hook.
4
- */
5
- import { D1Adapter } from "../database/adapters/d1.js";
6
- import { initDatabase, migrateSchema } from "../database/db.js";
7
- import { storeLexicons, discoverCollections, buildSchemas } from "../database/schema.js";
8
- import { discoverViews } from "../views.js";
9
- import { getDialect } from "../database/dialect.js";
10
- import { setSearchPort } from "../database/fts.js";
11
- import { initOAuth } from "../oauth/server.js";
12
- import { parseSessionCookie, getSessionCookieName } from "../oauth/session.js";
13
- import { createHandler, registerCoreHandlers } from "../server.js";
14
- import { configureRelay } from "../xrpc.js";
15
- import { callXrpc } from "../xrpc.js";
16
- import { validateLexicons } from '@bigmoves/lexicon';
17
- let handler = null;
18
- let initPromise = null;
19
- /**
20
- * One-time initialization. Sets up D1 adapter, database schemas, XRPC
21
- * handlers, OAuth, and the globalThis bridge for SvelteKit SSR.
22
- */
23
- async function initialize(env) {
24
- const relay = env.HATK_RELAY || 'wss://bsky.network';
25
- const plc = env.HATK_PLC || 'https://plc.directory';
26
- configureRelay(relay);
27
- const admins = env.HATK_ADMINS ? env.HATK_ADMINS.split(',').map((s) => s.trim()) : [];
28
- // Load lexicons — injected at build time via virtual module
29
- let lexicons;
30
- try {
31
- // @ts-expect-error — virtual module generated at build time
32
- const lexiconModule = await import('virtual:hatk-lexicons');
33
- lexicons = new Map(Object.entries(lexiconModule.default));
34
- }
35
- catch {
36
- lexicons = new Map();
37
- }
38
- const lexiconErrors = validateLexicons([...lexicons.values()]);
39
- if (lexiconErrors) {
40
- for (const [nsid, errors] of Object.entries(lexiconErrors)) {
41
- for (const err of errors) {
42
- console.error(`[hatk] Invalid lexicon ${nsid}: ${err}`);
43
- }
44
- }
45
- throw new Error('Invalid lexicons — check build output');
46
- }
47
- storeLexicons(lexicons);
48
- const collections = env.HATK_COLLECTIONS
49
- ? env.HATK_COLLECTIONS.split(',').map((s) => s.trim())
50
- : discoverCollections(lexicons);
51
- // Build schemas and init D1
52
- discoverViews();
53
- const engineDialect = getDialect('d1');
54
- const { schemas, ddlStatements } = buildSchemas(lexicons, collections, engineDialect);
55
- const adapter = new D1Adapter();
56
- adapter.initWithBinding(env.DB);
57
- setSearchPort(null);
58
- await initDatabase(adapter, ':memory:', schemas, ddlStatements);
59
- await migrateSchema(schemas);
60
- // Register core XRPC handlers
61
- const oauthConfig = env.HATK_OAUTH_ISSUER
62
- ? {
63
- issuer: env.HATK_OAUTH_ISSUER,
64
- scopes: env.HATK_OAUTH_SCOPES ? env.HATK_OAUTH_SCOPES.split(',').map((s) => s.trim()) : ['read', 'write'],
65
- clients: [],
66
- }
67
- : null;
68
- registerCoreHandlers(collections, oauthConfig);
69
- if (oauthConfig) {
70
- await initOAuth(oauthConfig, plc, relay);
71
- }
72
- // Expose bridge for SvelteKit SSR
73
- ;
74
- globalThis.__hatk_callXrpc = callXrpc;
75
- globalThis.__hatk_parseSessionCookie = parseSessionCookie;
76
- globalThis.__hatk_sessionCookieName = getSessionCookieName();
77
- // Create the request handler
78
- handler = createHandler({
79
- collections,
80
- publicDir: null,
81
- oauth: oauthConfig,
82
- admins,
83
- onResync: undefined,
84
- });
85
- }
86
- /**
87
- * Ensure initialization has completed. Concurrent calls share the same promise.
88
- */
89
- export function ensureInit(env) {
90
- if (handler)
91
- return Promise.resolve();
92
- if (!initPromise) {
93
- initPromise = initialize(env).catch((err) => {
94
- initPromise = null;
95
- throw err;
96
- });
97
- }
98
- return initPromise;
99
- }
100
- /** Get the hatk request handler (only valid after init). */
101
- export function getHandler() {
102
- return handler;
103
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Cloudflare Worker entry point for hatk.
3
- *
4
- * Handles HTTP requests via the Workers fetch handler. The firehose indexer
5
- * and backfill run in a companion Container — the Worker only serves the
6
- * API and web UI.
7
- *
8
- * For SvelteKit apps, prefer using the handle hook from
9
- * '@hatk/hatk/cloudflare/hooks' with adapter-cloudflare instead.
10
- */
11
- import { type CloudflareEnv } from './init.ts';
12
- interface ContainerBinding {
13
- resync(did: string): Promise<void>;
14
- fetch(request: Request): Promise<Response>;
15
- }
16
- interface Env extends CloudflareEnv {
17
- CONTAINER: ContainerBinding;
18
- }
19
- interface ExecutionContext {
20
- waitUntil(promise: Promise<unknown>): void;
21
- passThroughOnException(): void;
22
- }
23
- declare const _default: {
24
- fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response>;
25
- };
26
- export default _default;
27
- //# sourceMappingURL=worker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/cloudflare/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAA0B,KAAK,aAAa,EAAE,MAAM,WAAW,CAAA;AAGtE,UAAU,gBAAgB;IACxB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3C;AAED,UAAU,GAAI,SAAQ,aAAa;IACjC,SAAS,EAAE,gBAAgB,CAAA;CAC5B;AAED,UAAU,gBAAgB;IACxB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;IAC1C,sBAAsB,IAAI,IAAI,CAAA;CAC/B;;mBAGsB,OAAO,OAAO,GAAG,OAAO,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;;AADnF,wBA2CC"}
@@ -1,54 +0,0 @@
1
- /**
2
- * Cloudflare Worker entry point for hatk.
3
- *
4
- * Handles HTTP requests via the Workers fetch handler. The firehose indexer
5
- * and backfill run in a companion Container — the Worker only serves the
6
- * API and web UI.
7
- *
8
- * For SvelteKit apps, prefer using the handle hook from
9
- * '@hatk/hatk/cloudflare/hooks' with adapter-cloudflare instead.
10
- */
11
- import { ensureInit, getHandler } from "./init.js";
12
- import { isHatkRoute } from "../adapter.js";
13
- export default {
14
- async fetch(request, env, ctx) {
15
- try {
16
- await ensureInit(env);
17
- }
18
- catch (err) {
19
- return new Response(JSON.stringify({ error: 'Initialization failed', detail: err.message }), {
20
- status: 500,
21
- headers: { 'Content-Type': 'application/json' },
22
- });
23
- }
24
- const url = new URL(request.url);
25
- const handler = getHandler();
26
- // Intercept admin resync to delegate to the Container via RPC
27
- if (url.pathname === '/admin/repos/resync' && request.method === 'POST') {
28
- try {
29
- const body = await request.text();
30
- const { dids } = body ? JSON.parse(body) : {};
31
- if (Array.isArray(dids)) {
32
- for (const did of dids) {
33
- ctx.waitUntil(env.CONTAINER.resync(did));
34
- }
35
- return new Response(JSON.stringify({ resyncing: dids.length }), {
36
- headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
37
- });
38
- }
39
- }
40
- catch (err) {
41
- return new Response(JSON.stringify({ error: err.message }), {
42
- status: 500,
43
- headers: { 'Content-Type': 'application/json' },
44
- });
45
- }
46
- }
47
- // hatk routes → handler
48
- if (isHatkRoute(url.pathname)) {
49
- return handler(request);
50
- }
51
- // Everything else → 404 (use cloudflare/hooks for SvelteKit integration)
52
- return new Response('Not found', { status: 404 });
53
- },
54
- };
@@ -1,56 +0,0 @@
1
- import type { DatabasePort, BulkInserter, Dialect } from '../ports.ts';
2
- /**
3
- * D1 database adapter for Cloudflare Workers/Containers.
4
- *
5
- * D1 is SQLite under the hood but accessed via an HTTP-based binding API.
6
- * Key differences from the SQLite adapter:
7
- * - No raw transactions — uses d1.batch() for atomic multi-statement execution
8
- * - No prepared statement reuse — each query is a fresh prepare+bind
9
- * - Bulk inserts use batched INSERT statements instead of native appenders
10
- */
11
- /** Minimal D1 type definitions (matches Cloudflare's D1Database binding) */
12
- interface D1Database {
13
- prepare(sql: string): D1PreparedStatement;
14
- batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
15
- exec(sql: string): Promise<D1ExecResult>;
16
- }
17
- interface D1PreparedStatement {
18
- bind(...values: unknown[]): D1PreparedStatement;
19
- all<T = Record<string, unknown>>(): Promise<D1Result<T>>;
20
- run(): Promise<D1Result>;
21
- first<T = Record<string, unknown>>(column?: string): Promise<T | null>;
22
- }
23
- interface D1Result<T = unknown> {
24
- results: T[];
25
- success: boolean;
26
- meta: Record<string, unknown>;
27
- }
28
- interface D1ExecResult {
29
- count: number;
30
- duration: number;
31
- }
32
- export declare class D1Adapter implements DatabasePort {
33
- dialect: Dialect;
34
- private db;
35
- private txBuffer;
36
- /**
37
- * Initialize with an existing D1 binding (from env.DB in Worker/Container).
38
- * The `path` argument is ignored — D1 bindings are configured in wrangler.jsonc.
39
- */
40
- open(_path: string): Promise<void>;
41
- /** Set the D1 binding directly (called before open). */
42
- initWithBinding(db: D1Database): void;
43
- close(): void;
44
- query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
45
- execute(sql: string, params?: unknown[]): Promise<void>;
46
- executeMultiple(sql: string): Promise<void>;
47
- beginTransaction(): Promise<void>;
48
- commit(): Promise<void>;
49
- rollback(): Promise<void>;
50
- createBulkInserter(table: string, columns: string[], options?: {
51
- onConflict?: 'ignore' | 'replace';
52
- batchSize?: number;
53
- }): Promise<BulkInserter>;
54
- }
55
- export {};
56
- //# sourceMappingURL=d1.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"d1.d.ts","sourceRoot":"","sources":["../../../src/database/adapters/d1.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAEtE;;;;;;;;GAQG;AAEH,4EAA4E;AAC5E,UAAU,UAAU;IAClB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAAA;IACzC,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAC7E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CACzC;AAED,UAAU,mBAAmB;IAC3B,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,mBAAmB,CAAA;IAC/C,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;IACxD,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAA;IACxB,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;CACvE;AAED,UAAU,QAAQ,CAAC,CAAC,GAAG,OAAO;IAC5B,OAAO,EAAE,CAAC,EAAE,CAAA;IACZ,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC9B;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB;AAmBD,qBAAa,SAAU,YAAW,YAAY;IAC5C,OAAO,EAAE,OAAO,CAAO;IAEvB,OAAO,CAAC,EAAE,CAAa;IACvB,OAAO,CAAC,QAAQ,CAAqC;IAErD;;;OAGG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxC,wDAAwD;IACxD,eAAe,CAAC,EAAE,EAAE,UAAU,GAAG,IAAI;IAIrC,KAAK,IAAI,IAAI;IAIP,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAOrF,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAa3D,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa3C,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IASvB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzB,kBAAkB,CACtB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAClE,OAAO,CAAC,YAAY,CAAC;CA+BzB"}