@gmickel/gno 2.12.0 → 2.12.2
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/README.md +1 -1
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.12.0.zip → gno-browser-clipper-v2.12.2.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.12.2.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/mcp.md +1 -1
- package/src/cli/program.ts +6 -2
- package/src/core/job-manager.ts +26 -28
- package/src/serve/public/index.html +5 -0
- package/src/store/sqlite/adapter.ts +190 -182
- package/browser-extension/artifacts/gno-browser-clipper-v2.12.0.zip.sha256 +0 -1
package/README.md
CHANGED
|
@@ -139,7 +139,7 @@ See the [guide](docs/COMPILED-CONTEXT.md).
|
|
|
139
139
|
|
|
140
140
|
<!-- public-truth:current-version -->
|
|
141
141
|
|
|
142
|
-
> Current source version: **v2.12.
|
|
142
|
+
> Current source version: **v2.12.2**. See [CHANGELOG.md](./CHANGELOG.md).
|
|
143
143
|
|
|
144
144
|
<!-- /public-truth -->
|
|
145
145
|
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
34953d40a06904ec1c0fc7c6df54aaa14cb24acb05b38c3f15d572a285f7b3fc gno-browser-clipper-v2.12.2.zip
|
package/package.json
CHANGED
package/spec/mcp.md
CHANGED
|
@@ -3105,7 +3105,7 @@ Clients should check schema version for compatibility.
|
|
|
3105
3105
|
- DB connection kept open for server lifetime
|
|
3106
3106
|
- No persistent state between tool calls
|
|
3107
3107
|
- Each tool call is independent
|
|
3108
|
-
- Server handles concurrent
|
|
3108
|
+
- Server handles concurrent tool calls sequentially; an accepted background job (`gno_sync`, `gno_embed`, `gno_index`) waits for the call in progress, then runs without holding that queue, so reads are not blocked while it runs
|
|
3109
3109
|
|
|
3110
3110
|
---
|
|
3111
3111
|
|
package/src/cli/program.ts
CHANGED
|
@@ -4691,8 +4691,12 @@ function wireGraphCommand(program: Command): void {
|
|
|
4691
4691
|
);
|
|
4692
4692
|
}
|
|
4693
4693
|
|
|
4694
|
-
|
|
4695
|
-
|
|
4694
|
+
// `gno graph` declares --max-depth for paths, so Commander hands the
|
|
4695
|
+
// flag to the parent command.
|
|
4696
|
+
const rawMaxDepth: unknown =
|
|
4697
|
+
cmdOpts.maxDepth ?? command.parent?.opts().maxDepth;
|
|
4698
|
+
const maxDepth = rawMaxDepth
|
|
4699
|
+
? parsePositiveInt("max-depth", rawMaxDepth)
|
|
4696
4700
|
: undefined;
|
|
4697
4701
|
const maxNodes = cmdOpts.maxNodes
|
|
4698
4702
|
? parsePositiveInt("max-nodes", cmdOpts.maxNodes)
|
package/src/core/job-manager.ts
CHANGED
|
@@ -287,20 +287,12 @@ export class JobManager {
|
|
|
287
287
|
lock: { release: () => Promise<void> }
|
|
288
288
|
): Promise<void> {
|
|
289
289
|
try {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
job.status = "completed";
|
|
297
|
-
job.result = result;
|
|
298
|
-
} catch (e) {
|
|
299
|
-
job.status = "failed";
|
|
300
|
-
job.error = e instanceof Error ? e.message : String(e);
|
|
301
|
-
} finally {
|
|
302
|
-
release();
|
|
303
|
-
}
|
|
290
|
+
await this.#admit(job);
|
|
291
|
+
assertInferenceActive();
|
|
292
|
+
const result = await fn(this.#jobControllers.get(job.id)!.signal);
|
|
293
|
+
assertInferenceActive();
|
|
294
|
+
job.status = "completed";
|
|
295
|
+
job.result = result;
|
|
304
296
|
} catch (e) {
|
|
305
297
|
job.status = "failed";
|
|
306
298
|
job.error = e instanceof Error ? e.message : String(e);
|
|
@@ -378,20 +370,12 @@ export class JobManager {
|
|
|
378
370
|
lock: { release: () => Promise<void> }
|
|
379
371
|
): Promise<void> {
|
|
380
372
|
try {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
job.status = "completed";
|
|
388
|
-
job.typedResult = result;
|
|
389
|
-
} catch (e) {
|
|
390
|
-
job.status = "failed";
|
|
391
|
-
job.error = e instanceof Error ? e.message : String(e);
|
|
392
|
-
} finally {
|
|
393
|
-
release();
|
|
394
|
-
}
|
|
373
|
+
await this.#admit(job);
|
|
374
|
+
assertInferenceActive();
|
|
375
|
+
const result = await fn(this.#jobControllers.get(job.id)!.signal);
|
|
376
|
+
assertInferenceActive();
|
|
377
|
+
job.status = "completed";
|
|
378
|
+
job.typedResult = result;
|
|
395
379
|
} catch (e) {
|
|
396
380
|
job.status = "failed";
|
|
397
381
|
job.error = e instanceof Error ? e.message : String(e);
|
|
@@ -435,6 +419,20 @@ export class JobManager {
|
|
|
435
419
|
}
|
|
436
420
|
}
|
|
437
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Wait for the tool call in progress, then re-check authorization. The
|
|
424
|
+
* mutex is released before the job's work runs, so reads are not held
|
|
425
|
+
* behind a long sync or embed; writers stay serialized by the write lease.
|
|
426
|
+
*/
|
|
427
|
+
async #admit(job: JobRecord): Promise<void> {
|
|
428
|
+
const release = await this.#toolMutex.acquire();
|
|
429
|
+
try {
|
|
430
|
+
this.#assertAuthorizationEpoch(job);
|
|
431
|
+
} finally {
|
|
432
|
+
release();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
438
436
|
#assertAuthorizationEpoch(job: JobRecord): void {
|
|
439
437
|
if (this.#jobAuthorizationEpochs.get(job.id) === this.#authorizationEpoch) {
|
|
440
438
|
return;
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="referrer" content="no-referrer" />
|
|
7
7
|
<title>GNO - Local Knowledge Index</title>
|
|
8
|
+
<link
|
|
9
|
+
rel="icon"
|
|
10
|
+
type="image/svg+xml"
|
|
11
|
+
href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDY0IDY0IiBmaWxsPSJub25lIiA+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnbm8tZmF2aWNvbi1ncmFkaWVudCIgeDE9IjEyIiB5MT0iOCIgeDI9IjU0IiB5Mj0iNTgiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBzdG9wLWNvbG9yPSIjZDA4YTNjIiAvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzhhNGExYSIgLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48cmVjdCB4PSI2IiB5PSI2IiB3aWR0aD0iNTIiIGhlaWdodD0iNTIiIHJ4PSIxNCIgZmlsbD0idXJsKCNnbm8tZmF2aWNvbi1ncmFkaWVudCkiIC8+PHBhdGggZmlsbD0iI2ZmZjhlZiIgZD0iTTM1LjYxOSAxOC42OTRjMy41NDYgMCA2LjAzIDEuNDMgNy42ODggMy41MzZsLTIuOTQ0IDIuMjk3Yy0xLjExLTEuMzk2LTIuNjMtMi40NTktNC45MzQtMi40NTktMy45MyAwLTYuNzA1IDMuMTU4LTYuNzA1IDcuMzYxIDAgNC4zMTYgMi43MTkgNy40MTggNi45MTkgNy40MTggMS43MSAwIDMuMzA1LS41MSA0LjI4NC0xLjM5NlYzMi4xaC01LjM3N3YtMy4yODloOS4xOTR2OC4wNTNjLTIuMTI2IDIuMzU0LTUuMTI2IDMuMzU0LTguNTEgMy4zNTQtNi4xOTggMC0xMC40MDctNC40MjItMTAuNDA3LTEwLjc4OSAwLTYuMzkgNC40ODMtMTAuNzM1IDEwLjc5Mi0xMC43MzVaIiAvPjwvc3ZnPg=="
|
|
12
|
+
/>
|
|
8
13
|
<link rel="stylesheet" href="./globals.built.css" />
|
|
9
14
|
</head>
|
|
10
15
|
<body>
|
|
@@ -5076,20 +5076,19 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5076
5076
|
const edgeTypeFilterFor = (alias: string): string =>
|
|
5077
5077
|
edgeType ? `AND ${alias}.edge_type = ?` : "";
|
|
5078
5078
|
const candidateEdgeLimit = Math.min(frontierLimit * 4, 4_000);
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
const frontierJoinClause = (frontierName: string): string =>
|
|
5079
|
+
// Each level runs as its own statement over the previous frontier and
|
|
5080
|
+
// the visited set, passed as JSON. One statement chaining every level
|
|
5081
|
+
// as CTEs re-expands each earlier level at every reference, which
|
|
5082
|
+
// grows exponentially with depth: SQLite 3.54 refuses it from depth 3
|
|
5083
|
+
// ("nested too deep") and older versions fail at depth 6.
|
|
5084
|
+
const visitedIds = "SELECT doc_id FROM visited";
|
|
5085
|
+
const frontierJoinClause =
|
|
5087
5086
|
direction === "out"
|
|
5088
|
-
?
|
|
5087
|
+
? "e.src_doc_id = prev.doc_id"
|
|
5089
5088
|
: direction === "in"
|
|
5090
|
-
?
|
|
5091
|
-
:
|
|
5092
|
-
const boundedEdgePredicate = (
|
|
5089
|
+
? "e.dst_doc_id = prev.doc_id"
|
|
5090
|
+
: "(e.src_doc_id = prev.doc_id OR e.dst_doc_id = prev.doc_id)";
|
|
5091
|
+
const boundedEdgePredicate = (): string => {
|
|
5093
5092
|
if (direction === "out") {
|
|
5094
5093
|
return `
|
|
5095
5094
|
e.id IN (
|
|
@@ -5110,10 +5109,10 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5110
5109
|
) AS next_rank
|
|
5111
5110
|
FROM doc_edges e2
|
|
5112
5111
|
JOIN documents next_doc ON next_doc.id = e2.dst_doc_id AND next_doc.active = 1${nextDocScope}
|
|
5113
|
-
WHERE e2.src_doc_id =
|
|
5112
|
+
WHERE e2.src_doc_id = prev.doc_id
|
|
5114
5113
|
${edgeTypeFilterFor("e2")}
|
|
5115
|
-
AND instr(
|
|
5116
|
-
AND e2.dst_doc_id NOT IN (${
|
|
5114
|
+
AND instr(prev.path, printf(',%d,', e2.dst_doc_id)) = 0
|
|
5115
|
+
AND e2.dst_doc_id NOT IN (${visitedIds})
|
|
5117
5116
|
)
|
|
5118
5117
|
WHERE next_rank = 1
|
|
5119
5118
|
ORDER BY edge_type ASC, next_doc_id ASC, edge_id ASC
|
|
@@ -5141,10 +5140,10 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5141
5140
|
) AS next_rank
|
|
5142
5141
|
FROM doc_edges e2
|
|
5143
5142
|
JOIN documents next_doc ON next_doc.id = e2.src_doc_id AND next_doc.active = 1${nextDocScope}
|
|
5144
|
-
WHERE e2.dst_doc_id =
|
|
5143
|
+
WHERE e2.dst_doc_id = prev.doc_id
|
|
5145
5144
|
${edgeTypeFilterFor("e2")}
|
|
5146
|
-
AND instr(
|
|
5147
|
-
AND e2.src_doc_id NOT IN (${
|
|
5145
|
+
AND instr(prev.path, printf(',%d,', e2.src_doc_id)) = 0
|
|
5146
|
+
AND e2.src_doc_id NOT IN (${visitedIds})
|
|
5148
5147
|
)
|
|
5149
5148
|
WHERE next_rank = 1
|
|
5150
5149
|
ORDER BY edge_type ASC, next_doc_id ASC, edge_id ASC
|
|
@@ -5172,10 +5171,10 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5172
5171
|
) AS next_rank
|
|
5173
5172
|
FROM doc_edges e2
|
|
5174
5173
|
JOIN documents next_doc ON next_doc.id = e2.dst_doc_id AND next_doc.active = 1${nextDocScope}
|
|
5175
|
-
WHERE e2.src_doc_id =
|
|
5174
|
+
WHERE e2.src_doc_id = prev.doc_id
|
|
5176
5175
|
${edgeTypeFilterFor("e2")}
|
|
5177
|
-
AND instr(
|
|
5178
|
-
AND e2.dst_doc_id NOT IN (${
|
|
5176
|
+
AND instr(prev.path, printf(',%d,', e2.dst_doc_id)) = 0
|
|
5177
|
+
AND e2.dst_doc_id NOT IN (${visitedIds})
|
|
5179
5178
|
)
|
|
5180
5179
|
WHERE next_rank = 1
|
|
5181
5180
|
ORDER BY edge_type ASC, next_doc_id ASC, edge_id ASC
|
|
@@ -5200,10 +5199,10 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5200
5199
|
) AS next_rank
|
|
5201
5200
|
FROM doc_edges e3
|
|
5202
5201
|
JOIN documents next_doc ON next_doc.id = e3.src_doc_id AND next_doc.active = 1${nextDocScope}
|
|
5203
|
-
WHERE e3.dst_doc_id =
|
|
5202
|
+
WHERE e3.dst_doc_id = prev.doc_id
|
|
5204
5203
|
${edgeTypeFilterFor("e3")}
|
|
5205
|
-
AND instr(
|
|
5206
|
-
AND e3.src_doc_id NOT IN (${
|
|
5204
|
+
AND instr(prev.path, printf(',%d,', e3.src_doc_id)) = 0
|
|
5205
|
+
AND e3.src_doc_id NOT IN (${visitedIds})
|
|
5207
5206
|
)
|
|
5208
5207
|
WHERE next_rank = 1
|
|
5209
5208
|
ORDER BY edge_type ASC, next_doc_id ASC, edge_id ASC
|
|
@@ -5212,40 +5211,62 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5212
5211
|
)
|
|
5213
5212
|
)`;
|
|
5214
5213
|
};
|
|
5215
|
-
const
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5214
|
+
const nextExpr =
|
|
5215
|
+
"CASE WHEN e.src_doc_id = prev.doc_id THEN e.dst_doc_id ELSE e.src_doc_id END";
|
|
5216
|
+
const levelParams: (number | string)[] = [];
|
|
5217
|
+
if (direction === "both") {
|
|
5218
|
+
if (edgeType) {
|
|
5219
|
+
levelParams.push(edgeType);
|
|
5220
|
+
}
|
|
5221
|
+
levelParams.push(candidateEdgeLimit);
|
|
5222
|
+
}
|
|
5223
|
+
if (edgeType) {
|
|
5224
|
+
levelParams.push(edgeType);
|
|
5225
|
+
}
|
|
5226
|
+
levelParams.push(candidateEdgeLimit);
|
|
5227
|
+
if (edgeType) {
|
|
5228
|
+
levelParams.push(edgeType);
|
|
5229
|
+
}
|
|
5230
|
+
levelParams.push(
|
|
5231
|
+
frontierLimit,
|
|
5232
|
+
frontierLimit,
|
|
5233
|
+
visitedLimit,
|
|
5234
|
+
frontierLimit
|
|
5235
|
+
);
|
|
5236
|
+
|
|
5237
|
+
const levelQuery = (depth: number) =>
|
|
5238
|
+
db.query<
|
|
5239
|
+
{
|
|
5240
|
+
candidate_count: number;
|
|
5241
|
+
has_candidates: number;
|
|
5242
|
+
expansion_overflow: number;
|
|
5243
|
+
doc_id: number | null;
|
|
5244
|
+
depth: number | null;
|
|
5245
|
+
path: string | null;
|
|
5246
|
+
sort_key: string | null;
|
|
5247
|
+
},
|
|
5248
|
+
(number | string)[]
|
|
5249
|
+
>(`
|
|
5250
|
+
WITH
|
|
5251
|
+
prev(doc_id, path) AS (
|
|
5252
|
+
SELECT json_extract(value, '$[0]'), json_extract(value, '$[1]')
|
|
5253
|
+
FROM json_each(?)
|
|
5254
|
+
),
|
|
5255
|
+
visited(doc_id) AS (
|
|
5256
|
+
SELECT value FROM json_each(?)
|
|
5257
|
+
),
|
|
5258
|
+
raw AS (
|
|
5230
5259
|
SELECT
|
|
5231
|
-
|
|
5260
|
+
prev.doc_id AS from_doc_id,
|
|
5232
5261
|
${nextExpr} AS doc_id,
|
|
5233
5262
|
${depth} AS depth,
|
|
5234
|
-
|
|
5263
|
+
prev.path || ${nextExpr} || ',' AS path,
|
|
5235
5264
|
printf('%06d|%s|%012d', ${depth}, e.edge_type, ${nextExpr}) AS sort_key,
|
|
5236
5265
|
e.src_doc_id,
|
|
5237
|
-
src.docid AS source_docid,
|
|
5238
|
-
src.uri AS source_uri,
|
|
5239
|
-
src.title AS source_title,
|
|
5240
5266
|
e.dst_doc_id,
|
|
5241
|
-
dst.docid AS target_docid,
|
|
5242
|
-
dst.uri AS target_uri,
|
|
5243
|
-
dst.title AS target_title,
|
|
5244
5267
|
e.edge_type,
|
|
5245
|
-
e.confidence,
|
|
5246
|
-
e.source,
|
|
5247
5268
|
row_number() OVER (
|
|
5248
|
-
PARTITION BY
|
|
5269
|
+
PARTITION BY prev.doc_id, e.src_doc_id, e.dst_doc_id, e.edge_type
|
|
5249
5270
|
ORDER BY
|
|
5250
5271
|
CASE e.confidence
|
|
5251
5272
|
WHEN 'manual' THEN 1
|
|
@@ -5258,47 +5279,47 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5258
5279
|
src.docid ASC,
|
|
5259
5280
|
dst.docid ASC
|
|
5260
5281
|
) AS dedup_rank
|
|
5261
|
-
FROM
|
|
5262
|
-
JOIN doc_edges e ON ${frontierJoinClause
|
|
5263
|
-
AND ${boundedEdgePredicate(
|
|
5282
|
+
FROM prev
|
|
5283
|
+
JOIN doc_edges e ON ${frontierJoinClause}
|
|
5284
|
+
AND ${boundedEdgePredicate()}
|
|
5264
5285
|
JOIN documents src ON src.id = e.src_doc_id AND src.active = 1
|
|
5265
5286
|
JOIN documents dst ON dst.id = e.dst_doc_id AND dst.active = 1
|
|
5266
5287
|
WHERE 1 = 1
|
|
5267
5288
|
${edgeTypeFilter}
|
|
5268
|
-
AND instr(
|
|
5269
|
-
AND ${nextExpr} NOT IN (${
|
|
5289
|
+
AND instr(prev.path, printf(',%d,', ${nextExpr})) = 0
|
|
5290
|
+
AND ${nextExpr} NOT IN (${visitedIds})
|
|
5270
5291
|
),
|
|
5271
|
-
|
|
5292
|
+
deduped AS (
|
|
5272
5293
|
SELECT *
|
|
5273
|
-
FROM
|
|
5294
|
+
FROM raw
|
|
5274
5295
|
WHERE dedup_rank = 1
|
|
5275
5296
|
),
|
|
5276
|
-
|
|
5297
|
+
node_deduped AS (
|
|
5277
5298
|
SELECT
|
|
5278
5299
|
from_doc_id,
|
|
5279
5300
|
doc_id,
|
|
5280
5301
|
depth,
|
|
5281
5302
|
min(path) AS path,
|
|
5282
5303
|
min(sort_key) AS sort_key
|
|
5283
|
-
FROM
|
|
5304
|
+
FROM deduped
|
|
5284
5305
|
GROUP BY from_doc_id, doc_id, depth
|
|
5285
5306
|
),
|
|
5286
|
-
|
|
5307
|
+
ranked AS (
|
|
5287
5308
|
SELECT
|
|
5288
5309
|
*,
|
|
5289
5310
|
row_number() OVER (
|
|
5290
5311
|
PARTITION BY from_doc_id
|
|
5291
5312
|
ORDER BY sort_key ASC, doc_id ASC
|
|
5292
5313
|
) AS expansion_rank
|
|
5293
|
-
FROM
|
|
5314
|
+
FROM node_deduped
|
|
5294
5315
|
),
|
|
5295
|
-
|
|
5316
|
+
candidates AS (
|
|
5296
5317
|
SELECT doc_id, depth, min(path) AS path, min(sort_key) AS sort_key
|
|
5297
|
-
FROM
|
|
5318
|
+
FROM ranked
|
|
5298
5319
|
WHERE expansion_rank <= ?
|
|
5299
5320
|
GROUP BY doc_id, depth
|
|
5300
5321
|
),
|
|
5301
|
-
|
|
5322
|
+
frontier AS (
|
|
5302
5323
|
SELECT doc_id, depth, path, sort_key
|
|
5303
5324
|
FROM (
|
|
5304
5325
|
SELECT
|
|
@@ -5307,35 +5328,88 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5307
5328
|
path,
|
|
5308
5329
|
sort_key,
|
|
5309
5330
|
row_number() OVER (ORDER BY sort_key ASC, doc_id ASC) AS depth_rank
|
|
5310
|
-
FROM
|
|
5331
|
+
FROM candidates
|
|
5311
5332
|
)
|
|
5312
5333
|
WHERE depth_rank <= ?
|
|
5313
|
-
AND depth_rank <= ? - (
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5334
|
+
AND depth_rank <= ? - (SELECT count(*) FROM visited)
|
|
5335
|
+
)
|
|
5336
|
+
SELECT
|
|
5337
|
+
stats.candidate_count,
|
|
5338
|
+
stats.has_candidates,
|
|
5339
|
+
stats.expansion_overflow,
|
|
5340
|
+
frontier.doc_id,
|
|
5341
|
+
frontier.depth,
|
|
5342
|
+
frontier.path,
|
|
5343
|
+
frontier.sort_key
|
|
5344
|
+
FROM (
|
|
5345
|
+
SELECT
|
|
5346
|
+
(SELECT count(*) FROM candidates) AS candidate_count,
|
|
5347
|
+
EXISTS (SELECT 1 FROM ranked) AS has_candidates,
|
|
5348
|
+
EXISTS (SELECT 1 FROM ranked WHERE expansion_rank > ?) AS expansion_overflow
|
|
5349
|
+
) stats
|
|
5350
|
+
LEFT JOIN frontier ON 1
|
|
5351
|
+
ORDER BY frontier.sort_key ASC, frontier.doc_id ASC`);
|
|
5352
|
+
|
|
5353
|
+
// Walk level by level; the extra level past maxDepth only reports
|
|
5354
|
+
// whether the depth cap cut anything off.
|
|
5355
|
+
type FrontierRow = {
|
|
5356
|
+
doc_id: number;
|
|
5357
|
+
depth: number;
|
|
5358
|
+
path: string;
|
|
5359
|
+
sort_key: string;
|
|
5360
|
+
};
|
|
5361
|
+
let previous: FrontierRow[] = [
|
|
5362
|
+
{
|
|
5363
|
+
doc_id: rootDocumentId,
|
|
5364
|
+
depth: 0,
|
|
5365
|
+
path: `,${rootDocumentId},`,
|
|
5366
|
+
sort_key: "",
|
|
5367
|
+
},
|
|
5368
|
+
];
|
|
5369
|
+
const walked: FrontierRow[] = [...previous];
|
|
5370
|
+
const visited = new Set([rootDocumentId]);
|
|
5371
|
+
let visitedOverflow = false;
|
|
5372
|
+
let frontierOverflow = false;
|
|
5373
|
+
let depthOverflow = false;
|
|
5374
|
+
for (let depth = 1; depth <= maxDepth + 1; depth += 1) {
|
|
5375
|
+
const rows = levelQuery(depth).all(
|
|
5376
|
+
JSON.stringify(previous.map((row) => [row.doc_id, row.path])),
|
|
5377
|
+
JSON.stringify([...visited]),
|
|
5378
|
+
...levelParams
|
|
5379
|
+
);
|
|
5380
|
+
const stats = rows[0];
|
|
5381
|
+
if (depth > maxDepth) {
|
|
5382
|
+
depthOverflow = Boolean(stats?.has_candidates);
|
|
5383
|
+
break;
|
|
5332
5384
|
}
|
|
5333
|
-
|
|
5334
|
-
|
|
5385
|
+
const candidateCount = stats?.candidate_count ?? 0;
|
|
5386
|
+
if (
|
|
5387
|
+
candidateCount > 0 &&
|
|
5388
|
+
candidateCount > visitedLimit - visited.size
|
|
5389
|
+
) {
|
|
5390
|
+
visitedOverflow = true;
|
|
5335
5391
|
}
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5392
|
+
if (
|
|
5393
|
+
(candidateCount > 0 && candidateCount > frontierLimit) ||
|
|
5394
|
+
stats?.expansion_overflow
|
|
5395
|
+
) {
|
|
5396
|
+
frontierOverflow = true;
|
|
5397
|
+
}
|
|
5398
|
+
previous = rows.flatMap((row) =>
|
|
5399
|
+
row.doc_id === null
|
|
5400
|
+
? []
|
|
5401
|
+
: [
|
|
5402
|
+
{
|
|
5403
|
+
doc_id: row.doc_id,
|
|
5404
|
+
depth: row.depth ?? depth,
|
|
5405
|
+
path: row.path ?? "",
|
|
5406
|
+
sort_key: row.sort_key ?? "",
|
|
5407
|
+
},
|
|
5408
|
+
]
|
|
5409
|
+
);
|
|
5410
|
+
for (const row of previous) {
|
|
5411
|
+
visited.add(row.doc_id);
|
|
5412
|
+
walked.push(row);
|
|
5339
5413
|
}
|
|
5340
5414
|
}
|
|
5341
5415
|
|
|
@@ -5345,62 +5419,18 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5345
5419
|
: direction === "in"
|
|
5346
5420
|
? "AND ns.depth > nt.depth"
|
|
5347
5421
|
: "";
|
|
5348
|
-
const
|
|
5349
|
-
.map((
|
|
5350
|
-
|
|
5351
|
-
const
|
|
5352
|
-
{ length: maxDepth },
|
|
5353
|
-
(_, index) => {
|
|
5354
|
-
const previousFrontiers = Array.from(
|
|
5355
|
-
{ length: index + 1 },
|
|
5356
|
-
(__, frontierIndex) => `SELECT doc_id FROM f${frontierIndex}`
|
|
5357
|
-
).join(" UNION ");
|
|
5358
|
-
return `
|
|
5359
|
-
SELECT 1 AS has_more
|
|
5360
|
-
FROM c${index + 1}
|
|
5361
|
-
GROUP BY 1
|
|
5362
|
-
HAVING count(*) > ? - (
|
|
5363
|
-
SELECT count(*)
|
|
5364
|
-
FROM (${previousFrontiers})
|
|
5365
|
-
)`;
|
|
5366
|
-
}
|
|
5367
|
-
).join(" UNION ALL ");
|
|
5368
|
-
const frontierOverflowChecks = Array.from(
|
|
5369
|
-
{ length: maxDepth },
|
|
5370
|
-
(_, index) => `
|
|
5371
|
-
SELECT 1 AS has_more
|
|
5372
|
-
FROM c${index + 1}
|
|
5373
|
-
GROUP BY 1
|
|
5374
|
-
HAVING count(*) > ?
|
|
5375
|
-
UNION ALL
|
|
5376
|
-
SELECT 1 AS has_more
|
|
5377
|
-
FROM c${index + 1}_ranked
|
|
5378
|
-
WHERE expansion_rank > ?`
|
|
5379
|
-
).join(" UNION ALL ");
|
|
5380
|
-
const walkCte = `
|
|
5381
|
-
WITH RECURSIVE
|
|
5382
|
-
f0(doc_id, depth, path, sort_key) AS (
|
|
5383
|
-
SELECT ?, 0, printf(',%d,', ?), ''
|
|
5384
|
-
),
|
|
5385
|
-
${frontierCtes.join(",")},
|
|
5422
|
+
const walkedJson = JSON.stringify(
|
|
5423
|
+
walked.map((row) => [row.doc_id, row.depth, row.sort_key])
|
|
5424
|
+
);
|
|
5425
|
+
const nodeDepthCte = `
|
|
5386
5426
|
node_depth AS (
|
|
5387
|
-
SELECT doc_id, min(depth) AS depth, min(sort_key) AS sort_key
|
|
5388
|
-
FROM (${frontierUnion})
|
|
5389
|
-
GROUP BY doc_id
|
|
5390
|
-
),
|
|
5391
|
-
ranked_nodes AS (
|
|
5392
5427
|
SELECT
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
)
|
|
5400
|
-
`;
|
|
5401
|
-
|
|
5402
|
-
const baseParams: (number | string)[] = [];
|
|
5403
|
-
baseParams.push(rootDocumentId, rootDocumentId, ...frontierParams);
|
|
5428
|
+
json_extract(value, '$[0]') AS doc_id,
|
|
5429
|
+
min(json_extract(value, '$[1]')) AS depth,
|
|
5430
|
+
min(json_extract(value, '$[2]')) AS sort_key
|
|
5431
|
+
FROM json_each(?)
|
|
5432
|
+
GROUP BY 1
|
|
5433
|
+
)`;
|
|
5404
5434
|
|
|
5405
5435
|
const nodeRows = db
|
|
5406
5436
|
.query<
|
|
@@ -5411,14 +5441,23 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5411
5441
|
},
|
|
5412
5442
|
(number | string)[]
|
|
5413
5443
|
>(
|
|
5414
|
-
|
|
5444
|
+
`WITH ${nodeDepthCte},
|
|
5445
|
+
ranked_nodes AS (
|
|
5446
|
+
SELECT
|
|
5447
|
+
d.*,
|
|
5448
|
+
nd.depth,
|
|
5449
|
+
row_number() OVER (ORDER BY nd.depth ASC, nd.sort_key ASC, d.id ASC) AS global_rank,
|
|
5450
|
+
count(*) OVER () AS total_count
|
|
5451
|
+
FROM node_depth nd
|
|
5452
|
+
JOIN documents d ON d.id = nd.doc_id AND d.active = 1
|
|
5453
|
+
)
|
|
5415
5454
|
SELECT *
|
|
5416
5455
|
FROM ranked_nodes
|
|
5417
5456
|
WHERE global_rank <= ?
|
|
5418
5457
|
ORDER BY depth ASC, uri ASC, docid ASC
|
|
5419
5458
|
LIMIT ?`
|
|
5420
5459
|
)
|
|
5421
|
-
.all(
|
|
5460
|
+
.all(walkedJson, nodeLimit + 1, nodeLimit + 1);
|
|
5422
5461
|
|
|
5423
5462
|
const returnedNodeRows = nodeRows.slice(0, nodeLimit);
|
|
5424
5463
|
const returnedIds = new Set(returnedNodeRows.map((row) => row.id));
|
|
@@ -5429,41 +5468,19 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5429
5468
|
truncated = true;
|
|
5430
5469
|
warnings.push("maxNodes reached");
|
|
5431
5470
|
}
|
|
5432
|
-
if (totalNodes > visitedLimit) {
|
|
5433
|
-
truncated = true;
|
|
5434
|
-
warnings.push("visitedLimit reached");
|
|
5435
|
-
}
|
|
5436
|
-
|
|
5437
|
-
const visitedRows = db
|
|
5438
|
-
.query<{ has_more: number }, (number | string)[]>(
|
|
5439
|
-
`${walkCte}
|
|
5440
|
-
SELECT 1 AS has_more
|
|
5441
|
-
FROM (${visitedOverflowChecks})
|
|
5442
|
-
LIMIT 1`
|
|
5443
|
-
)
|
|
5444
|
-
.get(...baseParams, ...Array(maxDepth).fill(visitedLimit));
|
|
5445
|
-
if (visitedRows) {
|
|
5471
|
+
if (totalNodes > visitedLimit || visitedOverflow) {
|
|
5446
5472
|
truncated = true;
|
|
5447
5473
|
warnings.push("visitedLimit reached");
|
|
5448
5474
|
}
|
|
5449
|
-
|
|
5450
|
-
const frontierRows = db
|
|
5451
|
-
.query<{ has_more: number }, (number | string)[]>(
|
|
5452
|
-
`${walkCte}
|
|
5453
|
-
SELECT 1 AS has_more
|
|
5454
|
-
FROM (${frontierOverflowChecks})
|
|
5455
|
-
LIMIT 1`
|
|
5456
|
-
)
|
|
5457
|
-
.get(...baseParams, ...Array(maxDepth * 2).fill(frontierLimit));
|
|
5458
|
-
if (frontierRows) {
|
|
5475
|
+
if (frontierOverflow) {
|
|
5459
5476
|
truncated = true;
|
|
5460
5477
|
warnings.push("frontierLimit reached");
|
|
5461
5478
|
}
|
|
5462
5479
|
|
|
5463
5480
|
const edgeRows = db
|
|
5464
5481
|
.query<DbDocEdgeRow & { traversal_depth: number }, (number | string)[]>(
|
|
5465
|
-
|
|
5466
|
-
|
|
5482
|
+
`WITH ${nodeDepthCte},
|
|
5483
|
+
returned_edges AS (
|
|
5467
5484
|
SELECT
|
|
5468
5485
|
e.src_doc_id,
|
|
5469
5486
|
src.docid AS source_docid,
|
|
@@ -5510,22 +5527,13 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5510
5527
|
ORDER BY traversal_depth ASC, edge_type ASC, source_docid ASC, target_docid ASC`
|
|
5511
5528
|
)
|
|
5512
5529
|
.all(
|
|
5513
|
-
|
|
5530
|
+
walkedJson,
|
|
5514
5531
|
...returnedIds,
|
|
5515
5532
|
...returnedIds,
|
|
5516
5533
|
...(edgeType ? [edgeType] : [])
|
|
5517
5534
|
);
|
|
5518
5535
|
|
|
5519
|
-
|
|
5520
|
-
.query<{ has_more: number }, (number | string)[]>(
|
|
5521
|
-
`${walkCte}
|
|
5522
|
-
SELECT 1 AS has_more
|
|
5523
|
-
FROM c${boundaryCandidateDepth}_ranked
|
|
5524
|
-
LIMIT 1`
|
|
5525
|
-
)
|
|
5526
|
-
.get(...baseParams);
|
|
5527
|
-
|
|
5528
|
-
if (boundaryRows) {
|
|
5536
|
+
if (depthOverflow) {
|
|
5529
5537
|
truncated = true;
|
|
5530
5538
|
warnings.push("maxDepth reached");
|
|
5531
5539
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
af41e9b6f20acb402696b5d05f2901b50a7454669f3b9b89f44e1370004ff411 gno-browser-clipper-v2.12.0.zip
|