@hatk/hatk 0.0.1-alpha.22 → 0.0.1-alpha.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/backfill.d.ts.map +1 -1
  2. package/dist/backfill.js +16 -4
  3. package/dist/cli.js +21 -33
  4. package/dist/config.d.ts +1 -0
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +1 -0
  7. package/dist/database/adapter-factory.d.ts +6 -0
  8. package/dist/database/adapter-factory.d.ts.map +1 -0
  9. package/dist/database/adapter-factory.js +20 -0
  10. package/dist/database/adapters/duckdb-search.d.ts +12 -0
  11. package/dist/database/adapters/duckdb-search.d.ts.map +1 -0
  12. package/dist/database/adapters/duckdb-search.js +27 -0
  13. package/dist/database/adapters/duckdb.d.ts +25 -0
  14. package/dist/database/adapters/duckdb.d.ts.map +1 -0
  15. package/dist/database/adapters/duckdb.js +161 -0
  16. package/dist/database/adapters/sqlite-search.d.ts +18 -0
  17. package/dist/database/adapters/sqlite-search.d.ts.map +1 -0
  18. package/dist/database/adapters/sqlite-search.js +38 -0
  19. package/dist/database/adapters/sqlite.d.ts +18 -0
  20. package/dist/database/adapters/sqlite.d.ts.map +1 -0
  21. package/dist/database/adapters/sqlite.js +87 -0
  22. package/dist/database/db.d.ts +149 -0
  23. package/dist/database/db.d.ts.map +1 -0
  24. package/dist/database/db.js +1456 -0
  25. package/dist/database/dialect.d.ts +45 -0
  26. package/dist/database/dialect.d.ts.map +1 -0
  27. package/dist/database/dialect.js +72 -0
  28. package/dist/database/fts.d.ts +24 -0
  29. package/dist/database/fts.d.ts.map +1 -0
  30. package/dist/database/fts.js +777 -0
  31. package/dist/database/index.d.ts +7 -0
  32. package/dist/database/index.d.ts.map +1 -0
  33. package/dist/database/index.js +6 -0
  34. package/dist/database/ports.d.ts +44 -0
  35. package/dist/database/ports.d.ts.map +1 -0
  36. package/dist/database/ports.js +1 -0
  37. package/dist/database/schema.d.ts +60 -0
  38. package/dist/database/schema.d.ts.map +1 -0
  39. package/dist/database/schema.js +388 -0
  40. package/dist/feeds.js +1 -1
  41. package/dist/hooks.js +1 -1
  42. package/dist/hydrate.js +1 -1
  43. package/dist/indexer.d.ts.map +1 -1
  44. package/dist/indexer.js +3 -3
  45. package/dist/labels.js +2 -2
  46. package/dist/main.js +30 -10
  47. package/dist/oauth/db.d.ts.map +1 -1
  48. package/dist/oauth/db.js +41 -15
  49. package/dist/oauth/server.js +4 -4
  50. package/dist/opengraph.js +1 -1
  51. package/dist/seed.js +1 -1
  52. package/dist/server.js +4 -4
  53. package/dist/setup.d.ts +10 -1
  54. package/dist/setup.d.ts.map +1 -1
  55. package/dist/setup.js +2 -2
  56. package/dist/test.d.ts +1 -1
  57. package/dist/test.d.ts.map +1 -1
  58. package/dist/test.js +22 -8
  59. package/dist/views.js +1 -1
  60. package/dist/vite-plugin.d.ts.map +1 -1
  61. package/dist/vite-plugin.js +10 -0
  62. package/dist/xrpc.js +2 -2
  63. package/package.json +3 -1
package/dist/main.js CHANGED
@@ -1,17 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import { mkdirSync } from 'node:fs';
2
+ import { mkdirSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, resolve } from 'node:path';
4
4
  import { log } from "./logger.js";
5
5
  import { loadConfig } from "./config.js";
6
- import { loadLexicons, storeLexicons, discoverCollections, buildSchemas, } from "./schema.js";
6
+ import { loadLexicons, storeLexicons, discoverCollections, buildSchemas } from "./database/schema.js";
7
7
  import { discoverViews } from "./views.js";
8
- import { initDatabase, getCursor, querySQL } from "./db.js";
8
+ import { initDatabase, getCursor, querySQL, getSqlDialect, getSchemaDump, migrateSchema } from "./database/db.js";
9
+ import { createAdapter } from "./database/adapter-factory.js";
10
+ import { getDialect } from "./database/dialect.js";
11
+ import { setSearchPort } from "./database/fts.js";
9
12
  import { initFeeds, listFeeds } from "./feeds.js";
