@hatk/hatk 0.0.1-alpha.6 → 0.0.1-alpha.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.ts +19 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +108 -0
- package/dist/backfill.d.ts +2 -2
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +78 -31
- package/dist/car.d.ts +42 -10
- package/dist/car.d.ts.map +1 -1
- package/dist/car.js +154 -14
- package/dist/cli.js +243 -1043
- package/dist/config.d.ts +31 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +40 -9
- package/dist/database/adapter-factory.d.ts +6 -0
- package/dist/database/adapter-factory.d.ts.map +1 -0
- package/dist/database/adapter-factory.js +20 -0
- package/dist/database/adapters/duckdb-search.d.ts +12 -0
- package/dist/database/adapters/duckdb-search.d.ts.map +1 -0
- package/dist/database/adapters/duckdb-search.js +27 -0
- package/dist/database/adapters/duckdb.d.ts +25 -0
- package/dist/database/adapters/duckdb.d.ts.map +1 -0
- package/dist/database/adapters/duckdb.js +161 -0
- package/dist/database/adapters/sqlite-search.d.ts +23 -0
- package/dist/database/adapters/sqlite-search.d.ts.map +1 -0
- package/dist/database/adapters/sqlite-search.js +74 -0
- package/dist/database/adapters/sqlite.d.ts +18 -0
- package/dist/database/adapters/sqlite.d.ts.map +1 -0
- package/dist/database/adapters/sqlite.js +88 -0
- package/dist/{db.d.ts → database/db.d.ts} +57 -6
- package/dist/database/db.d.ts.map +1 -0
- package/dist/{db.js → database/db.js} +730 -549
- package/dist/database/dialect.d.ts +45 -0
- package/dist/database/dialect.d.ts.map +1 -0
- package/dist/database/dialect.js +72 -0
- package/dist/{fts.d.ts → database/fts.d.ts} +7 -0
- package/dist/database/fts.d.ts.map +1 -0
- package/dist/{fts.js → database/fts.js} +116 -32
- package/dist/database/index.d.ts +7 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +6 -0
- package/dist/database/ports.d.ts +50 -0
- package/dist/database/ports.d.ts.map +1 -0
- package/dist/database/ports.js +1 -0
- package/dist/{schema.d.ts → database/schema.d.ts} +14 -3
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/{schema.js → database/schema.js} +81 -41
- package/dist/dev-entry.d.ts +8 -0
- package/dist/dev-entry.d.ts.map +1 -0
- package/dist/dev-entry.js +112 -0
- package/dist/feeds.d.ts +12 -8
- package/dist/feeds.d.ts.map +1 -1
- package/dist/feeds.js +51 -6
- package/dist/hooks.d.ts +85 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +161 -0
- package/dist/hydrate.d.ts +7 -6
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +4 -16
- package/dist/indexer.d.ts +22 -0
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +123 -32
- package/dist/labels.d.ts +36 -0
- package/dist/labels.d.ts.map +1 -1
- package/dist/labels.js +71 -6
- package/dist/lexicon-resolve.d.ts.map +1 -1
- package/dist/lexicon-resolve.js +27 -112
- package/dist/lexicons/com/atproto/label/defs.json +75 -0
- package/dist/lexicons/com/atproto/moderation/defs.json +30 -0
- package/dist/lexicons/com/atproto/repo/strongRef.json +24 -0
- package/dist/lexicons/dev/hatk/applyWrites.json +87 -0
- package/dist/lexicons/dev/hatk/createRecord.json +40 -0
- package/dist/lexicons/dev/hatk/createReport.json +48 -0
- package/dist/lexicons/dev/hatk/deleteRecord.json +25 -0
- package/dist/lexicons/dev/hatk/describeCollections.json +41 -0
- package/dist/lexicons/dev/hatk/describeFeeds.json +29 -0
- package/dist/lexicons/dev/hatk/describeLabels.json +45 -0
- package/dist/lexicons/dev/hatk/getFeed.json +30 -0
- package/dist/lexicons/dev/hatk/getPreferences.json +19 -0
- package/dist/lexicons/dev/hatk/getRecord.json +26 -0
- package/dist/lexicons/dev/hatk/getRecords.json +32 -0
- package/dist/lexicons/dev/hatk/putPreference.json +28 -0
- package/dist/lexicons/dev/hatk/putRecord.json +41 -0
- package/dist/lexicons/dev/hatk/searchRecords.json +32 -0
- package/dist/lexicons/dev/hatk/uploadBlob.json +23 -0
- package/dist/logger.d.ts +29 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +29 -0
- package/dist/main.js +137 -67
- package/dist/mst.d.ts +18 -1
- package/dist/mst.d.ts.map +1 -1
- package/dist/mst.js +19 -8
- package/dist/oauth/db.d.ts +3 -1
- package/dist/oauth/db.d.ts.map +1 -1
- package/dist/oauth/db.js +48 -19
- package/dist/oauth/server.d.ts +24 -0
- package/dist/oauth/server.d.ts.map +1 -1
- package/dist/oauth/server.js +198 -22
- package/dist/oauth/session.d.ts +11 -0
- package/dist/oauth/session.d.ts.map +1 -0
- package/dist/oauth/session.js +65 -0
- package/dist/opengraph.d.ts +10 -0
- package/dist/opengraph.d.ts.map +1 -1
- package/dist/opengraph.js +80 -40
- package/dist/pds-proxy.d.ts +60 -0
- package/dist/pds-proxy.d.ts.map +1 -0
- package/dist/pds-proxy.js +277 -0
- package/dist/push.d.ts +34 -0
- package/dist/push.d.ts.map +1 -0
- package/dist/push.js +184 -0
- package/dist/renderer.d.ts +27 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +46 -0
- package/dist/resolve-hatk.d.ts +6 -0
- package/dist/resolve-hatk.d.ts.map +1 -0
- package/dist/resolve-hatk.js +20 -0
- package/dist/response.d.ts +16 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +69 -0
- package/dist/scanner.d.ts +21 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +88 -0
- package/dist/seed.d.ts +19 -0
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +43 -4
- package/dist/server-init.d.ts +8 -0
- package/dist/server-init.d.ts.map +1 -0
- package/dist/server-init.js +62 -0
- package/dist/server.d.ts +26 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +629 -635
- package/dist/setup.d.ts +28 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +50 -3
- package/dist/templates/feed.tpl +14 -0
- package/dist/templates/hook.tpl +5 -0
- package/dist/templates/label.tpl +15 -0
- package/dist/templates/og.tpl +17 -0
- package/dist/templates/seed.tpl +11 -0
- package/dist/templates/setup.tpl +5 -0
- package/dist/templates/test-feed.tpl +19 -0
- package/dist/templates/test-xrpc.tpl +19 -0
- package/dist/templates/xrpc.tpl +41 -0
- package/dist/test.d.ts +1 -1
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +39 -32
- package/dist/views.js +1 -1
- package/dist/vite-plugin.d.ts +1 -1
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js +254 -66
- package/dist/xrpc.d.ts +75 -11
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +189 -39
- package/package.json +14 -7
- package/public/admin.html +133 -54
- package/dist/db.d.ts.map +0 -1
- package/dist/fts.d.ts.map +0 -1
- package/dist/oauth/hooks.d.ts +0 -10
- package/dist/oauth/hooks.d.ts.map +0 -1
- package/dist/oauth/hooks.js +0 -40
- package/dist/schema.d.ts.map +0 -1
- package/dist/test-browser.d.ts +0 -14
- package/dist/test-browser.d.ts.map +0 -1
- 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
|
-
|
|
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 {
|
|
15
|
+
return { sqlType: dialect.typeMap.timestamp, isRef: false, isJson: false };
|
|
11
16
|
if (prop.format === 'at-uri')
|
|
12
|
-
return {
|
|
13
|
-
return {
|
|
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 {
|
|
21
|
+
return { sqlType: dialect.typeMap.integer, isRef: false, isJson: false };
|
|
17
22
|
if (prop.type === 'boolean')
|
|
18
|
-
return {
|
|
23
|
+
return { sqlType: dialect.typeMap.boolean, isRef: false, isJson: false };
|
|
19
24
|
if (prop.type === 'bytes')
|
|
20
|
-
return {
|
|
25
|
+
return { sqlType: dialect.typeMap.blob, isRef: false, isJson: false };
|
|
21
26
|
if (prop.type === 'cid-link')
|
|
22
|
-
return {
|
|
27
|
+
return { sqlType: dialect.typeMap.text, isRef: false, isJson: false };
|
|
23
28
|
if (prop.type === 'array')
|
|
24
|
-
return {
|
|
29
|
+
return { sqlType: dialect.jsonType, isRef: false, isJson: true };
|
|
25
30
|
if (prop.type === 'blob')
|
|
26
|
-
return {
|
|
31
|
+
return { sqlType: dialect.jsonType, isRef: false, isJson: true };
|
|
27
32
|
if (prop.type === 'union')
|
|
28
|
-
return {
|
|
33
|
+
return { sqlType: dialect.jsonType, isRef: false, isJson: true };
|
|
29
34
|
if (prop.type === 'unknown')
|
|
30
|
-
return {
|
|
35
|
+
return { sqlType: dialect.jsonType, isRef: false, isJson: true };
|
|
31
36
|
if (prop.type === 'object')
|
|
32
|
-
return {
|
|
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 {
|
|
37
|
-
return {
|
|
41
|
+
return { sqlType: 'STRONG_REF', isRef: true, isJson: false };
|
|
42
|
+
return { sqlType: dialect.jsonType, isRef: false, isJson: true };
|
|
38
43
|
}
|
|
39
|
-
return {
|
|
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 {
|
|
189
|
+
const { sqlType, isRef, isJson } = mapType(prop, dialect);
|
|
185
190
|
// Skip STRONG_REF expansion in branch tables — treat as JSON
|
|
186
|
-
const finalType =
|
|
191
|
+
const finalType = sqlType === 'STRONG_REF' ? dialect.jsonType : sqlType;
|
|
187
192
|
columns.push({
|
|
188
193
|
name: toSnakeCase(propName),
|
|
189
194
|
originalName: propName,
|
|
190
|
-
|
|
195
|
+
sqlType: finalType,
|
|
191
196
|
notNull: branchRequired.has(propName),
|
|
192
|
-
isRef: finalType !==
|
|
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
|
-
|
|
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 {
|
|
248
|
+
const { sqlType, isRef, isJson } = mapType(itemProp, dialect);
|
|
242
249
|
childColumns.push({
|
|
243
250
|
name: toSnakeCase(itemField),
|
|
244
251
|
originalName: itemField,
|
|
245
|
-
|
|
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 {
|
|
261
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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":"AA6GA,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,112 @@
|
|
|
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
|
+
collections: collectionSet,
|
|
77
|
+
signalCollections: config.backfill.signalCollections ? new Set(config.backfill.signalCollections) : undefined,
|
|
78
|
+
pinnedRepos: config.backfill.repos ? new Set(config.backfill.repos) : undefined,
|
|
79
|
+
cursor,
|
|
80
|
+
fetchTimeout: config.backfill.fetchTimeout,
|
|
81
|
+
maxRetries: config.backfill.maxRetries,
|
|
82
|
+
parallelism: config.backfill.parallelism,
|
|
83
|
+
ftsRebuildInterval: config.ftsRebuildInterval,
|
|
84
|
+
});
|
|
85
|
+
// Run backfill in background (no restart in dev mode)
|
|
86
|
+
runBackfill({
|
|
87
|
+
pdsUrl: relayHttpUrl(config.relay),
|
|
88
|
+
plcUrl: config.plc,
|
|
89
|
+
collections: collectionSet,
|
|
90
|
+
config: config.backfill,
|
|
91
|
+
})
|
|
92
|
+
.then(() => rebuildAllIndexes(Array.from(collectionSet)))
|
|
93
|
+
.catch((err) => console.error('[backfill]', err.message));
|
|
94
|
+
// Export the handler for Vite middleware
|
|
95
|
+
export const handler = createHandler({
|
|
96
|
+
collections: Array.from(collectionSet),
|
|
97
|
+
publicDir: null, // Vite serves static assets in dev
|
|
98
|
+
oauth: config.oauth,
|
|
99
|
+
admins: config.admins,
|
|
100
|
+
});
|
|
101
|
+
/** Re-scan server/ directory to pick up changed handlers in dev mode. */
|
|
102
|
+
export async function reloadServer() {
|
|
103
|
+
await initServer(resolve(configDir, 'server'));
|
|
104
|
+
}
|
|
105
|
+
export { renderPage } from "./renderer.js";
|
|
106
|
+
export { getRenderer } from "./renderer.js";
|
|
107
|
+
export { callXrpc } from "./xrpc.js";
|
|
108
|
+
export { parseSessionCookie, getSessionCookieName } from "./oauth/session.js";
|
|
109
|
+
log(`[hatk] Dev server ready`);
|
|
110
|
+
log(` Relay: ${config.relay}`);
|
|
111
|
+
log(` Database: ${config.database}`);
|
|
112
|
+
log(` Collections: ${collections.join(', ')}`);
|
package/dist/feeds.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BaseContext, Row } from './hydrate.ts';
|
|
2
2
|
import type { Checked } from './lex-types.ts';
|
|
3
|
-
export type {
|
|
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?:
|
|
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:
|
|
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:
|
|
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?:
|
|
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:
|
|
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:
|
|
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?: {
|
package/dist/feeds.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../src/feeds.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,
|
|
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,
|
|
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 =
|
|
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 };
|
package/dist/hooks.d.ts
ADDED
|
@@ -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
|