@gmickel/gno 1.34.4 → 1.34.6
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 +11 -1
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v1.34.4.zip → gno-browser-clipper-v1.34.6.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.34.6.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +5 -1
- package/spec/cli.md +16 -10
- package/src/cli/pager.ts +29 -14
- package/src/ingestion/sync.ts +368 -84
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/watch-reconciliation-fallback-disk.ts +220 -0
- package/src/serve/watch-reconciliation-fallback.ts +404 -0
- package/src/serve/watch-reconciliation-shared.ts +343 -0
- package/src/serve/watch-reconciliation.ts +122 -0
- package/src/serve/watch-service-events.ts +261 -0
- package/src/serve/watch-service-flush-generation.ts +140 -0
- package/src/serve/watch-service-flush-helpers.ts +147 -0
- package/src/serve/watch-service-flush.ts +433 -0
- package/src/serve/watch-service-hosts.ts +109 -0
- package/src/serve/watch-service-lifecycle.ts +219 -0
- package/src/serve/watch-service-run-flush.ts +236 -0
- package/src/serve/watch-service-snapshot.ts +101 -0
- package/src/serve/watch-service-state.ts +146 -0
- package/src/serve/watch-service.ts +265 -306
- package/src/serve/watch-snapshot-handles.ts +285 -0
- package/src/serve/watch-snapshot-libc.ts +391 -0
- package/src/serve/watch-snapshot-ops.ts +399 -0
- package/src/serve/watch-snapshot-resolve.ts +246 -0
- package/src/serve/watch-snapshot-scan.ts +297 -0
- package/src/serve/watch-snapshot-types.ts +350 -0
- package/src/serve/watch-snapshot.ts +51 -0
- package/src/store/index.ts +1 -1
- package/src/store/sqlite/adapter.ts +191 -0
- package/src/store/types.ts +66 -0
- package/vendor/fts5-snowball/README.md +5 -1
- package/vendor/fts5-snowball/darwin-x64/fts5stemmer.dylib +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.34.4.zip.sha256 +0 -1
|
@@ -211,6 +211,51 @@ const SINGLE_LINE_QUERY_PATTERN = /[\r\n]/;
|
|
|
211
211
|
const DOUBLE_QUOTE_PATTERN = /"/g;
|
|
212
212
|
const DOC_EDGE_TYPE_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
213
213
|
const SQLITE_SAFE_PARAMETER_BATCH_SIZE = 900;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Effective physical source path for watcher fallback queries.
|
|
217
|
+
* Record-container logical rows resolve to their source container path.
|
|
218
|
+
*/
|
|
219
|
+
const WATCHER_SOURCE_PATH_SQL =
|
|
220
|
+
"COALESCE(NULLIF(record_source_path, ''), rel_path)";
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Parent directory of the effective source path (POSIX), with the collection
|
|
224
|
+
* root represented as the empty string.
|
|
225
|
+
*/
|
|
226
|
+
const WATCHER_SOURCE_PARENT_SQL = `CASE WHEN instr(${WATCHER_SOURCE_PATH_SQL}, '/') = 0 THEN '' ELSE substr(${WATCHER_SOURCE_PATH_SQL}, 1, length(rtrim(${WATCHER_SOURCE_PATH_SQL}, replace(${WATCHER_SOURCE_PATH_SQL}, '/', ''))) - 1) END`;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Normalize a collection-relative directory argument for watcher source-path
|
|
230
|
+
* queries. Returns null for absolute, drive-shaped, or escaping paths.
|
|
231
|
+
*/
|
|
232
|
+
function normalizeWatcherSourceDirRelPath(dirRelPath: string): string | null {
|
|
233
|
+
const normalized = dirRelPath.replaceAll("\\", "/");
|
|
234
|
+
if (normalized.startsWith("/")) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const segments: string[] = [];
|
|
238
|
+
for (const segment of normalized.split("/")) {
|
|
239
|
+
if (segment === "" || segment === ".") {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (segment === "..") {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
segments.push(segment);
|
|
246
|
+
}
|
|
247
|
+
const canonical = segments.join("/");
|
|
248
|
+
// `C:` / `C:/foo` after stripping `.` is a Windows absolute escape.
|
|
249
|
+
// A single segment like `a:notes` (no slash after the colon) stays legal.
|
|
250
|
+
if (
|
|
251
|
+
/^[A-Za-z]:(\/|$)/.test(canonical) &&
|
|
252
|
+
(canonical.length === 2 || canonical[2] === "/")
|
|
253
|
+
) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
return canonical;
|
|
257
|
+
}
|
|
258
|
+
|
|
214
259
|
const FTS5_FIELD_WEIGHTS = {
|
|
215
260
|
filepath: 1.5,
|
|
216
261
|
title: 4.0,
|
|
@@ -1664,6 +1709,152 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
1664
1709
|
}
|
|
1665
1710
|
}
|
|
1666
1711
|
|
|
1712
|
+
async listActiveDirectChildSourcePaths(
|
|
1713
|
+
collection: string,
|
|
1714
|
+
dirRelPath: string,
|
|
1715
|
+
max: number
|
|
1716
|
+
): Promise<StoreResult<string[]>> {
|
|
1717
|
+
if (!Number.isInteger(max) || max <= 0) {
|
|
1718
|
+
return err("INVALID_INPUT", "max must be a positive integer");
|
|
1719
|
+
}
|
|
1720
|
+
const parentPath = normalizeWatcherSourceDirRelPath(dirRelPath);
|
|
1721
|
+
if (parentPath === null) {
|
|
1722
|
+
return err(
|
|
1723
|
+
"INVALID_INPUT",
|
|
1724
|
+
`Directory path escapes the collection root: ${dirRelPath}`
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
try {
|
|
1729
|
+
const db = this.ensureOpen();
|
|
1730
|
+
// Effective source path: record containers resolve to physical container.
|
|
1731
|
+
// Parent key: empty string for root-level sources.
|
|
1732
|
+
// LIMIT max+1 detects overflow without returning a truncated success.
|
|
1733
|
+
const rows = db
|
|
1734
|
+
.query<{ source_path: string }, [string, string, number]>(
|
|
1735
|
+
`SELECT DISTINCT ${WATCHER_SOURCE_PATH_SQL} AS source_path
|
|
1736
|
+
FROM documents
|
|
1737
|
+
WHERE collection = ?
|
|
1738
|
+
AND active = 1
|
|
1739
|
+
AND ${WATCHER_SOURCE_PARENT_SQL} = ?
|
|
1740
|
+
ORDER BY source_path ASC
|
|
1741
|
+
LIMIT ?`
|
|
1742
|
+
)
|
|
1743
|
+
.all(collection, parentPath, max + 1);
|
|
1744
|
+
|
|
1745
|
+
if (rows.length > max) {
|
|
1746
|
+
return err(
|
|
1747
|
+
"OVERFLOW",
|
|
1748
|
+
`Active direct-child source paths exceed max=${max}`
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
return ok(rows.map((row) => row.source_path));
|
|
1752
|
+
} catch (cause) {
|
|
1753
|
+
return err(
|
|
1754
|
+
"QUERY_FAILED",
|
|
1755
|
+
cause instanceof Error
|
|
1756
|
+
? cause.message
|
|
1757
|
+
: "Failed to list active direct child source paths",
|
|
1758
|
+
cause
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
async listActiveDescendantSourcePaths(
|
|
1764
|
+
collection: string,
|
|
1765
|
+
dirRelPath: string,
|
|
1766
|
+
max: number
|
|
1767
|
+
): Promise<StoreResult<string[]>> {
|
|
1768
|
+
if (!Number.isInteger(max) || max <= 0) {
|
|
1769
|
+
return err("INVALID_INPUT", "max must be a positive integer");
|
|
1770
|
+
}
|
|
1771
|
+
const directory = normalizeWatcherSourceDirRelPath(dirRelPath);
|
|
1772
|
+
if (directory === null) {
|
|
1773
|
+
return err(
|
|
1774
|
+
"INVALID_INPUT",
|
|
1775
|
+
`Directory path escapes the collection root: ${dirRelPath}`
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
if (directory === "") {
|
|
1779
|
+
return err(
|
|
1780
|
+
"INVALID_INPUT",
|
|
1781
|
+
"Descendant lookup requires a directory below the collection root"
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
try {
|
|
1786
|
+
const db = this.ensureOpen();
|
|
1787
|
+
// Exact prefix boundary: `dir1/` never matches `dir10/...`.
|
|
1788
|
+
// length(?) is code-point-safe (JS prefix.length is UTF-16 and breaks non-BMP).
|
|
1789
|
+
// LIMIT max+1 detects overflow without returning a truncated success.
|
|
1790
|
+
const prefix = `${directory}/`;
|
|
1791
|
+
const rows = db
|
|
1792
|
+
.query<{ source_path: string }, [string, string, string, number]>(
|
|
1793
|
+
`SELECT DISTINCT ${WATCHER_SOURCE_PATH_SQL} AS source_path
|
|
1794
|
+
FROM documents
|
|
1795
|
+
WHERE collection = ?
|
|
1796
|
+
AND active = 1
|
|
1797
|
+
AND substr(${WATCHER_SOURCE_PATH_SQL}, 1, length(?)) = ?
|
|
1798
|
+
ORDER BY source_path ASC
|
|
1799
|
+
LIMIT ?`
|
|
1800
|
+
)
|
|
1801
|
+
.all(collection, prefix, prefix, max + 1);
|
|
1802
|
+
|
|
1803
|
+
if (rows.length > max) {
|
|
1804
|
+
return err(
|
|
1805
|
+
"OVERFLOW",
|
|
1806
|
+
`Active descendant source paths exceed max=${max}`
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
return ok(rows.map((row) => row.source_path));
|
|
1810
|
+
} catch (cause) {
|
|
1811
|
+
return err(
|
|
1812
|
+
"QUERY_FAILED",
|
|
1813
|
+
cause instanceof Error
|
|
1814
|
+
? cause.message
|
|
1815
|
+
: "Failed to list active descendant source paths",
|
|
1816
|
+
cause
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
async listActiveSourcePaths(
|
|
1822
|
+
collection: string,
|
|
1823
|
+
max: number
|
|
1824
|
+
): Promise<StoreResult<string[]>> {
|
|
1825
|
+
if (!Number.isInteger(max) || max <= 0) {
|
|
1826
|
+
return err("INVALID_INPUT", "max must be a positive integer");
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
try {
|
|
1830
|
+
const db = this.ensureOpen();
|
|
1831
|
+
// Root-wide DISTINCT physical sources; overflow after collapse (max+1).
|
|
1832
|
+
const rows = db
|
|
1833
|
+
.query<{ source_path: string }, [string, number]>(
|
|
1834
|
+
`SELECT DISTINCT ${WATCHER_SOURCE_PATH_SQL} AS source_path
|
|
1835
|
+
FROM documents
|
|
1836
|
+
WHERE collection = ?
|
|
1837
|
+
AND active = 1
|
|
1838
|
+
ORDER BY source_path ASC
|
|
1839
|
+
LIMIT ?`
|
|
1840
|
+
)
|
|
1841
|
+
.all(collection, max + 1);
|
|
1842
|
+
|
|
1843
|
+
if (rows.length > max) {
|
|
1844
|
+
return err("OVERFLOW", `Active source paths exceed max=${max}`);
|
|
1845
|
+
}
|
|
1846
|
+
return ok(rows.map((row) => row.source_path));
|
|
1847
|
+
} catch (cause) {
|
|
1848
|
+
return err(
|
|
1849
|
+
"QUERY_FAILED",
|
|
1850
|
+
cause instanceof Error
|
|
1851
|
+
? cause.message
|
|
1852
|
+
: "Failed to list active source paths",
|
|
1853
|
+
cause
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1667
1858
|
async listActiveDocumentsForBrowse(
|
|
1668
1859
|
collection?: string
|
|
1669
1860
|
): Promise<StoreResult<DocumentRow[]>> {
|
package/src/store/types.ts
CHANGED
|
@@ -24,6 +24,13 @@ import type {
|
|
|
24
24
|
// Error Types
|
|
25
25
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Fixed service default maximum for watcher active source-path queries.
|
|
29
|
+
* Callers (watcher fallback later) pass this explicitly; methods never invent
|
|
30
|
+
* an unbounded success path.
|
|
31
|
+
*/
|
|
32
|
+
export const WATCHER_ACTIVE_SOURCE_PATH_MAX = 100_000;
|
|
33
|
+
|
|
27
34
|
/** Store error codes */
|
|
28
35
|
export type StoreErrorCode =
|
|
29
36
|
| "NOT_FOUND"
|
|
@@ -38,6 +45,8 @@ export type StoreErrorCode =
|
|
|
38
45
|
| "IO_ERROR"
|
|
39
46
|
| "INTERNAL"
|
|
40
47
|
| "EGRESS_DENIED"
|
|
48
|
+
/** Result set exceeded the caller-supplied positive maximum (never truncated). */
|
|
49
|
+
| "OVERFLOW"
|
|
41
50
|
// Vector-specific error codes (EPIC 7)
|
|
42
51
|
| "VECTOR_WRITE_FAILED"
|
|
43
52
|
| "VECTOR_DELETE_FAILED"
|
|
@@ -1688,6 +1697,63 @@ export interface StorePort {
|
|
|
1688
1697
|
sourcePath: string
|
|
1689
1698
|
): Promise<StoreResult<DocumentRow[]>>;
|
|
1690
1699
|
|
|
1700
|
+
/**
|
|
1701
|
+
* List distinct effective source paths of ACTIVE documents that are direct
|
|
1702
|
+
* children of `dirRelPath` within `collection`.
|
|
1703
|
+
*
|
|
1704
|
+
* Effective source path is `COALESCE(NULLIF(record_source_path, ''), rel_path)`
|
|
1705
|
+
* so record-container logical documents resolve to their physical container
|
|
1706
|
+
* path. Deeper descendants, inactive rows, and other collections are excluded.
|
|
1707
|
+
*
|
|
1708
|
+
* `dirRelPath` is collection-relative and POSIX-style; the collection root is
|
|
1709
|
+
* `""`. Paths that escape the collection root are rejected with
|
|
1710
|
+
* `INVALID_INPUT`. An empty successful result is distinct from query failure.
|
|
1711
|
+
*
|
|
1712
|
+
* `max` must be a positive integer (see `WATCHER_ACTIVE_SOURCE_PATH_MAX` for
|
|
1713
|
+
* the fixed service default the watcher will pass). Matching rows beyond
|
|
1714
|
+
* `max` yield `OVERFLOW` — never a truncated successful list.
|
|
1715
|
+
*/
|
|
1716
|
+
listActiveDirectChildSourcePaths(
|
|
1717
|
+
collection: string,
|
|
1718
|
+
dirRelPath: string,
|
|
1719
|
+
max: number
|
|
1720
|
+
): Promise<StoreResult<string[]>>;
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* List distinct effective source paths of ACTIVE documents anywhere beneath
|
|
1724
|
+
* `dirRelPath` (direct children and deeper descendants).
|
|
1725
|
+
*
|
|
1726
|
+
* Used when a directory is gone from disk so the whole removed subtree can be
|
|
1727
|
+
* reconciled. Prefix containment is exact (`dir1` never matches `dir10/x.md`).
|
|
1728
|
+
*
|
|
1729
|
+
* `dirRelPath` must name a directory below the collection root: `""` is
|
|
1730
|
+
* rejected with `INVALID_INPUT` because a root-wide scan is intentionally out
|
|
1731
|
+
* of scope for this bounded seam. Escaping paths are also `INVALID_INPUT`.
|
|
1732
|
+
*
|
|
1733
|
+
* `max` must be a positive integer (see `WATCHER_ACTIVE_SOURCE_PATH_MAX` for
|
|
1734
|
+
* the fixed service default the watcher will pass). Matching rows beyond
|
|
1735
|
+
* `max` yield `OVERFLOW` — never a truncated successful list.
|
|
1736
|
+
*/
|
|
1737
|
+
listActiveDescendantSourcePaths(
|
|
1738
|
+
collection: string,
|
|
1739
|
+
dirRelPath: string,
|
|
1740
|
+
max: number
|
|
1741
|
+
): Promise<StoreResult<string[]>>;
|
|
1742
|
+
|
|
1743
|
+
/**
|
|
1744
|
+
* Root-wide bounded DISTINCT active physical source paths for one collection.
|
|
1745
|
+
*
|
|
1746
|
+
* Effective source path is `COALESCE(NULLIF(record_source_path, ''), rel_path)`.
|
|
1747
|
+
* Overflow is decided after DISTINCT collapse (`LIMIT max+1`), never on raw
|
|
1748
|
+
* logical document row counts. Inactive rows and other collections are excluded.
|
|
1749
|
+
* Results are ordered ascending. Matching unique sources beyond `max` yield
|
|
1750
|
+
* `OVERFLOW` — never a truncated successful list.
|
|
1751
|
+
*/
|
|
1752
|
+
listActiveSourcePaths(
|
|
1753
|
+
collection: string,
|
|
1754
|
+
max: number
|
|
1755
|
+
): Promise<StoreResult<string[]>>;
|
|
1756
|
+
|
|
1691
1757
|
/**
|
|
1692
1758
|
* Fetch documents by mirror hashes in batch.
|
|
1693
1759
|
* Useful for retrieval pipelines to avoid full document scans.
|
|
@@ -15,7 +15,11 @@ Prebuilt [fts5-snowball](https://github.com/abiliojr/fts5-snowball) SQLite exten
|
|
|
15
15
|
|
|
16
16
|
Built via GitHub Actions: `.github/workflows/build-fts5-snowball.yml`
|
|
17
17
|
|
|
18
|
-
Source: https://github.com/abiliojr/fts5-snowball
|
|
18
|
+
Source: https://github.com/abiliojr/fts5-snowball at commit
|
|
19
|
+
`f2a5450af5ce810cc3f981ff1ea8de2b4ff82a67`.
|
|
20
|
+
|
|
21
|
+
The macOS build jobs verify the runner architecture, the emitted Mach-O
|
|
22
|
+
architecture, and a real stemming query through Homebrew SQLite before upload.
|
|
19
23
|
|
|
20
24
|
## Supported Languages
|
|
21
25
|
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
645e1362d74ccaf55611a0d81d085de321106ee8befc6c3639870365655bd942 gno-browser-clipper-v1.34.4.zip
|