@hatk/hatk 0.0.1-alpha.43 → 0.0.1-alpha.45
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/cli.js +10 -510
- package/dist/database/db.d.ts +2 -0
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +22 -18
- package/dist/database/fts.js +6 -6
- package/dist/database/schema.d.ts +1 -0
- package/dist/database/schema.d.ts.map +1 -1
- package/dist/database/schema.js +10 -6
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +11 -2
- package/dist/main.js +0 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdirSync, writeFileSync, existsSync, unlinkSync, readdirSync, readFileSync
|
|
3
|
-
import { resolve, join
|
|
2
|
+
import { mkdirSync, writeFileSync, existsSync, unlinkSync, readdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve, join } from 'node:path';
|
|
4
4
|
import { execSync, spawn } from 'node:child_process';
|
|
5
|
-
import { loadLexicons
|
|
5
|
+
import { loadLexicons } 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];
|
|
@@ -69,15 +69,11 @@ function usage() {
|
|
|
69
69
|
console.log(`
|
|
70
70
|
Usage: hatk <command> [options]
|
|
71
71
|
|
|
72
|
-
Getting Started
|
|
73
|
-
new <name> [--svelte] [--duckdb] [--template <t>] Create a new hatk project
|
|
74
|
-
|
|
75
72
|
Running
|
|
76
73
|
start Start the hatk server
|
|
77
74
|
dev Start PDS, seed, and run hatk
|
|
78
75
|
seed Seed local PDS with fixture data
|
|
79
76
|
reset Reset database and PDS for a clean slate
|
|
80
|
-
schema Show database schema from lexicons
|
|
81
77
|
|
|
82
78
|
Code Quality
|
|
83
79
|
check Type-check and lint the project
|
|
@@ -193,493 +189,12 @@ const dirs = {
|
|
|
193
189
|
};
|
|
194
190
|
// --- Commands ---
|
|
195
191
|
if (command === 'new') {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const templateName = templateIdx !== -1 ? args[templateIdx + 1] : null;
|
|
203
|
-
if (templateIdx !== -1 && !templateName) {
|
|
204
|
-
console.error('Usage: hatk new <name> --template <template-name>');
|
|
205
|
-
process.exit(1);
|
|
206
|
-
}
|
|
207
|
-
const dir = resolve(name);
|
|
208
|
-
if (existsSync(dir)) {
|
|
209
|
-
console.error(`Directory ${name} already exists`);
|
|
210
|
-
process.exit(1);
|
|
211
|
-
}
|
|
212
|
-
if (templateName) {
|
|
213
|
-
const repo = `https://github.com/hatk-dev/hatk-template-${templateName}.git`;
|
|
214
|
-
console.log(`Cloning template ${templateName}...`);
|
|
215
|
-
try {
|
|
216
|
-
execSync(`git clone --depth 1 ${repo} ${dir}`, { stdio: 'inherit' });
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
console.error(`Failed to clone template: ${repo}`);
|
|
220
|
-
process.exit(1);
|
|
221
|
-
}
|
|
222
|
-
execSync(`rm -rf ${join(dir, '.git')}`);
|
|
223
|
-
const pkgPath = join(dir, 'package.json');
|
|
224
|
-
if (existsSync(pkgPath)) {
|
|
225
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
226
|
-
pkg.name = name;
|
|
227
|
-
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
228
|
-
}
|
|
229
|
-
console.log(`\nCreated ${name}/ from template ${templateName}`);
|
|
230
|
-
console.log(`\n cd ${name}`);
|
|
231
|
-
console.log(` npm install`);
|
|
232
|
-
console.log(` hatk dev`);
|
|
233
|
-
process.exit(0);
|
|
234
|
-
}
|
|
235
|
-
const withSvelte = args.includes('--svelte');
|
|
236
|
-
const withDuckdb = args.includes('--duckdb');
|
|
237
|
-
const dbEngine = withDuckdb ? 'duckdb' : 'sqlite';
|
|
238
|
-
mkdirSync(dir);
|
|
239
|
-
const subs = [
|
|
240
|
-
'lexicons',
|
|
241
|
-
'server',
|
|
242
|
-
'seeds',
|
|
243
|
-
'public',
|
|
244
|
-
'test',
|
|
245
|
-
'test/server',
|
|
246
|
-
'test/integration',
|
|
247
|
-
'test/browser',
|
|
248
|
-
'test/fixtures',
|
|
249
|
-
];
|
|
250
|
-
if (withSvelte)
|
|
251
|
-
subs.push('src', 'src/routes', 'src/lib');
|
|
252
|
-
for (const sub of subs) {
|
|
253
|
-
mkdirSync(join(dir, sub));
|
|
254
|
-
}
|
|
255
|
-
writeFileSync(join(dir, 'hatk.config.ts'), `import { defineConfig } from '@hatk/hatk/config'
|
|
256
|
-
|
|
257
|
-
export default defineConfig({
|
|
258
|
-
relay: 'ws://localhost:2583',
|
|
259
|
-
plc: 'http://localhost:2582',
|
|
260
|
-
port: 3000,
|
|
261
|
-
databaseEngine: '${dbEngine}',
|
|
262
|
-
database: 'data/hatk.db',
|
|
263
|
-
admins: [],
|
|
264
|
-
backfill: {
|
|
265
|
-
parallelism: 10,
|
|
266
|
-
},
|
|
267
|
-
})
|
|
268
|
-
`);
|
|
269
|
-
writeFileSync(join(dir, 'public', 'index.html'), `<!DOCTYPE html>
|
|
270
|
-
<html><head><title>${name}</title></head>
|
|
271
|
-
<body><h1>${name}</h1></body></html>
|
|
272
|
-
`);
|
|
273
|
-
// Copy core framework lexicons (dev.hatk.* and dependencies like com.atproto.repo.strongRef)
|
|
274
|
-
const builtinLexDir = join(import.meta.dirname, 'lexicons');
|
|
275
|
-
cpSync(builtinLexDir, join(dir, 'lexicons'), { recursive: true });
|
|
276
|
-
writeFileSync(join(dir, 'seeds', 'seed.ts'), loadTemplate('seed.tpl', ''));
|
|
277
|
-
writeFileSync(join(dir, 'docker-compose.yml'), `services:
|
|
278
|
-
plc:
|
|
279
|
-
build:
|
|
280
|
-
context: https://github.com/did-method-plc/did-method-plc.git
|
|
281
|
-
dockerfile: packages/server/Dockerfile
|
|
282
|
-
ports:
|
|
283
|
-
- '2582:2582'
|
|
284
|
-
environment:
|
|
285
|
-
- DATABASE_URL=postgres://plc:plc@postgres:5432/plc
|
|
286
|
-
- PORT=2582
|
|
287
|
-
command: ['dumb-init', 'node', '--enable-source-maps', '../dist/bin.js']
|
|
288
|
-
depends_on:
|
|
289
|
-
postgres:
|
|
290
|
-
condition: service_healthy
|
|
291
|
-
healthcheck:
|
|
292
|
-
test: ['CMD-SHELL', 'wget -q --spider http://localhost:2582/_health || exit 1']
|
|
293
|
-
interval: 2s
|
|
294
|
-
timeout: 5s
|
|
295
|
-
retries: 15
|
|
296
|
-
|
|
297
|
-
pds:
|
|
298
|
-
image: ghcr.io/bluesky-social/pds:latest
|
|
299
|
-
ports:
|
|
300
|
-
- '2583:2583'
|
|
301
|
-
environment:
|
|
302
|
-
- PDS_HOSTNAME=localhost
|
|
303
|
-
- PDS_PORT=2583
|
|
304
|
-
- PDS_DID_PLC_URL=http://plc:2582
|
|
305
|
-
- PDS_DATA_DIRECTORY=/pds
|
|
306
|
-
- PDS_BLOBSTORE_DISK_LOCATION=/pds/blobs
|
|
307
|
-
- PDS_JWT_SECRET=dev-jwt-secret
|
|
308
|
-
- PDS_ADMIN_PASSWORD=dev-admin
|
|
309
|
-
- PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
|
310
|
-
- PDS_INVITE_REQUIRED=false
|
|
311
|
-
- PDS_DEV_MODE=true
|
|
312
|
-
- LOG_ENABLED=true
|
|
313
|
-
volumes:
|
|
314
|
-
- pds_data:/pds
|
|
315
|
-
depends_on:
|
|
316
|
-
plc:
|
|
317
|
-
condition: service_healthy
|
|
318
|
-
healthcheck:
|
|
319
|
-
test: ['CMD-SHELL', 'wget -q --spider http://localhost:2583/xrpc/_health || exit 1']
|
|
320
|
-
interval: 2s
|
|
321
|
-
timeout: 5s
|
|
322
|
-
retries: 15
|
|
323
|
-
|
|
324
|
-
postgres:
|
|
325
|
-
image: postgres:16-alpine
|
|
326
|
-
environment:
|
|
327
|
-
- POSTGRES_USER=plc
|
|
328
|
-
- POSTGRES_PASSWORD=plc
|
|
329
|
-
- POSTGRES_DB=plc
|
|
330
|
-
volumes:
|
|
331
|
-
- plc_data:/var/lib/postgresql/data
|
|
332
|
-
healthcheck:
|
|
333
|
-
test: ['CMD-SHELL', 'pg_isready -U plc']
|
|
334
|
-
interval: 2s
|
|
335
|
-
timeout: 5s
|
|
336
|
-
retries: 10
|
|
337
|
-
|
|
338
|
-
volumes:
|
|
339
|
-
pds_data:
|
|
340
|
-
plc_data:
|
|
341
|
-
`);
|
|
342
|
-
writeFileSync(join(dir, '.dockerignore'), `node_modules
|
|
343
|
-
data
|
|
344
|
-
.svelte-kit
|
|
345
|
-
public
|
|
346
|
-
`);
|
|
347
|
-
writeFileSync(join(dir, 'Dockerfile'), `FROM node:25-slim
|
|
348
|
-
WORKDIR /app
|
|
349
|
-
COPY package.json package-lock.json ./
|
|
350
|
-
RUN npm ci
|
|
351
|
-
COPY . .
|
|
352
|
-
RUN node_modules/.bin/hatk build
|
|
353
|
-
RUN npm prune --omit=dev
|
|
354
|
-
EXPOSE 3000
|
|
355
|
-
CMD ["node", "--experimental-strip-types", "--max-old-space-size=512", "node_modules/@hatk/hatk/dist/main.js", "hatk.config.ts"]
|
|
356
|
-
`);
|
|
357
|
-
const pkgDeps = { '@hatk/oauth-client': '*', hatk: '*' };
|
|
358
|
-
if (!withDuckdb) {
|
|
359
|
-
pkgDeps['better-sqlite3'] = '^11';
|
|
360
|
-
}
|
|
361
|
-
const pkgDevDeps = {
|
|
362
|
-
'@playwright/test': '^1',
|
|
363
|
-
oxfmt: '^0.35.0',
|
|
364
|
-
oxlint: '^1',
|
|
365
|
-
typescript: '^5',
|
|
366
|
-
vite: '^6',
|
|
367
|
-
vitest: '^4',
|
|
368
|
-
'@types/node': '^22',
|
|
369
|
-
};
|
|
370
|
-
if (withSvelte) {
|
|
371
|
-
pkgDevDeps['@sveltejs/adapter-static'] = '^3';
|
|
372
|
-
pkgDevDeps['@sveltejs/kit'] = '^2';
|
|
373
|
-
pkgDevDeps['@sveltejs/vite-plugin-svelte'] = '^5';
|
|
374
|
-
pkgDevDeps['svelte'] = '^5';
|
|
375
|
-
pkgDevDeps['svelte-check'] = '^4';
|
|
376
|
-
}
|
|
377
|
-
writeFileSync(join(dir, 'package.json'), JSON.stringify({
|
|
378
|
-
name,
|
|
379
|
-
private: true,
|
|
380
|
-
type: 'module',
|
|
381
|
-
scripts: {
|
|
382
|
-
start: 'hatk start',
|
|
383
|
-
dev: 'hatk dev',
|
|
384
|
-
build: 'hatk build',
|
|
385
|
-
check: 'hatk check',
|
|
386
|
-
format: 'hatk format',
|
|
387
|
-
},
|
|
388
|
-
dependencies: pkgDeps,
|
|
389
|
-
devDependencies: pkgDevDeps,
|
|
390
|
-
}, null, 2) + '\n');
|
|
391
|
-
writeFileSync(join(dir, 'tsconfig.server.json'), JSON.stringify({
|
|
392
|
-
compilerOptions: {
|
|
393
|
-
target: 'ES2022',
|
|
394
|
-
module: 'Node16',
|
|
395
|
-
moduleResolution: 'Node16',
|
|
396
|
-
strict: true,
|
|
397
|
-
esModuleInterop: true,
|
|
398
|
-
skipLibCheck: true,
|
|
399
|
-
noEmit: true,
|
|
400
|
-
allowImportingTsExtensions: true,
|
|
401
|
-
resolveJsonModule: true,
|
|
402
|
-
},
|
|
403
|
-
include: ['server', 'seeds', 'hatk.generated.ts', 'hatk.config.ts'],
|
|
404
|
-
}, null, 2) + '\n');
|
|
405
|
-
writeFileSync(join(dir, 'playwright.config.ts'), `import { defineConfig } from '@playwright/test'
|
|
406
|
-
|
|
407
|
-
export default defineConfig({
|
|
408
|
-
testDir: 'test/browser',
|
|
409
|
-
use: { baseURL: 'http://127.0.0.1:3000' },
|
|
410
|
-
globalSetup: './test/browser/global-setup.ts',
|
|
411
|
-
})
|
|
412
|
-
`);
|
|
413
|
-
writeFileSync(join(dir, 'test/browser/global-setup.ts'), `import { execSync } from 'node:child_process'
|
|
414
|
-
import { existsSync } from 'node:fs'
|
|
415
|
-
|
|
416
|
-
export default function globalSetup() {
|
|
417
|
-
if (existsSync('src/app.html')) {
|
|
418
|
-
execSync('npx vite build', { stdio: 'inherit' })
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
`);
|
|
422
|
-
writeFileSync(join(dir, '.gitignore'), `node_modules/
|
|
423
|
-
*.db
|
|
424
|
-
data/
|
|
425
|
-
test-results/
|
|
426
|
-
.svelte-kit/
|
|
427
|
-
.DS_Store
|
|
428
|
-
public/
|
|
429
|
-
`);
|
|
430
|
-
writeFileSync(join(dir, '.oxlintrc.json'), `{
|
|
431
|
-
"ignorePatterns": ["public", "data", ".svelte-kit", "hatk.generated.ts"]
|
|
432
|
-
}
|
|
433
|
-
`);
|
|
434
|
-
writeFileSync(join(dir, '.oxfmtrc.json'), `{
|
|
435
|
-
"semi": false,
|
|
436
|
-
"singleQuote": true,
|
|
437
|
-
"trailingComma": "all",
|
|
438
|
-
"printWidth": 120,
|
|
439
|
-
"tabWidth": 2,
|
|
440
|
-
"ignorePatterns": ["public", "data", ".svelte-kit", "hatk.generated.ts"]
|
|
441
|
-
}
|
|
442
|
-
`);
|
|
443
|
-
if (withSvelte) {
|
|
444
|
-
writeFileSync(join(dir, 'svelte.config.js'), `import adapter from '@sveltejs/adapter-static'
|
|
445
|
-
|
|
446
|
-
export default {
|
|
447
|
-
kit: {
|
|
448
|
-
adapter: adapter({
|
|
449
|
-
pages: 'public',
|
|
450
|
-
assets: 'public',
|
|
451
|
-
fallback: 'index.html',
|
|
452
|
-
}),
|
|
453
|
-
paths: { base: '' },
|
|
454
|
-
alias: {
|
|
455
|
-
$hatk: './hatk.generated.ts',
|
|
456
|
-
},
|
|
457
|
-
},
|
|
458
|
-
}
|
|
459
|
-
`);
|
|
460
|
-
writeFileSync(join(dir, 'vite.config.ts'), `import { sveltekit } from '@sveltejs/kit/vite'
|
|
461
|
-
import { hatk } from '@hatk/hatk/vite-plugin'
|
|
462
|
-
import { defineConfig } from 'vite'
|
|
463
|
-
|
|
464
|
-
export default defineConfig({
|
|
465
|
-
plugins: [sveltekit(), hatk()],
|
|
466
|
-
})
|
|
467
|
-
`);
|
|
468
|
-
writeFileSync(join(dir, 'tsconfig.json'), JSON.stringify({
|
|
469
|
-
extends: './.svelte-kit/tsconfig.json',
|
|
470
|
-
compilerOptions: {
|
|
471
|
-
allowJs: true,
|
|
472
|
-
checkJs: false,
|
|
473
|
-
esModuleInterop: true,
|
|
474
|
-
forceConsistentCasingInFileNames: true,
|
|
475
|
-
resolveJsonModule: true,
|
|
476
|
-
skipLibCheck: true,
|
|
477
|
-
sourceMap: true,
|
|
478
|
-
strict: true,
|
|
479
|
-
moduleResolution: 'bundler',
|
|
480
|
-
allowImportingTsExtensions: true,
|
|
481
|
-
},
|
|
482
|
-
}, null, 2) + '\n');
|
|
483
|
-
writeFileSync(join(dir, 'src/app.html'), `<!doctype html>
|
|
484
|
-
<html lang="en">
|
|
485
|
-
<head>
|
|
486
|
-
<meta charset="utf-8" />
|
|
487
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
488
|
-
<meta name="description" content="${name}" />
|
|
489
|
-
<title>${name}</title>
|
|
490
|
-
%sveltekit.head%
|
|
491
|
-
</head>
|
|
492
|
-
<body data-sveltekit-preload-data="hover">
|
|
493
|
-
<div style="display: contents">%sveltekit.body%</div>
|
|
494
|
-
</body>
|
|
495
|
-
</html>
|
|
496
|
-
`);
|
|
497
|
-
writeFileSync(join(dir, 'src/app.css'), `*,
|
|
498
|
-
*::before,
|
|
499
|
-
*::after {
|
|
500
|
-
box-sizing: border-box;
|
|
501
|
-
margin: 0;
|
|
502
|
-
padding: 0;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
:root {
|
|
506
|
-
--bg-root: #080b12;
|
|
507
|
-
--bg-surface: #0f1419;
|
|
508
|
-
--bg-elevated: #161d27;
|
|
509
|
-
--bg-hover: #1c2633;
|
|
510
|
-
--border: #1e293b;
|
|
511
|
-
--teal: #14b8a6;
|
|
512
|
-
--text-primary: #e2e8f0;
|
|
513
|
-
--text-secondary: #94a3b8;
|
|
514
|
-
--text-muted: #64748b;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
html {
|
|
518
|
-
background: var(--bg-root);
|
|
519
|
-
color: var(--text-primary);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
body {
|
|
523
|
-
font-family: -apple-system, system-ui, sans-serif;
|
|
524
|
-
font-size: 15px;
|
|
525
|
-
line-height: 1.5;
|
|
526
|
-
min-height: 100vh;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
a {
|
|
530
|
-
color: inherit;
|
|
531
|
-
text-decoration: none;
|
|
532
|
-
}
|
|
533
|
-
`);
|
|
534
|
-
writeFileSync(join(dir, 'src/routes/+layout.svelte'), `<script lang="ts">
|
|
535
|
-
import type { Snippet } from 'svelte'
|
|
536
|
-
import '../app.css'
|
|
537
|
-
|
|
538
|
-
let { children }: { children: Snippet } = $props()
|
|
539
|
-
</script>
|
|
540
|
-
|
|
541
|
-
{@render children()}
|
|
542
|
-
`);
|
|
543
|
-
writeFileSync(join(dir, 'src/routes/+page.svelte'), `<h1>${name}</h1>
|
|
544
|
-
<p>Your hatk server is running.</p>
|
|
545
|
-
`);
|
|
546
|
-
writeFileSync(join(dir, 'src/error.html'), `<!doctype html>
|
|
547
|
-
<html lang="en">
|
|
548
|
-
<head>
|
|
549
|
-
<meta charset="utf-8" />
|
|
550
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
551
|
-
<title>%sveltekit.error.message% — ${name}</title>
|
|
552
|
-
<style>
|
|
553
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
554
|
-
body {
|
|
555
|
-
font-family: -apple-system, system-ui, sans-serif;
|
|
556
|
-
background: #080b12; color: #e2e8f0;
|
|
557
|
-
min-height: 100vh; display: flex; align-items: center; justify-content: center;
|
|
558
|
-
}
|
|
559
|
-
.error-page { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 8px; padding: 24px; }
|
|
560
|
-
.error-code { font-size: 72px; font-weight: 800; color: #14b8a6; line-height: 1; }
|
|
561
|
-
.error-title { font-size: 24px; font-weight: 800; }
|
|
562
|
-
.error-link {
|
|
563
|
-
margin-top: 16px; padding: 10px 24px; background: #14b8a6; color: #000;
|
|
564
|
-
border-radius: 20px; font-weight: 600; font-size: 14px; text-decoration: none;
|
|
565
|
-
}
|
|
566
|
-
</style>
|
|
567
|
-
</head>
|
|
568
|
-
<body>
|
|
569
|
-
<div class="error-page">
|
|
570
|
-
<span class="error-code">%sveltekit.status%</span>
|
|
571
|
-
<h1 class="error-title">%sveltekit.error.message%</h1>
|
|
572
|
-
<a href="/" class="error-link">Back to home</a>
|
|
573
|
-
</div>
|
|
574
|
-
</body>
|
|
575
|
-
</html>
|
|
576
|
-
`);
|
|
577
|
-
writeFileSync(join(dir, 'src/routes/+error.svelte'), `<script lang="ts">
|
|
578
|
-
import { page } from '$app/state'
|
|
579
|
-
</script>
|
|
580
|
-
|
|
581
|
-
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 80vh; gap: 8px;">
|
|
582
|
-
<span style="font-size: 72px; font-weight: 800; color: var(--teal);">{page.status}</span>
|
|
583
|
-
<h1 style="font-size: 24px; font-weight: 800;">{page.error?.message}</h1>
|
|
584
|
-
<a href="/" style="margin-top: 16px; padding: 10px 24px; background: var(--teal); color: #000; border-radius: 20px; font-weight: 600; font-size: 14px;">Back to home</a>
|
|
585
|
-
</div>
|
|
586
|
-
`);
|
|
587
|
-
}
|
|
588
|
-
let agentsMd = `# hatk project
|
|
589
|
-
|
|
590
|
-
This is an AT Protocol application built with [hatk](https://github.com/hatk-dev/hatk).
|
|
591
|
-
Read the project's lexicons in \`lexicons/\` to understand the data model.
|
|
592
|
-
Types are generated from lexicons into \`hatk.generated.ts\` — never edit this file directly.
|
|
593
|
-
|
|
594
|
-
## Project structure
|
|
595
|
-
|
|
596
|
-
| Directory | Purpose |
|
|
597
|
-
|-------------|------------------------------------------------------|
|
|
598
|
-
| \`lexicons/\` | AT Protocol lexicon schemas (JSON). Defines collections and XRPC methods |
|
|
599
|
-
| \`server/\` | All server-side code: feeds, XRPC handlers, hooks, labels, OG routes, setup scripts |
|
|
600
|
-
| \`seeds/\` | Test data seeding scripts for local development |
|
|
601
|
-
| \`test/\` | Test files (vitest). Run with \`vp test\` |
|
|
602
|
-
| \`public/\` | Static files served at the root |
|
|
603
|
-
`;
|
|
604
|
-
if (withSvelte) {
|
|
605
|
-
agentsMd += `| \`src/\` | SvelteKit frontend (routes, components, styles) |
|
|
606
|
-
|
|
607
|
-
`;
|
|
608
|
-
}
|
|
609
|
-
else {
|
|
610
|
-
agentsMd += `
|
|
611
|
-
`;
|
|
612
|
-
}
|
|
613
|
-
agentsMd += `## Key files
|
|
614
|
-
|
|
615
|
-
- \`hatk.config.ts\` — project configuration (see \`defineConfig\` for type info)
|
|
616
|
-
- \`hatk.generated.ts\` — auto-generated server types and helpers. Regenerate with \`hatk generate types\`
|
|
617
|
-
- \`hatk.generated.client.ts\` — auto-generated client-safe types and \`callXrpc\`. Never import \`hatk.generated.ts\` from frontend code
|
|
618
|
-
|
|
619
|
-
## The \`$hatk\` alias
|
|
620
|
-
|
|
621
|
-
Server files in \`server/\` import from \`$hatk\`:
|
|
622
|
-
\`\`\`ts
|
|
623
|
-
import { defineFeed, views, type Status } from "$hatk"
|
|
624
|
-
\`\`\`
|
|
625
|
-
`;
|
|
626
|
-
if (withSvelte) {
|
|
627
|
-
agentsMd += `
|
|
628
|
-
SvelteKit routes and components import from \`$hatk/client\`:
|
|
629
|
-
\`\`\`ts
|
|
630
|
-
import { callXrpc, getViewer } from "$hatk/client"
|
|
631
|
-
\`\`\`
|
|
632
|
-
|
|
633
|
-
\`$hatk\` resolves to \`hatk.generated.ts\` and \`$hatk/client\` to \`hatk.generated.client.ts\`.
|
|
634
|
-
The Vite plugin handles this in dev/build. In tests and production, a Node.js module resolve hook handles it.
|
|
635
|
-
`;
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
agentsMd += `
|
|
639
|
-
\`$hatk\` resolves to \`hatk.generated.ts\`. The Vite plugin handles this in dev/build.
|
|
640
|
-
In tests and production, a Node.js module resolve hook handles it.
|
|
641
|
-
`;
|
|
642
|
-
}
|
|
643
|
-
agentsMd += `
|
|
644
|
-
## Commands
|
|
645
|
-
|
|
646
|
-
Run \`npx hatk --help\` for the full list of commands.
|
|
647
|
-
|
|
648
|
-
Use \`npx hatk generate\` to scaffold new feeds, xrpc handlers, labels, and lexicons
|
|
649
|
-
rather than creating files manually. These generate files with the correct imports.
|
|
650
|
-
|
|
651
|
-
After modifying lexicons, always run \`npx hatk generate types\` to update the generated types.
|
|
652
|
-
`;
|
|
653
|
-
if (withSvelte) {
|
|
654
|
-
agentsMd += `
|
|
655
|
-
## Running
|
|
656
|
-
|
|
657
|
-
- \`vp dev\` — start dev server (hatk + SvelteKit + PDS)
|
|
658
|
-
- \`vp build\` — build for production (SvelteKit outputs to \`build/\`)
|
|
659
|
-
- \`hatk start\` — start production server (hatk + SvelteKit via \`build/handler.js\`)
|
|
660
|
-
- \`vp test\` — run tests
|
|
661
|
-
`;
|
|
662
|
-
}
|
|
663
|
-
writeFileSync(join(dir, 'AGENTS.md'), agentsMd);
|
|
664
|
-
console.log(`Created ${name}/`);
|
|
665
|
-
console.log(` hatk.config.ts`);
|
|
666
|
-
console.log(` lexicons/ — lexicon JSON files (core + your own)`);
|
|
667
|
-
console.log(` server/ — feeds, XRPC handlers, hooks, labels, OG routes, setup`);
|
|
668
|
-
console.log(` seeds/ — seed fixture data (hatk seed)`);
|
|
669
|
-
console.log(` test/ — test files (hatk test)`);
|
|
670
|
-
console.log(` public/ — static files`);
|
|
671
|
-
console.log(` docker-compose.yml — local PDS for development`);
|
|
672
|
-
console.log(` Dockerfile — production container`);
|
|
673
|
-
if (withSvelte) {
|
|
674
|
-
console.log(` src/ — SvelteKit frontend`);
|
|
675
|
-
console.log(` svelte.config.js`);
|
|
676
|
-
console.log(` vite.config.ts`);
|
|
677
|
-
}
|
|
678
|
-
// Generate types so the project is ready to go
|
|
679
|
-
execSync('npx hatk generate types', { stdio: 'inherit', cwd: dir });
|
|
680
|
-
if (withSvelte) {
|
|
681
|
-
execSync('npx svelte-kit sync', { stdio: 'inherit', cwd: dir });
|
|
682
|
-
}
|
|
192
|
+
console.error('`hatk new` has been removed. Create a new project with:');
|
|
193
|
+
console.error('');
|
|
194
|
+
console.error(' vp create github:hatk-dev/hatk-template-starter');
|
|
195
|
+
console.error('');
|
|
196
|
+
console.error('Install vp first: curl -fsSL https://vite.plus | bash');
|
|
197
|
+
process.exit(1);
|
|
683
198
|
}
|
|
684
199
|
else if (command === 'generate') {
|
|
685
200
|
const type = args[1];
|
|
@@ -1059,6 +574,7 @@ else if (command === 'generate') {
|
|
|
1059
574
|
clientOut += ` const blob = arg as Blob | ArrayBuffer\n`;
|
|
1060
575
|
clientOut += ` const ct = blob instanceof Blob ? blob.type : 'application/octet-stream'\n`;
|
|
1061
576
|
clientOut += ` const res = await _fetch(path, { method: 'POST', headers: { 'Content-Type': ct }, body: blob })\n`;
|
|
577
|
+
clientOut += ` if (typeof window !== 'undefined' && res.status === 401) { const _b = await res.json().catch(() => ({})); const _h = _b.handle ?? getViewer()?.handle; window.location.href = _h ? \`/oauth/login?handle=\${encodeURIComponent(_h)}\` : '/oauth/login'; return new Promise(() => {}) as any }\n`;
|
|
1062
578
|
clientOut += ` if (!res.ok) throw new Error(\`XRPC \${nsid} failed: \${res.status}\`)\n`;
|
|
1063
579
|
clientOut += ` return res.json() as Promise<OutputOf<K>>\n`;
|
|
1064
580
|
clientOut += ` }\n`;
|
|
@@ -1395,22 +911,6 @@ else if (command === 'resolve') {
|
|
|
1395
911
|
console.log(`\nResolved ${resolved.size} lexicon(s). Regenerating types...`);
|
|
1396
912
|
execSync('npx hatk generate types', { stdio: 'inherit', cwd: process.cwd() });
|
|
1397
913
|
}
|
|
1398
|
-
else if (command === 'schema') {
|
|
1399
|
-
const config = await loadConfig(resolve('hatk.config.ts'));
|
|
1400
|
-
const { initDatabase, getSchemaDump } = await import("./database/db.js");
|
|
1401
|
-
const { createAdapter } = await import("./database/adapter-factory.js");
|
|
1402
|
-
const { getDialect } = await import("./database/dialect.js");
|
|
1403
|
-
const configDir2 = resolve('.');
|
|
1404
|
-
const lexicons2 = loadLexicons(resolve(configDir2, 'lexicons'));
|
|
1405
|
-
const collections2 = config.collections.length > 0 ? config.collections : discoverCollections(lexicons2);
|
|
1406
|
-
const { schemas: schemas2, ddlStatements: ddl2 } = buildSchemas(lexicons2, collections2, getDialect(config.databaseEngine));
|
|
1407
|
-
if (config.database !== ':memory:') {
|
|
1408
|
-
mkdirSync(dirname(config.database), { recursive: true });
|
|
1409
|
-
}
|
|
1410
|
-
const { adapter: adapter2 } = await createAdapter(config.databaseEngine);
|
|
1411
|
-
await initDatabase(adapter2, config.database, schemas2, ddl2);
|
|
1412
|
-
console.log(await getSchemaDump());
|
|
1413
|
-
}
|
|
1414
914
|
else if (command === 'start') {
|
|
1415
915
|
const mainPath = resolve(import.meta.dirname, 'main.js');
|
|
1416
916
|
await spawnForward('npx', ['tsx', mainPath, 'hatk.config.ts']);
|
package/dist/database/db.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export declare function setRepoStatus(did: string, status: string, rev?: string,
|
|
|
25
25
|
retryAfter?: number;
|
|
26
26
|
handle?: string | null;
|
|
27
27
|
}): Promise<void>;
|
|
28
|
+
/** Update the handle for a DID if it exists in _repos. */
|
|
29
|
+
export declare function updateRepoHandle(did: string, handle: string): Promise<void>;
|
|
28
30
|
export declare function getRepoRev(did: string): Promise<string | null>;
|
|
29
31
|
export declare function getRepoRetryInfo(did: string): Promise<{
|
|
30
32
|
retryCount: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/database/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/database/db.ts"],"names":[],"mappings":"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"}
|
package/dist/database/db.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toSnakeCase } from "./schema.js";
|
|
1
|
+
import { toSnakeCase, q } from "./schema.js";
|
|
2
2
|
import { getSearchColumns, stripStopWords, getSearchPort, updateFtsRecord, deleteFtsRecord } from "./fts.js";
|
|
3
3
|
import { emit, timer } from "../logger.js";
|
|
4
4
|
import { OAUTH_DDL } from "../oauth/db.js";
|
|
@@ -354,6 +354,10 @@ export async function setRepoStatus(did, status, rev, opts) {
|
|
|
354
354
|
await run(`INSERT OR IGNORE INTO _repos (did, status) VALUES ($1, $2)`, [did, status]);
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
|
+
/** Update the handle for a DID if it exists in _repos. */
|
|
358
|
+
export async function updateRepoHandle(did, handle) {
|
|
359
|
+
await run(`UPDATE _repos SET handle = $1 WHERE did = $2`, [handle, did]);
|
|
360
|
+
}
|
|
357
361
|
export async function getRepoRev(did) {
|
|
358
362
|
const rows = await all(`SELECT rev FROM _repos WHERE did = $1`, [did]);
|
|
359
363
|
return rows[0]?.rev ?? null;
|
|
@@ -508,7 +512,7 @@ export function buildInsertOp(collection, uri, cid, authorDid, record) {
|
|
|
508
512
|
else if (col.originalName.endsWith('__cid') && record[col.originalName.replace('__cid', '')]) {
|
|
509
513
|
rawValue = record[col.originalName.replace('__cid', '')].cid;
|
|
510
514
|
}
|
|
511
|
-
colNames.push(col.name);
|
|
515
|
+
colNames.push(q(col.name));
|
|
512
516
|
placeholders.push(`$${paramIdx++}`);
|
|
513
517
|
if (rawValue === undefined || rawValue === null) {
|
|
514
518
|
values.push(null);
|
|
@@ -542,7 +546,7 @@ export async function insertRecord(collection, uri, cid, authorDid, record) {
|
|
|
542
546
|
const values = [uri, authorDid];
|
|
543
547
|
let idx = 3;
|
|
544
548
|
for (const col of child.columns) {
|
|
545
|
-
colNames.push(col.name);
|
|
549
|
+
colNames.push(q(col.name));
|
|
546
550
|
placeholders.push(`$${idx++}`);
|
|
547
551
|
const raw = item[col.originalName];
|
|
548
552
|
if (raw === undefined || raw === null) {
|
|
@@ -581,7 +585,7 @@ export async function insertRecord(collection, uri, cid, authorDid, record) {
|
|
|
581
585
|
const values = [uri, authorDid];
|
|
582
586
|
let idx = 3;
|
|
583
587
|
for (const col of branch.columns) {
|
|
584
|
-
colNames.push(col.name);
|
|
588
|
+
colNames.push(q(col.name));
|
|
585
589
|
placeholders.push(`$${idx++}`);
|
|
586
590
|
const raw = item[col.originalName];
|
|
587
591
|
if (raw === undefined || raw === null) {
|
|
@@ -605,7 +609,7 @@ export async function insertRecord(collection, uri, cid, authorDid, record) {
|
|
|
605
609
|
const values = [uri, authorDid];
|
|
606
610
|
let idx = 3;
|
|
607
611
|
for (const col of branch.columns) {
|
|
608
|
-
colNames.push(col.name);
|
|
612
|
+
colNames.push(q(col.name));
|
|
609
613
|
placeholders.push(`$${idx++}`);
|
|
610
614
|
const raw = branchData[col.originalName];
|
|
611
615
|
if (raw === undefined || raw === null) {
|
|
@@ -704,7 +708,7 @@ export async function bulkInsertRecords(records) {
|
|
|
704
708
|
if (!schema)
|
|
705
709
|
continue;
|
|
706
710
|
const stagingTable = `_staging_${collection.replace(/\./g, '_')}`;
|
|
707
|
-
const allCols = ['uri', 'cid', 'did', 'indexed_at', ...schema.columns.map((c) => c.name)];
|
|
711
|
+
const allCols = ['uri', 'cid', 'did', 'indexed_at', ...schema.columns.map((c) => q(c.name))];
|
|
708
712
|
const colDefs = [
|
|
709
713
|
'uri TEXT',
|
|
710
714
|
'cid TEXT',
|
|
@@ -713,7 +717,7 @@ export async function bulkInsertRecords(records) {
|
|
|
713
717
|
...schema.columns.map((c) => {
|
|
714
718
|
const t = c.sqlType;
|
|
715
719
|
// Use TEXT for timestamp columns in staging (will cast on merge)
|
|
716
|
-
return `${c.name} ${t === 'TIMESTAMP' || t === 'TIMESTAMPTZ' ? 'TEXT' : t}`;
|
|
720
|
+
return `${q(c.name)} ${t === 'TIMESTAMP' || t === 'TIMESTAMPTZ' ? 'TEXT' : t}`;
|
|
717
721
|
}),
|
|
718
722
|
];
|
|
719
723
|
await port.execute(`DROP TABLE IF EXISTS ${stagingTable}`, []);
|
|
@@ -736,7 +740,7 @@ export async function bulkInsertRecords(records) {
|
|
|
736
740
|
await inserter.close();
|
|
737
741
|
// Merge into target, filtering rows that would violate NOT NULL
|
|
738
742
|
const selectCols = allCols.map((name) => {
|
|
739
|
-
const col = schema.columns.find((c) => c.name === name);
|
|
743
|
+
const col = schema.columns.find((c) => q(c.name) === name);
|
|
740
744
|
if (name === 'indexed_at' || (col && (col.sqlType === 'TIMESTAMP' || col.sqlType === 'TIMESTAMPTZ'))) {
|
|
741
745
|
return `${dialect.tryCastTimestamp(name)} AS ${name}`;
|
|
742
746
|
}
|
|
@@ -746,10 +750,10 @@ export async function bulkInsertRecords(records) {
|
|
|
746
750
|
for (const col of schema.columns) {
|
|
747
751
|
if (col.notNull) {
|
|
748
752
|
if (col.sqlType === 'TIMESTAMP' || col.sqlType === 'TIMESTAMPTZ') {
|
|
749
|
-
notNullChecks.push(`${dialect.tryCastTimestamp(col.name)} IS NOT NULL`);
|
|
753
|
+
notNullChecks.push(`${dialect.tryCastTimestamp(q(col.name))} IS NOT NULL`);
|
|
750
754
|
}
|
|
751
755
|
else {
|
|
752
|
-
notNullChecks.push(`${col.name} IS NOT NULL`);
|
|
756
|
+
notNullChecks.push(`${q(col.name)} IS NOT NULL`);
|
|
753
757
|
}
|
|
754
758
|
}
|
|
755
759
|
}
|
|
@@ -764,10 +768,10 @@ export async function bulkInsertRecords(records) {
|
|
|
764
768
|
'parent_did TEXT',
|
|
765
769
|
...child.columns.map((c) => {
|
|
766
770
|
const t = c.sqlType;
|
|
767
|
-
return `${c.name} ${t === 'TIMESTAMP' || t === 'TIMESTAMPTZ' ? 'TEXT' : t}`;
|
|
771
|
+
return `${q(c.name)} ${t === 'TIMESTAMP' || t === 'TIMESTAMPTZ' ? 'TEXT' : t}`;
|
|
768
772
|
}),
|
|
769
773
|
];
|
|
770
|
-
const childAllCols = ['parent_uri', 'parent_did', ...child.columns.map((c) => c.name)];
|
|
774
|
+
const childAllCols = ['parent_uri', 'parent_did', ...child.columns.map((c) => q(c.name))];
|
|
771
775
|
await port.execute(`DROP TABLE IF EXISTS ${childStagingTable}`, []);
|
|
772
776
|
await port.execute(`CREATE TABLE ${childStagingTable} (${childColDefs.join(', ')})`, []);
|
|
773
777
|
const childInserter = await port.createBulkInserter(childStagingTable, childAllCols);
|
|
@@ -793,7 +797,7 @@ export async function bulkInsertRecords(records) {
|
|
|
793
797
|
const uriPlaceholders = recs.map((_, i) => `$${i + 1}`).join(',');
|
|
794
798
|
await port.execute(`DELETE FROM ${child.tableName} WHERE parent_uri IN (${uriPlaceholders})`, recs.map((r) => r.uri));
|
|
795
799
|
const childSelectCols = childAllCols.map((name) => {
|
|
796
|
-
const col = child.columns.find((c) => c.name === name);
|
|
800
|
+
const col = child.columns.find((c) => q(c.name) === name);
|
|
797
801
|
if (col && (col.sqlType === 'TIMESTAMP' || col.sqlType === 'TIMESTAMPTZ')) {
|
|
798
802
|
return `${dialect.tryCastTimestamp(name)} AS ${name}`;
|
|
799
803
|
}
|
|
@@ -811,10 +815,10 @@ export async function bulkInsertRecords(records) {
|
|
|
811
815
|
'parent_did TEXT',
|
|
812
816
|
...branch.columns.map((c) => {
|
|
813
817
|
const t = c.sqlType;
|
|
814
|
-
return `${c.name} ${t === 'TIMESTAMP' || t === 'TIMESTAMPTZ' ? 'TEXT' : t}`;
|
|
818
|
+
return `${q(c.name)} ${t === 'TIMESTAMP' || t === 'TIMESTAMPTZ' ? 'TEXT' : t}`;
|
|
815
819
|
}),
|
|
816
820
|
];
|
|
817
|
-
const branchAllCols = ['parent_uri', 'parent_did', ...branch.columns.map((c) => c.name)];
|
|
821
|
+
const branchAllCols = ['parent_uri', 'parent_did', ...branch.columns.map((c) => q(c.name))];
|
|
818
822
|
await port.execute(`DROP TABLE IF EXISTS ${branchStagingTable}`, []);
|
|
819
823
|
await port.execute(`CREATE TABLE ${branchStagingTable} (${branchColDefs.join(', ')})`, []);
|
|
820
824
|
const branchInserter = await port.createBulkInserter(branchStagingTable, branchAllCols);
|
|
@@ -860,7 +864,7 @@ export async function bulkInsertRecords(records) {
|
|
|
860
864
|
const uriPlaceholders = recs.map((_, i) => `$${i + 1}`).join(',');
|
|
861
865
|
await port.execute(`DELETE FROM ${branch.tableName} WHERE parent_uri IN (${uriPlaceholders})`, recs.map((r) => r.uri));
|
|
862
866
|
const branchSelectCols = branchAllCols.map((name) => {
|
|
863
|
-
const col = branch.columns.find((c) => c.name === name);
|
|
867
|
+
const col = branch.columns.find((c) => q(c.name) === name);
|
|
864
868
|
if (col && (col.sqlType === 'TIMESTAMP' || col.sqlType === 'TIMESTAMPTZ')) {
|
|
865
869
|
return `${dialect.tryCastTimestamp(name)} AS ${name}`;
|
|
866
870
|
}
|
|
@@ -1138,14 +1142,14 @@ export async function searchRecords(collection, query, opts = {}) {
|
|
|
1138
1142
|
const remaining = limit - bm25Results.length;
|
|
1139
1143
|
const jwFn = dialect.jaroWinklerSimilarity;
|
|
1140
1144
|
const simExprs = [
|
|
1141
|
-
...textCols.map((c) => `${jwFn}(lower(t.${c.name}), lower($1))`),
|
|
1145
|
+
...textCols.map((c) => `${jwFn}(lower(t.${q(c.name)}), lower($1))`),
|
|
1142
1146
|
`${jwFn}(lower(r.handle), lower($1))`,
|
|
1143
1147
|
];
|
|
1144
1148
|
// Include child table TEXT columns via correlated subquery
|
|
1145
1149
|
for (const child of schema.children) {
|
|
1146
1150
|
for (const col of child.columns) {
|
|
1147
1151
|
if (col.sqlType === 'TEXT') {
|
|
1148
|
-
simExprs.push(`COALESCE((SELECT MAX(${jwFn}(lower(c.${col.name}), lower($1))) FROM ${child.tableName} c WHERE c.parent_uri = t.uri), 0)`);
|
|
1152
|
+
simExprs.push(`COALESCE((SELECT MAX(${jwFn}(lower(c.${q(col.name)}), lower($1))) FROM ${child.tableName} c WHERE c.parent_uri = t.uri), 0)`);
|
|
1149
1153
|
}
|
|
1150
1154
|
}
|
|
1151
1155
|
}
|
package/dist/database/fts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSchema, runSQL, getSqlDialect, querySQL } from "./db.js";
|
|
2
|
-
import { getLexicon } from "./schema.js";
|
|
2
|
+
import { getLexicon, q } from "./schema.js";
|
|
3
3
|
import { emit, timer } from "../logger.js";
|
|
4
4
|
/**
|
|
5
5
|
* Resolve a lexicon ref like "#artist" to its definition.
|
|
@@ -103,7 +103,7 @@ function computeFtsSchema(collection) {
|
|
|
103
103
|
const searchColNames = [];
|
|
104
104
|
for (const col of schema.columns) {
|
|
105
105
|
if (col.sqlType === 'TEXT') {
|
|
106
|
-
selectExprs.push(`t.${col.name}`);
|
|
106
|
+
selectExprs.push(`t.${q(col.name)}`);
|
|
107
107
|
searchColNames.push(col.name);
|
|
108
108
|
}
|
|
109
109
|
else if (col.isJson && record?.properties) {
|
|
@@ -111,7 +111,7 @@ function computeFtsSchema(collection) {
|
|
|
111
111
|
if (prop?.type === 'blob')
|
|
112
112
|
continue; // skip blobs
|
|
113
113
|
if (prop && lexicon) {
|
|
114
|
-
const derived = jsonSearchColumns(`t.${col.name}`, prop, lexicon, dialect);
|
|
114
|
+
const derived = jsonSearchColumns(`t.${q(col.name)}`, prop, lexicon, dialect);
|
|
115
115
|
if (derived.length > 0) {
|
|
116
116
|
for (const d of derived) {
|
|
117
117
|
selectExprs.push(`${d.expr} AS ${d.alias}`);
|
|
@@ -121,7 +121,7 @@ function computeFtsSchema(collection) {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
// Fallback: cast JSON to TEXT
|
|
124
|
-
selectExprs.push(`CAST(t.${col.name} AS TEXT) AS ${col.name}`);
|
|
124
|
+
selectExprs.push(`CAST(t.${q(col.name)} AS TEXT) AS ${q(col.name)}`);
|
|
125
125
|
searchColNames.push(col.name);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
@@ -130,7 +130,7 @@ function computeFtsSchema(collection) {
|
|
|
130
130
|
for (const col of child.columns) {
|
|
131
131
|
if (col.sqlType === 'TEXT') {
|
|
132
132
|
const alias = `${child.fieldName}_${col.name}`;
|
|
133
|
-
const agg = dialect.stringAgg(`c.${col.name}`, "' '");
|
|
133
|
+
const agg = dialect.stringAgg(`c.${q(col.name)}`, "' '");
|
|
134
134
|
selectExprs.push(`(SELECT ${agg} FROM ${child.tableName} c WHERE c.parent_uri = t.uri) AS ${alias}`);
|
|
135
135
|
searchColNames.push(alias);
|
|
136
136
|
}
|
|
@@ -142,7 +142,7 @@ function computeFtsSchema(collection) {
|
|
|
142
142
|
for (const col of branch.columns) {
|
|
143
143
|
if (col.sqlType === 'TEXT') {
|
|
144
144
|
const alias = `${union.fieldName}_${branch.branchName}_${col.name}`;
|
|
145
|
-
const agg = dialect.stringAgg(`c.${col.name}`, "' '");
|
|
145
|
+
const agg = dialect.stringAgg(`c.${q(col.name)}`, "' '");
|
|
146
146
|
selectExprs.push(`(SELECT ${agg} FROM ${branch.tableName} c WHERE c.parent_uri = t.uri) AS ${alias}`);
|
|
147
147
|
searchColNames.push(alias);
|
|
148
148
|
}
|
|
@@ -35,6 +35,7 @@ export interface ChildTableSchema {
|
|
|
35
35
|
columns: ColumnDef[];
|
|
36
36
|
}
|
|
37
37
|
export declare function toSnakeCase(str: string): string;
|
|
38
|
+
export declare function q(name: string): string;
|
|
38
39
|
export declare function loadLexicons(lexiconsDir: string): Map<string, any>;
|
|
39
40
|
/**
|
|
40
41
|
* Discover collections by scanning lexicons for record-type definitions.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/database/schema.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,iBAAiB,EAAE,CAAA;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,QAAQ,EAAE,gBAAgB,EAAE,CAAA;IAC5B,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB;AAGD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AA+CD,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CASlE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CASxE;AAID,wBAAgB,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAI9D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAExD;AAED,wBAAgB,cAAc,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,CAEtE;AAED,iFAAiF;AACjF,wBAAgB,eAAe,IAAI,GAAG,EAAE,CAEvC;AAyHD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,GAAG,EACZ,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,OAAO,GAAE,UAA2B,GACnC,WAAW,CA+Gb;AAGD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,UAA2B,GAAG,MAAM,CAoExG;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,WAAW,EAAE,MAAM,EAAE,EACrB,OAAO,GAAE,UAA2B,GACnC;IAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CA2BrD"}
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/database/schema.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,iBAAiB,EAAE,CAAA;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,QAAQ,EAAE,gBAAgB,EAAE,CAAA;IAC5B,MAAM,EAAE,gBAAgB,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB;AAGD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAGD,wBAAgB,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtC;AA+CD,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CASlE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,EAAE,CASxE;AAID,wBAAgB,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAI9D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAExD;AAED,wBAAgB,cAAc,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAE,CAAC,CAEtE;AAED,iFAAiF;AACjF,wBAAgB,eAAe,IAAI,GAAG,EAAE,CAEvC;AAyHD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,GAAG,EACZ,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,OAAO,GAAE,UAA2B,GACnC,WAAW,CA+Gb;AAGD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,UAA2B,GAAG,MAAM,CAoExG;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,WAAW,EAAE,MAAM,EAAE,EACrB,OAAO,GAAE,UAA2B,GACnC;IAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CA2BrD"}
|
package/dist/database/schema.js
CHANGED
|
@@ -5,6 +5,10 @@ import { DUCKDB_DIALECT } from "./dialect.js";
|
|
|
5
5
|
export function toSnakeCase(str) {
|
|
6
6
|
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
7
7
|
}
|
|
8
|
+
// Quote a column name to avoid conflicts with SQL reserved words
|
|
9
|
+
export function q(name) {
|
|
10
|
+
return `"${name}"`;
|
|
11
|
+
}
|
|
8
12
|
function mapType(prop, dialect) {
|
|
9
13
|
if (prop.type === 'string') {
|
|
10
14
|
if (prop.format === 'datetime')
|
|
@@ -312,7 +316,7 @@ export function generateCreateTableSQL(schema, dialect = DUCKDB_DIALECT) {
|
|
|
312
316
|
];
|
|
313
317
|
for (const col of schema.columns) {
|
|
314
318
|
const nullable = col.notNull ? ' NOT NULL' : '';
|
|
315
|
-
lines.push(` ${col.name} ${col.sqlType}${nullable}`);
|
|
319
|
+
lines.push(` ${q(col.name)} ${col.sqlType}${nullable}`);
|
|
316
320
|
}
|
|
317
321
|
const createTable = `CREATE TABLE IF NOT EXISTS ${schema.tableName} (\n${lines.join(',\n')}\n);`;
|
|
318
322
|
const prefix = schema.collection.replace(/\./g, '_');
|
|
@@ -322,7 +326,7 @@ export function generateCreateTableSQL(schema, dialect = DUCKDB_DIALECT) {
|
|
|
322
326
|
];
|
|
323
327
|
// Index ref columns for hydration lookups
|
|
324
328
|
for (const refCol of schema.refColumns) {
|
|
325
|
-
indexes.push(`CREATE INDEX IF NOT EXISTS idx_${prefix}_${refCol} ON ${schema.tableName}(${refCol});`);
|
|
329
|
+
indexes.push(`CREATE INDEX IF NOT EXISTS idx_${prefix}_${refCol} ON ${schema.tableName}(${q(refCol)});`);
|
|
326
330
|
}
|
|
327
331
|
// Child table DDL
|
|
328
332
|
const childDDL = [];
|
|
@@ -330,7 +334,7 @@ export function generateCreateTableSQL(schema, dialect = DUCKDB_DIALECT) {
|
|
|
330
334
|
const childLines = [' parent_uri TEXT NOT NULL', ' parent_did TEXT NOT NULL'];
|
|
331
335
|
for (const col of child.columns) {
|
|
332
336
|
const nullable = col.notNull ? ' NOT NULL' : '';
|
|
333
|
-
childLines.push(` ${col.name} ${col.sqlType}${nullable}`);
|
|
337
|
+
childLines.push(` ${q(col.name)} ${col.sqlType}${nullable}`);
|
|
334
338
|
}
|
|
335
339
|
childDDL.push(`CREATE TABLE IF NOT EXISTS ${child.tableName} (\n${childLines.join(',\n')}\n);`);
|
|
336
340
|
const childPrefix = `${prefix}__${toSnakeCase(child.fieldName)}`;
|
|
@@ -339,7 +343,7 @@ export function generateCreateTableSQL(schema, dialect = DUCKDB_DIALECT) {
|
|
|
339
343
|
for (const col of child.columns) {
|
|
340
344
|
if (col.isJson || col.sqlType === 'BLOB')
|
|
341
345
|
continue;
|
|
342
|
-
childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${childPrefix}_${col.name} ON ${child.tableName}(${col.name});`);
|
|
346
|
+
childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${childPrefix}_${col.name} ON ${child.tableName}(${q(col.name)});`);
|
|
343
347
|
}
|
|
344
348
|
}
|
|
345
349
|
// Union branch table DDL
|
|
@@ -348,7 +352,7 @@ export function generateCreateTableSQL(schema, dialect = DUCKDB_DIALECT) {
|
|
|
348
352
|
const branchLines = [' parent_uri TEXT NOT NULL', ' parent_did TEXT NOT NULL'];
|
|
349
353
|
for (const col of branch.columns) {
|
|
350
354
|
const nullable = col.notNull ? ' NOT NULL' : '';
|
|
351
|
-
branchLines.push(` ${col.name} ${col.sqlType}${nullable}`);
|
|
355
|
+
branchLines.push(` ${q(col.name)} ${col.sqlType}${nullable}`);
|
|
352
356
|
}
|
|
353
357
|
childDDL.push(`CREATE TABLE IF NOT EXISTS ${branch.tableName} (\n${branchLines.join(',\n')}\n);`);
|
|
354
358
|
const branchPrefix = branch.tableName.replace(/"/g, '').replace(/\./g, '_');
|
|
@@ -357,7 +361,7 @@ export function generateCreateTableSQL(schema, dialect = DUCKDB_DIALECT) {
|
|
|
357
361
|
for (const col of branch.columns) {
|
|
358
362
|
if (col.isJson || col.sqlType === 'BLOB')
|
|
359
363
|
continue;
|
|
360
|
-
childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${branchPrefix}_${col.name} ON ${branch.tableName}(${col.name});`);
|
|
364
|
+
childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${branchPrefix}_${col.name} ON ${branch.tableName}(${q(col.name)});`);
|
|
361
365
|
}
|
|
362
366
|
}
|
|
363
367
|
}
|
package/dist/indexer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"AAyJA;;;;;;;GAOG;AACH,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGxD;AAED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA4EjF;AAED,8CAA8C;AAC9C,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAyBD;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAmDxE"}
|
package/dist/indexer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cborDecode } from "./cbor.js";
|
|
2
2
|
import { parseCarFrame } from "./car.js";
|
|
3
|
-
import { insertRecord, deleteRecord, setCursor, setRepoStatus, getRepoRetryInfo, listAllRepoStatuses, getDatabasePort, } from "./database/db.js";
|
|
3
|
+
import { insertRecord, deleteRecord, setCursor, setRepoStatus, getRepoRetryInfo, listAllRepoStatuses, getDatabasePort, updateRepoHandle, } from "./database/db.js";
|
|
4
4
|
import { backfillRepo } from "./backfill.js";
|
|
5
5
|
import { rebuildAllIndexes } from "./database/fts.js";
|
|
6
6
|
import { log, emit, timer } from "./logger.js";
|
|
@@ -260,7 +260,7 @@ export async function startIndexer(opts) {
|
|
|
260
260
|
}
|
|
261
261
|
log(`[indexer] Warmed repo status cache with ${statuses.length} entries`);
|
|
262
262
|
}
|
|
263
|
-
startMemoryDiagnostics()
|
|
263
|
+
// startMemoryDiagnostics()
|
|
264
264
|
let wsUrl = `${relayUrl}/xrpc/com.atproto.sync.subscribeRepos`;
|
|
265
265
|
if (cursor) {
|
|
266
266
|
wsUrl += `?cursor=${cursor}`;
|
|
@@ -297,6 +297,15 @@ export async function startIndexer(opts) {
|
|
|
297
297
|
function processMessage(bytes, collections) {
|
|
298
298
|
const header = cborDecode(bytes, 0);
|
|
299
299
|
const body = cborDecode(bytes, header.offset);
|
|
300
|
+
// Handle identity events (handle changes)
|
|
301
|
+
if (header.value.t === '#identity') {
|
|
302
|
+
const did = body.value.did;
|
|
303
|
+
const handle = body.value.handle;
|
|
304
|
+
if (did && handle && repoStatusCache.has(did)) {
|
|
305
|
+
updateRepoHandle(did, handle).catch(() => { });
|
|
306
|
+
}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
300
309
|
if (header.value.op !== 1 || header.value.t !== '#commit')
|
|
301
310
|
return;
|
|
302
311
|
if (!body.value.blocks || !body.value.ops)
|
package/dist/main.js
CHANGED
|
@@ -34,14 +34,9 @@ import { parseSessionCookie, getSessionCookieName } from "./oauth/session.js";
|
|
|
34
34
|
import { loadOnLoginHook } from "./hooks.js";
|
|
35
35
|
import { initSetup } from "./setup.js";
|
|
36
36
|
import { initServer } from "./server-init.js";
|
|
37
|
-
function logMemory(phase) {
|
|
38
|
-
const mem = process.memoryUsage();
|
|
39
|
-
log(`[mem] ${phase}: heap=${Math.round(mem.heapUsed / 1024 / 1024)}MB rss=${Math.round(mem.rss / 1024 / 1024)}MB external=${Math.round(mem.external / 1024 / 1024)}MB arrayBuffers=${Math.round(mem.arrayBuffers / 1024 / 1024)}MB`);
|
|
40
|
-
}
|
|
41
37
|
const configPath = process.argv[2] || 'hatk.config.ts';
|
|
42
38
|
const configDir = dirname(resolve(configPath));
|
|
43
39
|
registerHatkResolveHook();
|
|
44
|
-
logMemory('startup');
|
|
45
40
|
// 1. Load config
|
|
46
41
|
const config = await loadConfig(configPath);
|
|
47
42
|
configureRelay(config.relay);
|
|
@@ -82,7 +77,6 @@ if (config.database !== ':memory:') {
|
|
|
82
77
|
const { adapter, searchPort } = await createAdapter(config.databaseEngine);
|
|
83
78
|
setSearchPort(searchPort);
|
|
84
79
|
await initDatabase(adapter, config.database, schemas, ddlStatements);
|
|
85
|
-
logMemory('after-db-init');
|
|
86
80
|
log(`[main] Database initialized (${config.databaseEngine}, ${config.database === ':memory:' ? 'in-memory' : config.database})`);
|
|
87
81
|
// Auto-migrate schema if lexicons changed
|
|
88
82
|
const migrationChanges = await migrateSchema(schemas);
|
|
@@ -138,7 +132,6 @@ if (config.oauth) {
|
|
|
138
132
|
await initOAuth(config.oauth, config.plc, config.relay);
|
|
139
133
|
log(`[main] OAuth initialized (issuer: ${config.oauth.issuer})`);
|
|
140
134
|
}
|
|
141
|
-
logMemory('before-server');
|
|
142
135
|
// 5. Start server immediately (don't wait for backfill)
|
|
143
136
|
const collectionSet = new Set(collections);
|
|
144
137
|
const backfillOpts = {
|
|
@@ -157,7 +150,6 @@ function runBackfillAndRestart() {
|
|
|
157
150
|
})
|
|
158
151
|
.then((recordCount) => {
|
|
159
152
|
if (recordCount > 0 && !process.env.DEV_MODE) {
|
|
160
|
-
logMemory('after-backfill');
|
|
161
153
|
log('[main] Restarting to reclaim memory...');
|
|
162
154
|
process.exit(1);
|
|
163
155
|
}
|
|
@@ -193,7 +185,6 @@ log(` Collections: ${collections.join(', ')}`);
|
|
|
193
185
|
log(` Feeds: ${listFeeds()
|
|
194
186
|
.map((f) => f.name)
|
|
195
187
|
.join(', ')}`);
|
|
196
|
-
logMemory('after-server');
|
|
197
188
|
// 6. Start indexer with cursor
|
|
198
189
|
const cursor = await getCursor('relay');
|
|
199
190
|
startIndexer({
|