@hatk/hatk 0.0.1-alpha.30 → 0.0.1-alpha.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +54 -18
- package/dist/database/adapters/sqlite-search.d.ts +8 -3
- package/dist/database/adapters/sqlite-search.d.ts.map +1 -1
- package/dist/database/adapters/sqlite-search.js +43 -10
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +5 -1
- package/dist/database/fts.d.ts +3 -0
- package/dist/database/fts.d.ts.map +1 -1
- package/dist/database/fts.js +79 -10
- package/dist/database/ports.d.ts +6 -0
- package/dist/database/ports.d.ts.map +1 -1
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +6 -2
- package/dist/main.js +12 -5
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -95,7 +95,6 @@ function usage() {
|
|
|
95
95
|
generate xrpc <nsid> Generate an XRPC handler
|
|
96
96
|
generate label <name> Generate a label definition
|
|
97
97
|
generate og <name> Generate an OpenGraph route
|
|
98
|
-
generate job <name> Generate a periodic job
|
|
99
98
|
generate hook <name> Generate a lifecycle hook
|
|
100
99
|
generate setup <name> Generate a setup script
|
|
101
100
|
generate types Regenerate TypeScript types from lexicons
|
|
@@ -195,13 +194,6 @@ export default {
|
|
|
195
194
|
}
|
|
196
195
|
},
|
|
197
196
|
}
|
|
198
|
-
`,
|
|
199
|
-
job: (_name) => `export default {
|
|
200
|
-
interval: 300, // seconds
|
|
201
|
-
async run(_ctx: any) {
|
|
202
|
-
// Periodic task logic here
|
|
203
|
-
},
|
|
204
|
-
}
|
|
205
197
|
`,
|
|
206
198
|
hook: (name) => `import { defineHook } from '../hatk.generated.ts'
|
|
207
199
|
|
|
@@ -328,7 +320,6 @@ const dirs = {
|
|
|
328
320
|
xrpc: 'server',
|
|
329
321
|
label: 'server',
|
|
330
322
|
og: 'server',
|
|
331
|
-
job: 'server',
|
|
332
323
|
hook: 'server',
|
|
333
324
|
setup: 'server',
|
|
334
325
|
};
|
|
@@ -1131,7 +1122,7 @@ a {
|
|
|
1131
1122
|
</div>
|
|
1132
1123
|
`);
|
|
1133
1124
|
}
|
|
1134
|
-
|
|
1125
|
+
let agentsMd = `# hatk project
|
|
1135
1126
|
|
|
1136
1127
|
This is an AT Protocol application built with [hatk](https://github.com/hatk-dev/hatk).
|
|
1137
1128
|
Read the project's lexicons in \`lexicons/\` to understand the data model.
|
|
@@ -1142,30 +1133,75 @@ Types are generated from lexicons into \`hatk.generated.ts\` — never edit this
|
|
|
1142
1133
|
| Directory | Purpose |
|
|
1143
1134
|
|-------------|------------------------------------------------------|
|
|
1144
1135
|
| \`lexicons/\` | AT Protocol lexicon schemas (JSON). Defines collections and XRPC methods |
|
|
1145
|
-
| \`server/\` | All server-side code: feeds, XRPC handlers, hooks, labels, OG routes,
|
|
1136
|
+
| \`server/\` | All server-side code: feeds, XRPC handlers, hooks, labels, OG routes, setup scripts |
|
|
1146
1137
|
| \`seeds/\` | Test data seeding scripts for local development |
|
|
1147
|
-
| \`test/\` | Test files (vitest). Run with \`
|
|
1138
|
+
| \`test/\` | Test files (vitest). Run with \`vp test\` |
|
|
1148
1139
|
| \`public/\` | Static files served at the root |
|
|
1140
|
+
`;
|
|
1141
|
+
if (withSvelte) {
|
|
1142
|
+
agentsMd += `| \`src/\` | SvelteKit frontend (routes, components, styles) |
|
|
1149
1143
|
|
|
1150
|
-
|
|
1144
|
+
`;
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
agentsMd += `
|
|
1148
|
+
`;
|
|
1149
|
+
}
|
|
1150
|
+
agentsMd += `## Key files
|
|
1151
1151
|
|
|
1152
1152
|
- \`hatk.config.ts\` — project configuration (see \`defineConfig\` for type info)
|
|
1153
|
-
- \`hatk.generated.ts\` — auto-generated types and
|
|
1153
|
+
- \`hatk.generated.ts\` — auto-generated server types and helpers. Regenerate with \`hatk generate types\`
|
|
1154
|
+
- \`hatk.generated.client.ts\` — auto-generated client-safe types and \`callXrpc\`. Never import \`hatk.generated.ts\` from frontend code
|
|
1154
1155
|
|
|
1156
|
+
## The \`$hatk\` alias
|
|
1157
|
+
|
|
1158
|
+
Server files in \`server/\` import from \`$hatk\`:
|
|
1159
|
+
\`\`\`ts
|
|
1160
|
+
import { defineFeed, views, type Status } from "$hatk"
|
|
1161
|
+
\`\`\`
|
|
1162
|
+
`;
|
|
1163
|
+
if (withSvelte) {
|
|
1164
|
+
agentsMd += `
|
|
1165
|
+
SvelteKit routes and components import from \`$hatk/client\`:
|
|
1166
|
+
\`\`\`ts
|
|
1167
|
+
import { callXrpc, getViewer } from "$hatk/client"
|
|
1168
|
+
\`\`\`
|
|
1169
|
+
|
|
1170
|
+
\`$hatk\` resolves to \`hatk.generated.ts\` and \`$hatk/client\` to \`hatk.generated.client.ts\`.
|
|
1171
|
+
The Vite plugin handles this in dev/build. In tests and production, a Node.js module resolve hook handles it.
|
|
1172
|
+
`;
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
agentsMd += `
|
|
1176
|
+
\`$hatk\` resolves to \`hatk.generated.ts\`. The Vite plugin handles this in dev/build.
|
|
1177
|
+
In tests and production, a Node.js module resolve hook handles it.
|
|
1178
|
+
`;
|
|
1179
|
+
}
|
|
1180
|
+
agentsMd += `
|
|
1155
1181
|
## Commands
|
|
1156
1182
|
|
|
1157
1183
|
Run \`npx hatk --help\` for the full list of commands.
|
|
1158
1184
|
|
|
1159
1185
|
Use \`npx hatk generate\` to scaffold new feeds, xrpc handlers, labels, and lexicons
|
|
1160
|
-
rather than creating files manually. These generate files with the correct imports
|
|
1161
|
-
from \`hatk.generated.ts\`.
|
|
1186
|
+
rather than creating files manually. These generate files with the correct imports.
|
|
1162
1187
|
|
|
1163
1188
|
After modifying lexicons, always run \`npx hatk generate types\` to update the generated types.
|
|
1164
|
-
|
|
1189
|
+
`;
|
|
1190
|
+
if (withSvelte) {
|
|
1191
|
+
agentsMd += `
|
|
1192
|
+
## Running
|
|
1193
|
+
|
|
1194
|
+
- \`vp dev\` — start dev server (hatk + SvelteKit + PDS)
|
|
1195
|
+
- \`vp build\` — build for production (SvelteKit outputs to \`build/\`)
|
|
1196
|
+
- \`hatk start\` — start production server (hatk + SvelteKit via \`build/handler.js\`)
|
|
1197
|
+
- \`vp test\` — run tests
|
|
1198
|
+
`;
|
|
1199
|
+
}
|
|
1200
|
+
writeFileSync(join(dir, 'AGENTS.md'), agentsMd);
|
|
1165
1201
|
console.log(`Created ${name}/`);
|
|
1166
1202
|
console.log(` hatk.config.ts`);
|
|
1167
1203
|
console.log(` lexicons/ — lexicon JSON files (core + your own)`);
|
|
1168
|
-
console.log(` server/ — feeds, XRPC handlers, hooks, labels, OG routes,
|
|
1204
|
+
console.log(` server/ — feeds, XRPC handlers, hooks, labels, OG routes, setup`);
|
|
1169
1205
|
console.log(` seeds/ — seed fixture data (hatk seed)`);
|
|
1170
1206
|
console.log(` test/ — test files (hatk test)`);
|
|
1171
1207
|
console.log(` public/ — static files`);
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import type { SearchPort } from '../ports.ts';
|
|
2
2
|
import type { DatabasePort } from '../ports.ts';
|
|
3
3
|
/**
|
|
4
|
-
* SQLite FTS5-based search port.
|
|
4
|
+
* SQLite FTS5-based search port with incremental updates.
|
|
5
5
|
*
|
|
6
|
-
* Uses
|
|
7
|
-
*
|
|
6
|
+
* Uses external content FTS5 tables (content=shadowTable) so the FTS index
|
|
7
|
+
* references the shadow data table. Updates happen incrementally per-record
|
|
8
|
+
* instead of dropping and rebuilding the entire index.
|
|
8
9
|
*/
|
|
9
10
|
export declare class SQLiteSearchPort implements SearchPort {
|
|
10
11
|
private port;
|
|
11
12
|
constructor(port: DatabasePort);
|
|
13
|
+
indexExists(shadowTable: string): Promise<boolean>;
|
|
12
14
|
buildIndex(shadowTable: string, sourceQuery: string, searchColumns: string[]): Promise<void>;
|
|
15
|
+
updateIndex(shadowTable: string, uri: string, row: Record<string, string | null>, searchColumns: string[]): Promise<void>;
|
|
16
|
+
deleteFromIndex(shadowTable: string, uri: string, searchColumns: string[]): Promise<void>;
|
|
17
|
+
private _deleteFromFts;
|
|
13
18
|
search(shadowTable: string, query: string, _searchColumns: string[], limit: number, offset: number): Promise<Array<{
|
|
14
19
|
uri: string;
|
|
15
20
|
score: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-search.d.ts","sourceRoot":"","sources":["../../../src/database/adapters/sqlite-search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C
|
|
1
|
+
{"version":3,"file":"sqlite-search.d.ts","sourceRoot":"","sources":["../../../src/database/adapters/sqlite-search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C;;;;;;GAMG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IACrC,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,YAAY;IAEhC,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQlD,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA0B5F,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EAClC,aAAa,EAAE,MAAM,EAAE,GACtB,OAAO,CAAC,IAAI,CAAC;IA2BV,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAKjF,cAAc;IAoBtB,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EAAE,EACxB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAWlD"}
|
|
@@ -1,33 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SQLite FTS5-based search port.
|
|
2
|
+
* SQLite FTS5-based search port with incremental updates.
|
|
3
3
|
*
|
|
4
|
-
* Uses
|
|
5
|
-
*
|
|
4
|
+
* Uses external content FTS5 tables (content=shadowTable) so the FTS index
|
|
5
|
+
* references the shadow data table. Updates happen incrementally per-record
|
|
6
|
+
* instead of dropping and rebuilding the entire index.
|
|
6
7
|
*/
|
|
7
8
|
export class SQLiteSearchPort {
|
|
8
9
|
port;
|
|
9
10
|
constructor(port) {
|
|
10
11
|
this.port = port;
|
|
11
12
|
}
|
|
13
|
+
async indexExists(shadowTable) {
|
|
14
|
+
const rows = await this.port.query(`SELECT 1 FROM sqlite_master WHERE type='table' AND name IN ($1, $2)`, [shadowTable, `${shadowTable}_fts`]);
|
|
15
|
+
return rows.length >= 2;
|
|
16
|
+
}
|
|
12
17
|
async buildIndex(shadowTable, sourceQuery, searchColumns) {
|
|
13
|
-
// Drop existing FTS table and data table
|
|
14
18
|
await this.port.execute(`DROP TABLE IF EXISTS ${shadowTable}_fts`, []);
|
|
15
19
|
await this.port.execute(`DROP TABLE IF EXISTS ${shadowTable}`, []);
|
|
16
|
-
// Create
|
|
20
|
+
// Create shadow data table from source query
|
|
17
21
|
await this.port.execute(`CREATE TABLE ${shadowTable} AS ${sourceQuery}`, []);
|
|
18
|
-
|
|
22
|
+
await this.port.execute(`CREATE UNIQUE INDEX IF NOT EXISTS ${shadowTable}_uri ON ${shadowTable}(uri)`, []);
|
|
23
|
+
// Create FTS5 virtual table with external content pointing to shadow table
|
|
19
24
|
const colList = searchColumns.join(', ');
|
|
20
|
-
await this.port.execute(`CREATE VIRTUAL TABLE ${shadowTable}_fts USING fts5(uri UNINDEXED, ${colList}, tokenize='porter unicode61 remove_diacritics 2')`, []);
|
|
21
|
-
// Populate FTS
|
|
25
|
+
await this.port.execute(`CREATE VIRTUAL TABLE ${shadowTable}_fts USING fts5(uri UNINDEXED, ${colList}, content=${shadowTable}, content_rowid=rowid, tokenize='porter unicode61 remove_diacritics 2')`, []);
|
|
26
|
+
// Populate FTS from shadow table
|
|
22
27
|
const selectCols = ['uri', ...searchColumns].map((c) => `COALESCE(CAST(${c} AS TEXT), '')`);
|
|
23
28
|
await this.port.execute(`INSERT INTO ${shadowTable}_fts (uri, ${colList}) SELECT ${selectCols.join(', ')} FROM ${shadowTable}`, []);
|
|
24
29
|
}
|
|
30
|
+
async updateIndex(shadowTable, uri, row, searchColumns) {
|
|
31
|
+
const colList = searchColumns.join(', ');
|
|
32
|
+
// Remove old FTS entry if record already indexed
|
|
33
|
+
await this._deleteFromFts(shadowTable, uri, searchColumns);
|
|
34
|
+
// Upsert shadow table
|
|
35
|
+
const placeholders = searchColumns.map((_, i) => `$${i + 2}`);
|
|
36
|
+
const setClauses = searchColumns.map((c, i) => `${c} = $${i + 2}`);
|
|
37
|
+
const values = [uri, ...searchColumns.map((c) => row[c] ?? null)];
|
|
38
|
+
await this.port.execute(`INSERT INTO ${shadowTable} (uri, ${colList}) VALUES ($1, ${placeholders.join(', ')}) ON CONFLICT(uri) DO UPDATE SET ${setClauses.join(', ')}`, values);
|
|
39
|
+
// Read back rowid and insert new FTS entry
|
|
40
|
+
const rows = await this.port.query(`SELECT rowid FROM ${shadowTable} WHERE uri = $1`, [uri]);
|
|
41
|
+
if (rows.length > 0) {
|
|
42
|
+
const rowid = rows[0].rowid;
|
|
43
|
+
const ftsPlaceholders = searchColumns.map((_, i) => `$${i + 3}`);
|
|
44
|
+
await this.port.execute(`INSERT INTO ${shadowTable}_fts(rowid, uri, ${colList}) VALUES($1, $2, ${ftsPlaceholders.join(', ')})`, [rowid, uri, ...searchColumns.map((c) => row[c] ?? '')]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async deleteFromIndex(shadowTable, uri, searchColumns) {
|
|
48
|
+
await this._deleteFromFts(shadowTable, uri, searchColumns);
|
|
49
|
+
await this.port.execute(`DELETE FROM ${shadowTable} WHERE uri = $1`, [uri]);
|
|
50
|
+
}
|
|
51
|
+
async _deleteFromFts(shadowTable, uri, searchColumns) {
|
|
52
|
+
const colList = searchColumns.join(', ');
|
|
53
|
+
const rows = await this.port.query(`SELECT rowid, uri, ${colList} FROM ${shadowTable} WHERE uri = $1`, [uri]);
|
|
54
|
+
if (rows.length === 0)
|
|
55
|
+
return;
|
|
56
|
+
const old = rows[0];
|
|
57
|
+
const placeholders = searchColumns.map((_, i) => `$${i + 3}`);
|
|
58
|
+
await this.port.execute(`INSERT INTO ${shadowTable}_fts(${shadowTable}_fts, rowid, uri, ${colList}) VALUES('delete', $1, $2, ${placeholders.join(', ')})`, [old.rowid, uri, ...searchColumns.map((c) => old[c] ?? '')]);
|
|
59
|
+
}
|
|
25
60
|
async search(shadowTable, query, _searchColumns, limit, offset) {
|
|
26
|
-
// Escape FTS5 special characters and build query
|
|
27
61
|
const escaped = query.replace(/['"*(){}[\]^~\\:]/g, ' ').trim();
|
|
28
62
|
if (!escaped)
|
|
29
63
|
return [];
|
|
30
|
-
// Use FTS5 MATCH with bm25() ranking (lower = better match, negate for DESC)
|
|
31
64
|
const sql = `SELECT uri, -bm25(${shadowTable}_fts) AS score
|
|
32
65
|
FROM ${shadowTable}_fts
|
|
33
66
|
WHERE ${shadowTable}_fts MATCH $1
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/database/db.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/database/db.ts"],"names":[],"mappings":"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"}
|
package/dist/database/db.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toSnakeCase } from "./schema.js";
|
|
2
|
-
import { getSearchColumns, stripStopWords, getSearchPort } from "./fts.js";
|
|
2
|
+
import { getSearchColumns, stripStopWords, getSearchPort, updateFtsRecord, deleteFtsRecord } from "./fts.js";
|
|
3
3
|
import { emit, timer } from "../logger.js";
|
|
4
4
|
import { OAUTH_DDL } from "../oauth/db.js";
|
|
5
5
|
import { getDialect } from "./dialect.js";
|
|
@@ -538,6 +538,8 @@ export async function insertRecord(collection, uri, cid, authorDid, record) {
|
|
|
538
538
|
await run(`INSERT INTO ${branch.tableName} (${colNames.join(', ')}) VALUES (${placeholders.join(', ')})`, values);
|
|
539
539
|
}
|
|
540
540
|
}
|
|
541
|
+
// Incrementally update FTS index for this record
|
|
542
|
+
await updateFtsRecord(collection, uri);
|
|
541
543
|
}
|
|
542
544
|
/** Extract branch data from a union value, handling wrapper properties */
|
|
543
545
|
function resolveBranchData(unionValue, branch) {
|
|
@@ -552,6 +554,8 @@ export async function deleteRecord(collection, uri) {
|
|
|
552
554
|
const schema = schemas.get(collection);
|
|
553
555
|
if (!schema)
|
|
554
556
|
return;
|
|
557
|
+
// Remove from FTS index before deleting the record data
|
|
558
|
+
await deleteFtsRecord(collection, uri);
|
|
555
559
|
for (const child of schema.children) {
|
|
556
560
|
await run(`DELETE FROM ${child.tableName} WHERE parent_uri = $1`, [uri]);
|
|
557
561
|
}
|
package/dist/database/fts.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export declare function ftsTableName(collection: string): string;
|
|
|
15
15
|
* using Porter stemmer with English stopwords.
|
|
16
16
|
*/
|
|
17
17
|
export declare function buildFtsIndex(collection: string): Promise<void>;
|
|
18
|
+
export declare function buildFtsRow(collection: string, uri: string): Promise<Record<string, string | null> | null>;
|
|
19
|
+
export declare function updateFtsRecord(collection: string, uri: string): Promise<void>;
|
|
20
|
+
export declare function deleteFtsRecord(collection: string, uri: string): Promise<void>;
|
|
18
21
|
/**
|
|
19
22
|
* Strip English stop words from a search query, preserving non-stop-word terms.
|
|
20
23
|
* Returns the cleaned query string. If all words are stop words, returns the original query.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fts.d.ts","sourceRoot":"","sources":["../../src/database/fts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAuE5C,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAEjD;
|
|
1
|
+
{"version":3,"file":"fts.d.ts","sourceRoot":"","sources":["../../src/database/fts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAuE5C,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAEjD;AAWD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAE7D;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAElE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvD;AAgFD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBrE;AAED,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAe/C;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAepF;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYpF;AAokBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIpD;AAED,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B5E"}
|
package/dist/database/fts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getSchema, runSQL, getSqlDialect } from "./db.js";
|
|
1
|
+
import { getSchema, runSQL, getSqlDialect, querySQL } from "./db.js";
|
|
2
2
|
import { getLexicon } from "./schema.js";
|
|
3
3
|
import { emit, timer } from "../logger.js";
|
|
4
4
|
/**
|
|
@@ -70,6 +70,8 @@ export function getSearchPort() {
|
|
|
70
70
|
const lastRebuiltAt = new Map();
|
|
71
71
|
// Cache of search column metadata per collection, populated during buildFtsIndex
|
|
72
72
|
const searchColumnCache = new Map();
|
|
73
|
+
// Cache of computed FTS schemas per collection (deterministic, so compute once)
|
|
74
|
+
const ftsSchemaCache = new Map();
|
|
73
75
|
export function getSearchColumns(collection) {
|
|
74
76
|
return searchColumnCache.get(collection) || [];
|
|
75
77
|
}
|
|
@@ -84,13 +86,12 @@ export function ftsTableName(collection) {
|
|
|
84
86
|
return '_fts_' + collection.replace(/\./g, '_');
|
|
85
87
|
}
|
|
86
88
|
/**
|
|
87
|
-
*
|
|
88
|
-
* Creates a shadow table copy and indexes all TEXT NOT NULL columns
|
|
89
|
-
* using Porter stemmer with English stopwords.
|
|
89
|
+
* Compute the FTS schema for a collection: search column names, source query, and safe table name.
|
|
90
90
|
*/
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
function computeFtsSchema(collection) {
|
|
92
|
+
const cached = ftsSchemaCache.get(collection);
|
|
93
|
+
if (cached)
|
|
94
|
+
return cached;
|
|
94
95
|
const schema = getSchema(collection);
|
|
95
96
|
if (!schema)
|
|
96
97
|
throw new Error(`Unknown collection: ${collection}`);
|
|
@@ -151,15 +152,83 @@ export async function buildFtsIndex(collection) {
|
|
|
151
152
|
// Include handle from _repos for people search
|
|
152
153
|
selectExprs.push('r.handle');
|
|
153
154
|
searchColNames.push('handle');
|
|
154
|
-
if (searchColNames.length === 0) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
155
|
const safeName = ftsTableName(collection);
|
|
158
156
|
const sourceQuery = `SELECT ${selectExprs.join(', ')} FROM ${schema.tableName} t LEFT JOIN _repos r ON t.did = r.did`;
|
|
157
|
+
const result = { searchColNames, sourceQuery, safeName };
|
|
158
|
+
ftsSchemaCache.set(collection, result);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Build FTS index for a collection.
|
|
163
|
+
* Creates a shadow table copy and indexes all TEXT NOT NULL columns
|
|
164
|
+
* using Porter stemmer with English stopwords.
|
|
165
|
+
*/
|
|
166
|
+
export async function buildFtsIndex(collection) {
|
|
167
|
+
if (!searchPort)
|
|
168
|
+
return; // No FTS support for this adapter
|
|
169
|
+
const { searchColNames, sourceQuery, safeName } = computeFtsSchema(collection);
|
|
170
|
+
if (searchColNames.length === 0)
|
|
171
|
+
return;
|
|
172
|
+
// For incremental ports: skip rebuild if index already exists
|
|
173
|
+
if (searchPort.indexExists) {
|
|
174
|
+
const exists = await searchPort.indexExists(safeName);
|
|
175
|
+
if (exists) {
|
|
176
|
+
searchColumnCache.set(collection, searchColNames);
|
|
177
|
+
lastRebuiltAt.set(collection, new Date().toISOString());
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
159
181
|
await searchPort.buildIndex(safeName, sourceQuery, searchColNames);
|
|
160
182
|
searchColumnCache.set(collection, searchColNames);
|
|
161
183
|
lastRebuiltAt.set(collection, new Date().toISOString());
|
|
162
184
|
}
|
|
185
|
+
export async function buildFtsRow(collection, uri) {
|
|
186
|
+
const { searchColNames, sourceQuery } = computeFtsSchema(collection);
|
|
187
|
+
if (searchColNames.length === 0)
|
|
188
|
+
return null;
|
|
189
|
+
// Append WHERE clause to filter for single record
|
|
190
|
+
const sql = sourceQuery + ' WHERE t.uri = $1';
|
|
191
|
+
const rows = await querySQL(sql, [uri]);
|
|
192
|
+
if (!rows || rows.length === 0)
|
|
193
|
+
return null;
|
|
194
|
+
const row = rows[0];
|
|
195
|
+
const result = {};
|
|
196
|
+
for (const col of searchColNames) {
|
|
197
|
+
result[col] = row[col] != null ? String(row[col]) : null;
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
export async function updateFtsRecord(collection, uri) {
|
|
202
|
+
if (!searchPort || !searchPort.updateIndex)
|
|
203
|
+
return;
|
|
204
|
+
const searchCols = searchColumnCache.get(collection);
|
|
205
|
+
if (!searchCols || searchCols.length === 0)
|
|
206
|
+
return;
|
|
207
|
+
try {
|
|
208
|
+
const row = await buildFtsRow(collection, uri);
|
|
209
|
+
if (!row)
|
|
210
|
+
return;
|
|
211
|
+
const safeName = ftsTableName(collection);
|
|
212
|
+
await searchPort.updateIndex(safeName, uri, row, searchCols);
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
emit('fts', 'update_error', { collection, uri, error: err.message });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
export async function deleteFtsRecord(collection, uri) {
|
|
219
|
+
if (!searchPort || !searchPort.deleteFromIndex)
|
|
220
|
+
return;
|
|
221
|
+
const searchCols = searchColumnCache.get(collection);
|
|
222
|
+
if (!searchCols || searchCols.length === 0)
|
|
223
|
+
return;
|
|
224
|
+
try {
|
|
225
|
+
const safeName = ftsTableName(collection);
|
|
226
|
+
await searchPort.deleteFromIndex(safeName, uri, searchCols);
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
emit('fts', 'delete_error', { collection, uri, error: err.message });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
163
232
|
/**
|
|
164
233
|
* Rebuild FTS indexes for all registered collections.
|
|
165
234
|
*/
|
package/dist/database/ports.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export interface BulkInserter {
|
|
|
35
35
|
export interface SearchPort {
|
|
36
36
|
/** Build/rebuild an FTS index for a table */
|
|
37
37
|
buildIndex(shadowTable: string, sourceQuery: string, searchColumns: string[]): Promise<void>;
|
|
38
|
+
/** Incrementally update a single record in the FTS index */
|
|
39
|
+
updateIndex?(shadowTable: string, uri: string, row: Record<string, string | null>, searchColumns: string[]): Promise<void>;
|
|
40
|
+
/** Remove a single record from the FTS index */
|
|
41
|
+
deleteFromIndex?(shadowTable: string, uri: string, searchColumns: string[]): Promise<void>;
|
|
42
|
+
/** Check if the FTS index already exists (for skipping rebuild on startup) */
|
|
43
|
+
indexExists?(shadowTable: string): Promise<boolean>;
|
|
38
44
|
/** Search a table, returning URIs with scores */
|
|
39
45
|
search(shadowTable: string, query: string, searchColumns: string[], limit: number, offset: number): Promise<Array<{
|
|
40
46
|
uri: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../../src/database/ports.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAA;AAEtD,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,OAAO,EAAE,OAAO,CAAA;IAEhB,kEAAkE;IAClE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjC,kDAAkD;IAClD,KAAK,IAAI,IAAI,CAAA;IAEb,yDAAyD;IACzD,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAA;IAEjF,8DAA8D;IAC9D,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEvD,gEAAgE;IAChE,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3C,0BAA0B;IAC1B,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjC,qCAAqC;IACrC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAEvB,uCAAuC;IACvC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAEzB,wDAAwD;IACxD,kBAAkB,CAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAClE,OAAO,CAAC,YAAY,CAAC,CAAA;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAE/B,0CAA0C;IAC1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAEtB,+CAA+C;IAC/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE5F,iDAAiD;IACjD,MAAM,CACJ,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EAAE,EACvB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;CAClD"}
|
|
1
|
+
{"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../../src/database/ports.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAA;AAEtD,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,OAAO,EAAE,OAAO,CAAA;IAEhB,kEAAkE;IAClE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjC,kDAAkD;IAClD,KAAK,IAAI,IAAI,CAAA;IAEb,yDAAyD;IACzD,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAA;IAEjF,8DAA8D;IAC9D,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEvD,gEAAgE;IAChE,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3C,0BAA0B;IAC1B,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjC,qCAAqC;IACrC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAEvB,uCAAuC;IACvC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAEzB,wDAAwD;IACxD,kBAAkB,CAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAClE,OAAO,CAAC,YAAY,CAAC,CAAA;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAE/B,0CAA0C;IAC1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAEtB,+CAA+C;IAC/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE5F,4DAA4D;IAC5D,WAAW,CAAC,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE1H,gDAAgD;IAChD,eAAe,CAAC,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE1F,8EAA8E;IAC9E,WAAW,CAAC,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEnD,iDAAiD;IACjD,MAAM,CACJ,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EAAE,EACvB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;CAClD"}
|
package/dist/indexer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../src/indexer.ts"],"names":[],"mappings":"AAuJA;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAgEjF;AAED,8CAA8C;AAC9C,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAyBD;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAmDxE"}
|
package/dist/indexer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cborDecode } from "./cbor.js";
|
|
2
2
|
import { parseCarFrame } from "./car.js";
|
|
3
|
-
import { insertRecord, deleteRecord, setCursor, setRepoStatus, getRepoRetryInfo, listAllRepoStatuses, } from "./database/db.js";
|
|
3
|
+
import { insertRecord, deleteRecord, setCursor, setRepoStatus, getRepoRetryInfo, listAllRepoStatuses, getDatabasePort, } from "./database/db.js";
|
|
4
4
|
import { backfillRepo } from "./backfill.js";
|
|
5
5
|
import { rebuildAllIndexes } from "./database/fts.js";
|
|
6
6
|
import { log, emit, timer } from "./logger.js";
|
|
@@ -92,7 +92,11 @@ async function flushBuffer() {
|
|
|
92
92
|
writesSinceRebuild += batch.length;
|
|
93
93
|
if (writesSinceRebuild >= ftsRebuildInterval) {
|
|
94
94
|
writesSinceRebuild = 0;
|
|
95
|
-
|
|
95
|
+
// Skip periodic full rebuild for SQLite — it uses incremental FTS updates
|
|
96
|
+
const port = getDatabasePort();
|
|
97
|
+
if (port.dialect !== 'sqlite') {
|
|
98
|
+
rebuildAllIndexes([...indexerCollections]).catch(() => { });
|
|
99
|
+
}
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
/** Schedule a flush after FLUSH_INTERVAL_MS if one isn't already pending. */
|
package/dist/main.js
CHANGED
|
@@ -14,7 +14,7 @@ import { log } from "./logger.js";
|
|
|
14
14
|
import { loadConfig } from "./config.js";
|
|
15
15
|
import { loadLexicons, storeLexicons, discoverCollections, buildSchemas } from "./database/schema.js";
|
|
16
16
|
import { discoverViews } from "./views.js";
|
|
17
|
-
import { initDatabase, getCursor, querySQL, getSqlDialect, getSchemaDump, migrateSchema } from "./database/db.js";
|
|
17
|
+
import { initDatabase, getCursor, querySQL, getSqlDialect, getSchemaDump, migrateSchema, getDatabasePort } from "./database/db.js";
|
|
18
18
|
import { createAdapter } from "./database/adapter-factory.js";
|
|
19
19
|
import { getDialect } from "./database/dialect.js";
|
|
20
20
|
import { setSearchPort } from "./database/fts.js";
|
|
@@ -146,12 +146,19 @@ const backfillOpts = {
|
|
|
146
146
|
};
|
|
147
147
|
function runBackfillAndRestart() {
|
|
148
148
|
runBackfill(backfillOpts)
|
|
149
|
-
.then((recordCount) => {
|
|
150
|
-
|
|
151
|
-
|
|
149
|
+
.then(async (recordCount) => {
|
|
150
|
+
const port = getDatabasePort();
|
|
151
|
+
if (port.dialect !== 'sqlite') {
|
|
152
|
+
log('[main] Backfill complete, rebuilding FTS indexes...');
|
|
153
|
+
await rebuildAllIndexes(collections);
|
|
154
|
+
log('[main] FTS indexes ready');
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
log('[main] Backfill complete (FTS updated incrementally)');
|
|
158
|
+
}
|
|
159
|
+
return recordCount;
|
|
152
160
|
})
|
|
153
161
|
.then((recordCount) => {
|
|
154
|
-
log('[main] FTS indexes ready');
|
|
155
162
|
if (recordCount > 0 && !process.env.DEV_MODE) {
|
|
156
163
|
logMemory('after-backfill');
|
|
157
164
|
log('[main] Restarting to reclaim memory...');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hatk/hatk",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.31",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hatk": "dist/cli.js"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"prepublishOnly": "npm run build"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@bigmoves/lexicon": "^0.2.
|
|
35
|
+
"@bigmoves/lexicon": "^0.2.2",
|
|
36
36
|
"@duckdb/node-api": "^1.4.4-r.1",
|
|
37
37
|
"@hatk/oauth-client": "*",
|
|
38
38
|
"@resvg/resvg-js": "^2.6.2",
|