@hatk/hatk 0.0.1-alpha.6 → 0.0.1-alpha.61

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 (163) hide show
  1. package/dist/adapter.d.ts +19 -0
  2. package/dist/adapter.d.ts.map +1 -0
  3. package/dist/adapter.js +108 -0
  4. package/dist/backfill.d.ts +2 -2
  5. package/dist/backfill.d.ts.map +1 -1
  6. package/dist/backfill.js +83 -41
  7. package/dist/car.d.ts +42 -10
  8. package/dist/car.d.ts.map +1 -1
  9. package/dist/car.js +154 -14
  10. package/dist/cli.js +243 -1043
  11. package/dist/config.d.ts +31 -1
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +40 -9
  14. package/dist/database/adapter-factory.d.ts +6 -0
  15. package/dist/database/adapter-factory.d.ts.map +1 -0
  16. package/dist/database/adapter-factory.js +20 -0
  17. package/dist/database/adapters/duckdb-search.d.ts +12 -0
  18. package/dist/database/adapters/duckdb-search.d.ts.map +1 -0
  19. package/dist/database/adapters/duckdb-search.js +27 -0
  20. package/dist/database/adapters/duckdb.d.ts +25 -0
  21. package/dist/database/adapters/duckdb.d.ts.map +1 -0
  22. package/dist/database/adapters/duckdb.js +161 -0
  23. package/dist/database/adapters/sqlite-search.d.ts +23 -0
  24. package/dist/database/adapters/sqlite-search.d.ts.map +1 -0
  25. package/dist/database/adapters/sqlite-search.js +74 -0
  26. package/dist/database/adapters/sqlite.d.ts +18 -0
  27. package/dist/database/adapters/sqlite.d.ts.map +1 -0
  28. package/dist/database/adapters/sqlite.js +88 -0
  29. package/dist/{db.d.ts → database/db.d.ts} +57 -6
  30. package/dist/database/db.d.ts.map +1 -0
  31. package/dist/{db.js → database/db.js} +730 -549
  32. package/dist/database/dialect.d.ts +45 -0
  33. package/dist/database/dialect.d.ts.map +1 -0
  34. package/dist/database/dialect.js +72 -0
  35. package/dist/{fts.d.ts → database/fts.d.ts} +7 -0
  36. package/dist/database/fts.d.ts.map +1 -0
  37. package/dist/{fts.js → database/fts.js} +116 -32
  38. package/dist/database/index.d.ts +7 -0
  39. package/dist/database/index.d.ts.map +1 -0
  40. package/dist/database/index.js +6 -0
  41. package/dist/database/ports.d.ts +50 -0
  42. package/dist/database/ports.d.ts.map +1 -0
  43. package/dist/database/ports.js +1 -0
  44. package/dist/{schema.d.ts → database/schema.d.ts} +14 -3
  45. package/dist/database/schema.d.ts.map +1 -0
  46. package/dist/{schema.js → database/schema.js} +81 -41
  47. package/dist/dev-entry.d.ts +8 -0
  48. package/dist/dev-entry.d.ts.map +1 -0
  49. package/dist/dev-entry.js +113 -0
  50. package/dist/feeds.d.ts +12 -8
  51. package/dist/feeds.d.ts.map +1 -1
  52. package/dist/feeds.js +51 -6
  53. package/dist/hooks.d.ts +85 -0
  54. package/dist/hooks.d.ts.map +1 -0
  55. package/dist/hooks.js +161 -0
  56. package/dist/hydrate.d.ts +7 -6
  57. package/dist/hydrate.d.ts.map +1 -1
  58. package/dist/hydrate.js +4 -16
  59. package/dist/indexer.d.ts +23 -0
  60. package/dist/indexer.d.ts.map +1 -1
  61. package/dist/indexer.js +181 -34
  62. package/dist/labels.d.ts +36 -0
  63. package/dist/labels.d.ts.map +1 -1
  64. package/dist/labels.js +71 -6
  65. package/dist/lexicon-resolve.d.ts.map +1 -1
  66. package/dist/lexicon-resolve.js +27 -112
  67. package/dist/lexicons/com/atproto/label/defs.json +75 -0
  68. package/dist/lexicons/com/atproto/moderation/defs.json +30 -0
  69. package/dist/lexicons/com/atproto/repo/strongRef.json +24 -0
  70. package/dist/lexicons/dev/hatk/applyWrites.json +87 -0
  71. package/dist/lexicons/dev/hatk/createRecord.json +40 -0
  72. package/dist/lexicons/dev/hatk/createReport.json +48 -0
  73. package/dist/lexicons/dev/hatk/deleteRecord.json +25 -0
  74. package/dist/lexicons/dev/hatk/describeCollections.json +41 -0
  75. package/dist/lexicons/dev/hatk/describeFeeds.json +29 -0
  76. package/dist/lexicons/dev/hatk/describeLabels.json +45 -0
  77. package/dist/lexicons/dev/hatk/getFeed.json +30 -0
  78. package/dist/lexicons/dev/hatk/getPreferences.json +19 -0
  79. package/dist/lexicons/dev/hatk/getRecord.json +26 -0
  80. package/dist/lexicons/dev/hatk/getRecords.json +32 -0
  81. package/dist/lexicons/dev/hatk/putPreference.json +28 -0
  82. package/dist/lexicons/dev/hatk/putRecord.json +41 -0
  83. package/dist/lexicons/dev/hatk/searchRecords.json +32 -0
  84. package/dist/lexicons/dev/hatk/uploadBlob.json +23 -0
  85. package/dist/logger.d.ts +29 -0
  86. package/dist/logger.d.ts.map +1 -1
  87. package/dist/logger.js +29 -0
  88. package/dist/main.js +138 -67
  89. package/dist/mst.d.ts +18 -1
  90. package/dist/mst.d.ts.map +1 -1
  91. package/dist/mst.js +19 -8
  92. package/dist/oauth/db.d.ts +3 -1
  93. package/dist/oauth/db.d.ts.map +1 -1
  94. package/dist/oauth/db.js +48 -19
  95. package/dist/oauth/server.d.ts +24 -0
  96. package/dist/oauth/server.d.ts.map +1 -1
  97. package/dist/oauth/server.js +198 -22
  98. package/dist/oauth/session.d.ts +11 -0
  99. package/dist/oauth/session.d.ts.map +1 -0
  100. package/dist/oauth/session.js +65 -0
  101. package/dist/opengraph.d.ts +10 -0
  102. package/dist/opengraph.d.ts.map +1 -1
  103. package/dist/opengraph.js +80 -40
  104. package/dist/pds-proxy.d.ts +60 -0
  105. package/dist/pds-proxy.d.ts.map +1 -0
  106. package/dist/pds-proxy.js +277 -0
  107. package/dist/push.d.ts +34 -0
  108. package/dist/push.d.ts.map +1 -0
  109. package/dist/push.js +184 -0
  110. package/dist/renderer.d.ts +27 -0
  111. package/dist/renderer.d.ts.map +1 -0
  112. package/dist/renderer.js +46 -0
  113. package/dist/resolve-hatk.d.ts +6 -0
  114. package/dist/resolve-hatk.d.ts.map +1 -0
  115. package/dist/resolve-hatk.js +20 -0
  116. package/dist/response.d.ts +16 -0
  117. package/dist/response.d.ts.map +1 -0
  118. package/dist/response.js +69 -0
  119. package/dist/scanner.d.ts +21 -0
  120. package/dist/scanner.d.ts.map +1 -0
  121. package/dist/scanner.js +88 -0
  122. package/dist/seed.d.ts +19 -0
  123. package/dist/seed.d.ts.map +1 -1
  124. package/dist/seed.js +43 -4
  125. package/dist/server-init.d.ts +8 -0
  126. package/dist/server-init.d.ts.map +1 -0
  127. package/dist/server-init.js +62 -0
  128. package/dist/server.d.ts +26 -3
  129. package/dist/server.d.ts.map +1 -1
  130. package/dist/server.js +629 -635
  131. package/dist/setup.d.ts +28 -1
  132. package/dist/setup.d.ts.map +1 -1
  133. package/dist/setup.js +50 -3
  134. package/dist/templates/feed.tpl +14 -0
  135. package/dist/templates/hook.tpl +5 -0
  136. package/dist/templates/label.tpl +15 -0
  137. package/dist/templates/og.tpl +17 -0
  138. package/dist/templates/seed.tpl +11 -0
  139. package/dist/templates/setup.tpl +5 -0
  140. package/dist/templates/test-feed.tpl +19 -0
  141. package/dist/templates/test-xrpc.tpl +19 -0
  142. package/dist/templates/xrpc.tpl +41 -0
  143. package/dist/test.d.ts +1 -1
  144. package/dist/test.d.ts.map +1 -1
  145. package/dist/test.js +39 -32
  146. package/dist/views.js +1 -1
  147. package/dist/vite-plugin.d.ts +1 -1
  148. package/dist/vite-plugin.d.ts.map +1 -1
  149. package/dist/vite-plugin.js +254 -66
  150. package/dist/xrpc.d.ts +75 -11
  151. package/dist/xrpc.d.ts.map +1 -1
  152. package/dist/xrpc.js +189 -39
  153. package/package.json +14 -7
  154. package/public/admin.html +133 -54
  155. package/dist/db.d.ts.map +0 -1
  156. package/dist/fts.d.ts.map +0 -1
  157. package/dist/oauth/hooks.d.ts +0 -10
  158. package/dist/oauth/hooks.d.ts.map +0 -1
  159. package/dist/oauth/hooks.js +0 -40
  160. package/dist/schema.d.ts.map +0 -1
  161. package/dist/test-browser.d.ts +0 -14
  162. package/dist/test-browser.d.ts.map +0 -1
  163. package/dist/test-browser.js +0 -26
