@hatk/hatk 0.0.1-alpha.35 → 0.0.1-alpha.37

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.
@@ -8,36 +8,36 @@ export function toSnakeCase(str) {
8
8
  function mapType(prop, dialect) {
9
9
  if (prop.type === 'string') {
10
10
  if (prop.format === 'datetime')
11
- return { sqlType: dialect.typeMap.timestamp, isRef: false };
11
+ return { sqlType: dialect.typeMap.timestamp, isRef: false, isJson: false };
12
12
  if (prop.format === 'at-uri')
13
- return { sqlType: dialect.typeMap.text, isRef: true };
14
- return { sqlType: dialect.typeMap.text, isRef: false };
13
+ return { sqlType: dialect.typeMap.text, isRef: true, isJson: false };
14
+ return { sqlType: dialect.typeMap.text, isRef: false, isJson: false };
15
15
  }
16
16
  if (prop.type === 'integer')
17
- return { sqlType: dialect.typeMap.integer, isRef: false };
17
+ return { sqlType: dialect.typeMap.integer, isRef: false, isJson: false };
18
18
  if (prop.type === 'boolean')
19
- return { sqlType: dialect.typeMap.boolean, isRef: false };
19
+ return { sqlType: dialect.typeMap.boolean, isRef: false, isJson: false };
20
20
  if (prop.type === 'bytes')
21
- return { sqlType: dialect.typeMap.blob, isRef: false };
21
+ return { sqlType: dialect.typeMap.blob, isRef: false, isJson: false };
22
22
  if (prop.type === 'cid-link')
23
- return { sqlType: dialect.typeMap.text, isRef: false };
23
+ return { sqlType: dialect.typeMap.text, isRef: false, isJson: false };
24
24
  if (prop.type === 'array')
25
- return { sqlType: dialect.jsonType, isRef: false };
25
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
26
26
  if (prop.type === 'blob')
27
- return { sqlType: dialect.jsonType, isRef: false };
27
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
28
28
  if (prop.type === 'union')
29
- return { sqlType: dialect.jsonType, isRef: false };
29
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
30
30
  if (prop.type === 'unknown')
31
- return { sqlType: dialect.jsonType, isRef: false };
31
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
32
32
  if (prop.type === 'object')
33
- return { sqlType: dialect.jsonType, isRef: false };
33
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
34
34
  if (prop.type === 'ref') {
35
35
  // strongRef contains { uri, cid } — handled specially in generateTableSchema
36
36
  if (prop.ref === 'com.atproto.repo.strongRef')
37
- return { sqlType: 'STRONG_REF', isRef: true };
38
- return { sqlType: dialect.jsonType, isRef: false };
37
+ return { sqlType: 'STRONG_REF', isRef: true, isJson: false };
38
+ return { sqlType: dialect.jsonType, isRef: false, isJson: true };
39
39
  }
40
- return { sqlType: dialect.typeMap.text, isRef: false };
40
+ return { sqlType: dialect.typeMap.text, isRef: false, isJson: false };
41
41
  }
42
42
  // Recursively find all .json files in a directory
43
43
  function findJsonFiles(dir) {
@@ -182,7 +182,7 @@ function resolveUnionBranch(ref, collection, fieldName, defs, lexicons, dialect)
182
182
  const tableName = `"${collection}__${snakeField}_${branchName}"`;
183
183
  const columns = [];
184
184
  for (const [propName, prop] of Object.entries(propSource)) {
185
- const { sqlType, isRef } = mapType(prop, dialect);
185
+ const { sqlType, isRef, isJson } = mapType(prop, dialect);
186
186
  // Skip STRONG_REF expansion in branch tables — treat as JSON
187
187
  const finalType = sqlType === 'STRONG_REF' ? dialect.jsonType : sqlType;
188
188
  columns.push({
@@ -190,7 +190,8 @@ function resolveUnionBranch(ref, collection, fieldName, defs, lexicons, dialect)
190
190
  originalName: propName,
191
191
  sqlType: finalType,
192
192
  notNull: branchRequired.has(propName),
193
- isRef: finalType !== 'JSON' && isRef,
193
+ isRef: finalType !== dialect.jsonType && isRef,
194
+ isJson: isJson || sqlType === 'STRONG_REF',
194
195
  });
195
196
  }
196
197
  return { type: fullType, branchName, tableName, columns, isArray, arrayField, wrapperField };
@@ -229,6 +230,7 @@ export function generateTableSchema(nsid, lexicon, lexicons, dialect = DUCKDB_DI
229
230
  sqlType: dialect.jsonType,
230
231
  notNull: required.has(fieldName),
231
232
  isRef: false,
233
+ isJson: true,
232
234
  });
233
235
  continue;
234
236
  }
