@hatk/hatk 0.0.1-alpha.3 → 0.0.1-alpha.30
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 +94 -0
- package/dist/backfill.d.ts +60 -1
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +166 -32
- 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 +356 -123
- 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/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 +18 -0
- package/dist/database/adapters/sqlite-search.d.ts.map +1 -0
- package/dist/database/adapters/sqlite-search.js +38 -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 +149 -0
- package/dist/database/db.d.ts.map +1 -0
- package/dist/database/db.js +1460 -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 +24 -0
- package/dist/database/fts.d.ts.map +1 -0
- package/dist/database/fts.js +777 -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 +44 -0
- package/dist/database/ports.d.ts.map +1 -0
- package/dist/database/ports.js +1 -0
- package/dist/database/schema.d.ts +60 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +388 -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 +109 -0
- package/dist/feeds.d.ts +4 -0
- package/dist/feeds.d.ts.map +1 -1
- package/dist/feeds.js +42 -3
- 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.js +1 -1
- package/dist/indexer.d.ts +20 -0
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +48 -6
- package/dist/labels.d.ts +34 -0
- package/dist/labels.d.ts.map +1 -1
- package/dist/labels.js +63 -3
- 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 +131 -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 +41 -15
- 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 +9 -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 +103 -5
- package/dist/pds-proxy.d.ts +39 -0
- package/dist/pds-proxy.d.ts.map +1 -0
- package/dist/pds-proxy.js +173 -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 +59 -0
- package/dist/server.d.ts +26 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +487 -616
- 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 +252 -66
- package/dist/xrpc.d.ts +36 -0
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +124 -3
- package/package.json +12 -5
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
|
|
@@ -71,6 +96,8 @@ function usage() {
|
|
|
71
96
|
generate label <name> Generate a label definition
|
|
72
97
|
generate og <name> Generate an OpenGraph route
|
|
73
98
|
generate job <name> Generate a periodic job
|
|
99
|
+
generate hook <name> Generate a lifecycle hook
|
|
100
|
+
generate setup <name> Generate a setup script
|
|
74
101
|
generate types Regenerate TypeScript types from lexicons
|
|
75
102
|
destroy <type> <name> Remove a generated file
|
|
76
103
|
|
|
@@ -98,7 +125,7 @@ export default defineFeed({
|
|
|
98
125
|
},
|
|
99
126
|
})
|
|
100
127
|
`,
|
|
101
|
-
xrpc: (name) => `import { defineQuery } from '
|
|
128
|
+
xrpc: (name) => `import { defineQuery } from '../hatk.generated.ts'
|
|
102
129
|
|
|
103
130
|
export default defineQuery('${name}', async (ctx) => {
|
|
104
131
|
const { ok, db, params, packCursor, unpackCursor } = ctx
|
|
@@ -175,16 +202,20 @@ export default {
|
|
|
175
202
|
// Periodic task logic here
|
|
176
203
|
},
|
|
177
204
|
}
|
|
205
|
+
`,
|
|
206
|
+
hook: (name) => `import { defineHook } from '../hatk.generated.ts'
|
|
207
|
+
|
|
208
|
+
export default defineHook('${name}', async (ctx) => {
|
|
209
|
+
// Hook logic here
|
|
210
|
+
})
|
|
211
|
+
`,
|
|
212
|
+
setup: (_name) => `import { defineSetup } from '../hatk.generated.ts'
|
|
213
|
+
|
|
214
|
+
export default defineSetup(async (ctx) => {
|
|
215
|
+
// Setup logic here — runs before the server starts
|
|
216
|
+
})
|
|
178
217
|
`,
|
|
179
218
|
};
|
|
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
219
|
const testTemplates = {
|
|
189
220
|
feed: (name) => `import { describe, test, expect, beforeAll, afterAll } from 'vitest'
|
|
190
221
|
import { createTestContext } from '@hatk/hatk/test'
|
|
@@ -293,17 +324,19 @@ const lexiconTemplates = {
|
|
|
293
324
|
}),
|
|
294
325
|
};
|
|
295
326
|
const dirs = {
|
|
296
|
-
feed: '
|
|
297
|
-
xrpc: '
|
|
298
|
-
label: '
|
|
299
|
-
og: '
|
|
300
|
-
job: '
|
|
327
|
+
feed: 'server',
|
|
328
|
+
xrpc: 'server',
|
|
329
|
+
label: 'server',
|
|
330
|
+
og: 'server',
|
|
331
|
+
job: 'server',
|
|
332
|
+
hook: 'server',
|
|
333
|
+
setup: 'server',
|
|
301
334
|
};
|
|
302
335
|
// --- Commands ---
|
|
303
336
|
if (command === 'new') {
|
|
304
337
|
const name = args[1];
|
|
305
338
|
if (!name) {
|
|
306
|
-
console.error('Usage: hatk new <name> [--svelte] [--template <template-name>]');
|
|
339
|
+
console.error('Usage: hatk new <name> [--svelte] [--duckdb] [--template <template-name>]');
|
|
307
340
|
process.exit(1);
|
|
308
341
|
}
|
|
309
342
|
const templateIdx = args.indexOf('--template');
|
|
@@ -341,20 +374,16 @@ if (command === 'new') {
|
|
|
341
374
|
process.exit(0);
|
|
342
375
|
}
|
|
343
376
|
const withSvelte = args.includes('--svelte');
|
|
377
|
+
const withDuckdb = args.includes('--duckdb');
|
|
378
|
+
const dbEngine = withDuckdb ? 'duckdb' : 'sqlite';
|
|
344
379
|
mkdirSync(dir);
|
|
345
380
|
const subs = [
|
|
346
381
|
'lexicons',
|
|
347
|
-
'
|
|
348
|
-
'xrpc',
|
|
349
|
-
'og',
|
|
350
|
-
'labels',
|
|
351
|
-
'jobs',
|
|
382
|
+
'server',
|
|
352
383
|
'seeds',
|
|
353
|
-
'setup',
|
|
354
384
|
'public',
|
|
355
385
|
'test',
|
|
356
|
-
'test/
|
|
357
|
-
'test/xrpc',
|
|
386
|
+
'test/server',
|
|
358
387
|
'test/integration',
|
|
359
388
|
'test/browser',
|
|
360
389
|
'test/fixtures',
|
|
@@ -364,14 +393,19 @@ if (command === 'new') {
|
|
|
364
393
|
for (const sub of subs) {
|
|
365
394
|
mkdirSync(join(dir, sub));
|
|
366
395
|
}
|
|
367
|
-
writeFileSync(join(dir, 'config.
|
|
368
|
-
plc: http://localhost:2582
|
|
369
|
-
port: 3000
|
|
370
|
-
database: data/hatk.db
|
|
371
|
-
admins: []
|
|
396
|
+
writeFileSync(join(dir, 'hatk.config.ts'), `import { defineConfig } from '@hatk/hatk/config'
|
|
372
397
|
|
|
373
|
-
|
|
374
|
-
|
|
398
|
+
export default defineConfig({
|
|
399
|
+
relay: 'ws://localhost:2583',
|
|
400
|
+
plc: 'http://localhost:2582',
|
|
401
|
+
port: 3000,
|
|
402
|
+
databaseEngine: '${dbEngine}',
|
|
403
|
+
database: 'data/hatk.db',
|
|
404
|
+
admins: [],
|
|
405
|
+
backfill: {
|
|
406
|
+
parallelism: 10,
|
|
407
|
+
},
|
|
408
|
+
})
|
|
375
409
|
`);
|
|
376
410
|
writeFileSync(join(dir, 'public', 'index.html'), `<!DOCTYPE html>
|
|
377
411
|
<html><head><title>${name}</title></head>
|
|
@@ -507,6 +541,14 @@ backfill:
|
|
|
507
541
|
properties: {
|
|
508
542
|
uri: { type: 'string', format: 'at-uri' },
|
|
509
543
|
cid: { type: 'string', format: 'cid' },
|
|
544
|
+
commit: {
|
|
545
|
+
type: 'object',
|
|
546
|
+
properties: {
|
|
547
|
+
cid: { type: 'string', format: 'cid' },
|
|
548
|
+
rev: { type: 'string' },
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
validationStatus: { type: 'string' },
|
|
510
552
|
},
|
|
511
553
|
},
|
|
512
554
|
},
|
|
@@ -562,6 +604,14 @@ backfill:
|
|
|
562
604
|
properties: {
|
|
563
605
|
uri: { type: 'string', format: 'at-uri' },
|
|
564
606
|
cid: { type: 'string', format: 'cid' },
|
|
607
|
+
commit: {
|
|
608
|
+
type: 'object',
|
|
609
|
+
properties: {
|
|
610
|
+
cid: { type: 'string', format: 'cid' },
|
|
611
|
+
rev: { type: 'string' },
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
validationStatus: { type: 'string' },
|
|
565
615
|
},
|
|
566
616
|
},
|
|
567
617
|
},
|
|
@@ -591,6 +641,53 @@ backfill:
|
|
|
591
641
|
},
|
|
592
642
|
},
|
|
593
643
|
}, null, 2) + '\n');
|
|
644
|
+
writeFileSync(join(coreLexDir, 'getPreferences.json'), JSON.stringify({
|
|
645
|
+
lexicon: 1,
|
|
646
|
+
id: 'dev.hatk.getPreferences',
|
|
647
|
+
defs: {
|
|
648
|
+
main: {
|
|
649
|
+
type: 'query',
|
|
650
|
+
description: 'Get all preferences for the authenticated user.',
|
|
651
|
+
output: {
|
|
652
|
+
encoding: 'application/json',
|
|
653
|
+
schema: {
|
|
654
|
+
type: 'object',
|
|
655
|
+
properties: {
|
|
656
|
+
preferences: { type: 'unknown' },
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
},
|
|
662
|
+
}, null, 2) + '\n');
|
|
663
|
+
writeFileSync(join(coreLexDir, 'putPreference.json'), JSON.stringify({
|
|
664
|
+
lexicon: 1,
|
|
665
|
+
id: 'dev.hatk.putPreference',
|
|
666
|
+
defs: {
|
|
667
|
+
main: {
|
|
668
|
+
type: 'procedure',
|
|
669
|
+
description: 'Set a single preference by key.',
|
|
670
|
+
input: {
|
|
671
|
+
encoding: 'application/json',
|
|
672
|
+
schema: {
|
|
673
|
+
type: 'object',
|
|
674
|
+
required: ['key', 'value'],
|
|
675
|
+
properties: {
|
|
676
|
+
key: { type: 'string' },
|
|
677
|
+
value: { type: 'unknown' },
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
output: {
|
|
682
|
+
encoding: 'application/json',
|
|
683
|
+
schema: {
|
|
684
|
+
type: 'object',
|
|
685
|
+
properties: {},
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
}, null, 2) + '\n');
|
|
594
691
|
writeFileSync(join(coreLexDir, 'getFeed.json'), JSON.stringify({
|
|
595
692
|
lexicon: 1,
|
|
596
693
|
id: 'dev.hatk.getFeed',
|
|
@@ -611,6 +708,7 @@ backfill:
|
|
|
611
708
|
encoding: 'application/json',
|
|
612
709
|
schema: {
|
|
613
710
|
type: 'object',
|
|
711
|
+
required: ['items'],
|
|
614
712
|
properties: {
|
|
615
713
|
items: { type: 'array', items: { type: 'unknown' } },
|
|
616
714
|
cursor: { type: 'string' },
|
|
@@ -668,6 +766,7 @@ backfill:
|
|
|
668
766
|
encoding: 'application/json',
|
|
669
767
|
schema: {
|
|
670
768
|
type: 'object',
|
|
769
|
+
required: ['items'],
|
|
671
770
|
properties: {
|
|
672
771
|
items: { type: 'array', items: { type: 'unknown' } },
|
|
673
772
|
cursor: { type: 'string' },
|
|
@@ -699,6 +798,7 @@ backfill:
|
|
|
699
798
|
encoding: 'application/json',
|
|
700
799
|
schema: {
|
|
701
800
|
type: 'object',
|
|
801
|
+
required: ['items'],
|
|
702
802
|
properties: {
|
|
703
803
|
items: { type: 'array', items: { type: 'unknown' } },
|
|
704
804
|
cursor: { type: 'string' },
|
|
@@ -793,13 +893,17 @@ public
|
|
|
793
893
|
writeFileSync(join(dir, 'Dockerfile'), `FROM node:25-slim
|
|
794
894
|
WORKDIR /app
|
|
795
895
|
COPY package.json package-lock.json ./
|
|
796
|
-
RUN npm ci
|
|
896
|
+
RUN npm ci
|
|
797
897
|
COPY . .
|
|
798
898
|
RUN node_modules/.bin/hatk build
|
|
899
|
+
RUN npm prune --omit=dev
|
|
799
900
|
EXPOSE 3000
|
|
800
|
-
CMD ["node", "node_modules/@hatk/hatk/dist/main.js", "config.
|
|
901
|
+
CMD ["node", "--experimental-strip-types", "--max-old-space-size=512", "node_modules/@hatk/hatk/dist/main.js", "hatk.config.ts"]
|
|
801
902
|
`);
|
|
802
903
|
const pkgDeps = { '@hatk/oauth-client': '*', hatk: '*' };
|
|
904
|
+
if (!withDuckdb) {
|
|
905
|
+
pkgDeps['better-sqlite3'] = '^11';
|
|
906
|
+
}
|
|
803
907
|
const pkgDevDeps = {
|
|
804
908
|
'@playwright/test': '^1',
|
|
805
909
|
oxfmt: '^0.35.0',
|
|
@@ -807,6 +911,7 @@ CMD ["node", "node_modules/@hatk/hatk/dist/main.js", "config.yaml"]
|
|
|
807
911
|
typescript: '^5',
|
|
808
912
|
vite: '^6',
|
|
809
913
|
vitest: '^4',
|
|
914
|
+
'@types/node': '^22',
|
|
810
915
|
};
|
|
811
916
|
if (withSvelte) {
|
|
812
917
|
pkgDevDeps['@sveltejs/adapter-static'] = '^3';
|
|
@@ -841,7 +946,7 @@ CMD ["node", "node_modules/@hatk/hatk/dist/main.js", "config.yaml"]
|
|
|
841
946
|
allowImportingTsExtensions: true,
|
|
842
947
|
resolveJsonModule: true,
|
|
843
948
|
},
|
|
844
|
-
include: ['
|
|
949
|
+
include: ['server', 'seeds', 'hatk.generated.ts', 'hatk.config.ts'],
|
|
845
950
|
}, null, 2) + '\n');
|
|
846
951
|
writeFileSync(join(dir, 'playwright.config.ts'), `import { defineConfig } from '@playwright/test'
|
|
847
952
|
|
|
@@ -1026,16 +1131,42 @@ a {
|
|
|
1026
1131
|
</div>
|
|
1027
1132
|
`);
|
|
1028
1133
|
}
|
|
1134
|
+
writeFileSync(join(dir, 'AGENTS.md'), `# hatk project
|
|
1135
|
+
|
|
1136
|
+
This is an AT Protocol application built with [hatk](https://github.com/hatk-dev/hatk).
|
|
1137
|
+
Read the project's lexicons in \`lexicons/\` to understand the data model.
|
|
1138
|
+
Types are generated from lexicons into \`hatk.generated.ts\` — never edit this file directly.
|
|
1139
|
+
|
|
1140
|
+
## Project structure
|
|
1141
|
+
|
|
1142
|
+
| Directory | Purpose |
|
|
1143
|
+
|-------------|------------------------------------------------------|
|
|
1144
|
+
| \`lexicons/\` | AT Protocol lexicon schemas (JSON). Defines collections and XRPC methods |
|
|
1145
|
+
| \`server/\` | All server-side code: feeds, XRPC handlers, hooks, labels, OG routes, jobs, setup scripts |
|
|
1146
|
+
| \`seeds/\` | Test data seeding scripts for local development |
|
|
1147
|
+
| \`test/\` | Test files (vitest). Run with \`hatk test\` |
|
|
1148
|
+
| \`public/\` | Static files served at the root |
|
|
1149
|
+
|
|
1150
|
+
## Key files
|
|
1151
|
+
|
|
1152
|
+
- \`hatk.config.ts\` — project configuration (see \`defineConfig\` for type info)
|
|
1153
|
+
- \`hatk.generated.ts\` — auto-generated types and typed helpers. Regenerate with \`hatk generate types\`
|
|
1154
|
+
|
|
1155
|
+
## Commands
|
|
1156
|
+
|
|
1157
|
+
Run \`npx hatk --help\` for the full list of commands.
|
|
1158
|
+
|
|
1159
|
+
Use \`npx hatk generate\` to scaffold new feeds, xrpc handlers, labels, and lexicons
|
|
1160
|
+
rather than creating files manually. These generate files with the correct imports
|
|
1161
|
+
from \`hatk.generated.ts\`.
|
|
1162
|
+
|
|
1163
|
+
After modifying lexicons, always run \`npx hatk generate types\` to update the generated types.
|
|
1164
|
+
`);
|
|
1029
1165
|
console.log(`Created ${name}/`);
|
|
1030
|
-
console.log(` config.
|
|
1166
|
+
console.log(` hatk.config.ts`);
|
|
1031
1167
|
console.log(` lexicons/ — lexicon JSON files (core + your own)`);
|
|
1032
|
-
console.log(`
|
|
1033
|
-
console.log(` xrpc/ — XRPC method handlers`);
|
|
1034
|
-
console.log(` og/ — OpenGraph image routes`);
|
|
1035
|
-
console.log(` labels/ — label definitions + rules`);
|
|
1036
|
-
console.log(` jobs/ — periodic tasks`);
|
|
1168
|
+
console.log(` server/ — feeds, XRPC handlers, hooks, labels, OG routes, jobs, setup`);
|
|
1037
1169
|
console.log(` seeds/ — seed fixture data (hatk seed)`);
|
|
1038
|
-
console.log(` setup/ — boot-time setup scripts (run before server starts)`);
|
|
1039
1170
|
console.log(` test/ — test files (hatk test)`);
|
|
1040
1171
|
console.log(` public/ — static files`);
|
|
1041
1172
|
console.log(` docker-compose.yml — local PDS for development`);
|
|
@@ -1074,6 +1205,21 @@ else if (command === 'generate') {
|
|
|
1074
1205
|
}
|
|
1075
1206
|
}
|
|
1076
1207
|
entries.sort((a, b) => a.nsid.localeCompare(b.nsid));
|
|
1208
|
+
// Collect procedure nsids and blob-input nsids for client callXrpc
|
|
1209
|
+
const procedureNsids = [];
|
|
1210
|
+
const blobInputNsids = [];
|
|
1211
|
+
for (const { nsid, defType } of entries) {
|
|
1212
|
+
if (defType === 'procedure') {
|
|
1213
|
+
const lex = lexicons.get(nsid);
|
|
1214
|
+
const inputEncoding = lex?.defs?.main?.input?.encoding;
|
|
1215
|
+
if (inputEncoding === '*/*') {
|
|
1216
|
+
blobInputNsids.push(nsid);
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
procedureNsids.push(nsid);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1077
1223
|
if (entries.length === 0) {
|
|
1078
1224
|
console.error('No lexicons found');
|
|
1079
1225
|
process.exit(1);
|
|
@@ -1121,6 +1267,7 @@ else if (command === 'generate') {
|
|
|
1121
1267
|
let out = '// Auto-generated from lexicons. Do not edit.\n';
|
|
1122
1268
|
out += `import type { ${[...usedWrappers].sort().join(', ')}, LexServerParams, Checked, Prettify, StrictArg } from '@hatk/hatk/lex-types'\n`;
|
|
1123
1269
|
out += `import type { XrpcContext } from '@hatk/hatk/xrpc'\n`;
|
|
1270
|
+
out += `import { callXrpc as _callXrpc } from '@hatk/hatk/xrpc'\n`;
|
|
1124
1271
|
out += `import { defineFeed as _defineFeed, type FeedResult, type FeedContext, type HydrateContext } from '@hatk/hatk/feeds'\n`;
|
|
1125
1272
|
out += `import { seed as _seed, type SeedOpts } from '@hatk/hatk/seed'\n`;
|
|
1126
1273
|
// Emit ALL lexicons as `const ... = {...} as const` (including defs-only)
|
|
@@ -1279,6 +1426,11 @@ else if (command === 'generate') {
|
|
|
1279
1426
|
out += `\n// ─── XRPC Helpers ───────────────────────────────────────────────────\n\n`;
|
|
1280
1427
|
out += `export type { HydrateContext } from '@hatk/hatk/feeds'\n`;
|
|
1281
1428
|
out += `export { InvalidRequestError, NotFoundError } from '@hatk/hatk/xrpc'\n`;
|
|
1429
|
+
out += `export { defineSetup } from '@hatk/hatk/setup'\n`;
|
|
1430
|
+
out += `export { defineHook } from '@hatk/hatk/hooks'\n`;
|
|
1431
|
+
out += `export { defineLabels } from '@hatk/hatk/labels'\n`;
|
|
1432
|
+
out += `export { defineOG } from '@hatk/hatk/opengraph'\n`;
|
|
1433
|
+
out += `export { defineRenderer } from '@hatk/hatk/renderer'\n`;
|
|
1282
1434
|
out += `export type Ctx<K extends keyof XrpcSchema & keyof Registry> = XrpcContext<\n`;
|
|
1283
1435
|
out += ` LexServerParams<Registry[K], Registry>,\n`;
|
|
1284
1436
|
out += ` RecordRegistry,\n`;
|
|
@@ -1291,13 +1443,21 @@ else if (command === 'generate') {
|
|
|
1291
1443
|
out += ` nsid: K,\n`;
|
|
1292
1444
|
out += ` handler: (ctx: Ctx<K> & { ok: <T extends OutputOf<K>>(value: StrictArg<T, OutputOf<K>>) => Checked<OutputOf<K>> }) => Promise<Checked<OutputOf<K>>>,\n`;
|
|
1293
1445
|
out += `) {\n`;
|
|
1294
|
-
out += ` return { handler: (ctx: any) => handler({ ...ctx, ok: (v: any) => v }) }\n`;
|
|
1446
|
+
out += ` return { __type: 'query' as const, nsid, handler: (ctx: any) => handler({ ...ctx, ok: (v: any) => v }) }\n`;
|
|
1295
1447
|
out += `}\n\n`;
|
|
1296
1448
|
out += `export function defineProcedure<K extends keyof XrpcSchema & string>(\n`;
|
|
1297
1449
|
out += ` nsid: K,\n`;
|
|
1298
1450
|
out += ` handler: (ctx: Ctx<K> & { ok: <T extends OutputOf<K>>(value: StrictArg<T, OutputOf<K>>) => Checked<OutputOf<K>> }) => Promise<Checked<OutputOf<K>>>,\n`;
|
|
1299
1451
|
out += `) {\n`;
|
|
1300
|
-
out += ` return { handler: (ctx: any) => handler({ ...ctx, ok: (v: any) => v }) }\n`;
|
|
1452
|
+
out += ` return { __type: 'procedure' as const, nsid, handler: (ctx: any) => handler({ ...ctx, ok: (v: any) => v }) }\n`;
|
|
1453
|
+
out += `}\n\n`;
|
|
1454
|
+
out += `// ─── Server-side XRPC Caller ────────────────────────────────────────\n\n`;
|
|
1455
|
+
out += `type ExtractParams<T> = T extends { params: infer P } ? P : Record<string, unknown>\n`;
|
|
1456
|
+
out += `export async function callXrpc<K extends keyof XrpcSchema & string>(\n`;
|
|
1457
|
+
out += ` nsid: K,\n`;
|
|
1458
|
+
out += ` params?: ExtractParams<XrpcSchema[K]>,\n`;
|
|
1459
|
+
out += `): Promise<OutputOf<K>> {\n`;
|
|
1460
|
+
out += ` return _callXrpc(nsid, params as any) as Promise<OutputOf<K>>\n`;
|
|
1301
1461
|
out += `}\n\n`;
|
|
1302
1462
|
out += `// ─── Feed & Seed Helpers ────────────────────────────────────────────\n\n`;
|
|
1303
1463
|
out += `type FeedGenerate = (ctx: FeedContext & { ok: (value: FeedResult) => Checked<FeedResult> }) => Promise<Checked<FeedResult>>\n`;
|
|
@@ -1328,7 +1488,125 @@ else if (command === 'generate') {
|
|
|
1328
1488
|
out = out.replace(/import type \{ ([^}]+) \} from '@hatk\/hatk\/lex-types'/, `import type { ${[...usedWrappers].sort().join(', ')}, LexServerParams, Checked, Prettify, StrictArg } from '@hatk/hatk/lex-types'`);
|
|
1329
1489
|
}
|
|
1330
1490
|
writeFileSync(outPath, out);
|
|
1491
|
+
// Generate client-safe version (types + callXrpc only, no server module re-exports)
|
|
1492
|
+
// Types use `export type` from main file (erased at compile time, no runtime import).
|
|
1493
|
+
// callXrpc imports from @hatk/hatk/xrpc directly to avoid pulling in server deps.
|
|
1494
|
+
let clientOut = '// Auto-generated client-safe subset. Do not edit.\n';
|
|
1495
|
+
clientOut += `// Import this in app components instead of hatk.generated.ts\n`;
|
|
1496
|
+
clientOut += `// to avoid pulling in server-only dependencies.\n`;
|
|
1497
|
+
clientOut += `export type { XrpcSchema } from './hatk.generated.ts'\n`;
|
|
1498
|
+
clientOut += `import type { XrpcSchema } from './hatk.generated.ts'\n`;
|
|
1499
|
+
// Re-export all types
|
|
1500
|
+
const typeExports = [];
|
|
1501
|
+
for (const { nsid, defType } of entries) {
|
|
1502
|
+
if (!defType)
|
|
1503
|
+
continue;
|
|
1504
|
+
if (nsid === 'dev.hatk.createRecord' || nsid === 'dev.hatk.deleteRecord' || nsid === 'dev.hatk.putRecord')
|
|
1505
|
+
continue;
|
|
1506
|
+
typeExports.push(capitalize(varNames.get(nsid)));
|
|
1507
|
+
}
|
|
1508
|
+
if (recordEntries.length > 0) {
|
|
1509
|
+
typeExports.push('RecordRegistry', 'CreateRecord', 'DeleteRecord', 'PutRecord');
|
|
1510
|
+
}
|
|
1511
|
+
// Named defs (views, objects) — collect from emittedDefNames minus main types
|
|
1512
|
+
const mainTypeNames = new Set(entries.filter(e => e.defType).map(e => capitalize(varNames.get(e.nsid))));
|
|
1513
|
+
for (const name of emittedDefNames) {
|
|
1514
|
+
if (!mainTypeNames.has(name) && !typeExports.includes(name)) {
|
|
1515
|
+
typeExports.push(name);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
if (typeExports.length > 0) {
|
|
1519
|
+
clientOut += `export type { ${typeExports.join(', ')} } from './hatk.generated.ts'\n`;
|
|
1520
|
+
}
|
|
1521
|
+
// Typed callXrpc — environment-aware:
|
|
1522
|
+
// SSR: uses globalThis.__hatk_callXrpc bridge (direct handler invocation)
|
|
1523
|
+
// Client: fetches via HTTP (GET for queries, POST for procedures, raw POST for blobs)
|
|
1524
|
+
if (procedureNsids.length > 0) {
|
|
1525
|
+
clientOut += `\nconst _procedures = new Set([${procedureNsids.map(n => `'${n}'`).join(', ')}])\n`;
|
|
1526
|
+
}
|
|
1527
|
+
if (blobInputNsids.length > 0) {
|
|
1528
|
+
clientOut += `const _blobInputs = new Set([${blobInputNsids.map(n => `'${n}'`).join(', ')}])\n`;
|
|
1529
|
+
}
|
|
1530
|
+
clientOut += `\ntype CallArg<K extends keyof XrpcSchema> =\n`;
|
|
1531
|
+
clientOut += ` XrpcSchema[K] extends { input: infer I } ? I :\n`;
|
|
1532
|
+
clientOut += ` XrpcSchema[K] extends { params: infer P } ? P :\n`;
|
|
1533
|
+
clientOut += ` Record<string, unknown>\n`;
|
|
1534
|
+
clientOut += `type OutputOf<K extends keyof XrpcSchema> = XrpcSchema[K]['output']\n\n`;
|
|
1535
|
+
clientOut += `export async function callXrpc<K extends keyof XrpcSchema & string>(\n`;
|
|
1536
|
+
clientOut += ` nsid: K,\n`;
|
|
1537
|
+
clientOut += ` arg?: CallArg<K>,\n`;
|
|
1538
|
+
clientOut += `): Promise<OutputOf<K>> {\n`;
|
|
1539
|
+
// Server-side bridge
|
|
1540
|
+
clientOut += ` if (typeof window === 'undefined') {\n`;
|
|
1541
|
+
clientOut += ` const bridge = (globalThis as any).__hatk_callXrpc\n`;
|
|
1542
|
+
clientOut += ` if (!bridge) throw new Error('callXrpc: server bridge not available — is hatk initialized?')\n`;
|
|
1543
|
+
if (procedureNsids.length > 0 || blobInputNsids.length > 0) {
|
|
1544
|
+
const checks = [];
|
|
1545
|
+
if (procedureNsids.length > 0)
|
|
1546
|
+
checks.push('_procedures.has(nsid)');
|
|
1547
|
+
if (blobInputNsids.length > 0)
|
|
1548
|
+
checks.push('_blobInputs.has(nsid)');
|
|
1549
|
+
clientOut += ` if (${checks.join(' || ')}) return bridge(nsid, {}, arg) as Promise<OutputOf<K>>\n`;
|
|
1550
|
+
}
|
|
1551
|
+
clientOut += ` return bridge(nsid, arg) as Promise<OutputOf<K>>\n`;
|
|
1552
|
+
clientOut += ` }\n`;
|
|
1553
|
+
// Client-side fetch
|
|
1554
|
+
clientOut += ` const url = new URL(\`/xrpc/\${nsid}\`, window.location.origin)\n`;
|
|
1555
|
+
if (blobInputNsids.length > 0) {
|
|
1556
|
+
clientOut += ` if (_blobInputs.has(nsid)) {\n`;
|
|
1557
|
+
clientOut += ` const blob = arg as Blob | ArrayBuffer\n`;
|
|
1558
|
+
clientOut += ` const ct = blob instanceof Blob ? blob.type : 'application/octet-stream'\n`;
|
|
1559
|
+
clientOut += ` const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': ct }, body: blob })\n`;
|
|
1560
|
+
clientOut += ` if (!res.ok) throw new Error(\`XRPC \${nsid} failed: \${res.status}\`)\n`;
|
|
1561
|
+
clientOut += ` return res.json() as Promise<OutputOf<K>>\n`;
|
|
1562
|
+
clientOut += ` }\n`;
|
|
1563
|
+
}
|
|
1564
|
+
if (procedureNsids.length > 0) {
|
|
1565
|
+
clientOut += ` if (_procedures.has(nsid)) {\n`;
|
|
1566
|
+
clientOut += ` const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(arg) })\n`;
|
|
1567
|
+
clientOut += ` if (!res.ok) throw new Error(\`XRPC \${nsid} failed: \${res.status}\`)\n`;
|
|
1568
|
+
clientOut += ` return res.json() as Promise<OutputOf<K>>\n`;
|
|
1569
|
+
clientOut += ` }\n`;
|
|
1570
|
+
}
|
|
1571
|
+
clientOut += ` for (const [k, v] of Object.entries(arg || {})) {\n`;
|
|
1572
|
+
clientOut += ` if (v != null) url.searchParams.set(k, String(v))\n`;
|
|
1573
|
+
clientOut += ` }\n`;
|
|
1574
|
+
clientOut += ` const res = await fetch(url)\n`;
|
|
1575
|
+
clientOut += ` if (!res.ok) throw new Error(\`XRPC \${nsid} failed: \${res.status}\`)\n`;
|
|
1576
|
+
clientOut += ` return res.json() as Promise<OutputOf<K>>\n`;
|
|
1577
|
+
clientOut += `}\n`;
|
|
1578
|
+
// getViewer — async, resolves from cookies on server via getRequestEvent()
|
|
1579
|
+
clientOut += `\nexport async function getViewer(): Promise<{ did: string } | null> {\n`;
|
|
1580
|
+
clientOut += ` if (typeof window === 'undefined') {\n`;
|
|
1581
|
+
clientOut += ` try {\n`;
|
|
1582
|
+
clientOut += ` const parse = (globalThis as any).__hatk_parseSessionCookie\n`;
|
|
1583
|
+
clientOut += ` if (parse) {\n`;
|
|
1584
|
+
clientOut += ` const { getRequestEvent } = await import('$app/server')\n`;
|
|
1585
|
+
clientOut += ` const event = getRequestEvent()\n`;
|
|
1586
|
+
clientOut += ` const cookieName = (globalThis as any).__hatk_sessionCookieName ?? '__hatk_session'\n`;
|
|
1587
|
+
clientOut += ` const cookieValue = event.cookies.get(cookieName)\n`;
|
|
1588
|
+
clientOut += ` if (cookieValue) {\n`;
|
|
1589
|
+
clientOut += ` const request = new Request('http://localhost', {\n`;
|
|
1590
|
+
clientOut += ` headers: { cookie: \`\${cookieName}=\${cookieValue}\` },\n`;
|
|
1591
|
+
clientOut += ` })\n`;
|
|
1592
|
+
clientOut += ` return parse(request)\n`;
|
|
1593
|
+
clientOut += ` }\n`;
|
|
1594
|
+
clientOut += ` }\n`;
|
|
1595
|
+
clientOut += ` } catch {}\n`;
|
|
1596
|
+
clientOut += ` return (globalThis as any).__hatk_viewer ?? null\n`;
|
|
1597
|
+
clientOut += ` }\n`;
|
|
1598
|
+
clientOut += ` try {\n`;
|
|
1599
|
+
clientOut += ` const mod = (globalThis as any).__hatk_auth\n`;
|
|
1600
|
+
clientOut += ` if (mod?.viewerDid) {\n`;
|
|
1601
|
+
clientOut += ` const did = mod.viewerDid()\n`;
|
|
1602
|
+
clientOut += ` if (did) return { did }\n`;
|
|
1603
|
+
clientOut += ` }\n`;
|
|
1604
|
+
clientOut += ` } catch {}\n`;
|
|
1605
|
+
clientOut += ` return (globalThis as any).__hatk_viewer ?? null\n`;
|
|
1606
|
+
clientOut += `}\n`;
|
|
1607
|
+
writeFileSync('./hatk.generated.client.ts', clientOut);
|
|
1331
1608
|
console.log(`Generated ${outPath} with ${entries.length} types: ${entries.map((e) => capitalize(varNames.get(e.nsid))).join(', ')}`);
|
|
1609
|
+
console.log(`Generated ./hatk.generated.client.ts (client-safe subset)`);
|
|
1332
1610
|
}
|
|
1333
1611
|
else if (lexiconTemplates[type]) {
|
|
1334
1612
|
const nsid = args[2];
|
|
@@ -1356,18 +1634,9 @@ else if (command === 'generate') {
|
|
|
1356
1634
|
process.exit(1);
|
|
1357
1635
|
}
|
|
1358
1636
|
const baseDir = dirs[type];
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
const parts = name.split('.');
|
|
1363
|
-
const subDir = join(baseDir, ...parts.slice(0, -1));
|
|
1364
|
-
mkdirSync(subDir, { recursive: true });
|
|
1365
|
-
filePath = join(subDir, `${parts[parts.length - 1]}.ts`);
|
|
1366
|
-
}
|
|
1367
|
-
else {
|
|
1368
|
-
mkdirSync(baseDir, { recursive: true });
|
|
1369
|
-
filePath = join(baseDir, `${name}.ts`);
|
|
1370
|
-
}
|
|
1637
|
+
mkdirSync(baseDir, { recursive: true });
|
|
1638
|
+
const fileName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1639
|
+
const filePath = join(baseDir, `${fileName}.ts`);
|
|
1371
1640
|
if (existsSync(filePath)) {
|
|
1372
1641
|
console.error(`${filePath} already exists`);
|
|
1373
1642
|
process.exit(1);
|
|
@@ -1377,7 +1646,7 @@ else if (command === 'generate') {
|
|
|
1377
1646
|
// Scaffold test file if template exists
|
|
1378
1647
|
const testTemplate = testTemplates[type];
|
|
1379
1648
|
if (testTemplate) {
|
|
1380
|
-
const testDir =
|
|
1649
|
+
const testDir = 'test/server';
|
|
1381
1650
|
mkdirSync(testDir, { recursive: true });
|
|
1382
1651
|
const testName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1383
1652
|
const testPath = join(testDir, `${testName}.test.ts`);
|
|
@@ -1396,18 +1665,9 @@ else if (command === 'destroy') {
|
|
|
1396
1665
|
process.exit(1);
|
|
1397
1666
|
}
|
|
1398
1667
|
const baseDir = dirs[type];
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
const leaf = parts[parts.length - 1];
|
|
1403
|
-
const subDir = join(baseDir, ...parts.slice(0, -1));
|
|
1404
|
-
tsPath = join(subDir, `${leaf}.ts`);
|
|
1405
|
-
jsPath = join(subDir, `${leaf}.js`);
|
|
1406
|
-
}
|
|
1407
|
-
else {
|
|
1408
|
-
tsPath = join(baseDir, `${name}.ts`);
|
|
1409
|
-
jsPath = join(baseDir, `${name}.js`);
|
|
1410
|
-
}
|
|
1668
|
+
const fileName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1669
|
+
const tsPath = join(baseDir, `${fileName}.ts`);
|
|
1670
|
+
const jsPath = join(baseDir, `${fileName}.js`);
|
|
1411
1671
|
const filePath = existsSync(tsPath) ? tsPath : existsSync(jsPath) ? jsPath : null;
|
|
1412
1672
|
if (!filePath) {
|
|
1413
1673
|
console.error(`No file found for ${type} "${name}"`);
|
|
@@ -1416,7 +1676,7 @@ else if (command === 'destroy') {
|
|
|
1416
1676
|
unlinkSync(filePath);
|
|
1417
1677
|
console.log(`Removed ${filePath}`);
|
|
1418
1678
|
// Clean up test file
|
|
1419
|
-
const testDir =
|
|
1679
|
+
const testDir = 'test/server';
|
|
1420
1680
|
const testName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1421
1681
|
const testFile = join(testDir, `${testName}.test.ts`);
|
|
1422
1682
|
if (existsSync(testFile)) {
|
|
@@ -1430,25 +1690,14 @@ else if (command === 'destroy') {
|
|
|
1430
1690
|
else if (command === 'dev') {
|
|
1431
1691
|
await ensurePds();
|
|
1432
1692
|
runSeed();
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
execSync('npx vite dev', { stdio: 'inherit', cwd: process.cwd() });
|
|
1437
|
-
}
|
|
1438
|
-
else {
|
|
1439
|
-
// No frontend — just run the hatk server directly
|
|
1440
|
-
const mainPath = resolve(import.meta.dirname, 'main.js');
|
|
1441
|
-
execSync(`npx tsx ${mainPath} config.yaml`, {
|
|
1442
|
-
stdio: 'inherit',
|
|
1443
|
-
cwd: process.cwd(),
|
|
1444
|
-
env: { ...process.env, DEV_MODE: '1' },
|
|
1445
|
-
});
|
|
1446
|
-
}
|
|
1693
|
+
if (existsSync(resolve('vite.config.ts')) || existsSync(resolve('vite.config.js'))) {
|
|
1694
|
+
// Vite project — vite dev starts the hatk server via the plugin
|
|
1695
|
+
await spawnForward('npx', ['vite', 'dev']);
|
|
1447
1696
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1697
|
+
else {
|
|
1698
|
+
// No frontend — just run the hatk server directly
|
|
1699
|
+
const mainPath = resolve(import.meta.dirname, 'main.js');
|
|
1700
|
+
await spawnForward('npx', ['tsx', mainPath, 'hatk.config.ts'], { DEV_MODE: '1' });
|
|
1452
1701
|
}
|
|
1453
1702
|
}
|
|
1454
1703
|
else if (command === 'format' || command === 'fmt') {
|
|
@@ -1468,9 +1717,9 @@ else if (command === 'build') {
|
|
|
1468
1717
|
}
|
|
1469
1718
|
}
|
|
1470
1719
|
else if (command === 'reset') {
|
|
1471
|
-
const config = loadConfig(resolve('config.
|
|
1720
|
+
const config = await loadConfig(resolve('hatk.config.ts'));
|
|
1472
1721
|
if (config.database !== ':memory:') {
|
|
1473
|
-
for (const suffix of ['', '.wal']) {
|
|
1722
|
+
for (const suffix of ['', '.wal', '-shm', '-wal']) {
|
|
1474
1723
|
const file = config.database + suffix;
|
|
1475
1724
|
if (existsSync(file)) {
|
|
1476
1725
|
unlinkSync(file);
|
|
@@ -1628,40 +1877,24 @@ else if (command === 'resolve') {
|
|
|
1628
1877
|
execSync('npx hatk generate types', { stdio: 'inherit', cwd: process.cwd() });
|
|
1629
1878
|
}
|
|
1630
1879
|
else if (command === 'schema') {
|
|
1631
|
-
const config = loadConfig(resolve('config.
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
const { DuckDBInstance } = await import('@duckdb/node-api');
|
|
1642
|
-
const instance = await DuckDBInstance.create(config.database);
|
|
1643
|
-
const con = await instance.connect();
|
|
1644
|
-
const tables = (await (await con.runAndReadAll(`SELECT table_name FROM information_schema.tables WHERE table_schema = 'main' ORDER BY table_name`)).getRowObjects());
|
|
1645
|
-
for (const { table_name } of tables) {
|
|
1646
|
-
console.log(`"${table_name}"`);
|
|
1647
|
-
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());
|
|
1648
|
-
for (const col of cols) {
|
|
1649
|
-
const nullable = col.is_nullable === 'YES' ? '' : ' NOT NULL';
|
|
1650
|
-
console.log(` ${col.column_name.padEnd(20)} ${col.data_type}${nullable}`);
|
|
1651
|
-
}
|
|
1652
|
-
console.log();
|
|
1880
|
+
const config = await loadConfig(resolve('hatk.config.ts'));
|
|
1881
|
+
const { initDatabase, getSchemaDump } = await import("./database/db.js");
|
|
1882
|
+
const { createAdapter } = await import("./database/adapter-factory.js");
|
|
1883
|
+
const { getDialect } = await import("./database/dialect.js");
|
|
1884
|
+
const configDir2 = resolve('.');
|
|
1885
|
+
const lexicons2 = loadLexicons(resolve(configDir2, 'lexicons'));
|
|
1886
|
+
const collections2 = config.collections.length > 0 ? config.collections : discoverCollections(lexicons2);
|
|
1887
|
+
const { schemas: schemas2, ddlStatements: ddl2 } = buildSchemas(lexicons2, collections2, getDialect(config.databaseEngine));
|
|
1888
|
+
if (config.database !== ':memory:') {
|
|
1889
|
+
mkdirSync(dirname(config.database), { recursive: true });
|
|
1653
1890
|
}
|
|
1891
|
+
const { adapter: adapter2 } = await createAdapter(config.databaseEngine);
|
|
1892
|
+
await initDatabase(adapter2, config.database, schemas2, ddl2);
|
|
1893
|
+
console.log(await getSchemaDump());
|
|
1654
1894
|
}
|
|
1655
1895
|
else if (command === 'start') {
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
execSync(`npx tsx ${mainPath} config.yaml`, { stdio: 'inherit', cwd: process.cwd() });
|
|
1659
|
-
}
|
|
1660
|
-
catch (e) {
|
|
1661
|
-
if (e.signal === 'SIGINT' || e.signal === 'SIGTERM')
|
|
1662
|
-
process.exit(0);
|
|
1663
|
-
throw e;
|
|
1664
|
-
}
|
|
1896
|
+
const mainPath = resolve(import.meta.dirname, 'main.js');
|
|
1897
|
+
await spawnForward('npx', ['tsx', mainPath, 'hatk.config.ts']);
|
|
1665
1898
|
}
|
|
1666
1899
|
else {
|
|
1667
1900
|
usage();
|