@hatk/hatk 0.0.1-alpha.3 → 0.0.1-alpha.31
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 +399 -130
- 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 +23 -0
- package/dist/database/adapters/sqlite-search.d.ts.map +1 -0
- package/dist/database/adapters/sqlite-search.js +71 -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 +1464 -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 +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 +53 -7
- 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 +138 -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 +13 -6
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',
|
|
@@ -611,6 +699,7 @@ backfill:
|
|
|
611
699
|
encoding: 'application/json',
|
|
612
700
|
schema: {
|
|
613
701
|
type: 'object',
|
|
702
|
+
required: ['items'],
|
|
614
703
|
properties: {
|
|
615
704
|
items: { type: 'array', items: { type: 'unknown' } },
|
|
616
705
|
cursor: { type: 'string' },
|
|
@@ -668,6 +757,7 @@ backfill:
|
|
|
668
757
|
encoding: 'application/json',
|
|
669
758
|
schema: {
|
|
670
759
|
type: 'object',
|
|
760
|
+
required: ['items'],
|
|
671
761
|
properties: {
|
|
672
762
|
items: { type: 'array', items: { type: 'unknown' } },
|
|
673
763
|
cursor: { type: 'string' },
|
|
@@ -699,6 +789,7 @@ backfill:
|
|
|
699
789
|
encoding: 'application/json',
|
|
700
790
|
schema: {
|
|
701
791
|
type: 'object',
|
|
792
|
+
required: ['items'],
|
|
702
793
|
properties: {
|
|
703
794
|
items: { type: 'array', items: { type: 'unknown' } },
|
|
704
795
|
cursor: { type: 'string' },
|
|
@@ -793,13 +884,17 @@ public
|
|
|
793
884
|
writeFileSync(join(dir, 'Dockerfile'), `FROM node:25-slim
|
|
794
885
|
WORKDIR /app
|
|
795
886
|
COPY package.json package-lock.json ./
|
|
796
|
-
RUN npm ci
|
|
887
|
+
RUN npm ci
|
|
797
888
|
COPY . .
|
|
798
889
|
RUN node_modules/.bin/hatk build
|
|
890
|
+
RUN npm prune --omit=dev
|
|
799
891
|
EXPOSE 3000
|
|
800
|
-
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"]
|
|
801
893
|
`);
|
|
802
894
|
const pkgDeps = { '@hatk/oauth-client': '*', hatk: '*' };
|
|
895
|
+
if (!withDuckdb) {
|
|
896
|
+
pkgDeps['better-sqlite3'] = '^11';
|
|
897
|
+
}
|
|
803
898
|
const pkgDevDeps = {
|
|
804
899
|
'@playwright/test': '^1',
|
|
805
900
|
oxfmt: '^0.35.0',
|
|
@@ -807,6 +902,7 @@ CMD ["node", "node_modules/@hatk/hatk/dist/main.js", "config.yaml"]
|
|
|
807
902
|
typescript: '^5',
|
|
808
903
|
vite: '^6',
|
|
809
904
|
vitest: '^4',
|
|
905
|
+
'@types/node': '^22',
|
|
810
906
|
};
|
|
811
907
|
if (withSvelte) {
|
|
812
908
|
pkgDevDeps['@sveltejs/adapter-static'] = '^3';
|
|
@@ -841,7 +937,7 @@ CMD ["node", "node_modules/@hatk/hatk/dist/main.js", "config.yaml"]
|
|
|
841
937
|
allowImportingTsExtensions: true,
|
|
842
938
|
resolveJsonModule: true,
|
|
843
939
|
},
|
|
844
|
-
include: ['
|
|
940
|
+
include: ['server', 'seeds', 'hatk.generated.ts', 'hatk.config.ts'],
|
|
845
941
|
}, null, 2) + '\n');
|
|
846
942
|
writeFileSync(join(dir, 'playwright.config.ts'), `import { defineConfig } from '@playwright/test'
|
|
847
943
|
|
|
@@ -1026,16 +1122,87 @@ a {
|
|
|
1026
1122
|
</div>
|
|
1027
1123
|
`);
|
|
1028
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);
|
|
1029
1201
|
console.log(`Created ${name}/`);
|
|
1030
|
-
console.log(` config.
|
|
1202
|
+
console.log(` hatk.config.ts`);
|
|
1031
1203
|
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`);
|
|
1204
|
+
console.log(` server/ — feeds, XRPC handlers, hooks, labels, OG routes, setup`);
|
|
1037
1205
|
console.log(` seeds/ — seed fixture data (hatk seed)`);
|
|
1038
|
-
console.log(` setup/ — boot-time setup scripts (run before server starts)`);
|
|
1039
1206
|
console.log(` test/ — test files (hatk test)`);
|
|
1040
1207
|
console.log(` public/ — static files`);
|
|
1041
1208
|
console.log(` docker-compose.yml — local PDS for development`);
|
|
@@ -1074,6 +1241,21 @@ else if (command === 'generate') {
|
|
|
1074
1241
|
}
|
|
1075
1242
|
}
|
|
1076
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
|
+
}
|
|
1077
1259
|
if (entries.length === 0) {
|
|
1078
1260
|
console.error('No lexicons found');
|
|
1079
1261
|
process.exit(1);
|
|
@@ -1121,6 +1303,7 @@ else if (command === 'generate') {
|
|
|
1121
1303
|
let out = '// Auto-generated from lexicons. Do not edit.\n';
|
|
1122
1304
|
out += `import type { ${[...usedWrappers].sort().join(', ')}, LexServerParams, Checked, Prettify, StrictArg } from '@hatk/hatk/lex-types'\n`;
|
|
1123
1305
|
out += `import type { XrpcContext } from '@hatk/hatk/xrpc'\n`;
|
|
1306
|
+
out += `import { callXrpc as _callXrpc } from '@hatk/hatk/xrpc'\n`;
|
|
1124
1307
|
out += `import { defineFeed as _defineFeed, type FeedResult, type FeedContext, type HydrateContext } from '@hatk/hatk/feeds'\n`;
|
|
1125
1308
|
out += `import { seed as _seed, type SeedOpts } from '@hatk/hatk/seed'\n`;
|
|
1126
1309
|
// Emit ALL lexicons as `const ... = {...} as const` (including defs-only)
|
|
@@ -1279,6 +1462,11 @@ else if (command === 'generate') {
|
|
|
1279
1462
|
out += `\n// ─── XRPC Helpers ───────────────────────────────────────────────────\n\n`;
|
|
1280
1463
|
out += `export type { HydrateContext } from '@hatk/hatk/feeds'\n`;
|
|
1281
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 { defineLabels } from '@hatk/hatk/labels'\n`;
|
|
1468
|
+
out += `export { defineOG } from '@hatk/hatk/opengraph'\n`;
|
|
1469
|
+
out += `export { defineRenderer } from '@hatk/hatk/renderer'\n`;
|
|
1282
1470
|
out += `export type Ctx<K extends keyof XrpcSchema & keyof Registry> = XrpcContext<\n`;
|
|
1283
1471
|
out += ` LexServerParams<Registry[K], Registry>,\n`;
|
|
1284
1472
|
out += ` RecordRegistry,\n`;
|
|
@@ -1291,13 +1479,21 @@ else if (command === 'generate') {
|
|
|
1291
1479
|
out += ` nsid: K,\n`;
|
|
1292
1480
|
out += ` handler: (ctx: Ctx<K> & { ok: <T extends OutputOf<K>>(value: StrictArg<T, OutputOf<K>>) => Checked<OutputOf<K>> }) => Promise<Checked<OutputOf<K>>>,\n`;
|
|
1293
1481
|
out += `) {\n`;
|
|
1294
|
-
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`;
|
|
1295
1483
|
out += `}\n\n`;
|
|
1296
1484
|
out += `export function defineProcedure<K extends keyof XrpcSchema & string>(\n`;
|
|
1297
1485
|
out += ` nsid: K,\n`;
|
|
1298
1486
|
out += ` handler: (ctx: Ctx<K> & { ok: <T extends OutputOf<K>>(value: StrictArg<T, OutputOf<K>>) => Checked<OutputOf<K>> }) => Promise<Checked<OutputOf<K>>>,\n`;
|
|
1299
1487
|
out += `) {\n`;
|
|
1300
|
-
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`;
|
|
1301
1497
|
out += `}\n\n`;
|
|
1302
1498
|
out += `// ─── Feed & Seed Helpers ────────────────────────────────────────────\n\n`;
|
|
1303
1499
|
out += `type FeedGenerate = (ctx: FeedContext & { ok: (value: FeedResult) => Checked<FeedResult> }) => Promise<Checked<FeedResult>>\n`;
|
|
@@ -1328,7 +1524,125 @@ else if (command === 'generate') {
|
|
|
1328
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'`);
|
|
1329
1525
|
}
|
|
1330
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 += `): Promise<OutputOf<K>> {\n`;
|
|
1575
|
+
// Server-side bridge
|
|
1576
|
+
clientOut += ` if (typeof window === 'undefined') {\n`;
|
|
1577
|
+
clientOut += ` const bridge = (globalThis as any).__hatk_callXrpc\n`;
|
|
1578
|
+
clientOut += ` if (!bridge) throw new Error('callXrpc: server bridge not available — is hatk initialized?')\n`;
|
|
1579
|
+
if (procedureNsids.length > 0 || blobInputNsids.length > 0) {
|
|
1580
|
+
const checks = [];
|
|
1581
|
+
if (procedureNsids.length > 0)
|
|
1582
|
+
checks.push('_procedures.has(nsid)');
|
|
1583
|
+
if (blobInputNsids.length > 0)
|
|
1584
|
+
checks.push('_blobInputs.has(nsid)');
|
|
1585
|
+
clientOut += ` if (${checks.join(' || ')}) return bridge(nsid, {}, arg) as Promise<OutputOf<K>>\n`;
|
|
1586
|
+
}
|
|
1587
|
+
clientOut += ` return bridge(nsid, arg) as Promise<OutputOf<K>>\n`;
|
|
1588
|
+
clientOut += ` }\n`;
|
|
1589
|
+
// Client-side fetch
|
|
1590
|
+
clientOut += ` const url = new URL(\`/xrpc/\${nsid}\`, window.location.origin)\n`;
|
|
1591
|
+
if (blobInputNsids.length > 0) {
|
|
1592
|
+
clientOut += ` if (_blobInputs.has(nsid)) {\n`;
|
|
1593
|
+
clientOut += ` const blob = arg as Blob | ArrayBuffer\n`;
|
|
1594
|
+
clientOut += ` const ct = blob instanceof Blob ? blob.type : 'application/octet-stream'\n`;
|
|
1595
|
+
clientOut += ` const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': ct }, body: blob })\n`;
|
|
1596
|
+
clientOut += ` if (!res.ok) throw new Error(\`XRPC \${nsid} failed: \${res.status}\`)\n`;
|
|
1597
|
+
clientOut += ` return res.json() as Promise<OutputOf<K>>\n`;
|
|
1598
|
+
clientOut += ` }\n`;
|
|
1599
|
+
}
|
|
1600
|
+
if (procedureNsids.length > 0) {
|
|
1601
|
+
clientOut += ` if (_procedures.has(nsid)) {\n`;
|
|
1602
|
+
clientOut += ` const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(arg) })\n`;
|
|
1603
|
+
clientOut += ` if (!res.ok) throw new Error(\`XRPC \${nsid} failed: \${res.status}\`)\n`;
|
|
1604
|
+
clientOut += ` return res.json() as Promise<OutputOf<K>>\n`;
|
|
1605
|
+
clientOut += ` }\n`;
|
|
1606
|
+
}
|
|
1607
|
+
clientOut += ` for (const [k, v] of Object.entries(arg || {})) {\n`;
|
|
1608
|
+
clientOut += ` if (v != null) url.searchParams.set(k, String(v))\n`;
|
|
1609
|
+
clientOut += ` }\n`;
|
|
1610
|
+
clientOut += ` const res = await fetch(url)\n`;
|
|
1611
|
+
clientOut += ` if (!res.ok) throw new Error(\`XRPC \${nsid} failed: \${res.status}\`)\n`;
|
|
1612
|
+
clientOut += ` return res.json() as Promise<OutputOf<K>>\n`;
|
|
1613
|
+
clientOut += `}\n`;
|
|
1614
|
+
// getViewer — async, resolves from cookies on server via getRequestEvent()
|
|
1615
|
+
clientOut += `\nexport async function getViewer(): Promise<{ did: string } | null> {\n`;
|
|
1616
|
+
clientOut += ` if (typeof window === 'undefined') {\n`;
|
|
1617
|
+
clientOut += ` try {\n`;
|
|
1618
|
+
clientOut += ` const parse = (globalThis as any).__hatk_parseSessionCookie\n`;
|
|
1619
|
+
clientOut += ` if (parse) {\n`;
|
|
1620
|
+
clientOut += ` const { getRequestEvent } = await import('$app/server')\n`;
|
|
1621
|
+
clientOut += ` const event = getRequestEvent()\n`;
|
|
1622
|
+
clientOut += ` const cookieName = (globalThis as any).__hatk_sessionCookieName ?? '__hatk_session'\n`;
|
|
1623
|
+
clientOut += ` const cookieValue = event.cookies.get(cookieName)\n`;
|
|
1624
|
+
clientOut += ` if (cookieValue) {\n`;
|
|
1625
|
+
clientOut += ` const request = new Request('http://localhost', {\n`;
|
|
1626
|
+
clientOut += ` headers: { cookie: \`\${cookieName}=\${cookieValue}\` },\n`;
|
|
1627
|
+
clientOut += ` })\n`;
|
|
1628
|
+
clientOut += ` return parse(request)\n`;
|
|
1629
|
+
clientOut += ` }\n`;
|
|
1630
|
+
clientOut += ` }\n`;
|
|
1631
|
+
clientOut += ` } catch {}\n`;
|
|
1632
|
+
clientOut += ` return (globalThis as any).__hatk_viewer ?? null\n`;
|
|
1633
|
+
clientOut += ` }\n`;
|
|
1634
|
+
clientOut += ` try {\n`;
|
|
1635
|
+
clientOut += ` const mod = (globalThis as any).__hatk_auth\n`;
|
|
1636
|
+
clientOut += ` if (mod?.viewerDid) {\n`;
|
|
1637
|
+
clientOut += ` const did = mod.viewerDid()\n`;
|
|
1638
|
+
clientOut += ` if (did) return { did }\n`;
|
|
1639
|
+
clientOut += ` }\n`;
|
|
1640
|
+
clientOut += ` } catch {}\n`;
|
|
1641
|
+
clientOut += ` return (globalThis as any).__hatk_viewer ?? null\n`;
|
|
1642
|
+
clientOut += `}\n`;
|
|
1643
|
+
writeFileSync('./hatk.generated.client.ts', clientOut);
|
|
1331
1644
|
console.log(`Generated ${outPath} with ${entries.length} types: ${entries.map((e) => capitalize(varNames.get(e.nsid))).join(', ')}`);
|
|
1645
|
+
console.log(`Generated ./hatk.generated.client.ts (client-safe subset)`);
|
|
1332
1646
|
}
|
|
1333
1647
|
else if (lexiconTemplates[type]) {
|
|
1334
1648
|
const nsid = args[2];
|
|
@@ -1356,18 +1670,9 @@ else if (command === 'generate') {
|
|
|
1356
1670
|
process.exit(1);
|
|
1357
1671
|
}
|
|
1358
1672
|
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
|
-
}
|
|
1673
|
+
mkdirSync(baseDir, { recursive: true });
|
|
1674
|
+
const fileName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1675
|
+
const filePath = join(baseDir, `${fileName}.ts`);
|
|
1371
1676
|
if (existsSync(filePath)) {
|
|
1372
1677
|
console.error(`${filePath} already exists`);
|
|
1373
1678
|
process.exit(1);
|
|
@@ -1377,7 +1682,7 @@ else if (command === 'generate') {
|
|
|
1377
1682
|
// Scaffold test file if template exists
|
|
1378
1683
|
const testTemplate = testTemplates[type];
|
|
1379
1684
|
if (testTemplate) {
|
|
1380
|
-
const testDir =
|
|
1685
|
+
const testDir = 'test/server';
|
|
1381
1686
|
mkdirSync(testDir, { recursive: true });
|
|
1382
1687
|
const testName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1383
1688
|
const testPath = join(testDir, `${testName}.test.ts`);
|
|
@@ -1396,18 +1701,9 @@ else if (command === 'destroy') {
|
|
|
1396
1701
|
process.exit(1);
|
|
1397
1702
|
}
|
|
1398
1703
|
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
|
-
}
|
|
1704
|
+
const fileName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1705
|
+
const tsPath = join(baseDir, `${fileName}.ts`);
|
|
1706
|
+
const jsPath = join(baseDir, `${fileName}.js`);
|
|
1411
1707
|
const filePath = existsSync(tsPath) ? tsPath : existsSync(jsPath) ? jsPath : null;
|
|
1412
1708
|
if (!filePath) {
|
|
1413
1709
|
console.error(`No file found for ${type} "${name}"`);
|
|
@@ -1416,7 +1712,7 @@ else if (command === 'destroy') {
|
|
|
1416
1712
|
unlinkSync(filePath);
|
|
1417
1713
|
console.log(`Removed ${filePath}`);
|
|
1418
1714
|
// Clean up test file
|
|
1419
|
-
const testDir =
|
|
1715
|
+
const testDir = 'test/server';
|
|
1420
1716
|
const testName = type === 'xrpc' ? name.split('.').pop() : name;
|
|
1421
1717
|
const testFile = join(testDir, `${testName}.test.ts`);
|
|
1422
1718
|
if (existsSync(testFile)) {
|
|
@@ -1430,25 +1726,14 @@ else if (command === 'destroy') {
|
|
|
1430
1726
|
else if (command === 'dev') {
|
|
1431
1727
|
await ensurePds();
|
|
1432
1728
|
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
|
-
}
|
|
1729
|
+
if (existsSync(resolve('vite.config.ts')) || existsSync(resolve('vite.config.js'))) {
|
|
1730
|
+
// Vite project — vite dev starts the hatk server via the plugin
|
|
1731
|
+
await spawnForward('npx', ['vite', 'dev']);
|
|
1447
1732
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1733
|
+
else {
|
|
1734
|
+
// No frontend — just run the hatk server directly
|
|
1735
|
+
const mainPath = resolve(import.meta.dirname, 'main.js');
|
|
1736
|
+
await spawnForward('npx', ['tsx', mainPath, 'hatk.config.ts'], { DEV_MODE: '1' });
|
|
1452
1737
|
}
|
|
1453
1738
|
}
|
|
1454
1739
|
else if (command === 'format' || command === 'fmt') {
|
|
@@ -1468,9 +1753,9 @@ else if (command === 'build') {
|
|
|
1468
1753
|
}
|
|
1469
1754
|
}
|
|
1470
1755
|
else if (command === 'reset') {
|
|
1471
|
-
const config = loadConfig(resolve('config.
|
|
1756
|
+
const config = await loadConfig(resolve('hatk.config.ts'));
|
|
1472
1757
|
if (config.database !== ':memory:') {
|
|
1473
|
-
for (const suffix of ['', '.wal']) {
|
|
1758
|
+
for (const suffix of ['', '.wal', '-shm', '-wal']) {
|
|
1474
1759
|
const file = config.database + suffix;
|
|
1475
1760
|
if (existsSync(file)) {
|
|
1476
1761
|
unlinkSync(file);
|
|
@@ -1628,40 +1913,24 @@ else if (command === 'resolve') {
|
|
|
1628
1913
|
execSync('npx hatk generate types', { stdio: 'inherit', cwd: process.cwd() });
|
|
1629
1914
|
}
|
|
1630
1915
|
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();
|
|
1916
|
+
const config = await loadConfig(resolve('hatk.config.ts'));
|
|
1917
|
+
const { initDatabase, getSchemaDump } = await import("./database/db.js");
|
|
1918
|
+
const { createAdapter } = await import("./database/adapter-factory.js");
|
|
1919
|
+
const { getDialect } = await import("./database/dialect.js");
|
|
1920
|
+
const configDir2 = resolve('.');
|
|
1921
|
+
const lexicons2 = loadLexicons(resolve(configDir2, 'lexicons'));
|
|
1922
|
+
const collections2 = config.collections.length > 0 ? config.collections : discoverCollections(lexicons2);
|
|
1923
|
+
const { schemas: schemas2, ddlStatements: ddl2 } = buildSchemas(lexicons2, collections2, getDialect(config.databaseEngine));
|
|
1924
|
+
if (config.database !== ':memory:') {
|
|
1925
|
+
mkdirSync(dirname(config.database), { recursive: true });
|
|
1653
1926
|
}
|
|
1927
|
+
const { adapter: adapter2 } = await createAdapter(config.databaseEngine);
|
|
1928
|
+
await initDatabase(adapter2, config.database, schemas2, ddl2);
|
|
1929
|
+
console.log(await getSchemaDump());
|
|
1654
1930
|
}
|
|
1655
1931
|
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
|
-
}
|
|
1932
|
+
const mainPath = resolve(import.meta.dirname, 'main.js');
|
|
1933
|
+
await spawnForward('npx', ['tsx', mainPath, 'hatk.config.ts']);
|
|
1665
1934
|
}
|
|
1666
1935
|
else {
|
|
1667
1936
|
usage();
|