@@ -239,13 +241,14 @@ export function generateTableSchema(nsid, lexicon, lexicons, dialect = DUCKDB_DI
239
241
  const childColumns = [];
240
242
  const itemRequired = new Set(p.items?.required || lexicon.defs?.[p.items?.ref?.slice(1)]?.required || []);
241
243
  for (const [itemField, itemProp] of Object.entries(itemProps)) {
242
- const { sqlType, isRef } = mapType(itemProp, dialect);
244
+ const { sqlType, isRef, isJson } = mapType(itemProp, dialect);
243
245
  childColumns.push({
244
246
  name: toSnakeCase(itemField),
245
247
  originalName: itemField,
246
248
  sqlType,
247
249
  notNull: itemRequired.has(itemField),
248
250
  isRef,
251
+ isJson,
249
252
  });
250
253
  }
251
254
  const snakeField = toSnakeCase(fieldName);
@@ -258,7 +261,7 @@ export function generateTableSchema(nsid, lexicon, lexicons, dialect = DUCKDB_DI
258
261
  continue;
259
262
  }
260
263
  }
261
- const { sqlType, isRef } = mapType(p, dialect);
264
+ const { sqlType, isRef, isJson } = mapType(p, dialect);
262
265
  if (sqlType === 'STRONG_REF') {
263
266
  // Expand strongRef into two columns: {name}_uri and {name}_cid
264
267
  columns.push({
@@ -267,6 +270,7 @@ export function generateTableSchema(nsid, lexicon, lexicons, dialect = DUCKDB_DI
267
270
  sqlType: dialect.typeMap.text,
268
271
  notNull: required.has(fieldName),
269
272
  isRef: true,
273
+ isJson: false,
270
274
  });
271
275
  columns.push({
272
276
  name: toSnakeCase(fieldName) + '_cid',
@@ -274,6 +278,7 @@ export function generateTableSchema(nsid, lexicon, lexicons, dialect = DUCKDB_DI
274
278
  sqlType: dialect.typeMap.text,
275
279
  notNull: required.has(fieldName),
276
280
  isRef: false,
281
+ isJson: false,
277
282
  });
278
283
  }
279
284
  else {
@@ -283,6 +288,7 @@ export function generateTableSchema(nsid, lexicon, lexicons, dialect = DUCKDB_DI
283
288
  sqlType,
284
289
  notNull: required.has(fieldName),
285
290
  isRef,
291
+ isJson,
286
292
  });
287
293
  }
288
294
  }
@@ -331,7 +337,7 @@ export function generateCreateTableSQL(schema, dialect = DUCKDB_DIALECT) {
331
337
  childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${childPrefix}_parent ON ${child.tableName}(parent_uri);`);
332
338
  childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${childPrefix}_did ON ${child.tableName}(parent_did);`);
333
339
  for (const col of child.columns) {
334
- if (col.sqlType === 'JSON' || col.sqlType === 'BLOB')
340
+ if (col.isJson || col.sqlType === 'BLOB')
335
341
  continue;
336
342
  childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${childPrefix}_${col.name} ON ${child.tableName}(${col.name});`);
337
343
  }
@@ -349,7 +355,7 @@ export function generateCreateTableSQL(schema, dialect = DUCKDB_DIALECT) {
349
355
  childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${branchPrefix}_parent ON ${branch.tableName}(parent_uri);`);
