@hatk/hatk 0.0.1-alpha.4 → 0.0.1-alpha.40
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/dist/adapter.d.ts +19 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +107 -0
- package/dist/backfill.d.ts +60 -1
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +167 -33
- package/dist/car.d.ts +59 -1
- package/dist/car.d.ts.map +1 -1
- package/dist/car.js +179 -7
- package/dist/cbor.d.ts +37 -0
- package/dist/cbor.d.ts.map +1 -1
- package/dist/cbor.js +36 -3
- package/dist/cid.d.ts +37 -0
- package/dist/cid.d.ts.map +1 -1
- package/dist/cid.js +38 -3
- package/dist/cli.js +417 -133
- package/dist/cloudflare/container.d.ts +73 -0
- package/dist/cloudflare/container.d.ts.map +1 -0
- package/dist/cloudflare/container.js +232 -0
- package/dist/cloudflare/hooks.d.ts +33 -0
- package/dist/cloudflare/hooks.d.ts.map +1 -0
- package/dist/cloudflare/hooks.js +40 -0
- package/dist/cloudflare/init.d.ts +27 -0
- package/dist/cloudflare/init.d.ts.map +1 -0
- package/dist/cloudflare/init.js +103 -0
- package/dist/cloudflare/worker.d.ts +27 -0
- package/dist/cloudflare/worker.d.ts.map +1 -0
- package/dist/cloudflare/worker.js +54 -0
- package/dist/config.d.ts +12 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +36 -9
- package/dist/database/adapter-factory.d.ts +6 -0
- package/dist/database/adapter-factory.d.ts.map +1 -0
- package/dist/database/adapter-factory.js +20 -0
- package/dist/database/adapters/d1.d.ts +56 -0
- package/dist/database/adapters/d1.d.ts.map +1 -0
- package/dist/database/adapters/d1.js +108 -0
- package/dist/database/adapters/duckdb-search.d.ts +12 -0
- package/dist/database/adapters/duckdb-search.d.ts.map +1 -0
- package/dist/database/adapters/duckdb-search.js +27 -0
- package/dist/database/adapters/duckdb.d.ts +25 -0
- package/dist/database/adapters/duckdb.d.ts.map +1 -0
- package/dist/database/adapters/duckdb.js +161 -0
- package/dist/database/adapters/sqlite-search.d.ts +23 -0
- package/dist/database/adapters/sqlite-search.d.ts.map +1 -0
- package/dist/database/adapters/sqlite-search.js +74 -0
- package/dist/database/adapters/sqlite.d.ts +18 -0
- package/dist/database/adapters/sqlite.d.ts.map +1 -0
- package/dist/database/adapters/sqlite.js +87 -0
- package/dist/database/db.d.ts +159 -0
- package/dist/database/db.d.ts.map +1 -0
- package/dist/database/db.js +1445 -0
- package/dist/database/dialect.d.ts +45 -0
- package/dist/database/dialect.d.ts.map +1 -0
- package/dist/database/dialect.js +72 -0
- package/dist/database/fts.d.ts +27 -0
- package/dist/database/fts.d.ts.map +1 -0
- package/dist/database/fts.js +846 -0
- package/dist/database/index.d.ts +7 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +6 -0
- package/dist/database/ports.d.ts +50 -0
- package/dist/database/ports.d.ts.map +1 -0
- package/dist/database/ports.js +1 -0
- package/dist/database/schema.d.ts +61 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +394 -0
- package/dist/db.d.ts +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +4 -38
- package/dist/dev-entry.d.ts +8 -0
- package/dist/dev-entry.d.ts.map +1 -0
- package/dist/dev-entry.js +110 -0
- package/dist/feeds.d.ts +12 -8
- package/dist/feeds.d.ts.map +1 -1
- package/dist/feeds.js +45 -6
- package/dist/fts.d.ts.map +1 -1
- package/dist/fts.js +5 -0
- package/dist/hooks.d.ts +22 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +75 -0
- package/dist/hydrate.d.ts +6 -5
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +4 -16
- package/dist/indexer.d.ts +20 -0
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +53 -7
- package/dist/labels.d.ts +34 -0
- package/dist/labels.d.ts.map +1 -1
- package/dist/labels.js +66 -6
- package/dist/logger.d.ts +29 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +29 -0
- package/dist/main.js +134 -67
- package/dist/mst.d.ts +18 -1
- package/dist/mst.d.ts.map +1 -1
- package/dist/mst.js +19 -8
- package/dist/oauth/db.d.ts.map +1 -1
- package/dist/oauth/db.js +43 -17
- package/dist/oauth/server.d.ts +2 -0
- package/dist/oauth/server.d.ts.map +1 -1
- package/dist/oauth/server.js +102 -7
- package/dist/oauth/session.d.ts +11 -0
- package/dist/oauth/session.d.ts.map +1 -0
- package/dist/oauth/session.js +65 -0
- package/dist/opengraph.d.ts +10 -0
- package/dist/opengraph.d.ts.map +1 -1
- package/dist/opengraph.js +73 -39
- package/dist/pds-proxy.d.ts +42 -0
- package/dist/pds-proxy.d.ts.map +1 -0
- package/dist/pds-proxy.js +189 -0
- package/dist/renderer.d.ts +27 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +46 -0
- package/dist/resolve-hatk.d.ts +6 -0
- package/dist/resolve-hatk.d.ts.map +1 -0
- package/dist/resolve-hatk.js +20 -0
- package/dist/response.d.ts +16 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +69 -0
- package/dist/scanner.d.ts +21 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +88 -0
- package/dist/schema.d.ts +8 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +29 -0
- package/dist/seed.d.ts +19 -0
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +43 -4
- package/dist/server-init.d.ts +8 -0
- package/dist/server-init.d.ts.map +1 -0
- package/dist/server-init.js +61 -0
- package/dist/server.d.ts +26 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +528 -635
- package/dist/setup.d.ts +28 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +50 -3
- package/dist/test.d.ts +1 -1
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +38 -32
- package/dist/views.js +1 -1
- package/dist/vite-plugin.d.ts +1 -1
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js +254 -66
- package/dist/xrpc.d.ts +46 -10
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +128 -39
- package/package.json +13 -6
- package/public/admin.html +0 -54
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { mkdirSync, writeFileSync, existsSync, unlinkSync, readdirSync, readFileSync } from 'node:fs';
|
|
3
|
-
import { resolve, join } from 'node:path';
|
|
4
|
-
import { execSync } from 'node:child_process';
|
|
5
|
-
import { loadLexicons } from "./schema.js";
|
|
3
|
+
import { resolve, join, dirname } from 'node:path';
|
|
4
|
+
import { execSync, spawn } from 'node:child_process';
|
|
5
|
+
import { loadLexicons, discoverCollections, buildSchemas } from "./database/schema.js";
|
|
6
6
|
import { loadConfig } from "./config.js";
|
|
7
7
|
const args = process.argv.slice(2);
|
|
8
8
|
const command = args[0];
|
|
@@ -34,6 +34,31 @@ async function ensurePds() {
|
|
|
34
34
|
console.error('[dev] PDS failed to start');
|
|
35
35
|
process.exit(1);
|
|
36
36
|
}
|
|
37
|
+
/** Spawn a long-running process and forward SIGINT/SIGTERM for clean shutdown. */
|
|
38
|
+
function spawnForward(cmd, args, env) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const child = spawn(cmd, args, {
|
|
41
|
+
stdio: 'inherit',
|
|
42
|
+
cwd: process.cwd(),
|
|
43
|
+
env: { ...process.env, ...env },
|
|
44
|
+
});
|
|
45
|
+
const onSignal = (sig) => {
|
|
46
|
+
child.kill(sig);
|
|
47
|
+
};
|
|
48
|
+
process.on('SIGINT', onSignal);
|
|
49
|
+
process.on('SIGTERM', onSignal);
|
|
50
|
+
child.on('close', (code, signal) => {
|
|
51
|
+
process.removeListener('SIGINT', onSignal);
|
|
52
|
+
process.removeListener('SIGTERM', onSignal);
|
|
53
|
+
if (signal === 'SIGINT' || signal === 'SIGTERM')
|
|
54
|
+
process.exit(0);
|
|
55
|
+
if (code === 0 || code === null)
|
|
56
|
+
resolve();
|
|
57
|
+
else
|
|
58
|
+
reject(new Error(`Process exited with code ${code}`));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
37
62
|
function runSeed() {
|
|
38
63
|
const seedFile = resolve('seeds/seed.ts');
|
|
39
64
|
if (!existsSync(seedFile))
|
|
@@ -45,7 +70,7 @@ function usage() {
|
|
|
45
70
|
Usage: hatk <command> [options]
|
|
46
71
|
|
|
47
72
|
Getting Started
|
|
48
|
-
new <name> [--svelte] [--template <t>] Create a new hatk project
|
|
73
|
+
new <name> [--svelte] [--duckdb] [--template <t>] Create a new hatk project
|
|
49
74
|
|
|
50
75
|
Running
|
|
51
76
|
start Start the hatk server
|
|
@@ -70,7 +95,8 @@ function usage() {
|
|
|
70
95
|
generate xrpc <nsid> Generate an XRPC handler
|
|
71
96
|
generate label <name> Generate a label definition
|
|
72
97
|
generate og <name> Generate an OpenGraph route
|
|
73
|
-
generate
|
|
98
|
+
generate hook <name> Generate a lifecycle hook
|
|
99
|
+
generate setup <name> Generate a setup script
|
|
74
100
|
generate types Regenerate TypeScript types from lexicons
|
|
75
101
|
destroy <type> <name> Remove a generated file
|
|
76
102
|
|
|
@@ -98,7 +124,7 @@ export default defineFeed({
|
|
|
98
124
|
},
|
|
99
125
|
})
|
|
100
126
|
`,
|
|
101
|
-
xrpc: (name) => `import { defineQuery } from '
|
|
127
|
+
xrpc: (name) => `import { defineQuery } from '../hatk.generated.ts'
|
|
102
128
|
|
|
103
129
|
export default defineQuery('${name}', async (ctx) => {
|
|
104
130
|
const { ok, db, params, packCursor, unpackCursor } = ctx
|
|
@@ -169,22 +195,19 @@ export default {
|
|
|
169
195
|
},
|
|
170
196
|
}
|
|
171
197
|
`,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
198
|
+
hook: (name) => `import { defineHook } from '../hatk.generated.ts'
|
|
199
|
+
|
|
200
|
+
export default defineHook('${name}', async (ctx) => {
|
|
201
|
+
// Hook logic here
|
|
202
|
+
})
|
|
203
|
+
`,
|
|
204
|
+
setup: (_name) => `import { defineSetup } from '../hatk.generated.ts'
|
|
205
|
+
|
|
206
|
+
export default defineSetup(async (ctx) => {
|
|
207
|
+
// Setup logic here — runs before the server starts
|
|
208
|
+
})
|
|
178
209
|
`,
|
|
179
210
|
};
|
|
180
|
-
// Compute relative import path from xrpc/ns/id/method.ts back to hatk.generated.ts
|
|
181
|
-
// e.g. fm.teal.getStats → xrpc/fm/teal/getStats.ts → needs ../../../hatk.generated.ts
|
|
182
|
-
// Parts: [fm, teal, getStats] → 2 namespace dirs + xrpc/ dir = 3 levels up
|
|
183
|
-
function xrpcImportPath(nsid) {
|
|
184
|
-
const parts = nsid.split('.');
|
|
185
|
-
const namespaceDirs = parts.length - 1; // dirs created from namespace segments
|
|
186
|
-
return '../'.repeat(namespaceDirs + 1) + 'hatk.generated.ts'; // +1 for xrpc/ dir itself
|
|
187
|
-
}
|
|
188
211
|
const testTemplates = {
|
|
189
212
|
feed: (name) => `import { describe, test, expect, beforeAll, afterAll } from 'vitest'
|
|
190
213
|
import { createTestContext } from '@hatk/hatk/test'
|
|
@@ -293,17 +316,18 @@ const lexiconTemplates = {
|
|
|
293
316
|
}),
|
|
294
317
|
};
|
|
295
318
|
const dirs = {
|
|
296
|
-
feed: '
|
|
297
|
-
xrpc: '
|
|
298
|
-
label: '
|
|
299
|
-
og: '
|
|
300
|
-
|
|
319
|
+
feed: 'server',
|
|
320
|
+
xrpc: 'server',
|
|
321
|
+
label: 'server',
|
|
322
|
+
og: 'server',
|
|
323
|
+
hook: 'server',
|
|
324
|
+
setup: 'server',
|
|
301
325
|
};
|
|
302
326
|
// --- Commands ---
|
|
303
327
|
if (command === 'new') {
|
|
304
328
|
const name = args[1];
|
|
305
329
|
if (!name) {
|
|
306
|
-
console.error('Usage: hatk new <name> [--svelte] [--template <template-name>]');
|
|
330
|
+
console.error('Usage: hatk new <name> [--svelte] [--duckdb] [--template <template-name>]');
|
|
307
331
|
process.exit(1);
|
|
308
332
|
}
|
|
309
333
|
const templateIdx = args.indexOf('--template');
|
|
@@ -341,20 +365,16 @@ if (command === 'new') {
|
|
|
341
365
|
process.exit(0);
|
|
342
366
|
}
|
|
343
367
|
const withSvelte = args.includes('--svelte');
|
|
368
|
+
const withDuckdb = args.includes('--duckdb');
|
|
369
|
+
const dbEngine = withDuckdb ? 'duckdb' : 'sqlite';
|
|
344
370
|
mkdirSync(dir);
|
|
345
371
|
const subs = [
|
|
346
372
|
'lexicons',
|
|
347
|
-
'
|
|
348
|
-
'xrpc',
|
|
349
|
-
'og',
|
|
350
|
-
'labels',
|
|
351
|
-
'jobs',
|
|
373
|
+
'server',
|
|
352
374
|
'seeds',
|
|
353
|
-
'setup',
|
|
354
375
|
'public',
|
|
355
376
|
'test',
|
|
356
|
-
'test/
|
|
357
|
-
'test/xrpc',
|
|
377
|
+
'test/server',
|
|
358
378
|
'test/integration',
|
|
359
379
|
'test/browser',
|
|
360
380
|
'test/fixtures',
|
|
@@ -364,14 +384,19 @@ if (command === 'new') {
|
|
|
364
384
|
for (const sub of subs) {
|
|
365
385
|
mkdirSync(join(dir, sub));
|
|
366
386
|
}
|
|
367
|
-
writeFileSync(join(dir, 'config.
|
|
368
|
-
plc: http://localhost:2582
|
|
369
|
-
port: 3000
|
|
370
|
-
database: data/hatk.db
|
|
371
|
-
admins: []
|
|
387
|
+
writeFileSync(join(dir, 'hatk.config.ts'), `import { defineConfig } from '@hatk/hatk/config'
|
|
372
388
|
|
|
373
|
-
|
|
374
|
-
|
|
389
|
+
export default defineConfig({
|
|
390
|
+
relay: 'ws://localhost:2583',
|
|
391
|
+
plc: 'http://localhost:2582',
|
|
392
|
+
port: 3000,
|
|
393
|
+
databaseEngine: '${dbEngine}',
|
|
394
|
+
database: 'data/hatk.db',
|
|
395
|
+
admins: [],
|
|
396
|
+
backfill: {
|
|
397
|
+
parallelism: 10,
|
|
398
|
+
},
|
|
399
|
+
})
|
|
375
400
|
`);
|
|
376
401
|
writeFileSync(join(dir, 'public', 'index.html'), `<!DOCTYPE html>
|
|
377
402
|
<html><head><title>${name}</title></head>
|
|
@@ -507,6 +532,14 @@ backfill:
|
|
|
507
532
|
properties: {
|
|
508
533
|
uri: { type: 'string', format: 'at-uri' },
|
|
509
534
|
cid: { type: 'string', format: 'cid' },
|
|
535
|
+
commit: {
|
|
536
|
+
type: 'object',
|
|
537
|
+
properties: {
|
|
538
|
+
cid: { type: 'string', format: 'cid' },
|
|
539
|
+
rev: { type: 'string' },
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
validationStatus: { type: 'string' },
|
|
510
543
|
},
|
|
511
544
|
},
|
|
512
545
|
},
|
|
@@ -562,6 +595,14 @@ backfill:
|
|
|
562
595
|
properties: {
|
|
563
596
|
uri: { type: 'string', format: 'at-uri' },
|
|
564
597
|
cid: { type: 'string', format: 'cid' },
|
|
598
|
+
commit: {
|
|
599
|
+
type: 'object',
|
|
600
|
+
properties: {
|
|
601
|
+
cid: { type: 'string', format: 'cid' },
|
|
602
|
+
rev: { type: 'string' },
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
validationStatus: { type: 'string' },
|
|
565
606
|
},
|
|
566
607
|
},
|
|
567
608
|
},
|
|
@@ -591,6 +632,53 @@ backfill:
|
|
|
591
632
|
},
|
|
592
633
|
},
|
|
593
634
|
}, null, 2) + '\n');
|
|
635
|
+
writeFileSync(join(coreLexDir, 'getPreferences.json'), JSON.stringify({
|
|
636
|
+
lexicon: 1,
|
|
637
|
+
id: 'dev.hatk.getPreferences',
|
|
638
|
+
defs: {
|
|
639
|
+
main: {
|
|
640
|
+
type: 'query',
|
|
641
|
+
description: 'Get all preferences for the authenticated user.',
|
|
642
|
+
output: {
|
|
643
|
+
encoding: 'application/json',
|
|
644
|
+
schema: {
|
|
645
|
+
type: 'object',
|
|
646
|
+
properties: {
|
|
647
|
+
preferences: { type: 'unknown' },
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
}, null, 2) + '\n');
|
|
654
|
+
writeFileSync(join(coreLexDir, 'putPreference.json'), JSON.stringify({
|
|
655
|
+
lexicon: 1,
|
|
656
|
+
id: 'dev.hatk.putPreference',
|
|
657
|
+
defs: {
|
|
658
|
+
main: {
|
|
659
|
+
type: 'procedure',
|
|
660
|
+
description: 'Set a single preference by key.',
|
|
661
|
+
input: {
|
|
662
|
+
encoding: 'application/json',
|
|
663
|
+
schema: {
|
|
664
|
+
type: 'object',
|
|
665
|
+
required: ['key', 'value'],
|
|
666
|
+
properties: {
|
|
667
|
+
key: { type: 'string' },
|
|
668
|
+
value: { type: 'unknown' },
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
output: {
|
|
673
|
+
encoding: 'application/json',
|
|
674
|
+
schema: {
|
|
675
|
+
type: 'object',
|
|
676
|
+
properties: {},
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
}, null, 2) + '\n');
|
|
594
682
|
writeFileSync(join(coreLexDir, 'getFeed.json'), JSON.stringify({
|
|
595
683
|
lexicon: 1,
|
|
596
684
|
id: 'dev.hatk.getFeed',
|
|
@@ -801,9 +889,12 @@ COPY . .
|
|
|
801
889
|
RUN node_modules/.bin/hatk build
|
|
802
890
|
RUN npm prune --omit=dev
|
|
803
891
|
EXPOSE 3000
|
|
804
|
-
CMD ["node", "node_modules/@hatk/hatk/dist/main.js", "config.
|
|
892
|
+
CMD ["node", "--experimental-strip-types", "--max-old-space-size=512", "node_modules/@hatk/hatk/dist/main.js", "hatk.config.ts"]
|
|
805
893
|
`);
|
|
806
894
|
const pkgDeps = { '@hatk/oauth-client': '*', hatk: '*' };
|
|
895
|
+
if (!withDuckdb) {
|
|
896
|
+
pkgDeps['better-sqlite3'] = '^11';
|
|
897
|
+
}
|
|
807
898
|
const pkgDevDeps = {
|
|
808
899
|
'@playwright/test': '^1',
|
|
809
900
|
oxfmt: '^0.35.0',
|
|
@@ -846,7 +937,7 @@ CMD ["node", "node_modules/@hatk/hatk/dist/main.js", "config.yaml"]
|
|
|
846
937
|
allowImportingTsExtensions: true,
|
|
847
938
|
resolveJsonModule: true,
|
|
848
939
|
},
|
|
849
|
-
include: ['
|
|
940
|
+
include: ['server', 'seeds', 'hatk.generated.ts', 'hatk.config.ts'],
|
|
850
941
|
}, null, 2) + '\n');
|
|
851
942
|
writeFileSync(join(dir, 'playwright.config.ts'), `import { defineConfig } from '@playwright/test'
|
|
852
943
|
|
|
@@ -1031,16 +1122,87 @@ a {
|
|
|
1031
1122
|
</div>
|
|
1032
1123
|
`);
|
|
1033
1124
|
}
|
|
1125
|
+
let agentsMd = `# hatk project
|
|
1126
|
+
|
|
1127
|
+
This is an AT Protocol application built with [hatk](https://github.com/hatk-dev/hatk).
|
|
1128
|
+
Read the project's lexicons in \`lexicons/\` to understand the data model.
|
|
1129
|
+
Types are generated from lexicons into \`hatk.generated.ts\` — never edit this file directly.
|
|
1130
|
+
|
|
1131
|
+
## Project structure
|
|
1132
|
+
|
|
1133
|
+
| Directory | Purpose |
|
|
1134
|
+
|-------------|------------------------------------------------------|
|
|
1135
|
+
| \`lexicons/\` | AT Protocol lexicon schemas (JSON). Defines collections and XRPC methods |
|
|
1136
|
+
| \`server/\` | All server-side code: feeds, XRPC handlers, hooks, labels, OG routes, setup scripts |
|
|
1137
|
+
| \`seeds/\` | Test data seeding scripts for local development |
|
|
1138
|
+
| \`test/\` | Test files (vitest). Run with \`vp test\` |
|
|
1139
|
+
| \`public/\` | Static files served at the root |
|
|
1140
|
+
`;
|
|
1141
|
+
if (withSvelte) {
|
|
1142
|
+
agentsMd += `| \`src/\` | SvelteKit frontend (routes, components, styles) |
|
|
1143
|
+
|
|
1144
|
+
`;
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
agentsMd += `
|
|
1148
|
+
`;
|
|
1149
|
+
}
|
|
1150
|
+
agentsMd += `## Key files
|
|
1151
|
+
|
|
1152
|
+
- \`hatk.config.ts\` — project configuration (see \`defineConfig\` for type info)
|
|
1153
|
+
- \`hatk.generated.ts\` — auto-generated server types and helpers. Regenerate with \`hatk generate types\`
|
|
1154
|
+
- \`hatk.generated.client.ts\` — auto-generated client-safe types and \`callXrpc\`. Never import \`hatk.generated.ts\` from frontend code
|
|
1155
|
+
|
|
1156
|
+
## The \`$hatk\` alias
|
|
1157
|
+
|
|
1158
|
+
Server files in \`server/\` import from \`$hatk\`:
|
|
1159
|
+
\`\`\`ts
|
|
1160
|
+
import { defineFeed, views, type Status } from "$hatk"
|
|
1161
|
+
\`\`\`
|
|
1162
|
+
`;
|
|
1163
|
+
if (withSvelte) {
|
|
1164
|
+
agentsMd += `
|
|
1165
|
+
SvelteKit routes and components import from \`$hatk/client\`:
|
|
1166
|
+
\`\`\`ts
|
|
1167
|
+
import { callXrpc, getViewer } from "$hatk/client"
|
|
1168
|
+
\`\`\`
|
|
1169
|
+
|
|
1170
|
+
\`$hatk\` resolves to \`hatk.generated.ts\` and \`$hatk/client\` to \`hatk.generated.client.ts\`.
|
|
1171
|
+
The Vite plugin handles this in dev/build. In tests and production, a Node.js module resolve hook handles it.
|
|
1172
|
+
`;
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
agentsMd += `
|
|
1176
|
+
\`$hatk\` resolves to \`hatk.generated.ts\`. The Vite plugin handles this in dev/build.
|
|
1177
|
+
In tests and production, a Node.js module resolve hook handles it.
|
|
1178
|
+
`;
|
|
1179
|
+
}
|
|
1180
|
+
agentsMd += `
|
|
1181
|
+
## Commands
|
|
1182
|
+
|
|
1183
|
+
Run \`npx hatk --help\` for the full list of commands.
|
|
1184
|
+
|
|
1185
|
+
Use \`npx hatk generate\` to scaffold new feeds, xrpc handlers, labels, and lexicons
|
|
1186
|
+
rather than creating files manually. These generate files with the correct imports.
|
|
1187
|
+
|
|
1188
|
+
After modifying lexicons, always run \`npx hatk generate types\` to update the generated types.
|
|
1189
|
+
`;
|
|
1190
|
+
if (withSvelte) {
|
|
1191
|
+
agentsMd += `
|
|
1192
|
+
## Running
|
|
1193
|
+
|
|
1194
|
+
- \`vp dev\` — start dev server (hatk + SvelteKit + PDS)
|
|
1195
|
+
- \`vp build\` — build for production (SvelteKit outputs to \`build/\`)
|
|
1196
|
+
- \`hatk start\` — start production server (hatk + SvelteKit via \`build/handler.js\`)
|
|
1197
|
+
- \`vp test\` — run tests
|
|
1198
|
+
`;
|
|
1199
|
+
}
|
|
1200
|
+
writeFileSync(join(dir, 'AGENTS.md'), agentsMd);
|
|
1034
1201
|
console.log(`Created ${name}/`);
|
|
1035
|
-
console.log(` config.
|
|
1202
|
+
console.log(` hatk.config.ts`);
|
|
1036
1203
|
console.log(` lexicons/ — lexicon JSON files (core + your own)`);
|
|
1037
|
-
console.log(`
|
|
1038
|
-
console.log(` xrpc/ — XRPC method handlers`);
|
|
1039
|
-
console.log(` og/ — OpenGraph image routes`);
|
|
1040
|
-
console.log(` labels/ — label definitions + rules`);
|
|
1041
|
-
console.log(` jobs/ — periodic tasks`);
|
|
1204
|
+
console.log(` server/ — feeds, XRPC handlers, hooks, labels, OG routes, setup`);
|
|
1042
1205
|
console.log(` seeds/ — seed fixture data (hatk seed)`);
|
|
1043
|
-
console.log(` setup/ — boot-time setup scripts (run before server starts)`);
|
|
1044
1206
|
console.log(` test/ — test files (hatk test)`);
|
|
1045
1207
|
console.log(` public/ — static files`);
|
|
1046
1208
|
console.log(` docker-compose.yml — local PDS for development`);
|
|
@@ -1079,6 +1241,21 @@ else if (command === 'generate') {
|
|
|
1079
1241
|
}
|
|
1080
1242
|
}
|
|
1081
1243
|
entries.sort((a, b) => a.nsid.localeCompare(b.nsid));
|
|
1244
|
+
// Collect procedure nsids and blob-input nsids for client callXrpc
|
|
1245
|
+
const procedureNsids = [];
|
|
1246
|
+
const blobInputNsids = [];
|
|
1247
|
+
for (const { nsid, defType } of entries) {
|
|
1248
|
+
if (defType === 'procedure') {
|
|
1249
|
+
const lex = lexicons.get(nsid);
|
|
1250
|
+
const inputEncoding = lex?.defs?.main?.input?.encoding;
|
|
1251
|
+
if (inputEncoding === '*/*') {
|
|
1252
|
+
blobInputNsids.push(nsid);
|
|
1253
|
+
}
|
|
1254
|
+
else {
|
|
1255
|
+
procedureNsids.push(nsid);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1082
1259
|
if (entries.length === 0) {
|
|
1083
1260
|
console.error('No lexicons found');
|
|
1084
1261
|
process.exit(1);
|
|
@@ -1126,7 +1303,8 @@ else if (command === 'generate') {
|
|
|
1126
1303
|
let out = '// Auto-generated from lexicons. Do not edit.\n';
|
|
1127
1304
|
out += `import type { ${[...usedWrappers].sort().join(', ')}, LexServerParams, Checked, Prettify, StrictArg } from '@hatk/hatk/lex-types'\n`;
|
|
1128
1305
|
out += `import type { XrpcContext } from '@hatk/hatk/xrpc'\n`;
|
|
1129
|
-
out += `import {
|
|
1306
|
+
out += `import { callXrpc as _callXrpc } from '@hatk/hatk/xrpc'\n`;
|
|
1307
|
+
out += `import { defineFeed as _defineFeed, type FeedResult, type FeedContext, type BaseContext, type Row } from '@hatk/hatk/feeds'\n`;
|
|
1130
1308
|
out += `import { seed as _seed, type SeedOpts } from '@hatk/hatk/seed'\n`;
|
|
1131
1309
|
// Emit ALL lexicons as `const ... = {...} as const` (including defs-only)
|
|
1132
1310
|
out += `\n// ─── Lexicon Definitions ────────────────────────────────────────────\n\n`;
|
|
@@ -1282,8 +1460,13 @@ else if (command === 'generate') {
|
|
|
1282
1460
|
out += `}\n`;
|
|
1283
1461
|
// Emit Ctx helper for typesafe XRPC handler contexts
|
|
1284
1462
|
out += `\n// ─── XRPC Helpers ───────────────────────────────────────────────────\n\n`;
|
|
1285
|
-
out += `export type {
|
|
1463
|
+
out += `export type { BaseContext, Row } from '@hatk/hatk/feeds'\n`;
|
|
1286
1464
|
out += `export { InvalidRequestError, NotFoundError } from '@hatk/hatk/xrpc'\n`;
|
|
1465
|
+
out += `export { defineSetup } from '@hatk/hatk/setup'\n`;
|
|
1466
|
+
out += `export { defineHook } from '@hatk/hatk/hooks'\n`;
|
|
1467
|
+
out += `export { defineLabel } from '@hatk/hatk/labels'\n`;
|
|
1468
|
+
out += `export { defineOG } from '@hatk/hatk/opengraph'\n`;
|
|
1469
|
+
out += `export { defineRenderer } from '@hatk/hatk/renderer'\n`;
|
|
1287
1470
|
out += `export type Ctx<K extends keyof XrpcSchema & keyof Registry> = XrpcContext<\n`;
|
|
1288
1471
|
out += ` LexServerParams<Registry[K], Registry>,\n`;
|
|
1289
1472
|
out += ` RecordRegistry,\n`;
|
|
@@ -1296,21 +1479,29 @@ else if (command === 'generate') {
|
|
|
1296
1479
|
out += ` nsid: K,\n`;
|
|
1297
1480
|
out += ` handler: (ctx: Ctx<K> & { ok: <T extends OutputOf<K>>(value: StrictArg<T, OutputOf<K>>) => Checked<OutputOf<K>> }) => Promise<Checked<OutputOf<K>>>,\n`;
|
|
1298
1481
|
out += `) {\n`;
|
|
1299
|
-
out += ` return { handler: (ctx: any) => handler({ ...ctx, ok: (v: any) => v }) }\n`;
|
|
1482
|
+
out += ` return { __type: 'query' as const, nsid, handler: (ctx: any) => handler({ ...ctx, ok: (v: any) => v }) }\n`;
|
|
1300
1483
|
out += `}\n\n`;
|
|
1301
1484
|
out += `export function defineProcedure<K extends keyof XrpcSchema & string>(\n`;
|
|
1302
1485
|
out += ` nsid: K,\n`;
|
|
1303
1486
|
out += ` handler: (ctx: Ctx<K> & { ok: <T extends OutputOf<K>>(value: StrictArg<T, OutputOf<K>>) => Checked<OutputOf<K>> }) => Promise<Checked<OutputOf<K>>>,\n`;
|
|
1304
1487
|
out += `) {\n`;
|
|
1305
|
-
out += ` return { handler: (ctx: any) => handler({ ...ctx, ok: (v: any) => v }) }\n`;
|
|
1488
|
+
out += ` return { __type: 'procedure' as const, nsid, handler: (ctx: any) => handler({ ...ctx, ok: (v: any) => v }) }\n`;
|
|
1489
|
+
out += `}\n\n`;
|
|
1490
|
+
out += `// ─── Server-side XRPC Caller ────────────────────────────────────────\n\n`;
|
|
1491
|
+
out += `type ExtractParams<T> = T extends { params: infer P } ? P : Record<string, unknown>\n`;
|
|
1492
|
+
out += `export async function callXrpc<K extends keyof XrpcSchema & string>(\n`;
|
|
1493
|
+
out += ` nsid: K,\n`;
|
|
1494
|
+
out += ` params?: ExtractParams<XrpcSchema[K]>,\n`;
|
|
1495
|
+
out += `): Promise<OutputOf<K>> {\n`;
|
|
1496
|
+
out += ` return _callXrpc(nsid, params as any) as Promise<OutputOf<K>>\n`;
|
|
1306
1497
|
out += `}\n\n`;
|
|
1307
1498
|
out += `// ─── Feed & Seed Helpers ────────────────────────────────────────────\n\n`;
|
|
1308
1499
|
out += `type FeedGenerate = (ctx: FeedContext & { ok: (value: FeedResult) => Checked<FeedResult> }) => Promise<Checked<FeedResult>>\n`;
|
|
1309
1500
|
out += `export function defineFeed<K extends keyof RecordRegistry>(\n`;
|
|
1310
|
-
out += ` opts: { collection: K; view?: string; label: string; generate: FeedGenerate; hydrate?: (ctx:
|
|
1501
|
+
out += ` opts: { collection: K; view?: string; label: string; generate: FeedGenerate; hydrate?: (ctx: BaseContext, items: Row<RecordRegistry[K]>[]) => Promise<unknown[]> }\n`;
|
|
1311
1502
|
out += `): ReturnType<typeof _defineFeed>\n`;
|
|
1312
1503
|
out += `export function defineFeed(\n`;
|
|
1313
|
-
out += ` opts: { collection?: never; view?: never; label: string; generate: FeedGenerate; hydrate: (ctx:
|
|
1504
|
+
out += ` opts: { collection?: never; view?: never; label: string; generate: FeedGenerate; hydrate: (ctx: BaseContext, items: Row<unknown>[]) => Promise<unknown[]> }\n`;
|
|
1314
1505
|
out += `): ReturnType<typeof _defineFeed>\n`;
|
|
1315
1506
|
out += `export function defineFeed(opts: any) { return _defineFeed(opts) }\n`;
|
|
1316
1507
|
out += `export function seed(opts?: SeedOpts) { return _seed<RecordRegistry>(opts) }\n`;
|
|
@@ -1333,7 +1524,145 @@ else if (command === 'generate') {
|
|
|
1333
1524
|
out = out.replace(/import type \{ ([^}]+) \} from '@hatk\/hatk\/lex-types'/, `import type { ${[...usedWrappers].sort().join(', ')}, LexServerParams, Checked, Prettify, StrictArg } from '@hatk/hatk/lex-types'`);
|
|
1334
1525
|
}
|
|
1335
1526
|
writeFileSync(outPath, out);
|
|
1527
|
+
// Generate client-safe version (types + callXrpc only, no server module re-exports)
|
|
1528
|
+
// Types use `export type` from main file (erased at compile time, no runtime import).
|
|
1529
|
+
// callXrpc imports from @hatk/hatk/xrpc directly to avoid pulling in server deps.
|
|
1530
|
+
let clientOut = '// Auto-generated client-safe subset. Do not edit.\n';
|
|
1531
|
+
clientOut += `// Import this in app components instead of hatk.generated.ts\n`;
|
|
1532
|
+
clientOut += `// to avoid pulling in server-only dependencies.\n`;
|
|
1533
|
+
clientOut += `export type { XrpcSchema } from './hatk.generated.ts'\n`;
|
|
1534
|
+
clientOut += `import type { XrpcSchema } from './hatk.generated.ts'\n`;
|
|
1535
|
+
// Re-export all types
|
|
1536
|
+
const typeExports = [];
|
|
1537
|
+
for (const { nsid, defType } of entries) {
|
|
1538
|
+
if (!defType)
|
|
1539
|
+
continue;
|
|
1540
|
+
if (nsid === 'dev.hatk.createRecord' || nsid === 'dev.hatk.deleteRecord' || nsid === 'dev.hatk.putRecord')
|
|
1541
|
+
continue;
|
|
1542
|
+
typeExports.push(capitalize(varNames.get(nsid)));
|
|
1543
|
+
}
|
|
1544
|
+
if (recordEntries.length > 0) {
|
|
1545
|
+
typeExports.push('RecordRegistry', 'CreateRecord', 'DeleteRecord', 'PutRecord');
|
|
1546
|
+
}
|
|
1547
|
+
// Named defs (views, objects) — collect from emittedDefNames minus main types
|
|
1548
|
+
const mainTypeNames = new Set(entries.filter((e) => e.defType).map((e) => capitalize(varNames.get(e.nsid))));
|
|
1549
|
+
for (const name of emittedDefNames) {
|
|
1550
|
+
if (!mainTypeNames.has(name) && !typeExports.includes(name)) {
|
|
1551
|
+
typeExports.push(name);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
if (typeExports.length > 0) {
|
|
1555
|
+
clientOut += `export type { ${typeExports.join(', ')} } from './hatk.generated.ts'\n`;
|
|
1556
|
+
}
|
|
1557
|
+
// Typed callXrpc — environment-aware:
|
|
1558
|
+
// SSR: uses globalThis.__hatk_callXrpc bridge (direct handler invocation)
|
|
1559
|
+
// Client: fetches via HTTP (GET for queries, POST for procedures, raw POST for blobs)
|
|
1560
|
+
if (procedureNsids.length > 0) {
|
|
1561
|
+
clientOut += `\nconst _procedures = new Set([${procedureNsids.map((n) => `'${n}'`).join(', ')}])\n`;
|
|
1562
|
+
}
|
|
1563
|
+
if (blobInputNsids.length > 0) {
|
|
1564
|
+
clientOut += `const _blobInputs = new Set([${blobInputNsids.map((n) => `'${n}'`).join(', ')}])\n`;
|
|
1565
|
+
}
|
|
1566
|
+
clientOut += `\ntype CallArg<K extends keyof XrpcSchema> =\n`;
|
|
1567
|
+
clientOut += ` XrpcSchema[K] extends { input: infer I } ? I :\n`;
|
|
1568
|
+
clientOut += ` XrpcSchema[K] extends { params: infer P } ? P :\n`;
|
|
1569
|
+
clientOut += ` Record<string, unknown>\n`;
|
|
1570
|
+
clientOut += `type OutputOf<K extends keyof XrpcSchema> = XrpcSchema[K]['output']\n\n`;
|
|
1571
|
+
clientOut += `export async function callXrpc<K extends keyof XrpcSchema & string>(\n`;
|
|
1572
|
+
clientOut += ` nsid: K,\n`;
|
|
1573
|
+
clientOut += ` arg?: CallArg<K>,\n`;
|
|
1574
|
+
clientOut += ` customFetch?: typeof globalThis.fetch,\n`;
|
|
1575
|
+
clientOut += `): Promise<OutputOf<K>> {\n`;
|
|
1576
|
+
// Server-side bridge (skip when customFetch is provided — let SvelteKit's fetch handle it)
|
|
1577
|
+
clientOut += ` if (typeof window === 'undefined' && !customFetch) {\n`;
|
|
1578
|
+
clientOut += ` const bridge = (globalThis as any).__hatk_callXrpc\n`;
|
|
1579
|
+
clientOut += ` if (!bridge) throw new Error('callXrpc: server bridge not available — is hatk initialized?')\n`;
|
|
1580
|
+
if (procedureNsids.length > 0 || blobInputNsids.length > 0) {
|
|
1581
|
+
const checks = [];
|
|
1582
|
+
if (procedureNsids.length > 0)
|
|
1583
|
+
checks.push('_procedures.has(nsid)');
|
|
1584
|
+
if (blobInputNsids.length > 0)
|
|
1585
|
+
checks.push('_blobInputs.has(nsid)');
|
|
1586
|
+
clientOut += ` if (${checks.join(' || ')}) return bridge(nsid, {}, arg) as Promise<OutputOf<K>>\n`;
|
|
1587
|
+
}
|
|
1588
|
+
clientOut += ` return bridge(nsid, arg) as Promise<OutputOf<K>>\n`;
|
|
1589
|
+
clientOut += ` }\n`;
|
|
1590
|
+
// Client-side fetch (or server-side with customFetch for SSR deduplication)
|
|
1591
|
+
clientOut += ` const _fetch = customFetch ?? globalThis.fetch\n`;
|
|
1592
|
+
clientOut += ` // Use relative URL so SvelteKit's fetch can deduplicate server/client requests\n`;
|
|
1593
|
+
clientOut += ` let path = \`/xrpc/\${nsid}\`\n`;
|
|
1594
|
+
if (blobInputNsids.length > 0) {
|
|
1595
|
+
clientOut += ` if (_blobInputs.has(nsid)) {\n`;
|
|
1596
|
+
clientOut += ` const blob = arg as Blob | ArrayBuffer\n`;
|
|
1597
|
+
clientOut += ` const ct = blob instanceof Blob ? blob.type : 'application/octet-stream'\n`;
|
|
1598
|
+
clientOut += ` const res = await _fetch(path, { method: 'POST', headers: { 'Content-Type': ct }, body: blob })\n`;
|
|
1599
|
+
clientOut += ` if (!res.ok) throw new Error(\`XRPC \${nsid} failed: \${res.status}\`)\n`;
|
|
1600
|
+
clientOut += ` return res.json() as Promise<OutputOf<K>>\n`;
|
|
1601
|
+
clientOut += ` }\n`;
|
|
1602
|
+
}
|
|
1603
|
+
if (procedureNsids.length > 0) {
|
|
1604
|
+
clientOut += ` if (_procedures.has(nsid)) {\n`;
|
|
1605
|
+
clientOut += ` const res = await _fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(arg) })\n`;
|
|
1606
|
+
clientOut += ` if (typeof window !== 'undefined' && res.status === 401) { const _h = getViewer()?.handle; window.location.href = _h ? \`/oauth/login?handle=\${encodeURIComponent(_h)}\` : '/oauth/login'; return new Promise(() => {}) as any }\n`;
|
|
1607
|
+
clientOut += ` if (!res.ok) throw new Error(\`XRPC \${nsid} failed: \${res.status}\`)\n`;
|
|
1608
|
+
clientOut += ` return res.json() as Promise<OutputOf<K>>\n`;
|
|
1609
|
+
clientOut += ` }\n`;
|
|
1610
|
+
}
|
|
1611
|
+
clientOut += ` const params = new URLSearchParams()\n`;
|
|
1612
|
+
clientOut += ` for (const [k, v] of Object.entries(arg || {})) {\n`;
|
|
1613
|
+
clientOut += ` if (v != null) params.set(k, String(v))\n`;
|
|
1614
|
+
clientOut += ` }\n`;
|
|
1615
|
+
clientOut += ` const qs = params.toString()\n`;
|
|
1616
|
+
clientOut += ` if (qs) path += \`?\${qs}\`\n`;
|
|
1617
|
+
clientOut += ` const res = await _fetch(path)\n`;
|
|
1618
|
+
clientOut += ` if (typeof window !== 'undefined' && res.status === 401) { window.location.href = '/oauth/login'; return new Promise(() => {}) as any }\n`;
|
|
1619
|
+
clientOut += ` if (!res.ok) throw new Error(\`XRPC \${nsid} failed: \${res.status}\`)\n`;
|
|
1620
|
+
clientOut += ` return res.json() as Promise<OutputOf<K>>\n`;
|
|
1621
|
+
clientOut += `}\n`;
|
|
1622
|
+
// getViewer — returns the viewer set by layout load (server) or $effect (client)
|
|
1623
|
+
clientOut += `\nexport function getViewer(): { did: string; handle: string } | null {\n`;
|
|
1624
|
+
clientOut += ` return (globalThis as any).__hatk_viewer ?? null\n`;
|
|
1625
|
+
clientOut += `}\n`;
|
|
1626
|
+
// Auth helpers — login, logout, viewerDid
|
|
1627
|
+
clientOut += `\n// ─── Auth Helpers ────────────────────────────────────────────────────\n\n`;
|
|
1628
|
+
clientOut += `export async function login(handle: string): Promise<void> {\n`;
|
|
1629
|
+
clientOut += ` const res = await fetch(\`/oauth/login?handle=\${encodeURIComponent(handle)}\`, { redirect: 'manual' })\n`;
|
|
1630
|
+
clientOut += ` if (res.type === 'opaqueredirect') {\n`;
|
|
1631
|
+
clientOut += ` window.location.href = \`/oauth/login?handle=\${encodeURIComponent(handle)}\`\n`;
|
|
1632
|
+
clientOut += ` return\n`;
|
|
1633
|
+
clientOut += ` }\n`;
|
|
1634
|
+
clientOut += ` if (res.ok) return\n`;
|
|
1635
|
+
clientOut += ` const body = await res.json().catch(() => ({ error: 'Login failed' }))\n`;
|
|
1636
|
+
clientOut += ` throw new Error(body.error || 'Login failed')\n`;
|
|
1637
|
+
clientOut += `}\n\n`;
|
|
1638
|
+
clientOut += `export async function logout(): Promise<void> {\n`;
|
|
1639
|
+
clientOut += ` ;(globalThis as any).__hatk_viewer = null\n`;
|
|
1640
|
+
clientOut += ` await fetch('/auth/logout', { method: 'POST' }).catch(() => {})\n`;
|
|
1641
|
+
clientOut += `}\n\n`;
|
|
1642
|
+
clientOut += `export function viewerDid(): string | null {\n`;
|
|
1643
|
+
clientOut += ` if (typeof window === 'undefined') return null\n`;
|
|
1644
|
+
clientOut += ` const viewer = (globalThis as any).__hatk_viewer\n`;
|
|
1645
|
+
clientOut += ` return viewer?.did ?? null\n`;
|
|
1646
|
+
clientOut += `}\n\n`;
|
|
1647
|
+
clientOut += `// Expose viewer for getViewer() bridge\n`;
|
|
1648
|
+
clientOut += `;(globalThis as any).__hatk_auth = { viewerDid }\n`;
|
|
1649
|
+
// parseViewer — server-side session cookie resolution for +layout.server.ts
|
|
1650
|
+
clientOut += `\n// ─── Server Helpers ──────────────────────────────────────────────────\n\n`;
|
|
1651
|
+
clientOut += `export async function parseViewer(cookies: { get(name: string): string | undefined }): Promise<{ did: string; handle?: string } | null> {\n`;
|
|
1652
|
+
clientOut += ` const parseSessionCookie = (globalThis as any).__hatk_parseSessionCookie\n`;
|
|
1653
|
+
clientOut += ` if (!parseSessionCookie) return null\n`;
|
|
1654
|
+
clientOut += ` const cookieValue = cookies.get('__hatk_session')\n`;
|
|
1655
|
+
clientOut += ` if (!cookieValue) return null\n`;
|
|
1656
|
+
clientOut += ` try {\n`;
|
|
1657
|
+
clientOut += ` const request = new Request('http://localhost', { headers: { cookie: \`__hatk_session=\${cookieValue}\` } })\n`;
|
|
1658
|
+
clientOut += ` const viewer = await parseSessionCookie(request)\n`;
|
|
1659
|
+
clientOut += ` if (viewer) (globalThis as any).__hatk_viewer = viewer\n`;
|
|
1660
|
+
clientOut += ` return viewer\n`;
|
|
1661
|
+
clientOut += ` } catch { return null }\n`;
|
|
1662
|
+
clientOut += `}\n`;
|
|
1663
|
+
writeFileSync('./hatk.generated.client.ts', clientOut);
|
|
1336
1664
|
console.log(`Generated ${outPath} with ${entries.length} types: ${entries.map((e) => capitalize(varNames.get(e.nsid))).join(', ')}`);
|
|
1665
|
+
console.log(`Generated ./hatk.generated.client.ts (client-safe subset)`);
|
|
1337
1666
|
}
|
|
1338
1667
|
else if (lexiconTemplates[type]) {
|
|
1339
1668
|
const nsid = args[2];
|
|
@@ -1361,18 +1690,9 @@ else if (command === 'generate') {
|
|
|
1361
1690
|
process.exit(1);
|
|
1362
1691
|
}
|
|
1363
1692
|
const baseDir = dirs[type];
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
const parts = name.split('.');
|
|
1368
|
-
const subDir = join(baseDir, ...parts.slice(0, -1));
|
|
1369
|
-
mkdirSync(subDir, { recursive: true });
|
|
1370
|
-
filePath = join(subDir, `${parts[parts.length - 1]}.ts`);
|
|
1371
|
-
}
|
|
1372
|
-
else {
|
|
1373
|
-
mkdirSync(baseDir, { recursive: true });
|
|
1374
|
-
filePath = join(baseDir, `${name}.ts`);
|
|
1375
|
-
}
|
|
1693
|
+
mkdirSync(baseDir, { recursive: true });
|
|
1694
|
+
const fileName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1695
|
+
const filePath = join(baseDir, `${fileName}.ts`);
|
|
1376
1696
|
if (existsSync(filePath)) {
|
|
1377
1697
|
console.error(`${filePath} already exists`);
|
|
1378
1698
|
process.exit(1);
|
|
@@ -1382,7 +1702,7 @@ else if (command === 'generate') {
|
|
|
1382
1702
|
// Scaffold test file if template exists
|
|
1383
1703
|
const testTemplate = testTemplates[type];
|
|
1384
1704
|
if (testTemplate) {
|
|
1385
|
-
const testDir =
|
|
1705
|
+
const testDir = 'test/server';
|
|
1386
1706
|
mkdirSync(testDir, { recursive: true });
|
|
1387
1707
|
const testName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1388
1708
|
const testPath = join(testDir, `${testName}.test.ts`);
|
|
@@ -1401,18 +1721,9 @@ else if (command === 'destroy') {
|
|
|
1401
1721
|
process.exit(1);
|
|
1402
1722
|
}
|
|
1403
1723
|
const baseDir = dirs[type];
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
const leaf = parts[parts.length - 1];
|
|
1408
|
-
const subDir = join(baseDir, ...parts.slice(0, -1));
|
|
1409
|
-
tsPath = join(subDir, `${leaf}.ts`);
|
|
1410
|
-
jsPath = join(subDir, `${leaf}.js`);
|
|
1411
|
-
}
|
|
1412
|
-
else {
|
|
1413
|
-
tsPath = join(baseDir, `${name}.ts`);
|
|
1414
|
-
jsPath = join(baseDir, `${name}.js`);
|
|
1415
|
-
}
|
|
1724
|
+
const fileName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1725
|
+
const tsPath = join(baseDir, `${fileName}.ts`);
|
|
1726
|
+
const jsPath = join(baseDir, `${fileName}.js`);
|
|
1416
1727
|
const filePath = existsSync(tsPath) ? tsPath : existsSync(jsPath) ? jsPath : null;
|
|
1417
1728
|
if (!filePath) {
|
|
1418
1729
|
console.error(`No file found for ${type} "${name}"`);
|
|
@@ -1421,7 +1732,7 @@ else if (command === 'destroy') {
|
|
|
1421
1732
|
unlinkSync(filePath);
|
|
1422
1733
|
console.log(`Removed ${filePath}`);
|
|
1423
1734
|
// Clean up test file
|
|
1424
|
-
const testDir =
|
|
1735
|
+
const testDir = 'test/server';
|
|
1425
1736
|
const testName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1426
1737
|
const testFile = join(testDir, `${testName}.test.ts`);
|
|
1427
1738
|
if (existsSync(testFile)) {
|
|
@@ -1435,25 +1746,14 @@ else if (command === 'destroy') {
|
|
|
1435
1746
|
else if (command === 'dev') {
|
|
1436
1747
|
await ensurePds();
|
|
1437
1748
|
runSeed();
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
execSync('npx vite dev', { stdio: 'inherit', cwd: process.cwd() });
|
|
1442
|
-
}
|
|
1443
|
-
else {
|
|
1444
|
-
// No frontend — just run the hatk server directly
|
|
1445
|
-
const mainPath = resolve(import.meta.dirname, 'main.js');
|
|
1446
|
-
execSync(`npx tsx ${mainPath} config.yaml`, {
|
|
1447
|
-
stdio: 'inherit',
|
|
1448
|
-
cwd: process.cwd(),
|
|
1449
|
-
env: { ...process.env, DEV_MODE: '1' },
|
|
1450
|
-
});
|
|
1451
|
-
}
|
|
1749
|
+
if (existsSync(resolve('vite.config.ts')) || existsSync(resolve('vite.config.js'))) {
|
|
1750
|
+
// Vite project — vite dev starts the hatk server via the plugin
|
|
1751
|
+
await spawnForward('npx', ['vite', 'dev']);
|
|
1452
1752
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1753
|
+
else {
|
|
1754
|
+
// No frontend — just run the hatk server directly
|
|
1755
|
+
const mainPath = resolve(import.meta.dirname, 'main.js');
|
|
1756
|
+
await spawnForward('npx', ['tsx', mainPath, 'hatk.config.ts'], { DEV_MODE: '1' });
|
|
1457
1757
|
}
|
|
1458
1758
|
}
|
|
1459
1759
|
else if (command === 'format' || command === 'fmt') {
|
|
@@ -1473,9 +1773,9 @@ else if (command === 'build') {
|
|
|
1473
1773
|
}
|
|
1474
1774
|
}
|
|
1475
1775
|
else if (command === 'reset') {
|
|
1476
|
-
const config = loadConfig(resolve('config.
|
|
1776
|
+
const config = await loadConfig(resolve('hatk.config.ts'));
|
|
1477
1777
|
if (config.database !== ':memory:') {
|
|
1478
|
-
for (const suffix of ['', '.wal']) {
|
|
1778
|
+
for (const suffix of ['', '.wal', '-shm', '-wal']) {
|
|
1479
1779
|
const file = config.database + suffix;
|
|
1480
1780
|
if (existsSync(file)) {
|
|
1481
1781
|
unlinkSync(file);
|
|
@@ -1633,40 +1933,24 @@ else if (command === 'resolve') {
|
|
|
1633
1933
|
execSync('npx hatk generate types', { stdio: 'inherit', cwd: process.cwd() });
|
|
1634
1934
|
}
|
|
1635
1935
|
else if (command === 'schema') {
|
|
1636
|
-
const config = loadConfig(resolve('config.
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
const { DuckDBInstance } = await import('@duckdb/node-api');
|
|
1647
|
-
const instance = await DuckDBInstance.create(config.database);
|
|
1648
|
-
const con = await instance.connect();
|
|
1649
|
-
const tables = (await (await con.runAndReadAll(`SELECT table_name FROM information_schema.tables WHERE table_schema = 'main' ORDER BY table_name`)).getRowObjects());
|
|
1650
|
-
for (const { table_name } of tables) {
|
|
1651
|
-
console.log(`"${table_name}"`);
|
|
1652
|
-
const cols = (await (await con.runAndReadAll(`SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = '${table_name}' ORDER BY ordinal_position`)).getRowObjects());
|
|
1653
|
-
for (const col of cols) {
|
|
1654
|
-
const nullable = col.is_nullable === 'YES' ? '' : ' NOT NULL';
|
|
1655
|
-
console.log(` ${col.column_name.padEnd(20)} ${col.data_type}${nullable}`);
|
|
1656
|
-
}
|
|
1657
|
-
console.log();
|
|
1936
|
+
const config = await loadConfig(resolve('hatk.config.ts'));
|
|
1937
|
+
const { initDatabase, getSchemaDump } = await import("./database/db.js");
|
|
1938
|
+
const { createAdapter } = await import("./database/adapter-factory.js");
|
|
1939
|
+
const { getDialect } = await import("./database/dialect.js");
|
|
1940
|
+
const configDir2 = resolve('.');
|
|
1941
|
+
const lexicons2 = loadLexicons(resolve(configDir2, 'lexicons'));
|
|
1942
|
+
const collections2 = config.collections.length > 0 ? config.collections : discoverCollections(lexicons2);
|
|
1943
|
+
const { schemas: schemas2, ddlStatements: ddl2 } = buildSchemas(lexicons2, collections2, getDialect(config.databaseEngine));
|
|
1944
|
+
if (config.database !== ':memory:') {
|
|
1945
|
+
mkdirSync(dirname(config.database), { recursive: true });
|
|
1658
1946
|
}
|
|
1947
|
+
const { adapter: adapter2 } = await createAdapter(config.databaseEngine);
|
|
1948
|
+
await initDatabase(adapter2, config.database, schemas2, ddl2);
|
|
1949
|
+
console.log(await getSchemaDump());
|
|
1659
1950
|
}
|
|
1660
1951
|
else if (command === 'start') {
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
execSync(`npx tsx ${mainPath} config.yaml`, { stdio: 'inherit', cwd: process.cwd() });
|
|
1664
|
-
}
|
|
1665
|
-
catch (e) {
|
|
1666
|
-
if (e.signal === 'SIGINT' || e.signal === 'SIGTERM')
|
|
1667
|
-
process.exit(0);
|
|
1668
|
-
throw e;
|
|
1669
|
-
}
|
|
1952
|
+
const mainPath = resolve(import.meta.dirname, 'main.js');
|
|
1953
|
+
await spawnForward('npx', ['tsx', mainPath, 'hatk.config.ts']);
|
|
1670
1954
|
}
|
|
1671
1955
|
else {
|
|
1672
1956
|
usage();
|