@@ -1,42 +1,47 @@
1
1
  import { readFileSync, readdirSync, statSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import { DUCKDB_DIALECT } from "./dialect.js";
3
4
  // Convert camelCase to snake_case
4
5
  export function toSnakeCase(str) {
5
6
  return str.replace(/([A-Z])/g, '_$1').toLowerCase();
6
7
  }
7
- function mapType(prop) {
8
+ // Quote a column name to avoid conflicts with SQL reserved words
9
+ export function q(name) {
10
+ return `"${name}"`;
11
+ }
12
+ function mapType(prop, dialect) {
8
13
  if (prop.type === 'string') {
9
14
  if (prop.format === 'datetime')
10
- return { duckdbType: 'TIMESTAMP', isRef: false };
15
+ return { sqlType: dialect.typeMap.timestamp, isRef: false, isJson: false };
11
16
  if (prop.format === 'at-uri')
12
- return { duckdbType: 'TEXT', isRef: true };
13
- return { duckdbType: 'TEXT', isRef: false };
17
+ return { sqlType: dialect.typeMap.text, isRef: true, isJson: false };
18
+ return { sqlType: dialect.typeMap.text, isRef: false, isJson: false };
14
19
  }
15
20
  if (prop.type === 'integer')
16
- return { duckdbType: 'INTEGER', isRef: false };
21
+ return { sqlType: dialect.typeMap.integer, isRef: false, isJson: false };
17
22
  if (prop.type === 'boolean')
18
- return { duckdbType: 'BOOLEAN', isRef: false };
23
+ return { sqlType: dialect.typeMap.boolean, isRef: false, isJson: false };
19
24
  if (prop.type === 'bytes')
20
- return { duckdbType: 'BLOB', isRef: false };
25
+ return { sqlType: dialect.typeMap.blob, isRef: false, isJson: false };
21
26
  if (prop.type === 'cid-link')
22
- return { duckdbType: 'TEXT', isRef: false };
27
+ return { sqlType: dialect.typeMap.text, isRef: false, isJson: false };
23
28
  if (prop.type === 'array')
24
- return { duckdbType: 'JSON', isRef: false };
29
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
25
30
  if (prop.type === 'blob')
26
- return { duckdbType: 'JSON', isRef: false };
31
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
27
32
  if (prop.type === 'union')
28
- return { duckdbType: 'JSON', isRef: false };
33
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
29
34
  if (prop.type === 'unknown')
30
- return { duckdbType: 'JSON', isRef: false };
35
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
31
36
  if (prop.type === 'object')
32
- return { duckdbType: 'JSON', isRef: false };
37
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
33
38
  if (prop.type === 'ref') {
34
39
  // strongRef contains { uri, cid } — handled specially in generateTableSchema
35
40
  if (prop.ref === 'com.atproto.repo.strongRef')
36
- return { duckdbType: 'STRONG_REF', isRef: true };
37
- return { duckdbType: 'JSON', isRef: false };
41
+ return { sqlType: 'STRONG_REF', isRef: true, isJson: false };
42
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
38
43
  }
39
- return { duckdbType: 'TEXT', isRef: false };
44
+ return { sqlType: dialect.typeMap.text, isRef: false, isJson: false };
40
45
  }
41
46
  // Recursively find all .json files in a directory
42
47
  function findJsonFiles(dir) {
@@ -121,7 +126,7 @@ function resolveRefDef(ref, defs, lexicons) {
121
126
  return lexicons?.get(ref)?.defs?.main || null;
122
127
  }
123
128
  /** Resolve a single union ref to a branch schema */
124
- function resolveUnionBranch(ref, collection, fieldName, defs, lexicons) {
129
+ function resolveUnionBranch(ref, collection, fieldName, defs, lexicons, dialect) {
125
130
  let branchDef = null;
126
131
  let branchName;
127
132
  let fullType;
@@ -181,21 +186,22 @@ function resolveUnionBranch(ref, collection, fieldName, defs, lexicons) {
181
186
  const tableName = `"${collection}__${snakeField}_${branchName}"`;
182
187
  const columns = [];
183
188
  for (const [propName, prop] of Object.entries(propSource)) {
184
- const { duckdbType, isRef } = mapType(prop);
189
+ const { sqlType, isRef, isJson } = mapType(prop, dialect);
185
190
  // Skip STRONG_REF expansion in branch tables — treat as JSON
186
- const finalType = duckdbType === 'STRONG_REF' ? 'JSON' : duckdbType;
191
+ const finalType = sqlType === 'STRONG_REF' ? dialect.jsonType : sqlType;
187
192
  columns.push({
188
193
  name: toSnakeCase(propName),
189
194
  originalName: propName,
190
- duckdbType: finalType,
195
+ sqlType: finalType,
191
196
  notNull: branchRequired.has(propName),
192
- isRef: finalType !== 'JSON' && isRef,
197
+ isRef: finalType !== dialect.jsonType && isRef,
198
+ isJson: isJson || sqlType === 'STRONG_REF',
193
199
  });
194
200
  }
195
201
  return { type: fullType, branchName, tableName, columns, isArray, arrayField, wrapperField };
196
202
  }
197
203
  // Generate a TableSchema from a lexicon record definition
198
- export function generateTableSchema(nsid, lexicon, lexicons) {
204
+ export function generateTableSchema(nsid, lexicon, lexicons, dialect = DUCKDB_DIALECT) {
199
205
  const mainDef = lexicon.defs?.main;
200
206
  if (!mainDef || mainDef.type !== 'record') {
201
207
  throw new Error(`Lexicon ${nsid} does not define a record type`);
@@ -214,7 +220,7 @@ export function generateTableSchema(nsid, lexicon, lexicons) {
214
220
  if (p.type === 'union' && p.refs) {
215
221
  const branches = [];
216
222
  for (const ref of p.refs) {
217
- const branch = resolveUnionBranch(ref, nsid, fieldName, lexicon.defs, lexicons);
223
+ const branch = resolveUnionBranch(ref, nsid, fieldName, lexicon.defs, lexicons, dialect);
218
224
  if (branch)
219
225
  branches.push(branch);
220
226
  }
@@ -225,9 +231,10 @@ export function generateTableSchema(nsid, lexicon, lexicons) {
225
231
  columns.push({
226
232
  name: toSnakeCase(fieldName),
227
233
  originalName: fieldName,
228
- duckdbType: 'JSON',
234
+ sqlType: dialect.jsonType,
229
235
  notNull: required.has(fieldName),
230
236
  isRef: false,
237
+ isJson: true,
231
238
  });
232
239
  continue;
233
240
  }
@@ -238,13 +245,14 @@ export function generateTableSchema(nsid, lexicon, lexicons) {
238
245
  const childColumns = [];
239
246
  const itemRequired = new Set(p.items?.required || lexicon.defs?.[p.items?.ref?.slice(1)]?.required || []);
240
247
  for (const [itemField, itemProp] of Object.entries(itemProps)) {
241
- const { duckdbType, isRef } = mapType(itemProp);
248
+ const { sqlType, isRef, isJson } = mapType(itemProp, dialect);
242
249
  childColumns.push({
243
250
  name: toSnakeCase(itemField),
244
251
  originalName: itemField,
245
- duckdbType,
252
+ sqlType,
246
253
  notNull: itemRequired.has(itemField),
247
254
  isRef,
255
+ isJson,
248
256
  });
249
257
  }
250
258
  const snakeField = toSnakeCase(fieldName);
@@ -257,31 +265,34 @@ export function generateTableSchema(nsid, lexicon, lexicons) {
257
265
  continue;
258
266
  }
259
267
  }
260
- const { duckdbType, isRef } = mapType(p);
261
- if (duckdbType === 'STRONG_REF') {
268
+ const { sqlType, isRef, isJson } = mapType(p, dialect);
269
+ if (sqlType === 'STRONG_REF') {
262
270
  // Expand strongRef into two columns: {name}_uri and {name}_cid
263
271
  columns.push({
264
272
  name: toSnakeCase(fieldName) + '_uri',
265
273
  originalName: fieldName,
266
- duckdbType: 'TEXT',
274
+ sqlType: dialect.typeMap.text,
267
275
  notNull: required.has(fieldName),
268
276
  isRef: true,
277
+ isJson: false,
269
278
  });
270
279
  columns.push({
271
280
  name: toSnakeCase(fieldName) + '_cid',
272
281
  originalName: fieldName + '__cid',
273
- duckdbType: 'TEXT',
282
+ sqlType: dialect.typeMap.text,
274
283
  notNull: required.has(fieldName),
275
284
  isRef: false,
285
+ isJson: false,
276
286
  });
277
287
  }
278
288
  else {
279
289
  columns.push({
280
290
  name: toSnakeCase(fieldName),
281
291
  originalName: fieldName,
282
- duckdbType,
292
+ sqlType,
283
293
  notNull: required.has(fieldName),
284
294
  isRef,
295
+ isJson,
285
296
  });
286
297
  }
287
298
  }
@@ -296,16 +307,16 @@ export function generateTableSchema(nsid, lexicon, lexicons) {
296
307
  };
297
308
  }
298
309
  // Generate CREATE TABLE SQL from a TableSchema
299
- export function generateCreateTableSQL(schema) {
310
+ export function generateCreateTableSQL(schema, dialect = DUCKDB_DIALECT) {
300
311
  const lines = [
301
312
  ' uri TEXT PRIMARY KEY',
302
313
  ' cid TEXT',
303
314
  ' did TEXT NOT NULL',
304
- ' indexed_at TIMESTAMP NOT NULL',
315
+ ` indexed_at ${dialect.timestampType} NOT NULL`,
305
316
  ];
306
317
  for (const col of schema.columns) {
307
318
  const nullable = col.notNull ? ' NOT NULL' : '';
308
- lines.push(` ${col.name} ${col.duckdbType}${nullable}`);
319
+ lines.push(` ${q(col.name)} ${col.sqlType}${nullable}`);
309
320
  }
310
321
  const createTable = `CREATE TABLE IF NOT EXISTS ${schema.tableName} (\n${lines.join(',\n')}\n);`;
311
322
  const prefix = schema.collection.replace(/\./g, '_');
@@ -315,7 +326,7 @@ export function generateCreateTableSQL(schema) {
315
326
  ];
316
327
  // Index ref columns for hydration lookups
317
328
  for (const refCol of schema.refColumns) {
318
- 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)});`);
319
330
  }
320
331
  // Child table DDL
321
332
  const childDDL = [];
@@ -323,16 +334,16 @@ export function generateCreateTableSQL(schema) {
323
334
  const childLines = [' parent_uri TEXT NOT NULL', ' parent_did TEXT NOT NULL'];
324
335
  for (const col of child.columns) {
325
336
  const nullable = col.notNull ? ' NOT NULL' : '';
326
- childLines.push(` ${col.name} ${col.duckdbType}${nullable}`);
337
+ childLines.push(` ${q(col.name)} ${col.sqlType}${nullable}`);
327
338
  }
328
339
  childDDL.push(`CREATE TABLE IF NOT EXISTS ${child.tableName} (\n${childLines.join(',\n')}\n);`);
329
340
  const childPrefix = `${prefix}__${toSnakeCase(child.fieldName)}`;
330
341
  childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${childPrefix}_parent ON ${child.tableName}(parent_uri);`);
331
342
  childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${childPrefix}_did ON ${child.tableName}(parent_did);`);
332
343
  for (const col of child.columns) {
333
- if (col.duckdbType === 'JSON' || col.duckdbType === 'BLOB')
344
+ if (col.isJson || col.sqlType === 'BLOB')
334
345
  continue;
335
- 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)});`);
336
347
  }
337
348
  }
338
349
  // Union branch table DDL
@@ -341,18 +352,47 @@ export function generateCreateTableSQL(schema) {
341
352
  const branchLines = [' parent_uri TEXT NOT NULL', ' parent_did TEXT NOT NULL'];
342
353
  for (const col of branch.columns) {
343
354
  const nullable = col.notNull ? ' NOT NULL' : '';
344
- branchLines.push(` ${col.name} ${col.duckdbType}${nullable}`);
355
+ branchLines.push(` ${q(col.name)} ${col.sqlType}${nullable}`);
345
356
  }
346
357
  childDDL.push(`CREATE TABLE IF NOT EXISTS ${branch.tableName} (\n${branchLines.join(',\n')}\n);`);
347
358
  const branchPrefix = branch.tableName.replace(/"/g, '').replace(/\./g, '_');
348
359
  childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${branchPrefix}_parent ON ${branch.tableName}(parent_uri);`);
349
360
  childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${branchPrefix}_did ON ${branch.tableName}(parent_did);`);
350
361
  for (const col of branch.columns) {
351
- if (col.duckdbType === 'JSON' || col.duckdbType === 'BLOB')
362
+ if (col.isJson || col.sqlType === 'BLOB')
352
363
  continue;
353
- 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)});`);
354
365
  }
355
366
  }
356
367
  }
357
368
  return [createTable, ...indexes, ...childDDL].join('\n');
358
369
  }
370
+ /**
371
+ * Build table schemas and DDL from lexicons and collections.
372
+ * Shared by main.ts (server boot) and cli.ts (hatk schema command).
373
+ */
374
+ export function buildSchemas(lexicons, collections, dialect = DUCKDB_DIALECT) {
375
+ const schemas = [];
376
+ const ddlStatements = [];
377
+ for (const nsid of collections) {
378
+ const lexicon = lexicons.get(nsid);
379
+ if (!lexicon) {
380
+ const genericDDL = `CREATE TABLE IF NOT EXISTS "${nsid}" (
381
+ uri TEXT PRIMARY KEY,
382
+ cid TEXT,
383
+ did TEXT NOT NULL,
384
+ indexed_at ${dialect.timestampType} NOT NULL,
385
+ data ${dialect.jsonType}
386
+ );
387
+ CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_indexed ON "${nsid}"(indexed_at DESC);
388
+ CREATE INDEX IF NOT EXISTS idx_${nsid.replace(/\./g, '_')}_author ON "${nsid}"(did);`;
389
+ schemas.push({ collection: nsid, tableName: `"${nsid}"`, columns: [], refColumns: [], children: [], unions: [] });
390
+ ddlStatements.push(genericDDL);
391
+ continue;
392
+ }
393
+ const schema = generateTableSchema(nsid, lexicon, lexicons, dialect);
394
+ schemas.push(schema);
395
+ ddlStatements.push(generateCreateTableSQL(schema, dialect));
396
+ }
397
+ return { schemas, ddlStatements };
398
+ }
@@ -0,0 +1,8 @@
1
+ export declare const handler: (request: Request) => Promise<Response>;
2
+ /** Re-scan server/ directory to pick up changed handlers in dev mode. */
3
+ export declare function reloadServer(): Promise<void>;
4
+ export { renderPage } from './renderer.ts';
5
+ export { getRenderer } from './renderer.ts';
6
+ export { callXrpc } from './xrpc.ts';
7
+ export { parseSessionCookie, getSessionCookieName } from './oauth/session.ts';
8
+ //# sourceMappingURL=dev-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-entry.d.ts","sourceRoot":"","sources":["../src/dev-entry.ts"],"names":[],"mappings":"AA8GA,eAAO,MAAM,OAAO,yCAKlB,CAAA;AAEF,yEAAyE;AACzE,wBAAsB,YAAY,kBAEjC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Dev mode entry point — loaded through Vite's module runner.
3
+ * Boots hatk infrastructure and exports the fetch handler.
4
+ */
5
+ import { loadConfig } from "./config.js";
6
+ import { loadLexicons, storeLexicons, discoverCollections, buildSchemas } from "./database/schema.js";
7
+ import { discoverViews } from "./views.js";
8
+ import { initDatabase, migrateSchema, getSchemaDump } 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";
12
+ import { configureRelay, configureCdn, configureOAuth } from "./xrpc.js";
13
+ import { initOAuth } from "./oauth/server.js";
14
+ import { initServer } from "./server-init.js";
15
+ import { createHandler, registerCoreHandlers } from "./server.js";
16
+ import { startIndexer } from "./indexer.js";
17
+ import { getCursor } from "./database/db.js";
18
+ import { runBackfill } from "./backfill.js";
19
+ import { rebuildAllIndexes } from "./database/fts.js";
20
+ import { relayHttpUrl } from "./config.js";
21
+ import { validateLexicons } from '@bigmoves/lexicon';
22
+ import { log } from "./logger.js";
23
+ import { mkdirSync } from 'node:fs';
24
+ import { dirname, resolve } from 'node:path';
25
+ process.env.DEV_MODE = '1';
26
+ // Boot sequence (mirrors main.ts but exports handler instead of starting server)
27
+ const configPath = 'hatk.config.ts';
28
+ const configDir = dirname(resolve(configPath));
29
+ const config = await loadConfig(configPath);
30
+ configureRelay(config.relay);
31
+ configureCdn(config.cdn);
32
+ const lexicons = loadLexicons(resolve(configDir, 'lexicons'));
33
+ const lexiconErrors = validateLexicons([...lexicons.values()]);
34
+ if (lexiconErrors) {
35
+ for (const [nsid, errors] of Object.entries(lexiconErrors)) {
36
+ for (const err of errors)
37
+ console.error(`Invalid lexicon ${nsid}: ${err}`);
38
+ }
39
+ throw new Error('Invalid lexicons');
40
+ }
41
+ storeLexicons(lexicons);
42
+ const collections = config.collections.length > 0 ? config.collections : discoverCollections(lexicons);
43
+ discoverViews();
44
+ const engineDialect = getDialect(config.databaseEngine);
45
+ const { schemas, ddlStatements } = buildSchemas(lexicons, collections, engineDialect);
46
+ if (config.database !== ':memory:') {
47
+ mkdirSync(dirname(config.database), { recursive: true });
48
+ }
49
+ const { adapter, searchPort } = await createAdapter(config.databaseEngine);
50
+ setSearchPort(searchPort);
51
+ await initDatabase(adapter, config.database, schemas, ddlStatements);
52
+ await migrateSchema(schemas);
53
+ // Write db/schema.sql
54
+ try {
55
+ const { mkdirSync, writeFileSync } = await import('node:fs');
56
+ const schemaDir = resolve(configDir, 'db');
57
+ mkdirSync(schemaDir, { recursive: true });
58
+ const schemaDump = await getSchemaDump();
59
+ 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`);
60
+ log(`[hatk] Schema written to db/schema.sql`);
61
+ }
62
+ catch { }
63
+ // Initialize handlers from server/ directory
64
+ await initServer(resolve(configDir, 'server'));
65
+ // Register built-in dev.hatk.* handlers so callXrpc() can find them
66
+ registerCoreHandlers(collections, config.oauth);
67
+ configureOAuth(config.oauth);
68
+ if (config.oauth) {
69
+ await initOAuth(config.oauth, config.plc, config.relay);
70
+ }
71
+ // Start indexer
72
+ const collectionSet = new Set(collections);
73
+ const cursor = await getCursor('relay');
74
+ startIndexer({
75
+ relayUrl: config.relay,
76
+ plcUrl: config.plc,
77
+ collections: collectionSet,
78
+ signalCollections: config.backfill.signalCollections ? new Set(config.backfill.signalCollections) : undefined,
79
+ pinnedRepos: config.backfill.repos ? new Set(config.backfill.repos) : undefined,
80
+ cursor,
81
+ fetchTimeout: config.backfill.fetchTimeout,
82
+ maxRetries: config.backfill.maxRetries,
83
+ parallelism: config.backfill.parallelism,
84
+ ftsRebuildInterval: config.ftsRebuildInterval,
85
+ });
86
+ // Run backfill in background (no restart in dev mode)
87
+ runBackfill({
88
+ pdsUrl: relayHttpUrl(config.relay),
89
+ plcUrl: config.plc,
90
+ collections: collectionSet,
91
+ config: config.backfill,
92
+ })
93
+ .then(() => rebuildAllIndexes(Array.from(collectionSet)))
94
+ .catch((err) => console.error('[backfill]', err.message));
95
+ // Export the handler for Vite middleware
96
+ export const handler = createHandler({
97
+ collections: Array.from(collectionSet),
98
+ publicDir: null, // Vite serves static assets in dev
99
+ oauth: config.oauth,
100
+ admins: config.admins,
101
+ });
102
+ /** Re-scan server/ directory to pick up changed handlers in dev mode. */
103
+ export async function reloadServer() {
104
+ await initServer(resolve(configDir, 'server'));
105
+ }
106
+ export { renderPage } from "./renderer.js";
107
+ export { getRenderer } from "./renderer.js";
108
+ export { callXrpc } from "./xrpc.js";
109
+ export { parseSessionCookie, getSessionCookieName } from "./oauth/session.js";
110
+ log(`[hatk] Dev server ready`);
111
+ log(` Relay: ${config.relay}`);
112
+ log(` Database: ${config.database}`);
113
+ log(` Collections: ${collections.join(', ')}`);
package/dist/feeds.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { HydrateContext, Row } from './hydrate.ts';
1
+ import type { BaseContext, Row } from './hydrate.ts';
2
2
  import type { Checked } from './lex-types.ts';
3
- export type { HydrateContext, Row };
3
+ export type { BaseContext, Row };
4
4
  export interface FeedResult {
5
5
  uris: string[];
6
6
  cursor?: string;
@@ -16,7 +16,7 @@ export interface PaginateResult<T> {
16
16
  }
17
17
  export interface FeedContext {
18
18
  db: {
19
- query: (sql: string, params?: any[]) => Promise<any[]>;
19
+ query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
20
20
  };
21
21
  params: Record<string, string>;
22
22
  cursor?: string;
@@ -43,17 +43,17 @@ type FeedOpts = {
43
43
  view?: string;
44
44
  label: string;
45
45
  generate: FeedGenerate;
46
- hydrate?: (ctx: HydrateContext<any>) => Promise<unknown[]>;
46
+ hydrate?: (ctx: BaseContext, items: Row<any>[]) => Promise<unknown[]>;
47
47
  } | {
48
48
  collection?: never;
49
49
  view?: never;
50
50
  label: string;
51
51
  generate: FeedGenerate;
52
- hydrate: (ctx: HydrateContext<any>) => Promise<unknown[]>;
52
+ hydrate: (ctx: BaseContext, items: Row<any>[]) => Promise<unknown[]>;
53
53
  };
54
54
  export declare function createPaginate(deps: {
55
55
  db: {
56
- query: (sql: string, params?: any[]) => Promise<any[]>;
56
+ query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
57
57
  };
58
58
  cursor?: string;
59
59
  limit: number;
@@ -70,14 +70,18 @@ export declare function defineFeed(opts: FeedOpts): {
70
70
  collection: string;
71
71
  view?: string;
72
72
  label: string;
73
- hydrate?: (ctx: HydrateContext<any>) => Promise<unknown[]>;
73
+ hydrate?: (ctx: BaseContext, items: Row<any>[]) => Promise<unknown[]>;
74
+ __type: "feed";
74
75
  } | {
75
76
  generate: (ctx: any) => Promise<Checked<FeedResult>>;
76
77
  collection?: never;
77
78
  view?: never;
78
79
  label: string;
79
- hydrate: (ctx: HydrateContext<any>) => Promise<unknown[]>;
80
+ hydrate: (ctx: BaseContext, items: Row<any>[]) => Promise<unknown[]>;
81
+ __type: "feed";
80
82
  };
83
+ /** Register a single feed from a scanned server/ module. */
84
+ export declare function registerFeed(name: string, generator: ReturnType<typeof defineFeed>): void;
81
85
  export declare function initFeeds(feedsDir: string): Promise<void>;
82
86
  /** Execute a feed and run its hydrate pipeline if present. */
83
87
  export declare function executeFeed(name: string, params: Record<string, string>, cursor: string | undefined, limit: number, viewer?: {
@@ -1 +1 @@
1
- {"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../src/feeds.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../src/feeds.ts"],"names":[],"mappings":"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"}
package/dist/feeds.js CHANGED
@@ -9,8 +9,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
9
9
  import { resolve } from 'node:path';
10
10
  import { readdirSync } from 'node:fs';
11
11
  import { log } from "./logger.js";
12
- import { querySQL, packCursor, unpackCursor, isTakendownDid, filterTakendownDids } from "./db.js";
13
- import { resolveRecords, buildHydrateContext } from "./hydrate.js";
12
+ import { querySQL, packCursor, unpackCursor, isTakendownDid, filterTakendownDids, resolveHandleToDid } from "./database/db.js";
13
+ import { resolveRecords, buildBaseContext } from "./hydrate.js";
14
14
  export function createPaginate(deps) {
15
15
  return async (sql, opts) => {
16
16
  const { db, cursor, limit, packCursor: pack, unpackCursor: unpack } = deps;
@@ -59,7 +59,46 @@ export function createPaginate(deps) {
59
59
  };
60
60
  }
61
61
  export function defineFeed(opts) {
62
- return { ...opts, generate: (ctx) => opts.generate({ ...ctx, ok: (v) => v }) };
62
+ return { __type: 'feed', ...opts, generate: (ctx) => opts.generate({ ...ctx, ok: (v) => v }) };
63
+ }
64
+ /** Register a single feed from a scanned server/ module. */
65
+ export function registerFeed(name, generator) {
66
+ const handler = {
67
+ name,
68
+ label: generator.label || name,
69
+ collection: generator.collection,
70
+ view: generator.view,
71
+ generate: async (params, cursor, limit, viewer) => {
72
+ const paginateDeps = {
73
+ db: { query: querySQL },
74
+ cursor,
75
+ limit,
76
+ packCursor,
77
+ unpackCursor,
78
+ };
79
+ const ctx = {
80
+ db: { query: querySQL },
81
+ params,
82
+ cursor,
83
+ limit,
84
+ viewer,
85
+ packCursor,
86
+ unpackCursor,
87
+ isTakendown: isTakendownDid,
88
+ filterTakendownDids,
89
+ paginate: createPaginate(paginateDeps),
90
+ };
91
+ const result = await generator.generate(ctx);
92
+ if (Array.isArray(result)) {
93
+ return { uris: result.map((r) => r.uri || r) };
94
+ }
95
+ return { uris: result.uris, cursor: result.cursor };
96
+ },
97
+ };
98
+ if (typeof generator.hydrate === 'function') {
99
+ handler.hydrate = generator.hydrate;
100
+ }
101
+ feeds.set(name, handler);
63
102
  }
64
103
  const feeds = new Map();
65
104
  export async function initFeeds(feedsDir) {
@@ -75,7 +114,7 @@ export async function initFeeds(feedsDir) {
75
114
  for (const file of files) {
76
115
  const name = file.replace(/\.(ts|js)$/, '');
77
116
  const scriptPath = resolve(feedsDir, file);
78
- const mod = await import(__rewriteRelativeImportExtension(scriptPath));
117
+ const mod = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ `${scriptPath}?t=${Date.now()}`));
79
118
  const generator = mod.default;
80
119
  const handler = {
81
120
  name,
@@ -127,11 +166,17 @@ export async function executeFeed(name, params, cursor, limit, viewer) {
127
166
  const handler = feeds.get(name);
128
167
  if (!handler)
129
168
  return null;
169
+ // Resolve handle-based actor param to DID
170
+ if (params.actor && !params.actor.startsWith('did:')) {
171
+ const resolved = await resolveHandleToDid(params.actor);
172
+ if (resolved)
173
+ params.actor = resolved;
174
+ }
130
175
  const result = await handler.generate(params, cursor, limit, viewer || null);
131
176
  if (handler.hydrate) {
132
177
  const items = await resolveRecords(result.uris);
133
- const ctx = buildHydrateContext(items, viewer || null);
134
- const hydrated = await handler.hydrate(ctx);
178
+ const ctx = buildBaseContext(viewer || null);
179
+ const hydrated = await handler.hydrate(ctx, items);
135
180
  return { items: hydrated, cursor: result.cursor };
136
181
  }
137
182
  return { uris: result.uris, cursor: result.cursor };
@@ -0,0 +1,85 @@
1
+ import type { OAuthConfig } from './config.ts';
2
+ import { type BaseContext } from './hydrate.ts';
3
+ import { type PushInterface } from './push.ts';
4
+ /** Context passed to the on-login hook after a successful OAuth login. */
5
+ export type OnLoginCtx = Omit<BaseContext, 'db'> & {
6
+ /** DID of the user who just logged in. */
7
+ did: string;
8
+ /** Database access with both read and write. */
9
+ db: {
10
+ query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
11
+ run: (sql: string, params?: unknown[]) => Promise<void>;
12
+ };
13
+ /** Trigger a backfill for a DID and wait for it to complete. */
14
+ ensureRepo: (did: string) => Promise<void>;
15
+ /** Write a record to the user's PDS and index locally. */
16
+ createRecord: (collection: string, record: Record<string, unknown>, opts?: {
17
+ rkey?: string;
18
+ }) => Promise<{
19
+ uri?: string;
20
+ cid?: string;
21
+ }>;
22
+ /** Create or update a record on the user's PDS and index locally. */
23
+ putRecord: (collection: string, rkey: string, record: Record<string, unknown>) => Promise<{
24
+ uri?: string;
25
+ cid?: string;
26
+ }>;
27
+ /** Delete a record from the user's PDS and local index. */
28
+ deleteRecord: (collection: string, rkey: string) => Promise<void>;
29
+ };
30
+ /** Context passed to on-commit hooks after a record is indexed. */
31
+ export type OnCommitCtx = {
32
+ /** Whether the record was created or deleted. */
33
+ action: 'create' | 'delete';
34
+ /** The collection NSID that matched. */
35
+ collection: string;
36
+ /** The record value (null for deletes). */
37
+ record: Record<string, any> | null;
38
+ /** DID of the committing actor. */
39
+ repo: string;
40
+ /** AT URI of the record. */
41
+ uri: string;
42
+ /** Database access (read and write). */
43
+ db: {
44
+ query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
45
+ run: (sql: string, params?: unknown[]) => Promise<void>;
46
+ };
47
+ /** Typed record lookup (same as BaseContext). */
48
+ lookup: BaseContext['lookup'];
49
+ /** Push notification delivery. */
50
+ push: PushInterface;
51
+ };
52
+ export declare function defineHook(event: 'on-login', handler: (ctx: OnLoginCtx) => Promise<void>): {
53
+ __type: 'hook';
54
+ event: 'on-login';
55
+ handler: (ctx: OnLoginCtx) => Promise<void>;
56
+ };
57
+ export declare function defineHook(event: 'on-commit', options: {
58
+ collections: string[];
59
+ }, handler: (ctx: OnCommitCtx) => Promise<void>): {
60
+ __type: 'hook';
61
+ event: 'on-commit';
62
+ collections: string[];
63
+ handler: (ctx: OnCommitCtx) => Promise<void>;
64
+ };
65
+ /**
66
+ * Discover and load the on-login hook from the project's `hooks/` directory.
67
+ * Looks for `on-login.ts` or `on-login.js`. Safe to call if no hook exists.
68
+ */
69
+ export declare function loadOnLoginHook(hooksDir: string): Promise<void>;
70
+ /** Register a hook from a scanned server/ module. */
71
+ export declare function registerHook(event: string, handler: Function, options?: any): void;
72
+ /** Fire the on-login hook if loaded. Errors are logged but never block login. */
73
+ export declare function fireOnLoginHook(did: string, oauthConfig: OAuthConfig | null): Promise<void>;
74
+ /**
75
+ * Fire on-commit hooks for a batch of indexed records.
76
+ * Runs async and non-blocking — errors are logged but never throw.
77
+ */
78
+ export declare function fireOnCommitHooks(items: Array<{
79
+ action: 'create' | 'delete';
80
+ collection: string;
81
+ uri: string;
82
+ authorDid: string;
83
+ record: Record<string, any> | null;
84
+ }>): void;
85
+ //# sourceMappingURL=hooks.d.ts.map