350
356
  childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${branchPrefix}_did ON ${branch.tableName}(parent_did);`);
351
357
  for (const col of branch.columns) {
352
- if (col.sqlType === 'JSON' || col.sqlType === 'BLOB')
358
+ if (col.isJson || col.sqlType === 'BLOB')
353
359
  continue;
354
360
  childDDL.push(`CREATE INDEX IF NOT EXISTS idx_${branchPrefix}_${col.name} ON ${branch.tableName}(${col.name});`);
355
361
  }
package/dist/labels.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface LabelModule {
17
17
  definition?: LabelDefinition;
18
18
  evaluate?: (ctx: LabelRuleContext) => Promise<string[]>;
19
19
  }
20
- export declare function defineLabels(module: LabelModule): {
20
+ export declare function defineLabel(module: LabelModule): {
21
21
  definition?: LabelDefinition;
22
22
  evaluate?: (ctx: LabelRuleContext) => Promise<string[]>;
23
23
  __type: "labels";
@@ -1 +1 @@
1
- {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIlD,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,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,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAA;QACX,GAAG,EAAE,MAAM,CAAA;QACX,GAAG,EAAE,MAAM,CAAA;QACX,UAAU,EAAE,MAAM,CAAA;QAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAC3B,CAAA;CACF;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CACxD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW;iBAJjC,eAAe;eACjB,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;;EAKxD;AAYD;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCjE;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE;IAAE,UAAU,CAAC,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GAClG,IAAI,CAON;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC3B,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBhB;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAuCvG;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,IAAI,eAAe,EAAE,CAEvD"}
1
+ {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIlD,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,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,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAA;QACX,GAAG,EAAE,MAAM,CAAA;QACX,GAAG,EAAE,MAAM,CAAA;QACX,UAAU,EAAE,MAAM,CAAA;QAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAC3B,CAAA;CACF;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CACxD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW;iBAJhC,eAAe;eACjB,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;;EAKxD;AAYD;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCjE;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE;IAAE,UAAU,CAAC,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GAClG,IAAI,CAON;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC3B,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBhB;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAuCvG;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,IAAI,eAAe,EAAE,CAEvD"}
package/dist/labels.js CHANGED
@@ -38,7 +38,7 @@ import { resolve } from 'node:path';
38
38
  import { readdirSync } from 'node:fs';
39
39
  import { querySQL, runSQL, insertLabels, getSchema } from "./database/db.js";
40
40
  import { log, emit } from "./logger.js";
41
- export function defineLabels(module) {
41
+ export function defineLabel(module) {
42
42
  return { __type: 'labels', ...module };
43
43
  }
44
44
  const rules = [];
@@ -144,7 +144,7 @@ export async function rescanLabels(collections) {
144
144
  let v = row[col.name];
145
145
  if (v === null || v === undefined)
146
146
  continue;
147
- if (col.sqlType === 'JSON' && typeof v === 'string') {
147
+ if (col.isJson && typeof v === 'string') {
148
148
  try {
149
149
  v = JSON.parse(v);
150
150
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AA0B9C;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAwH3F;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IACzB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACxF,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAwyB5F;AAGD,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EAAE,EACrB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,KAAK,EAAE,WAAW,GAAG,IAAI,EACzB,MAAM,GAAE,MAAM,EAAO,EACrB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,GACpB,OAAO,WAAW,EAAE,MAAM,CAG5B"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAqDA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AA0B9C;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI,CAwH3F;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IACzB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACxF,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA8xB5F;AAGD,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EAAE,EACrB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,KAAK,EAAE,WAAW,GAAG,IAAI,EACzB,MAAM,GAAE,MAAM,EAAO,EACrB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EAC5D,QAAQ,CAAC,EAAE,MAAM,IAAI,GACpB,OAAO,WAAW,EAAE,MAAM,CAG5B"}
package/dist/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { join, extname } from 'node:path';
4
- import { queryRecords, getRecordByUri, searchRecords, getSchema, reshapeRow, setRepoStatus, getRepoStatus, getRepoRetryInfo, querySQL, queryLabelsForUris, insertLabels, searchAccounts, listReposPaginated, getCollectionCounts, getSchemaDump, getPreferences, putPreference, } from "./database/db.js";
4
+ import { queryRecords, getRecordByUri, searchRecords, getSchema, reshapeRow, setRepoStatus, getRepoStatus, getRepoRetryInfo, queryLabelsForUris, insertLabels, searchAccounts, listReposPaginated, getCollectionCounts, getRepoStatusCounts, getDatabaseSize, deleteLabels, getRecentRecords, listActiveRepoDids, removeRepo, getRepoHandle, getPreferences, putPreference, } from "./database/db.js";
5
5
  import { executeFeed, listFeeds } from "./feeds.js";
6
6
  import { executeXrpc, InvalidRequestError, NotFoundError, registerCoreXrpcHandler } from "./xrpc.js";
7
7
  import { resolveRecords } from "./hydrate.js";
@@ -377,10 +377,8 @@ export function createHandler(config) {
377
377
  const { val } = JSON.parse(await request.text());
378
378
  if (!val)
379
379
  return withCors(jsonError(400, 'Missing val', acceptEncoding));
380
- const result = await querySQL(`SELECT COUNT(*)::INTEGER as count FROM _labels WHERE val = $1`, [val]);
381
- const count = Number(result[0]?.count || 0);
382
- await querySQL(`DELETE FROM _labels WHERE val = $1`, [val]);
383
- return withCors(json({ deleted: count }, 200, acceptEncoding));
380
+ const deleted = await deleteLabels(val);
381
+ return withCors(json({ deleted }, 200, acceptEncoding));
384
382
  }
385
383
  // POST /admin/labels/negate — negate a label
386
384
  if (url.pathname === '/admin/labels/negate' && request.method === 'POST') {
@@ -433,10 +431,9 @@ export function createHandler(config) {
433
431
  const allResults = [];
434
432
  for (const col of collections) {
435
433
  try {
436
- const schema = getSchema(col);
437
- if (!schema)
434
+ const rows = await getRecentRecords(col, limit + offset);
435
+ if (!rows.length)
438
436
  continue;
439
- const rows = await querySQL(`SELECT t.* FROM ${schema.tableName} t JOIN _repos r ON t.did = r.did WHERE t.indexed_at > r.backfilled_at ORDER BY t.indexed_at DESC LIMIT $1`, [limit + offset]);
440
437
  const uris = rows.map((r) => r.uri);
441
438
  const labelsMap = await queryLabelsForUris(uris);
442
439
  for (const rec of rows) {
@@ -515,14 +512,25 @@ export function createHandler(config) {
515
512
  repoList = dids;
516
513
  }
517
514
  else {
518
- const rows = await querySQL(`SELECT did FROM _repos WHERE status = 'active'`);
519
- repoList = rows.map((r) => r.did);
515
+ repoList = await listActiveRepoDids();
520
516
  }
517
+ const isTargeted = Array.isArray(dids) && dids.length > 0;
521
518
  for (const did of repoList) {
522
519
  await setRepoStatus(did, 'pending');
523
520
  }
524
- if (config.onResync)
521
+ if (isTargeted) {
522
+ for (const did of repoList) {
523
+ triggerAutoBackfill(did);
524
+ }
525
+ }
526
+ else if (config.onResync) {
525
527
  config.onResync();
528
+ }
529
+ else {
530
+ for (const did of repoList) {
531
+ triggerAutoBackfill(did);
532
+ }
533
+ }
526
534
  return withCors(json({ resyncing: repoList.length }, 200, acceptEncoding));
527
535
  }
528
536
  // POST /admin/repos/remove — remove DIDs from tracking
@@ -534,7 +542,7 @@ export function createHandler(config) {
534
542
  if (!Array.isArray(dids))
535
543
  return withCors(jsonError(400, 'Missing dids array', acceptEncoding));
536
544
  for (const did of dids) {
537
- await querySQL(`DELETE FROM _repos WHERE did = $1`, [did]);
545
+ await removeRepo(did);
538
546
  }
539
547
  return withCors(json({ removed: dids.length }, 200, acceptEncoding));
540
548
  }
@@ -543,12 +551,8 @@ export function createHandler(config) {
543
551
  const denied = requireAdmin(viewer, acceptEncoding);
544
552
  if (denied)
545
553
  return denied;
546
- const rows = await querySQL(`SELECT status, COUNT(*)::INTEGER as count FROM _repos GROUP BY status`);
547
- const counts = {};
548
- for (const row of rows)
549
- counts[row.status] = Number(row.count);
550
- const sizeRows = await querySQL(`SELECT database_size, memory_usage, memory_limit FROM pragma_database_size()`);
551
- const dbInfo = sizeRows[0] ?? {};
554
+ const counts = await getRepoStatusCounts();
555
+ const dbInfo = await getDatabaseSize();
552
556
  const collectionCounts = await getCollectionCounts();
553
557
  const mem = process.memoryUsage();
554
558
  const node = {
@@ -588,15 +592,6 @@ export function createHandler(config) {
588
592
  const result = await listReposPaginated({ limit, offset, status, q });
589
593
  return withCors(json(result, 200, acceptEncoding));
590
594
  }
591
- // GET /admin/schema — full DuckDB DDL dump + lexicons
592
- if (url.pathname === '/admin/schema') {
593
- const denied = requireAdmin(viewer, acceptEncoding);
594
- if (denied)
595
- return denied;
596
- const { getAllLexicons } = await import("./database/schema.js");
597
- const ddl = await getSchemaDump();
598
- return withCors(json({ ddl, lexicons: getAllLexicons() }, 200, acceptEncoding));
599
- }
600
595
  // ── Public Repo Endpoints (used by hatk clients for auto-sync) ──
601
596
  // POST /repos/add — enqueue DIDs for backfill (public)
602
597
  if (url.pathname === '/repos/add' && request.method === 'POST') {
@@ -642,8 +637,7 @@ export function createHandler(config) {
642
637
  const did = url.searchParams.get('did');
643
638
  if (!did)
644
639
  return withCors(jsonError(400, 'did required', acceptEncoding));
645
- const handleRows = await querySQL('SELECT handle FROM _repos WHERE did = $1', [did]);
646
- const handle = handleRows[0]?.handle ?? did;
640
+ const handle = await getRepoHandle(did) ?? did;
647
641
  const cookieValue = await createSessionCookie({ did, handle });
648
642
  const secure = url.protocol === 'https:';
649
643
  return new Response(JSON.stringify({ ok: true }), {
@@ -715,8 +709,7 @@ export function createHandler(config) {
715
709
  return withCors(jsonError(400, 'Missing code', acceptEncoding));
716
710
  const result = await handleCallback(oauth, code, state, iss);
717
711
  const isSecure = requestOrigin.startsWith('https');
718
- const handleRows = await querySQL('SELECT handle FROM _repos WHERE did = $1', [result.did]);
719
- const handle = handleRows[0]?.handle ?? result.did;
712
+ const handle = await getRepoHandle(result.did) ?? result.did;
720
713
  const cookie = await createSessionCookie({ did: result.did, handle });
721
714
  // Server-initiated login stores redirectUri as '/' — redirect cleanly without code/iss params
722
715
  const redirectTo = result.clientRedirectUri.startsWith('/?code=') ? '/' : result.clientRedirectUri;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatk/hatk",
3
- "version": "0.0.1-alpha.35",
3
+ "version": "0.0.1-alpha.37",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "hatk": "dist/cli.js"
package/public/admin.html CHANGED
@@ -783,23 +783,6 @@
783
783
  font-size: 1rem;
784
784
  }
785
785
 
786
- /* ── Schema ── */
787
- .schema-pre {
788
- font-family: var(--mono);
789
- font-size: 0.8rem;
790
- line-height: 1.6;
791
- padding: 1rem;
792
- margin: 0;
793
- background: var(--bg-recessed);
794
- border-radius: 0 0 6px 6px;
795
- white-space: pre-wrap;
796
- word-break: break-word;
797
- color: var(--text);
798
- overflow-x: auto;
799
- }
800
- .schema-section {
801
- margin-bottom: 1.5rem;
802
- }
803
786
  .loading {
804
787
  color: var(--text-3);
805
788
  font-size: 0.9375rem;
@@ -1220,7 +1203,6 @@
1220
1203
  <button class="tab active" data-tab="overview">Overview</button>
1221
1204
  <button class="tab" data-tab="repos">Repos</button>
1222
1205
  <button class="tab" data-tab="content">Content</button>
1223
- <button class="tab" data-tab="schema">Schema</button>
1224
1206
  </nav>
1225
1207
 
1226
1208
  <!-- Overview -->
@@ -1277,10 +1259,6 @@
1277
1259
  <div id="repos-results"><div class="loading">Loading</div></div>
1278
1260
  </div>
1279
1261
 
1280
- <!-- Schema -->
1281
- <div class="tab-panel" id="panel-schema">
1282
- <div id="schema-results"><div class="loading">Loading</div></div>
1283
- </div>
1284
1262
 
1285
1263
  <!-- Content -->
1286
1264
  <div class="tab-panel" id="panel-content">
@@ -1307,7 +1285,6 @@
1307
1285
  <button class="bnav-btn active" data-tab="overview">Overview</button>
1308
1286
  <button class="bnav-btn" data-tab="repos">Repos</button>
1309
1287
  <button class="bnav-btn" data-tab="content">Content</button>
1310
- <button class="bnav-btn" data-tab="schema">Schema</button>
1311
1288
  </div>
1312
1289
  </div>
1313
1290
  </div>
@@ -1490,7 +1467,6 @@
1490
1467
  document.getElementById(`panel-${tab}`).classList.add('active')
1491
1468
  if (tab === 'overview') loadOverview()
1492
1469
  if (tab === 'repos') loadRepos()
1493
- if (tab === 'schema') loadSchema()
1494
1470
  if (tab === 'content') loadContent()
1495
1471
  if (push) pushURL({ tab, status: '', q: '', offset: 0, cq: '' })
1496
1472
  }
@@ -1599,36 +1575,6 @@
1599
1575
  }
1600
1576
  })
1601
1577
 
1602
- // ── Schema ──
1603
-
1604
- async function loadSchema() {
1605
- const container = document.getElementById('schema-results')
1606
- try {
1607
- const data = await api('/admin/schema')
1608
- let html = ''
1609
-
1610
- // Lexicons section
1611
- if (data.lexicons && data.lexicons.length) {
1612
- html += '<div class="schema-section"><div class="section-label">Lexicons</div>'
1613
- for (const lex of data.lexicons) {
1614
- html += `<div class="card" style="margin-bottom:0.5rem;"><div style="font-family:var(--mono);font-size:0.8rem;font-weight:600;padding:0.5rem 0.75rem;border-bottom:1px solid var(--border);">${escapeHtml(lex.nsid)}</div><pre class="schema-pre">${escapeHtml(JSON.stringify(lex.lexicon, null, 2))}</pre></div>`
1615
- }
1616
- html += '</div>'
1617
- }
1618
-
1619
- // DDL section
1620
- if (data.ddl) {
1621
- html += '<div class="schema-section"><div class="section-label">Tables (DuckDB DDL)</div>'
1622
- html += `<div class="card"><pre class="schema-pre">${escapeHtml(data.ddl)}</pre></div>`
1623
- html += '</div>'
1624
- }
1625
-
1626
- container.innerHTML = html || '<div class="empty-state">No schema found</div>'
1627
- } catch (e) {
1628
- container.innerHTML = `<div class="empty-state">${escapeHtml(e.message)}</div>`
1629
- }
1630
- }
1631
-
1632
1578
  // ── Repos ──
1633
1579
 
1634
1580
  let reposLoaded = false