10
13
  import { initXrpc, listXrpc, configureRelay } from "./xrpc.js";
11
14
  import { initOpengraph } from "./opengraph.js";
12
15
  import { initLabels, getLabelDefinitions } from "./labels.js";
13
16
  import { startIndexer } from "./indexer.js";
14
- import { rebuildAllIndexes } from "./fts.js";
17
+ import { rebuildAllIndexes } from "./database/fts.js";
15
18
  import { startServer } from "./server.js";
16
19
  import { validateLexicons } from '@bigmoves/lexicon';
17
20
  import { relayHttpUrl } from "./config.js";
@@ -50,7 +53,8 @@ log(`[main] Loaded config: ${collections.length} collections`);
50
53
  // Discover view defs from lexicons
51
54
  discoverViews();
52
55
  await loadOnLoginHook(resolve(configDir, 'hooks'));
53
- const { schemas, ddlStatements } = buildSchemas(lexicons, collections);
56
+ const engineDialect = getDialect(config.databaseEngine);
57
+ const { schemas, ddlStatements } = buildSchemas(lexicons, collections, engineDialect);
54
58
  for (const s of schemas) {
55
59
  if (s.columns.length === 0) {
56
60
  log(`[main] No lexicon found for ${s.collection}, using generic JSON storage`);
@@ -59,18 +63,34 @@ for (const s of schemas) {
59
63
  log(`[main] Schema for ${s.collection}: ${s.columns.length} columns, ${s.unions.length} unions`);
60
64
  }
61
65
  }
62
- // 3. Ensure data directory exists and initialize DuckDB
66
+ // 3. Ensure data directory exists and initialize database
63
67
  if (config.database !== ':memory:') {
64
68
  mkdirSync(dirname(config.database), { recursive: true });
65
69
  }
66
- await initDatabase(config.database, schemas, ddlStatements);
70
+ const { adapter, searchPort } = await createAdapter(config.databaseEngine);
71
+ setSearchPort(searchPort);
72
+ await initDatabase(adapter, config.database, schemas, ddlStatements);
67
73
  logMemory('after-db-init');
68
- log(`[main] DuckDB initialized (${config.database === ':memory:' ? 'in-memory' : config.database})`);
74
+ log(`[main] Database initialized (${config.databaseEngine}, ${config.database === ':memory:' ? 'in-memory' : config.database})`);
75
+ // Auto-migrate schema if lexicons changed
76
+ const migrationChanges = await migrateSchema(schemas);
77
+ if (migrationChanges.length > 0) {
78
+ log(`[main] Applied ${migrationChanges.length} schema migration(s)`);
79
+ }
69
80
  // 3b. Run setup hooks (after DB init, before server)
70
81
  await initSetup(resolve(configDir, 'setup'));
82
+ // Write db/schema.sql (after setup, so setup-created tables are included)
83
+ try {
84
+ const schemaDir = resolve(configDir, 'db');
85
+ mkdirSync(schemaDir, { recursive: true });
86
+ const schemaDump = await getSchemaDump();
87
+ writeFileSync(resolve(schemaDir, 'schema.sql'), `-- This file is auto-generated by hatk on startup. Do not edit.\n-- Database engine: ${config.databaseEngine}\n\n${schemaDump}\n`);
88
+ log(`[main] Schema written to db/schema.sql`);
89
+ }
90
+ catch { }
71
91
  // Detect orphaned tables
72
92
  try {
73
- const existingTables = await querySQL(`SELECT table_name FROM information_schema.tables WHERE table_schema = 'main' AND table_name NOT LIKE '\\_%' ESCAPE '\\'`);
93
+ const existingTables = await querySQL(getSqlDialect().listTablesQuery);
74
94
  for (const row of existingTables) {
75
95
  const tableName = row.table_name;
76
96
  const isChildTable = collections.some((c) => tableName.startsWith(c + '__'));
@@ -112,7 +132,7 @@ function runBackfillAndRestart() {
112
132
  })
113
133
  .then((recordCount) => {
114
134
  log('[main] FTS indexes ready');
115
- if (recordCount > 0) {
135
+ if (recordCount > 0 && !process.env.DEV_MODE) {
116
136
  logMemory('after-backfill');
117
137
  log('[main] Restarting to reclaim memory...');
118
138
  process.exit(1);
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/oauth/db.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,SAAS,87CA0DrB,CAAA;AAID,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAIzG;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOtG;AAID,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB,GACA,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAM7E;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1E;AAID,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOnF;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAK1E;AAID,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IACJ,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,GACA,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAGjE;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9D;AAID,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GACA,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAGxE;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAErE;AAID,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAK3F;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CASzD"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/oauth/db.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,SAAS,87CA0DrB,CAAA;AAID,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAIzG;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMtG;AAID,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB,GACA,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAM7E;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1E;AAID,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnF;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAK1E;AAID,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IACJ,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,GACA,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAGjE;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9D;AAID,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GACA,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAGxE;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAErE;AAID,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAK3F;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAQzD"}
package/dist/oauth/db.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // packages/hatk/src/oauth/db.ts
2
- import { querySQL, runSQL } from "../db.js";
2
+ import { querySQL, runSQL } from "../database/db.js";
3
3
  // --- DDL ---
4
4
  export const OAUTH_DDL = `
5
5
  CREATE TABLE IF NOT EXISTS _oauth_keys (
@@ -68,12 +68,32 @@ export async function getServerKey(kid) {
68
68
  return { privateKey: rows[0].private_key, publicKey: rows[0].public_key };
69
69
  }
70
70
  export async function storeServerKey(kid, privateKey, publicKey) {
71
- await runSQL('INSERT OR REPLACE INTO _oauth_keys (kid, private_key, public_key) VALUES ($1, $2, $3)', kid, privateKey, publicKey);
71
+ await runSQL('INSERT OR REPLACE INTO _oauth_keys (kid, private_key, public_key) VALUES ($1, $2, $3)', [
72
+ kid,
73
+ privateKey,
74
+ publicKey,
75
+ ]);
72
76
  }
73
77
  // --- OAuth Request Storage ---
74
78
  export async function storeOAuthRequest(requestUri, data) {
75
79
  await runSQL(`INSERT INTO _oauth_requests (request_uri, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method, dpop_jkt, pds_request_uri, pds_auth_server, pds_code_verifier, pds_state, did, login_hint, expires_at)
76
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)`, requestUri, data.clientId, data.redirectUri, data.scope || null, data.state || null, data.codeChallenge, data.codeChallengeMethod || 'S256', data.dpopJkt, data.pdsRequestUri || null, data.pdsAuthServer || null, data.pdsCodeVerifier || null, data.pdsState || null, data.did || null, data.loginHint || null, data.expiresAt);
80
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)`, [
81
+ requestUri,
82
+ data.clientId,
83
+ data.redirectUri,
84
+ data.scope || null,
85
+ data.state || null,
86
+ data.codeChallenge,
87
+ data.codeChallengeMethod || 'S256',
88
+ data.dpopJkt,
89
+ data.pdsRequestUri || null,
90
+ data.pdsAuthServer || null,
91
+ data.pdsCodeVerifier || null,
92
+ data.pdsState || null,
93
+ data.did || null,
94
+ data.loginHint || null,
95
+ data.expiresAt,
96
+ ]);
77
97
  }
78
98
  export async function getOAuthRequest(requestUri) {
79
99
  const rows = await querySQL('SELECT * FROM _oauth_requests WHERE request_uri = $1 AND expires_at > $2', [
@@ -83,57 +103,63 @@ export async function getOAuthRequest(requestUri) {
83
103
  return rows.length > 0 ? rows[0] : null;
84
104
  }
85
105
  export async function deleteOAuthRequest(requestUri) {
86
- await runSQL('DELETE FROM _oauth_requests WHERE request_uri = $1', requestUri);
106
+ await runSQL('DELETE FROM _oauth_requests WHERE request_uri = $1', [requestUri]);
87
107
  }
88
108
  // --- Authorization Codes ---
89
109
  export async function storeAuthCode(code, requestUri) {
90
- await runSQL('INSERT INTO _oauth_codes (code, request_uri, created_at) VALUES ($1, $2, $3)', code, requestUri, Math.floor(Date.now() / 1000));
110
+ await runSQL('INSERT INTO _oauth_codes (code, request_uri, created_at) VALUES ($1, $2, $3)', [
111
+ code,
112
+ requestUri,
113
+ Math.floor(Date.now() / 1000),
114
+ ]);
91
115
  }
92
116
  export async function consumeAuthCode(code) {
93
117
  const rows = await querySQL('SELECT request_uri FROM _oauth_codes WHERE code = $1', [code]);
94
118
  if (rows.length === 0)
95
119
  return null;
96
- await runSQL('DELETE FROM _oauth_codes WHERE code = $1', code);
120
+ await runSQL('DELETE FROM _oauth_codes WHERE code = $1', [code]);
97
121
  return rows[0].request_uri;
98
122
  }
99
123
  // --- Sessions ---
100
124
  export async function storeSession(did, data) {
101
125
  await runSQL(`INSERT OR REPLACE INTO _oauth_sessions (did, pds_endpoint, access_token, refresh_token, dpop_jkt, token_expires_at, updated_at)
102
- VALUES ($1,$2,$3,$4,$5,$6,CURRENT_TIMESTAMP)`, did, data.pdsEndpoint, data.accessToken, data.refreshToken || null, data.dpopJkt, data.tokenExpiresAt || null);
126
+ VALUES ($1,$2,$3,$4,$5,$6,CURRENT_TIMESTAMP)`, [did, data.pdsEndpoint, data.accessToken, data.refreshToken || null, data.dpopJkt, data.tokenExpiresAt || null]);
103
127
  }
104
128
  export async function getSession(did) {
105
129
  const rows = await querySQL('SELECT * FROM _oauth_sessions WHERE did = $1', [did]);
106
130
  return rows.length > 0 ? rows[0] : null;
107
131
  }
108
132
  export async function deleteSession(did) {
109
- await runSQL('DELETE FROM _oauth_sessions WHERE did = $1', did);
133
+ await runSQL('DELETE FROM _oauth_sessions WHERE did = $1', [did]);
110
134
  }
111
135
  // --- Refresh Tokens ---
112
136
  export async function storeRefreshToken(token, data) {
113
137
  const now = Math.floor(Date.now() / 1000);
114
138
  const expiresAt = data.expiresAt ?? now + 14 * 86400; // 14 days default
115
139
  await runSQL(`INSERT INTO _oauth_refresh_tokens (token, client_id, did, dpop_jkt, scope, created_at, expires_at)
116
- VALUES ($1,$2,$3,$4,$5,$6,$7)`, token, data.clientId, data.did, data.dpopJkt, data.scope || null, now, expiresAt);
140
+ VALUES ($1,$2,$3,$4,$5,$6,$7)`, [token, data.clientId, data.did, data.dpopJkt, data.scope || null, now, expiresAt]);
117
141
  }
118
142
  export async function getRefreshToken(token) {
119
143
  const rows = await querySQL('SELECT * FROM _oauth_refresh_tokens WHERE token = $1', [token]);
120
144
  return rows.length > 0 ? rows[0] : null;
121
145
  }
122
146
  export async function revokeRefreshToken(token) {
123
- await runSQL('UPDATE _oauth_refresh_tokens SET revoked = 1 WHERE token = $1', token);
147
+ await runSQL('UPDATE _oauth_refresh_tokens SET revoked = 1 WHERE token = $1', [token]);
124
148
  }
125
149
  // --- DPoP JTI Replay Protection ---
126
150
  export async function checkAndStoreDpopJti(jti, expiresAt) {
127
151
  const rows = await querySQL('SELECT 1 FROM _oauth_dpop_jtis WHERE jti = $1', [jti]);
128
152
  if (rows.length > 0)
129
153
  return false;
130
- await runSQL('INSERT INTO _oauth_dpop_jtis (jti, expires_at) VALUES ($1, $2)', jti, expiresAt);
154
+ await runSQL('INSERT INTO _oauth_dpop_jtis (jti, expires_at) VALUES ($1, $2)', [jti, expiresAt]);
131
155
  return true;
132
156
  }
133
157
  export async function cleanupExpiredOAuth() {
134
158
  const now = Math.floor(Date.now() / 1000);
135
- await runSQL('DELETE FROM _oauth_dpop_jtis WHERE expires_at < $1', now);
136
- await runSQL('DELETE FROM _oauth_requests WHERE expires_at < $1', now);
137
- await runSQL('DELETE FROM _oauth_codes WHERE created_at < $1', now - 600);
138
- await runSQL('DELETE FROM _oauth_refresh_tokens WHERE revoked = 1 OR (expires_at IS NOT NULL AND expires_at < $1)', now);
159
+ await runSQL('DELETE FROM _oauth_dpop_jtis WHERE expires_at < $1', [now]);
160
+ await runSQL('DELETE FROM _oauth_requests WHERE expires_at < $1', [now]);
161
+ await runSQL('DELETE FROM _oauth_codes WHERE created_at < $1', [now - 600]);
162
+ await runSQL('DELETE FROM _oauth_refresh_tokens WHERE revoked = 1 OR (expires_at IS NOT NULL AND expires_at < $1)', [
163
+ now,
164
+ ]);
139
165
  }
@@ -5,7 +5,7 @@ import { resolveClient, validateRedirectUri, isLoopbackClient } from "./client.j
5
5
  import { discoverAuthServer, resolveHandle } from "./discovery.js";
6
6
  import { getServerKey, storeServerKey, storeOAuthRequest, getOAuthRequest, deleteOAuthRequest, storeAuthCode, consumeAuthCode, storeSession, checkAndStoreDpopJti, cleanupExpiredOAuth, storeRefreshToken, getRefreshToken, revokeRefreshToken, } from "./db.js";
7
7
  import { emit } from "../logger.js";
8
- import { querySQL } from "../db.js";
8
+ import { querySQL } from "../database/db.js";
9
9
  import { fireOnLoginHook } from "../hooks.js";
10
10
  const SERVER_KEY_KID = 'appview-oauth-key';
11
11
  async function resolveHandleForDid(did) {
@@ -261,7 +261,7 @@ export function buildAuthorizeRedirect(config, request) {
261
261
  // --- OAuth Callback (PDS redirects here) ---
262
262
  export async function handleCallback(config, code, state, iss) {
263
263
  // Find the matching OAuth request by pds_state (unique per PAR)
264
- const { querySQL } = await import("../db.js");
264
+ const { querySQL } = await import("../database/db.js");
265
265
  let request = null;
266
266
  if (state) {
267
267
  const rows = await querySQL(`SELECT * FROM _oauth_requests WHERE pds_state = $1 AND expires_at > $2`, [
@@ -348,8 +348,8 @@ export async function handleCallback(config, code, state, iss) {
348
348
  await storeAuthCode(clientCode, request.request_uri);
349
349
  // Update the request with the DID (in case it wasn't set during PAR)
350
350
  if (!request.did && did) {
351
- const { runSQL } = await import("../db.js");
352
- await runSQL('UPDATE _oauth_requests SET did = $1 WHERE request_uri = $2', did, request.request_uri);
351
+ const { runSQL } = await import("../database/db.js");
352
+ await runSQL('UPDATE _oauth_requests SET did = $1 WHERE request_uri = $2', [did, request.request_uri]);
353
353
  }
354
354
  // Build redirect back to client
355
355
  const params = new URLSearchParams({ code: clientCode, iss: config.issuer });
package/dist/opengraph.js CHANGED
@@ -11,7 +11,7 @@ import { readFileSync, readdirSync } from 'node:fs';
11
11
  import { log } from "./logger.js";
12
12
  import satori from 'satori';
13
13
  import { Resvg } from '@resvg/resvg-js';
14
- import { querySQL, runSQL, packCursor, unpackCursor, isTakendownDid, filterTakendownDids, searchRecords, findUriByFields, lookupByFieldBatch, countByFieldBatch, queryLabelsForUris, } from "./db.js";
14
+ import { querySQL, runSQL, packCursor, unpackCursor, isTakendownDid, filterTakendownDids, searchRecords, findUriByFields, lookupByFieldBatch, countByFieldBatch, queryLabelsForUris, } from "./database/db.js";
15
15
  import { resolveRecords } from "./hydrate.js";
16
16
  import { blobUrl } from "./xrpc.js";
17
17
  const handlers = [];
package/dist/seed.js CHANGED
@@ -23,7 +23,7 @@
23
23
  * )
24
24
  * ```
25
25
  */
26
- import { loadLexicons } from "./schema.js";
26
+ import { loadLexicons } from "./database/schema.js";
27
27
  import { validateRecord } from '@bigmoves/lexicon';
28
28
  import { resolve } from 'node:path';
29
29
  import { readFileSync } from 'node:fs';
package/dist/server.js CHANGED
@@ -3,10 +3,10 @@ import { gzipSync } from 'node:zlib';
3
3
  import { existsSync } from 'node:fs';
4
4
  import { readFile } from 'node:fs/promises';
5
5
  import { join, extname } from 'node:path';
6
- import { queryRecords, getRecordByUri, searchRecords, getSchema, reshapeRow, setRepoStatus, getRepoStatus, getRepoRetryInfo, querySQL, insertRecord, deleteRecord, queryLabelsForUris, insertLabels, searchAccounts, listReposPaginated, getCollectionCounts, normalizeValue, getSchemaDump, getPreferences, putPreference, } from "./db.js";
6
+ import { queryRecords, getRecordByUri, searchRecords, getSchema, reshapeRow, setRepoStatus, getRepoStatus, getRepoRetryInfo, querySQL, insertRecord, deleteRecord, queryLabelsForUris, insertLabels, searchAccounts, listReposPaginated, getCollectionCounts, normalizeValue, getSchemaDump, getPreferences, putPreference, } from "./database/db.js";
7
7
  import { executeFeed, listFeeds } from "./feeds.js";
8
8
  import { executeXrpc, InvalidRequestError } from "./xrpc.js";
9
- import { getLexiconArray } from "./schema.js";
9
+ import { getLexiconArray } from "./database/schema.js";
10
10
  import { validateRecord } from '@bigmoves/lexicon';
11
11
  import { resolveRecords } from "./hydrate.js";
12
12
  import { handleOpengraphRequest, buildOgMeta } from "./opengraph.js";
@@ -192,7 +192,7 @@ export function startServer(port, collections, publicDir, oauth, admins = [], re
192
192
  columns: schema?.columns.map((col) => ({
193
193
  name: col.name,
194
194
  originalName: col.originalName,
195
- type: col.duckdbType,
195
+ type: col.sqlType,
196
196
  required: col.notNull,
197
197
  })),
198
198
  };
@@ -527,7 +527,7 @@ export function startServer(port, collections, publicDir, oauth, admins = [], re
527
527
  if (url.pathname === '/admin/schema') {
528
528
  if (!requireAdmin(viewer, res))
529
529
  return;
530
- const { getAllLexicons } = await import("./schema.js");
530
+ const { getAllLexicons } = await import("./database/schema.js");
531
531
  const ddl = await getSchemaDump();
532
532
  jsonResponse(res, { ddl, lexicons: getAllLexicons() });
533
533
  return;
package/dist/setup.d.ts CHANGED
@@ -1,8 +1,17 @@
1
+ import type { BulkInserter } from './database/ports.ts';
1
2
  /** Context passed to each setup script's handler function. */
2
3
  export interface SetupContext {
3
4
  db: {
4
5
  query: (sql: string, params?: any[]) => Promise<any[]>;
5
- run: (sql: string, ...params: any[]) => Promise<void>;
6
+ run: (sql: string, params?: any[]) => Promise<void>;
7
+ runBatch: (operations: Array<{
8
+ sql: string;
9
+ params: any[];
10
+ }>) => Promise<void>;
11
+ createBulkInserter: (table: string, columns: string[], options?: {
12
+ onConflict?: 'ignore' | 'replace';
13
+ batchSize?: number;
14
+ }) => Promise<BulkInserter>;
6
15
  };
7
16
  }
8
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AA6BA,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACtD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KACtD,CAAA;CACF;AAkBD;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/D"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACtD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;QACnD,QAAQ,EAAE,CAAC,UAAU,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,GAAG,EAAE,CAAA;SAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;QAC9E,kBAAkB,EAAE,CAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,CAAC,EAAE;YAAE,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,KAChE,OAAO,CAAC,YAAY,CAAC,CAAA;KAC3B,CAAA;CACF;AAkBD;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/D"}
package/dist/setup.js CHANGED
@@ -33,7 +33,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
33
33
  import { resolve, relative } from 'node:path';
34
34
  import { readdirSync, statSync } from 'node:fs';
35
35
  import { log } from "./logger.js";
36
- import { querySQL, runSQL } from "./db.js";
36
+ import { querySQL, runSQL, runBatch, createBulkInserterSQL } from "./database/db.js";
37
37
  /** Recursively collect .ts/.js files in a directory, skipping files prefixed with `_`. */
38
38
  function walkDir(dir) {
39
39
  const results = [];
@@ -74,7 +74,7 @@ export async function initSetup(setupDir) {
74
74
  continue;
75
75
  }
76
76
  const ctx = {
77
- db: { query: querySQL, run: runSQL },
77
+ db: { query: querySQL, run: runSQL, runBatch, createBulkInserter: createBulkInserterSQL },
78
78
  };
79
79
  log(`[setup] running: ${name}`);
80
80
  await handler(ctx);
package/dist/test.d.ts CHANGED
@@ -4,7 +4,7 @@ import type { FeedContext } from './feeds.ts';
4
4
  export interface TestContext {
5
5
  db: {
6
6
  query: (sql: string, params?: any[]) => Promise<any[]>;
7
- run: (sql: string, ...params: any[]) => Promise<void>;
7
+ run: (sql: string, params?: any[]) => Promise<void>;
8
8
  };
9
9
  loadFixtures: (dir?: string) => Promise<void>;
10
10
  loadFeed: (name: string) => {
@@ -1 +1 @@
1
- {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAiBzD,OAAO,EAAE,IAAI,IAAI,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACtD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KACtD,CAAA;IACD,YAAY,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAAE,CAAA;IAC5E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAAE,CAAA;IACnE,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE;QACnB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;QAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAChC,KAAK,WAAW,CAAA;IACjB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAA;IACpC,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CACxC;AAED,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC7E,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,QAAQ,KAAK,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA;IAC/D,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAClE;AAYD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,WAAW,CAAC,CAiL9D;AA8BD,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAgD3D"}
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAmBzD,OAAO,EAAE,IAAI,IAAI,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACtD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KACpD,CAAA;IACD,YAAY,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAAE,CAAA;IAC5E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAAE,CAAA;IACnE,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE;QACnB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;QAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAChC,KAAK,WAAW,CAAA;IACjB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAA;IACpC,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CACxC;AAED,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC7E,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,QAAQ,KAAK,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA;IAC/D,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAClE;AAYD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,WAAW,CAAC,CAiL9D;AA8BD,wBAAsB,eAAe,IAAI,OAAO,CAAC,UAAU,CAAC,CAgD3D"}
package/dist/test.js CHANGED
@@ -2,8 +2,10 @@ import { resolve, dirname } from 'node:path';
2
2
  import { readdirSync, readFileSync } from 'node:fs';
3
3
  import YAML from 'yaml';
4
4
  import { loadConfig } from "./config.js";
5
- import { loadLexicons, storeLexicons, discoverCollections, generateTableSchema, generateCreateTableSQL, } from "./schema.js";
6
- import { initDatabase, querySQL, runSQL, insertRecord, closeDatabase } from "./db.js";
5
+ import { loadLexicons, storeLexicons, discoverCollections, generateTableSchema, generateCreateTableSQL, } from "./database/schema.js";
6
+ import { initDatabase, querySQL, runSQL, insertRecord, closeDatabase } from "./database/db.js";
7
+ import { createAdapter } from "./database/adapter-factory.js";
8
+ import { setSearchPort } from "./database/fts.js";
7
9
  import { initFeeds, executeFeed, listFeeds, createPaginate } from "./feeds.js";
8
10
  import { initXrpc, executeXrpc, listXrpc, configureRelay } from "./xrpc.js";
9
11
  import { initOpengraph } from "./opengraph.js";
@@ -11,7 +13,7 @@ import { initLabels } from "./labels.js";
11
13
  import { discoverViews } from "./views.js";
12
14
  import { loadOnLoginHook } from "./hooks.js";
13
15
  import { validateLexicons } from '@bigmoves/lexicon';
14
- import { packCursor, unpackCursor, isTakendownDid, filterTakendownDids } from "./db.js";
16
+ import { packCursor, unpackCursor, isTakendownDid, filterTakendownDids } from "./database/db.js";
15
17
  import { seed as createSeedHelpers } from "./seed.js";
16
18
  /**
17
19
  * Find the project's hatk.config.ts by walking up from cwd.
@@ -58,8 +60,10 @@ export async function createTestContext() {
58
60
  schemas.push(schema);
59
61
  ddlStatements.push(generateCreateTableSQL(schema));
60
62
  }
61
- // In-memory DuckDB
62
- await initDatabase(':memory:', schemas, ddlStatements);
63
+ // In-memory database
64
+ const { adapter, searchPort } = await createAdapter('duckdb');
65
+ setSearchPort(searchPort);
66
+ await initDatabase(adapter, ':memory:', schemas, ddlStatements);
63
67
  // Discover views + hooks
64
68
  discoverViews();
65
69
  try {
@@ -94,7 +98,12 @@ export async function createTestContext() {
94
98
  if (Array.isArray(records)) {
95
99
  for (const rec of records) {
96
100
  const row = interpolateHelpers(rec);
97
- await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`, row.did, row.status || 'active', row.handle || row.did.split(':').pop() + '.test', new Date().toISOString());
101
+ await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`, [
102
+ row.did,
103
+ row.status || 'active',
104
+ row.handle || row.did.split(':').pop() + '.test',
105
+ new Date().toISOString(),
106
+ ]);
98
107
  }
99
108
  }
100
109
  }
@@ -119,7 +128,7 @@ export async function createTestContext() {
119
128
  const row = interpolateHelpers(rec);
120
129
  const vals = keys.map((k) => row[k]);
121
130
  const placeholders = keys.map((_, i) => `$${i + 1}`).join(', ');
122
- await runSQL(`INSERT INTO "${tableName}" (${keys.map((k) => `"${k}"`).join(', ')}) VALUES (${placeholders})`, ...vals);
131
+ await runSQL(`INSERT INTO "${tableName}" (${keys.map((k) => `"${k}"`).join(', ')}) VALUES (${placeholders})`, vals);
123
132
  }
124
133
  continue;
125
134
  }
@@ -133,7 +142,12 @@ export async function createTestContext() {
133
142
  // Auto-register DID in _repos if not already present
134
143
  if (!seenDids.has(did)) {
135
144
  seenDids.add(did);
136
- await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`, did, 'active', did.split(':').pop() + '.test', new Date().toISOString());
145
+ await runSQL(`INSERT OR IGNORE INTO _repos (did, status, handle, backfilled_at) VALUES ($1, $2, $3, $4)`, [
146
+ did,
147
+ 'active',
148
+ did.split(':').pop() + '.test',
149
+ new Date().toISOString(),
150
+ ]);
137
151
  }
138
152
  await insertRecord(tableName, uri, cid, did, fields);
139
153
  }
package/dist/views.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // 1. Inline views: defined in the record lexicon with ref: "#main" (e.g., playView)
4
4
  // 2. Defs views: defined in a defs lexicon, associated by naming convention (e.g., profileView)
5
5
  import { log } from "./logger.js";
6
- import { getAllLexicons, getLexicon } from "./schema.js";
6
+ import { getAllLexicons, getLexicon } from "./database/schema.js";
7
7
  // --- Registry ---
8
8
  /** All views keyed by full NSID (e.g., "fm.teal.alpha.feed.play#playView") */
9
9
  const views = new Map();
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAKlC,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAuFrD"}
1
+ {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAKlC,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAiGrD"}
@@ -16,6 +16,9 @@ export function hatk(opts) {
16
16
  server: {
17
17
  host: '127.0.0.1',
18
18
  port: devPort,
19
+ watch: {
20
+ ignored: ['**/db/**', '**/data/**'],
21
+ },
19
22
  proxy: {
20
23
  '/xrpc': rule,
21
24
  '/oauth/par': rule,
@@ -74,6 +77,13 @@ export function hatk(opts) {
74
77
  DEV_MODE: '1',
75
78
  },
76
79
  });
80
+ // Suppress ECONNREFUSED proxy errors while backend is booting
81
+ const origError = server.config.logger.error;
82
+ server.config.logger.error = (msg, opts) => {
83
+ if (typeof msg === 'string' && msg.includes('ECONNREFUSED'))
84
+ return;
85
+ origError(msg, opts);
86
+ };
77
87
  server.httpServer?.on('close', () => {
78
88
  serverProcess?.kill();
79
89
  serverProcess = null;
package/dist/xrpc.js CHANGED
@@ -29,9 +29,9 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
29
29
  import { resolve, relative } from 'node:path';
30
30
  import { readdirSync, statSync } from 'node:fs';
31
31
  import { log } from "./logger.js";
32
- import { querySQL, runSQL, packCursor, unpackCursor, isTakendownDid, filterTakendownDids, searchRecords, findUriByFields, lookupByFieldBatch, countByFieldBatch, queryLabelsForUris, } from "./db.js";
32
+ import { querySQL, runSQL, packCursor, unpackCursor, isTakendownDid, filterTakendownDids, searchRecords, findUriByFields, lookupByFieldBatch, countByFieldBatch, queryLabelsForUris, } from "./database/db.js";
33
33
  import { resolveRecords } from "./hydrate.js";
34
- import { getLexicon } from "./schema.js";
34
+ import { getLexicon } from "./database/schema.js";
35
35
  /** Thrown from XRPC handlers to return a 400 response with an error message. */
36
36
  export class InvalidRequestError extends Error {
37
37
  status = 400;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatk/hatk",
3
- "version": "0.0.1-alpha.22",
3
+ "version": "0.0.1-alpha.24",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "hatk": "dist/cli.js"
@@ -35,12 +35,14 @@
35
35
  "@duckdb/node-api": "^1.4.4-r.1",
36
36
  "@hatk/oauth-client": "*",
37
37
  "@resvg/resvg-js": "^2.6.2",
38
+ "better-sqlite3": "^12.6.2",
38
39
  "satori": "^0.19.2",
39
40
  "vitest": "^4",
40
41
  "yaml": "^2.7.0"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@playwright/test": "^1.58.2",
45
+ "@types/better-sqlite3": "^7.6.13",
44
46
  "@types/react": "^19.2.14",
45
47
  "vite": "^6"
46
48
  }