sensemaking 0.25.1 → 0.27.0
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 +48 -12
- package/dist/cjs/cli/index.d.cts +3 -3
- package/dist/cjs/cli/index.d.ts +3 -3
- package/dist/cjs/cli/index.js +3 -3
- package/dist/cjs/cli/index.js.map +1 -1
- package/dist/cjs/cli/path.js +5 -29
- package/dist/cjs/cli/path.js.map +1 -1
- package/dist/cjs/cli/peek.js +43 -2
- package/dist/cjs/cli/peek.js.map +1 -1
- package/dist/cjs/cli/related.js +4 -3
- package/dist/cjs/cli/related.js.map +1 -1
- package/dist/cjs/cli/search.js +38 -6
- package/dist/cjs/cli/search.js.map +1 -1
- package/dist/cjs/commands/index.d.cts +6 -3
- package/dist/cjs/commands/index.d.ts +6 -3
- package/dist/cjs/commands/index.js +4 -0
- package/dist/cjs/commands/index.js.map +1 -1
- package/dist/cjs/commands/map.d.cts +14 -5
- package/dist/cjs/commands/map.d.ts +14 -5
- package/dist/cjs/commands/map.js +42 -35
- package/dist/cjs/commands/map.js.map +1 -1
- package/dist/cjs/commands/path.d.cts +14 -0
- package/dist/cjs/commands/path.d.ts +14 -0
- package/dist/cjs/commands/path.js +209 -0
- package/dist/cjs/commands/path.js.map +1 -0
- package/dist/cjs/commands/peek.d.cts +25 -2
- package/dist/cjs/commands/peek.d.ts +25 -2
- package/dist/cjs/commands/peek.js +131 -50
- package/dist/cjs/commands/peek.js.map +1 -1
- package/dist/cjs/commands/related.d.cts +16 -3
- package/dist/cjs/commands/related.d.ts +16 -3
- package/dist/cjs/commands/related.js +131 -98
- package/dist/cjs/commands/related.js.map +1 -1
- package/dist/cjs/commands/scope.d.cts +1 -0
- package/dist/cjs/commands/scope.d.ts +1 -0
- package/dist/cjs/commands/scope.js +149 -0
- package/dist/cjs/commands/scope.js.map +1 -1
- package/dist/cjs/commands/search.d.cts +30 -3
- package/dist/cjs/commands/search.d.ts +30 -3
- package/dist/cjs/commands/search.js +240 -73
- package/dist/cjs/commands/search.js.map +1 -1
- package/dist/cjs/commands/signals.d.cts +22 -7
- package/dist/cjs/commands/signals.d.ts +22 -7
- package/dist/cjs/commands/signals.js +77 -15
- package/dist/cjs/commands/signals.js.map +1 -1
- package/dist/cjs/commands/snapshot.d.cts +3 -0
- package/dist/cjs/commands/snapshot.d.ts +3 -0
- package/dist/cjs/commands/snapshot.js +180 -0
- package/dist/cjs/commands/snapshot.js.map +1 -0
- package/dist/cjs/config/load.js +6 -0
- package/dist/cjs/config/load.js.map +1 -1
- package/dist/cjs/embed/cohere.js +41 -16
- package/dist/cjs/embed/cohere.js.map +1 -1
- package/dist/cjs/embed/endpoint.d.cts +2 -0
- package/dist/cjs/embed/endpoint.d.ts +2 -0
- package/dist/cjs/embed/endpoint.js +30 -0
- package/dist/cjs/embed/endpoint.js.map +1 -0
- package/dist/cjs/embed/handoff.d.cts +7 -2
- package/dist/cjs/embed/handoff.d.ts +7 -2
- package/dist/cjs/embed/handoff.js +10 -0
- package/dist/cjs/embed/handoff.js.map +1 -1
- package/dist/cjs/embed/http.d.cts +18 -1
- package/dist/cjs/embed/http.d.ts +18 -1
- package/dist/cjs/embed/http.js +239 -40
- package/dist/cjs/embed/http.js.map +1 -1
- package/dist/cjs/embed/openai.js +39 -17
- package/dist/cjs/embed/openai.js.map +1 -1
- package/dist/cjs/embed/query.d.cts +9 -3
- package/dist/cjs/embed/query.d.ts +9 -3
- package/dist/cjs/embed/query.js +257 -90
- package/dist/cjs/embed/query.js.map +1 -1
- package/dist/cjs/embed/registry.d.cts +2 -2
- package/dist/cjs/embed/registry.d.ts +2 -2
- package/dist/cjs/embed/registry.js +36 -4
- package/dist/cjs/embed/registry.js.map +1 -1
- package/dist/cjs/embed/static.js +6 -2
- package/dist/cjs/embed/static.js.map +1 -1
- package/dist/cjs/embed/store.js +42 -77
- package/dist/cjs/embed/store.js.map +1 -1
- package/dist/cjs/embed/types.d.cts +5 -2
- package/dist/cjs/embed/types.d.ts +5 -2
- package/dist/cjs/embed/types.js.map +1 -1
- package/dist/cjs/features/embed.js +290 -43
- package/dist/cjs/features/embed.js.map +1 -1
- package/dist/cjs/graph/traverse.d.cts +2 -2
- package/dist/cjs/graph/traverse.d.ts +2 -2
- package/dist/cjs/graph/traverse.js +15 -5
- package/dist/cjs/graph/traverse.js.map +1 -1
- package/dist/cjs/index.d.cts +2 -2
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/output/output.d.cts +4 -36
- package/dist/cjs/output/output.d.ts +4 -36
- package/dist/cjs/output/output.js +1 -2
- package/dist/cjs/output/output.js.map +1 -1
- package/dist/cjs/scan/pool.d.cts +2 -1
- package/dist/cjs/scan/pool.d.ts +2 -1
- package/dist/cjs/scan/pool.js +116 -27
- package/dist/cjs/scan/pool.js.map +1 -1
- package/dist/cjs/scan/reparse.d.cts +1 -0
- package/dist/cjs/scan/reparse.d.ts +1 -0
- package/dist/cjs/scan/reparse.js +75 -21
- package/dist/cjs/scan/reparse.js.map +1 -1
- package/dist/cjs/store/builder.d.cts +11 -21
- package/dist/cjs/store/builder.d.ts +11 -21
- package/dist/cjs/store/builder.js +1077 -438
- package/dist/cjs/store/builder.js.map +1 -1
- package/dist/cjs/store/duckdb/connection.js +16 -2
- package/dist/cjs/store/duckdb/connection.js.map +1 -1
- package/dist/cjs/store/duckdb/open.d.cts +1 -1
- package/dist/cjs/store/duckdb/open.d.ts +1 -1
- package/dist/cjs/store/duckdb/open.js +73 -65
- package/dist/cjs/store/duckdb/open.js.map +1 -1
- package/dist/cjs/store/duckdb/reconcile.js +533 -15
- package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
- package/dist/cjs/store/embed-scope.js +19 -9
- package/dist/cjs/store/embed-scope.js.map +1 -1
- package/dist/cjs/store/index.d.cts +6 -0
- package/dist/cjs/store/index.d.ts +6 -0
- package/dist/cjs/store/index.js +8 -4
- package/dist/cjs/store/index.js.map +1 -1
- package/dist/cjs/store/open.d.cts +6 -1
- package/dist/cjs/store/open.d.ts +6 -1
- package/dist/cjs/store/open.js +230 -307
- package/dist/cjs/store/open.js.map +1 -1
- package/dist/cjs/store/readiness.d.cts +5 -0
- package/dist/cjs/store/readiness.d.ts +5 -0
- package/dist/cjs/store/readiness.js +39 -0
- package/dist/cjs/store/readiness.js.map +1 -0
- package/dist/cjs/store/reconcile.d.cts +26 -1
- package/dist/cjs/store/reconcile.d.ts +26 -1
- package/dist/cjs/store/reconcile.js +670 -576
- package/dist/cjs/store/reconcile.js.map +1 -1
- package/dist/cjs/store/signature.d.cts +1 -0
- package/dist/cjs/store/signature.d.ts +1 -0
- package/dist/cjs/store/signature.js +9 -0
- package/dist/cjs/store/signature.js.map +1 -1
- package/dist/cjs/store/sqlite/open.d.cts +1 -1
- package/dist/cjs/store/sqlite/open.d.ts +1 -1
- package/dist/cjs/store/sqlite/open.js +84 -90
- package/dist/cjs/store/sqlite/open.js.map +1 -1
- package/dist/cjs/store/sqlite/reconcile.js.map +1 -1
- package/dist/cjs/store/sqlite/store.js +63 -60
- package/dist/cjs/store/sqlite/store.js.map +1 -1
- package/dist/cjs/store/sqlite/vectors.d.cts +2 -2
- package/dist/cjs/store/sqlite/vectors.d.ts +2 -2
- package/dist/cjs/store/sqlite/vectors.js +8 -4
- package/dist/cjs/store/sqlite/vectors.js.map +1 -1
- package/dist/cjs/store/stages.js.map +1 -1
- package/dist/cjs/store/turso/connection.d.cts +1 -0
- package/dist/cjs/store/turso/connection.d.ts +1 -0
- package/dist/cjs/store/turso/connection.js +319 -94
- package/dist/cjs/store/turso/connection.js.map +1 -1
- package/dist/cjs/store/turso/open.d.cts +1 -1
- package/dist/cjs/store/turso/open.d.ts +1 -1
- package/dist/cjs/store/turso/open.js +70 -74
- package/dist/cjs/store/turso/open.js.map +1 -1
- package/dist/cjs/store/turso/store.js +25 -23
- package/dist/cjs/store/turso/store.js.map +1 -1
- package/dist/cjs/store/turso/vectors.d.cts +2 -2
- package/dist/cjs/store/turso/vectors.d.ts +2 -2
- package/dist/cjs/store/turso/vectors.js +26 -16
- package/dist/cjs/store/turso/vectors.js.map +1 -1
- package/dist/cjs/store/types.d.cts +4 -1
- package/dist/cjs/store/types.d.ts +4 -1
- package/dist/cjs/store/types.js.map +1 -1
- package/dist/cjs/store/vectors.d.cts +14 -1
- package/dist/cjs/store/vectors.d.ts +14 -1
- package/dist/cjs/store/vectors.js +61 -0
- package/dist/cjs/store/vectors.js.map +1 -1
- package/dist/cjs/watch.d.cts +6 -0
- package/dist/cjs/watch.d.ts +6 -0
- package/dist/cjs/watch.js +42 -10
- package/dist/cjs/watch.js.map +1 -1
- package/dist/cjs/workers/parse.d.cts +4 -3
- package/dist/cjs/workers/parse.d.ts +4 -3
- package/dist/cjs/workers/parse.js +19 -17
- package/dist/cjs/workers/parse.js.map +1 -1
- package/dist/esm/cli/index.d.ts +3 -3
- package/dist/esm/cli/index.js +3 -3
- package/dist/esm/cli/index.js.map +1 -1
- package/dist/esm/cli/path.js +4 -10
- package/dist/esm/cli/path.js.map +1 -1
- package/dist/esm/cli/peek.js +20 -2
- package/dist/esm/cli/peek.js.map +1 -1
- package/dist/esm/cli/related.js +5 -3
- package/dist/esm/cli/related.js.map +1 -1
- package/dist/esm/cli/search.js +12 -2
- package/dist/esm/cli/search.js.map +1 -1
- package/dist/esm/commands/index.d.ts +6 -3
- package/dist/esm/commands/index.js +1 -0
- package/dist/esm/commands/index.js.map +1 -1
- package/dist/esm/commands/map.d.ts +14 -5
- package/dist/esm/commands/map.js +2 -0
- package/dist/esm/commands/map.js.map +1 -1
- package/dist/esm/commands/path.d.ts +14 -0
- package/dist/esm/commands/path.js +33 -0
- package/dist/esm/commands/path.js.map +1 -0
- package/dist/esm/commands/peek.d.ts +25 -2
- package/dist/esm/commands/peek.js +59 -19
- package/dist/esm/commands/peek.js.map +1 -1
- package/dist/esm/commands/related.d.ts +16 -3
- package/dist/esm/commands/related.js +45 -28
- package/dist/esm/commands/related.js.map +1 -1
- package/dist/esm/commands/scope.d.ts +1 -0
- package/dist/esm/commands/scope.js +58 -0
- package/dist/esm/commands/scope.js.map +1 -1
- package/dist/esm/commands/search.d.ts +30 -3
- package/dist/esm/commands/search.js +95 -20
- package/dist/esm/commands/search.js.map +1 -1
- package/dist/esm/commands/signals.d.ts +22 -7
- package/dist/esm/commands/signals.js +74 -17
- package/dist/esm/commands/signals.js.map +1 -1
- package/dist/esm/commands/snapshot.d.ts +3 -0
- package/dist/esm/commands/snapshot.js +25 -0
- package/dist/esm/commands/snapshot.js.map +1 -0
- package/dist/esm/config/load.js +6 -0
- package/dist/esm/config/load.js.map +1 -1
- package/dist/esm/embed/cohere.js +35 -10
- package/dist/esm/embed/cohere.js.map +1 -1
- package/dist/esm/embed/endpoint.d.ts +2 -0
- package/dist/esm/embed/endpoint.js +11 -0
- package/dist/esm/embed/endpoint.js.map +1 -0
- package/dist/esm/embed/handoff.d.ts +7 -2
- package/dist/esm/embed/handoff.js +7 -0
- package/dist/esm/embed/handoff.js.map +1 -1
- package/dist/esm/embed/http.d.ts +18 -1
- package/dist/esm/embed/http.js +137 -17
- package/dist/esm/embed/http.js.map +1 -1
- package/dist/esm/embed/openai.js +34 -12
- package/dist/esm/embed/openai.js.map +1 -1
- package/dist/esm/embed/query.d.ts +9 -3
- package/dist/esm/embed/query.js +98 -26
- package/dist/esm/embed/query.js.map +1 -1
- package/dist/esm/embed/registry.d.ts +2 -2
- package/dist/esm/embed/registry.js +34 -5
- package/dist/esm/embed/registry.js.map +1 -1
- package/dist/esm/embed/static.js +10 -2
- package/dist/esm/embed/static.js.map +1 -1
- package/dist/esm/embed/store.js +30 -21
- package/dist/esm/embed/store.js.map +1 -1
- package/dist/esm/embed/types.d.ts +5 -2
- package/dist/esm/embed/types.js.map +1 -1
- package/dist/esm/features/embed.js +82 -21
- package/dist/esm/features/embed.js.map +1 -1
- package/dist/esm/graph/traverse.d.ts +2 -2
- package/dist/esm/graph/traverse.js +4 -2
- package/dist/esm/graph/traverse.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/output/output.d.ts +4 -36
- package/dist/esm/output/output.js +1 -2
- package/dist/esm/output/output.js.map +1 -1
- package/dist/esm/scan/pool.d.ts +2 -1
- package/dist/esm/scan/pool.js +80 -23
- package/dist/esm/scan/pool.js.map +1 -1
- package/dist/esm/scan/reparse.d.ts +1 -0
- package/dist/esm/scan/reparse.js +40 -12
- package/dist/esm/scan/reparse.js.map +1 -1
- package/dist/esm/store/builder.d.ts +11 -21
- package/dist/esm/store/builder.js +337 -133
- package/dist/esm/store/builder.js.map +1 -1
- package/dist/esm/store/duckdb/connection.js +9 -1
- package/dist/esm/store/duckdb/connection.js.map +1 -1
- package/dist/esm/store/duckdb/open.d.ts +1 -1
- package/dist/esm/store/duckdb/open.js +13 -9
- package/dist/esm/store/duckdb/open.js.map +1 -1
- package/dist/esm/store/duckdb/reconcile.js +145 -10
- package/dist/esm/store/duckdb/reconcile.js.map +1 -1
- package/dist/esm/store/embed-scope.js +16 -6
- package/dist/esm/store/embed-scope.js.map +1 -1
- package/dist/esm/store/index.d.ts +6 -0
- package/dist/esm/store/index.js +10 -4
- package/dist/esm/store/index.js.map +1 -1
- package/dist/esm/store/open.d.ts +6 -1
- package/dist/esm/store/open.js +77 -87
- package/dist/esm/store/open.js.map +1 -1
- package/dist/esm/store/readiness.d.ts +5 -0
- package/dist/esm/store/readiness.js +11 -0
- package/dist/esm/store/readiness.js.map +1 -0
- package/dist/esm/store/reconcile.d.ts +26 -1
- package/dist/esm/store/reconcile.js +142 -120
- package/dist/esm/store/reconcile.js.map +1 -1
- package/dist/esm/store/signature.d.ts +1 -0
- package/dist/esm/store/signature.js +8 -0
- package/dist/esm/store/signature.js.map +1 -1
- package/dist/esm/store/sqlite/open.d.ts +1 -1
- package/dist/esm/store/sqlite/open.js +12 -12
- package/dist/esm/store/sqlite/open.js.map +1 -1
- package/dist/esm/store/sqlite/reconcile.js +2 -2
- package/dist/esm/store/sqlite/reconcile.js.map +1 -1
- package/dist/esm/store/sqlite/store.js +21 -18
- package/dist/esm/store/sqlite/store.js.map +1 -1
- package/dist/esm/store/sqlite/vectors.d.ts +2 -2
- package/dist/esm/store/sqlite/vectors.js +7 -5
- package/dist/esm/store/sqlite/vectors.js.map +1 -1
- package/dist/esm/store/stages.js.map +1 -1
- package/dist/esm/store/turso/connection.d.ts +1 -0
- package/dist/esm/store/turso/connection.js +97 -28
- package/dist/esm/store/turso/connection.js.map +1 -1
- package/dist/esm/store/turso/open.d.ts +1 -1
- package/dist/esm/store/turso/open.js +9 -8
- package/dist/esm/store/turso/open.js.map +1 -1
- package/dist/esm/store/turso/store.js +16 -14
- package/dist/esm/store/turso/store.js.map +1 -1
- package/dist/esm/store/turso/vectors.d.ts +2 -2
- package/dist/esm/store/turso/vectors.js +27 -17
- package/dist/esm/store/turso/vectors.js.map +1 -1
- package/dist/esm/store/types.d.ts +4 -1
- package/dist/esm/store/types.js.map +1 -1
- package/dist/esm/store/vectors.d.ts +14 -1
- package/dist/esm/store/vectors.js +55 -0
- package/dist/esm/store/vectors.js.map +1 -1
- package/dist/esm/watch.d.ts +6 -0
- package/dist/esm/watch.js +40 -14
- package/dist/esm/watch.js.map +1 -1
- package/dist/esm/workers/parse.d.ts +4 -3
- package/dist/esm/workers/parse.js +19 -17
- package/dist/esm/workers/parse.js.map +1 -1
- package/package.json +1 -1
- package/skills/sense/EXAMPLES.md +3 -3
- package/skills/sense/SKILL.md +11 -3
- package/skills/sense/references/search.md +17 -1
- package/skills/sense/references/sql.md +1 -1
- package/skills/sense/references/stores/duckdb.md +1 -1
- package/skills/sense/references/terminology.md +32 -0
- package/skills/sense-setup/SKILL.md +2 -2
- package/skills/sense-setup/references/embeddings.md +1 -1
- package/skills/sense-setup/references/store-benchmarks.md +5 -5
- package/skills/sense-setup/references/stores/duckdb.md +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output/output.ts"],"sourcesContent":["// rows -> table (default) | json | csv\n\nexport type Row = Record<string, unknown>;\n\n// map/peek/status/path render structures rather than a row set, so csv has nothing to mean\n// there; the commands that emit rows take RowFormat.\nexport type Format = 'table' | 'json';\nexport type RowFormat = Format | 'csv';\n\n// Warned about on stderr (keeps --format json stdout machine-readable), never truncated.\nconst OVERSIZE_BYTES = 50_000;\n\n// Flatten embedded newlines so they can't break table column alignment; JSON output keeps them as-is.\nfunction cell(value: unknown): string {\n return String(value ?? '').replace(/\\s*\\n\\s*/g, ' ');\n}\n\n// RFC 4180, deliberately not cell(): csv is the redirect-to-file format, so it keeps every\n// character, newlines included; what it cannot carry (NULL vs empty, types) is what json is for.\n// An array (e.g. snippets) joins with the one delimiter our content provably cannot contain,\n// inside the quoted field newlines already trigger (DESIGN.md \"Rendering a list of snippets\").\nfunction csvField(value: unknown): string {\n const text = value === null || value === undefined ? '' : Array.isArray(value) ? value.join('\\n') : String(value);\n return /[\"\\r\\n,]/.test(text) ? `\"${text.replace(/\"/g, '\"\"')}\"` : text;\n}\n\nfunction csvLine(values: unknown[]): string {\n return values.map(csvField).join(',');\n}\n\nfunction warnOversize(bytes: number, rowCount: number): void {\n if (bytes <= OVERSIZE_BYTES) return;\n console.warn(`warning: result is ${Math.round(bytes / 1000)} KB across ${rowCount} row(s); consider a LIMIT, fewer columns, snippet() instead of whole values, or --format csv redirected to a file`);\n}\n\n// JSON.stringify throws on BigInt, and stores return int64 as BigInt (DuckDB does): a value\n// that fits a safe integer stays a number, a larger one becomes its decimal string.\nconst MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);\nconst bigintSafe = (_key: string, value: unknown): unknown => (typeof value === 'bigint' ? (value >= -MAX_SAFE && value <= MAX_SAFE ? Number(value) : value.toString()) : value);\n\nexport function stringifyJson(value: unknown, indent?: number): string {\n return JSON.stringify(value, bigintSafe, indent);\n}\n\nexport function printRows(rows: Row[], format: RowFormat, columns?: string[]): void {\n if (format === 'csv') {\n // Names from a row wherever there is one: the statement's column list keeps duplicates the\n // row collapsed. Nothing at all with no row and no columns -- a bare newline is an empty record.\n const names = rows.length > 0 ? Object.keys(rows[0]) : [...new Set(columns ?? [])];\n if (names.length === 0) return;\n const rendered = [csvLine(names), ...rows.map((row) => csvLine(names.map((name) => row[name])))].join('\\n');\n console.log(rendered);\n // Bytes, not code units -- CJK is 3 bytes per character.\n warnOversize(Buffer.byteLength(rendered), rows.length);\n return;\n }\n const rendered = renderRows(rows, format);\n console.log(rendered);\n warnOversize(Buffer.byteLength(rendered), rows.length);\n}\n\n// Streaming counterpart for the one unbounded caller, `sense sql`: the result is never held as\n// rows and again as a rendered string. Table buffers (column width depends on the last row); json/csv write row by row.\nexport async function printRowStream(rows: AsyncIterable<Row>, format: RowFormat, columns: string[]): Promise<void> {\n const iterator = rows[Symbol.asyncIterator]();\n // One step before any write. FTS5 parses a MATCH expression at step time rather than at\n // prepare time, so a bad search string throws here, with stdout still clean for the error.\n const first = await iterator.next();\n const rest: AsyncIterable<Row> = { [Symbol.asyncIterator]: () => iterator };\n\n if (format === 'table') {\n const collected: Row[] = first.done ? [] : [first.value];\n for await (const row of rest) collected.push(row);\n printRows(collected, format, columns);\n return;\n }\n\n let bytes = 0;\n let rowCount = 0;\n const write = (text: string): void => {\n // Bytes, not code units -- CJK is 3 bytes per character.\n bytes += Buffer.byteLength(text);\n process.stdout.write(text);\n };\n\n if (format === 'json') {\n // Byte-identical to JSON.stringify(rows, null, 2) + newline: each row re-indented one\n // level, comma-joined, inside the array brackets written here.\n const indent = (row: Row) =>\n stringifyJson(row, 2)\n .split('\\n')\n .map((line) => ` ${line}`)\n .join('\\n');\n if (first.done) {\n write('[]\\n');\n } else {\n write('[\\n');\n write(indent(first.value));\n rowCount = 1;\n for await (const row of rest) {\n write(',\\n');\n write(indent(row));\n rowCount++;\n }\n write('\\n]\\n');\n }\n } else if (first.done) {\n const names = [...new Set(columns)];\n if (names.length > 0) write(`${csvLine(names)}\\n`);\n } else {\n const names = Object.keys(first.value);\n write(`${csvLine(names)}\\n`);\n write(`${csvLine(names.map((name) => first.value[name]))}\\n`);\n rowCount = 1;\n for await (const row of rest) {\n write(`${csvLine(names.map((name) => row[name]))}\\n`);\n rowCount++;\n }\n }\n warnOversize(bytes, rowCount);\n}\n\n// Tables are for reading, and a row wider than the terminal wraps unreadably: widest columns\n// give up space first, down to a floor. --format json is never truncated.\nconst MIN_COLUMN = 12;\nconst TRUNCATED = '…';\n\nfunction fitWidths(widths: number[], limit: number): number[] {\n const gaps = (widths.length - 1) * 2;\n const fitted = [...widths];\n for (;;) {\n const total = fitted.reduce((a, b) => a + b, 0) + gaps;\n if (total <= limit) return fitted;\n const widest = fitted.indexOf(Math.max(...fitted));\n if (fitted[widest] <= MIN_COLUMN) return fitted;\n fitted[widest] = Math.max(MIN_COLUMN, fitted[widest] - Math.max(1, total - limit));\n }\n}\n\n// An array cell (snippets) becomes one physical line per element; every other value stays the\n// single line cell() already produced. No cell in any other command holds one today, so a row\n// with no array-valued cell yields exactly one physical line, unchanged from before this existed.\nfunction cellLines(value: unknown): string[] {\n if (Array.isArray(value)) return value.length === 0 ? [''] : value.map((v) => cell(v));\n return [cell(value)];\n}\n\nfunction renderRows(rows: Row[], format: Format, width = process.stdout.columns): string {\n if (format === 'json') return stringifyJson(rows, 2);\n if (rows.length === 0) return '(0 rows)';\n\n const columns = Object.keys(rows[0]);\n const linesByCell = rows.map((row) => columns.map((col) => cellLines(row[col])));\n const heights = linesByCell.map((cellsInRow) => Math.max(1, ...cellsInRow.map((lines) => lines.length)));\n const natural = columns.map((col, i) => Math.max(col.length, ...linesByCell.flatMap((cellsInRow) => cellsInRow[i]).map((line) => line.length)));\n const widths = width && width > MIN_COLUMN ? fitWidths(natural, width) : natural;\n\n const clip = (value: string, w: number) => (value.length <= w ? value.padEnd(w) : `${value.slice(0, w - 1)}${TRUNCATED}`);\n const formatRow = (values: string[]) =>\n values\n .map((value, i) => clip(value, widths[i]))\n .join(' ')\n .trimEnd();\n\n // A row becomes as many physical lines as its tallest cell; other columns blank-pad on\n // continuation lines (DESIGN.md \"Rendering a list of snippets\").\n const body: string[] = [];\n for (let r = 0; r < rows.length; r++) {\n for (let li = 0; li < heights[r]; li++) body.push(formatRow(columns.map((_, c) => linesByCell[r][c][li] ?? '')));\n }\n\n return [formatRow(columns), widths.map((w) => '-'.repeat(w)).join(' '), ...body].join('\\n');\n}\n\n// Text renderers for the top-level commands; cli.ts prints what these return.\n\n// Names the config key that turns a feature back on. Only the three features-block toggles\n// reach here; `embed` has its own status line.\nexport function featureOffHint(name: string): string {\n return `features.${name}`;\n}\n\nexport function featuresLine(features: { on: string[]; off: string[] }): string {\n const off = features.off.length > 0 ? ` · off: ${features.off.map((name) => `${name} (${featureOffHint(name)})`).join(', ')}` : '';\n return `features: ${features.on.join(', ')}${off}`;\n}\n\n// name, or name:weight when the weight is not the default 1.\nfunction signalLabels(signals: Record<string, number>): string {\n return Object.entries(signals)\n .map(([name, weight]) => (weight === 1 ? name : `${name}:${weight}`))\n .join(', ');\n}\n\nexport function presetsLines(presets: Array<{ name: string; files: number; embedded: number; signals: Record<string, number> }>): string[] {\n // \"0 embedded\" is ambiguous between a scope that declined vectors and one that has not built\n // them yet, so a preset without the vectors signal says what it uses instead.\n return ['presets:', ...presets.map((p) => ` ${p.name}: ${p.files} file(s), ${p.embedded} embedded${p.signals.vectors !== undefined ? '' : ` (signals: ${signalLabels(p.signals)})`}`)];\n}\n\nexport function renderMap(result: {\n docs: { count: number; bytes: number };\n fields: Row[];\n fieldsTotal: number;\n features: { on: string[]; off: string[] };\n presets: Array<{ name: string; files: number; embedded: number; signals: Record<string, number> }>;\n hubs: Row[];\n recent: Row[];\n recentCaveat: string | null;\n}): string {\n const parts = [`docs: ${result.docs.count} (${Math.round(result.docs.bytes / 1024)} KB)`, featuresLine(result.features), '', ...presetsLines(result.presets), '', renderRows(result.fields, 'table')];\n if (result.fieldsTotal > result.fields.length) parts.push(`(+${result.fieldsTotal - result.fields.length} more fields)`);\n if (result.hubs.length > 0) parts.push('\\nhubs (by link rank):', renderRows(result.hubs, 'table'));\n else if (result.features.off.includes('rank')) parts.push(`\\nhubs: off (${featureOffHint('rank')})`);\n parts.push('\\nrecent:');\n if (result.recentCaveat) parts.push(result.recentCaveat);\n parts.push(renderRows(result.recent, 'table'));\n return parts.join('\\n');\n}\n\nexport function renderPeek(result: { path: string; tokens: number; frontmatter: Row; parseError?: string | null; sections: Row[]; outbound: string[]; backlinks: string[]; unresolved: string[]; sectionsTotal: number; outboundTotal: number; backlinksTotal: number; unresolvedTotal: number; off: string[] }): string {\n const lines = [`${result.path} (~${result.tokens} tokens)`];\n // Otherwise a refused parse is indistinguishable from a note that has no frontmatter, which\n // is the confusion the whole quarantine design exists to remove.\n if (result.parseError) lines.push(` frontmatter: did not parse, so none of it is indexed (${result.parseError})`);\n for (const [key, value] of Object.entries(result.frontmatter)) lines.push(` ${key}: ${value}`);\n if (result.sections.length > 0) {\n lines.push('', 'sections:');\n for (const s of result.sections) lines.push(` ${'#'.repeat(s.level as number)} ${s.heading} [L${s.start_line}-${s.end_line}, ~${s.tokens}t]`);\n if (result.sectionsTotal > result.sections.length) lines.push(` (+${result.sectionsTotal - result.sections.length} more sections -- sections table has all of them)`);\n } else if (result.off.includes('sections')) lines.push('', 'sections: off (features.sections)');\n if (result.off.includes('links')) {\n lines.push('', 'links: off (features.links)');\n } else {\n const linkLine = (label: string, shown: string[], total: number) => {\n if (total > 0) lines.push(`${label} (${total}): ${shown.join(', ')}${total > shown.length ? `, +${total - shown.length} more` : ''}`);\n };\n if (result.outboundTotal + result.unresolvedTotal + result.backlinksTotal > 0) lines.push('');\n linkLine('links out', result.outbound, result.outboundTotal);\n linkLine('unresolved', result.unresolved, result.unresolvedTotal);\n linkLine('backlinks', result.backlinks, result.backlinksTotal);\n }\n return lines.join('\\n');\n}\n"],"names":["OVERSIZE_BYTES","cell","value","String","replace","csvField","text","undefined","Array","isArray","join","test","csvLine","values","map","warnOversize","bytes","rowCount","console","warn","Math","round","MAX_SAFE","BigInt","Number","MAX_SAFE_INTEGER","bigintSafe","_key","toString","stringifyJson","indent","JSON","stringify","printRows","rows","format","columns","names","length","Object","keys","Set","rendered","row","name","log","Buffer","byteLength","renderRows","printRowStream","iterator","Symbol","asyncIterator","first","next","rest","collected","done","push","write","process","stdout","split","line","MIN_COLUMN","TRUNCATED","fitWidths","widths","limit","gaps","fitted","total","reduce","a","b","widest","indexOf","max","cellLines","v","width","linesByCell","col","heights","cellsInRow","lines","natural","i","flatMap","clip","w","padEnd","slice","formatRow","trimEnd","body","r","li","_","c","repeat","featureOffHint","featuresLine","features","off","on","signalLabels","signals","entries","weight","presetsLines","presets","p","files","embedded","vectors","renderMap","result","parts","docs","count","fields","fieldsTotal","hubs","includes","recentCaveat","recent","renderPeek","path","tokens","parseError","key","frontmatter","sections","s","level","heading","start_line","end_line","sectionsTotal","linkLine","label","shown","outboundTotal","unresolvedTotal","backlinksTotal","outbound","unresolved","backlinks"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output/output.ts"],"sourcesContent":["import type { TreeMap } from '../commands/map.ts';\nimport type { Peek } from '../commands/peek.ts';\n\n// rows -> table (default) | json | csv\n\nexport type Row = Record<string, unknown>;\n\n// map/peek/status/path render structures rather than a row set, so csv has nothing to mean\n// there; the commands that emit rows take RowFormat.\nexport type Format = 'table' | 'json';\nexport type RowFormat = Format | 'csv';\n\n// Warned about on stderr (keeps --format json stdout machine-readable), never truncated.\nconst OVERSIZE_BYTES = 50_000;\n\n// Flatten embedded newlines so they can't break table column alignment; JSON output keeps them as-is.\nfunction cell(value: unknown): string {\n return String(value ?? '').replace(/\\s*\\n\\s*/g, ' ');\n}\n\n// RFC 4180, deliberately not cell(): csv is the redirect-to-file format, so it keeps every\n// character, newlines included; what it cannot carry (NULL vs empty, types) is what json is for.\n// An array (e.g. snippets) joins with the one delimiter our content provably cannot contain,\n// inside the quoted field newlines already trigger (DESIGN.md \"Rendering a list of snippets\").\nfunction csvField(value: unknown): string {\n const text = value === null || value === undefined ? '' : Array.isArray(value) ? value.join('\\n') : String(value);\n return /[\"\\r\\n,]/.test(text) ? `\"${text.replace(/\"/g, '\"\"')}\"` : text;\n}\n\nfunction csvLine(values: unknown[]): string {\n return values.map(csvField).join(',');\n}\n\nfunction warnOversize(bytes: number, rowCount: number): void {\n if (bytes <= OVERSIZE_BYTES) return;\n console.warn(`warning: result is ${Math.round(bytes / 1000)} KB across ${rowCount} row(s); consider a LIMIT, fewer columns, snippet() instead of whole values, or --format csv redirected to a file`);\n}\n\n// JSON.stringify throws on BigInt, and stores return int64 as BigInt (DuckDB does): a value\n// that fits a safe integer stays a number, a larger one becomes its decimal string.\nconst MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);\nconst bigintSafe = (_key: string, value: unknown): unknown => (typeof value === 'bigint' ? (value >= -MAX_SAFE && value <= MAX_SAFE ? Number(value) : value.toString()) : value);\n\nexport function stringifyJson(value: unknown, indent?: number): string {\n return JSON.stringify(value, bigintSafe, indent);\n}\n\nexport function printRows(rows: Row[], format: RowFormat, columns?: string[]): void {\n if (format === 'csv') {\n // Names from a row wherever there is one: the statement's column list keeps duplicates the\n // row collapsed. Nothing at all with no row and no columns -- a bare newline is an empty record.\n const names = rows.length > 0 ? Object.keys(rows[0]) : [...new Set(columns ?? [])];\n if (names.length === 0) return;\n const rendered = [csvLine(names), ...rows.map((row) => csvLine(names.map((name) => row[name])))].join('\\n');\n console.log(rendered);\n // Bytes, not code units -- CJK is 3 bytes per character.\n warnOversize(Buffer.byteLength(rendered), rows.length);\n return;\n }\n const rendered = renderRows(rows, format);\n console.log(rendered);\n warnOversize(Buffer.byteLength(rendered), rows.length);\n}\n\n// Streaming counterpart for the one unbounded caller, `sense sql`: the result is never held as\n// rows and again as a rendered string. Table buffers (column width depends on the last row); json/csv write row by row.\nexport async function printRowStream(rows: AsyncIterable<Row>, format: RowFormat, columns: string[]): Promise<void> {\n const iterator = rows[Symbol.asyncIterator]();\n // One step before any write. FTS5 parses a MATCH expression at step time rather than at\n // prepare time, so a bad search string throws here, with stdout still clean for the error.\n const first = await iterator.next();\n const rest: AsyncIterable<Row> = { [Symbol.asyncIterator]: () => iterator };\n\n if (format === 'table') {\n const collected: Row[] = first.done ? [] : [first.value];\n for await (const row of rest) collected.push(row);\n printRows(collected, format, columns);\n return;\n }\n\n let bytes = 0;\n let rowCount = 0;\n const write = (text: string): void => {\n // Bytes, not code units -- CJK is 3 bytes per character.\n bytes += Buffer.byteLength(text);\n process.stdout.write(text);\n };\n\n if (format === 'json') {\n // Byte-identical to JSON.stringify(rows, null, 2) + newline: each row re-indented one\n // level, comma-joined, inside the array brackets written here.\n const indent = (row: Row) =>\n stringifyJson(row, 2)\n .split('\\n')\n .map((line) => ` ${line}`)\n .join('\\n');\n if (first.done) {\n write('[]\\n');\n } else {\n write('[\\n');\n write(indent(first.value));\n rowCount = 1;\n for await (const row of rest) {\n write(',\\n');\n write(indent(row));\n rowCount++;\n }\n write('\\n]\\n');\n }\n } else if (first.done) {\n const names = [...new Set(columns)];\n if (names.length > 0) write(`${csvLine(names)}\\n`);\n } else {\n const names = Object.keys(first.value);\n write(`${csvLine(names)}\\n`);\n write(`${csvLine(names.map((name) => first.value[name]))}\\n`);\n rowCount = 1;\n for await (const row of rest) {\n write(`${csvLine(names.map((name) => row[name]))}\\n`);\n rowCount++;\n }\n }\n warnOversize(bytes, rowCount);\n}\n\n// Tables are for reading, and a row wider than the terminal wraps unreadably: widest columns\n// give up space first, down to a floor. --format json is never truncated.\nconst MIN_COLUMN = 12;\nconst TRUNCATED = '…';\n\nfunction fitWidths(widths: number[], limit: number): number[] {\n const gaps = (widths.length - 1) * 2;\n const fitted = [...widths];\n for (;;) {\n const total = fitted.reduce((a, b) => a + b, 0) + gaps;\n if (total <= limit) return fitted;\n const widest = fitted.indexOf(Math.max(...fitted));\n if (fitted[widest] <= MIN_COLUMN) return fitted;\n fitted[widest] = Math.max(MIN_COLUMN, fitted[widest] - Math.max(1, total - limit));\n }\n}\n\n// An array cell (snippets) becomes one physical line per element; every other value stays the\n// single line cell() already produced. No cell in any other command holds one today, so a row\n// with no array-valued cell yields exactly one physical line, unchanged from before this existed.\nfunction cellLines(value: unknown): string[] {\n if (Array.isArray(value)) return value.length === 0 ? [''] : value.map((v) => cell(v));\n return [cell(value)];\n}\n\nfunction renderRows(rows: object[], format: Format, width = process.stdout.columns): string {\n if (format === 'json') return stringifyJson(rows, 2);\n if (rows.length === 0) return '(0 rows)';\n\n const columns = Object.keys(rows[0]);\n const linesByCell = rows.map((row) => columns.map((col) => cellLines(Reflect.get(row, col))));\n const heights = linesByCell.map((cellsInRow) => Math.max(1, ...cellsInRow.map((lines) => lines.length)));\n const natural = columns.map((col, i) => Math.max(col.length, ...linesByCell.flatMap((cellsInRow) => cellsInRow[i]).map((line) => line.length)));\n const widths = width && width > MIN_COLUMN ? fitWidths(natural, width) : natural;\n\n const clip = (value: string, w: number) => (value.length <= w ? value.padEnd(w) : `${value.slice(0, w - 1)}${TRUNCATED}`);\n const formatRow = (values: string[]) =>\n values\n .map((value, i) => clip(value, widths[i]))\n .join(' ')\n .trimEnd();\n\n // A row becomes as many physical lines as its tallest cell; other columns blank-pad on\n // continuation lines (DESIGN.md \"Rendering a list of snippets\").\n const body: string[] = [];\n for (let r = 0; r < rows.length; r++) {\n for (let li = 0; li < heights[r]; li++) body.push(formatRow(columns.map((_, c) => linesByCell[r][c][li] ?? '')));\n }\n\n return [formatRow(columns), widths.map((w) => '-'.repeat(w)).join(' '), ...body].join('\\n');\n}\n\n// Text renderers for the top-level commands; cli.ts prints what these return.\n\n// Names the config key that turns a feature back on. Only the three features-block toggles\n// reach here; `embed` has its own status line.\nexport function featureOffHint(name: string): string {\n return `features.${name}`;\n}\n\nexport function featuresLine(features: { on: string[]; off: string[] }): string {\n const off = features.off.length > 0 ? ` · off: ${features.off.map((name) => `${name} (${featureOffHint(name)})`).join(', ')}` : '';\n return `features: ${features.on.join(', ')}${off}`;\n}\n\n// name, or name:weight when the weight is not the default 1.\nfunction signalLabels(signals: Record<string, number>): string {\n return Object.entries(signals)\n .map(([name, weight]) => (weight === 1 ? name : `${name}:${weight}`))\n .join(', ');\n}\n\nexport function presetsLines(presets: Array<{ name: string; files: number; embedded: number; signals: Record<string, number> }>): string[] {\n // \"0 embedded\" is ambiguous between a scope that declined vectors and one that has not built\n // them yet, so a preset without the vectors signal says what it uses instead.\n return ['presets:', ...presets.map((p) => ` ${p.name}: ${p.files} file(s), ${p.embedded} embedded${p.signals.vectors !== undefined ? '' : ` (signals: ${signalLabels(p.signals)})`}`)];\n}\n\nexport function renderMap(result: TreeMap): string {\n const parts = [`docs: ${result.docs.count} (${Math.round(result.docs.bytes / 1024)} KB)`, featuresLine(result.features), '', ...presetsLines(result.presets), '', renderRows(result.fields, 'table')];\n if (result.fieldsTotal > result.fields.length) parts.push(`(+${result.fieldsTotal - result.fields.length} more fields)`);\n if (result.hubs.length > 0) parts.push('\\nhubs (by link rank):', renderRows(result.hubs, 'table'));\n else if (result.features.off.includes('rank')) parts.push(`\\nhubs: off (${featureOffHint('rank')})`);\n parts.push('\\nrecent:');\n if (result.recentCaveat) parts.push(result.recentCaveat);\n parts.push(renderRows(result.recent, 'table'));\n return parts.join('\\n');\n}\n\nexport function renderPeek(result: Peek): string {\n const lines = [`${result.path} (~${result.tokens} tokens)`];\n // Otherwise a refused parse is indistinguishable from a note that has no frontmatter, which\n // is the confusion the whole quarantine design exists to remove.\n if (result.parseError) lines.push(` frontmatter: did not parse, so none of it is indexed (${result.parseError})`);\n for (const [key, value] of Object.entries(result.frontmatter)) lines.push(` ${key}: ${value}`);\n if (result.sections.length > 0) {\n lines.push('', 'sections:');\n for (const s of result.sections) lines.push(` ${'#'.repeat(s.level)} ${s.heading} [L${s.start_line}-${s.end_line}, ~${s.tokens}t]`);\n if (result.sectionsTotal > result.sections.length) lines.push(` (+${result.sectionsTotal - result.sections.length} more sections -- sections table has all of them)`);\n } else if (result.off.includes('sections')) lines.push('', 'sections: off (features.sections)');\n if (result.off.includes('links')) {\n lines.push('', 'links: off (features.links)');\n } else {\n const linkLine = (label: string, shown: string[], total: number) => {\n if (total > 0) lines.push(`${label} (${total}): ${shown.join(', ')}${total > shown.length ? `, +${total - shown.length} more` : ''}`);\n };\n if (result.outboundTotal + result.unresolvedTotal + result.backlinksTotal > 0) lines.push('');\n linkLine('links out', result.outbound, result.outboundTotal);\n linkLine('unresolved', result.unresolved, result.unresolvedTotal);\n linkLine('backlinks', result.backlinks, result.backlinksTotal);\n }\n return lines.join('\\n');\n}\n"],"names":["OVERSIZE_BYTES","cell","value","String","replace","csvField","text","undefined","Array","isArray","join","test","csvLine","values","map","warnOversize","bytes","rowCount","console","warn","Math","round","MAX_SAFE","BigInt","Number","MAX_SAFE_INTEGER","bigintSafe","_key","toString","stringifyJson","indent","JSON","stringify","printRows","rows","format","columns","names","length","Object","keys","Set","rendered","row","name","log","Buffer","byteLength","renderRows","printRowStream","iterator","Symbol","asyncIterator","first","next","rest","collected","done","push","write","process","stdout","split","line","MIN_COLUMN","TRUNCATED","fitWidths","widths","limit","gaps","fitted","total","reduce","a","b","widest","indexOf","max","cellLines","v","width","linesByCell","col","Reflect","get","heights","cellsInRow","lines","natural","i","flatMap","clip","w","padEnd","slice","formatRow","trimEnd","body","r","li","_","c","repeat","featureOffHint","featuresLine","features","off","on","signalLabels","signals","entries","weight","presetsLines","presets","p","files","embedded","vectors","renderMap","result","parts","docs","count","fields","fieldsTotal","hubs","includes","recentCaveat","recent","renderPeek","path","tokens","parseError","key","frontmatter","sections","s","level","heading","start_line","end_line","sectionsTotal","linkLine","label","shown","outboundTotal","unresolvedTotal","backlinksTotal","outbound","unresolved","backlinks"],"mappings":"AAYA,yFAAyF;AACzF,MAAMA,iBAAiB;AAEvB,sGAAsG;AACtG,SAASC,KAAKC,KAAc;IAC1B,OAAOC,OAAOD,kBAAAA,mBAAAA,QAAS,IAAIE,OAAO,CAAC,aAAa;AAClD;AAEA,2FAA2F;AAC3F,iGAAiG;AACjG,6FAA6F;AAC7F,+FAA+F;AAC/F,SAASC,SAASH,KAAc;IAC9B,MAAMI,OAAOJ,UAAU,QAAQA,UAAUK,YAAY,KAAKC,MAAMC,OAAO,CAACP,SAASA,MAAMQ,IAAI,CAAC,QAAQP,OAAOD;IAC3G,OAAO,WAAWS,IAAI,CAACL,QAAQ,CAAC,CAAC,EAAEA,KAAKF,OAAO,CAAC,MAAM,MAAM,CAAC,CAAC,GAAGE;AACnE;AAEA,SAASM,QAAQC,MAAiB;IAChC,OAAOA,OAAOC,GAAG,CAACT,UAAUK,IAAI,CAAC;AACnC;AAEA,SAASK,aAAaC,KAAa,EAAEC,QAAgB;IACnD,IAAID,SAAShB,gBAAgB;IAC7BkB,QAAQC,IAAI,CAAC,CAAC,mBAAmB,EAAEC,KAAKC,KAAK,CAACL,QAAQ,MAAM,WAAW,EAAEC,SAAS,iHAAiH,CAAC;AACtM;AAEA,4FAA4F;AAC5F,oFAAoF;AACpF,MAAMK,WAAWC,OAAOC,OAAOC,gBAAgB;AAC/C,MAAMC,aAAa,CAACC,MAAczB,QAA6B,OAAOA,UAAU,WAAYA,SAAS,CAACoB,YAAYpB,SAASoB,WAAWE,OAAOtB,SAASA,MAAM0B,QAAQ,KAAM1B;AAE1K,OAAO,SAAS2B,cAAc3B,KAAc,EAAE4B,MAAe;IAC3D,OAAOC,KAAKC,SAAS,CAAC9B,OAAOwB,YAAYI;AAC3C;AAEA,OAAO,SAASG,UAAUC,IAAW,EAAEC,MAAiB,EAAEC,OAAkB;IAC1E,IAAID,WAAW,OAAO;QACpB,2FAA2F;QAC3F,iGAAiG;QACjG,MAAME,QAAQH,KAAKI,MAAM,GAAG,IAAIC,OAAOC,IAAI,CAACN,IAAI,CAAC,EAAE,IAAI;eAAI,IAAIO,IAAIL,oBAAAA,qBAAAA,UAAW,EAAE;SAAE;QAClF,IAAIC,MAAMC,MAAM,KAAK,GAAG;QACxB,MAAMI,WAAW;YAAC9B,QAAQyB;eAAWH,KAAKpB,GAAG,CAAC,CAAC6B,MAAQ/B,QAAQyB,MAAMvB,GAAG,CAAC,CAAC8B,OAASD,GAAG,CAACC,KAAK;SAAI,CAAClC,IAAI,CAAC;QACtGQ,QAAQ2B,GAAG,CAACH;QACZ,yDAAyD;QACzD3B,aAAa+B,OAAOC,UAAU,CAACL,WAAWR,KAAKI,MAAM;QACrD;IACF;IACA,MAAMI,WAAWM,WAAWd,MAAMC;IAClCjB,QAAQ2B,GAAG,CAACH;IACZ3B,aAAa+B,OAAOC,UAAU,CAACL,WAAWR,KAAKI,MAAM;AACvD;AAEA,+FAA+F;AAC/F,wHAAwH;AACxH,OAAO,eAAeW,eAAef,IAAwB,EAAEC,MAAiB,EAAEC,OAAiB;IACjG,MAAMc,WAAWhB,IAAI,CAACiB,OAAOC,aAAa,CAAC;IAC3C,wFAAwF;IACxF,2FAA2F;IAC3F,MAAMC,QAAQ,MAAMH,SAASI,IAAI;IACjC,MAAMC,OAA2B;QAAE,CAACJ,OAAOC,aAAa,CAAC,EAAE,IAAMF;IAAS;IAE1E,IAAIf,WAAW,SAAS;QACtB,MAAMqB,YAAmBH,MAAMI,IAAI,GAAG,EAAE,GAAG;YAACJ,MAAMnD,KAAK;SAAC;QACxD,WAAW,MAAMyC,OAAOY,KAAMC,UAAUE,IAAI,CAACf;QAC7CV,UAAUuB,WAAWrB,QAAQC;QAC7B;IACF;IAEA,IAAIpB,QAAQ;IACZ,IAAIC,WAAW;IACf,MAAM0C,QAAQ,CAACrD;QACb,yDAAyD;QACzDU,SAAS8B,OAAOC,UAAU,CAACzC;QAC3BsD,QAAQC,MAAM,CAACF,KAAK,CAACrD;IACvB;IAEA,IAAI6B,WAAW,QAAQ;QACrB,sFAAsF;QACtF,+DAA+D;QAC/D,MAAML,SAAS,CAACa,MACdd,cAAcc,KAAK,GAChBmB,KAAK,CAAC,MACNhD,GAAG,CAAC,CAACiD,OAAS,CAAC,EAAE,EAAEA,MAAM,EACzBrD,IAAI,CAAC;QACV,IAAI2C,MAAMI,IAAI,EAAE;YACdE,MAAM;QACR,OAAO;YACLA,MAAM;YACNA,MAAM7B,OAAOuB,MAAMnD,KAAK;YACxBe,WAAW;YACX,WAAW,MAAM0B,OAAOY,KAAM;gBAC5BI,MAAM;gBACNA,MAAM7B,OAAOa;gBACb1B;YACF;YACA0C,MAAM;QACR;IACF,OAAO,IAAIN,MAAMI,IAAI,EAAE;QACrB,MAAMpB,QAAQ;eAAI,IAAII,IAAIL;SAAS;QACnC,IAAIC,MAAMC,MAAM,GAAG,GAAGqB,MAAM,GAAG/C,QAAQyB,OAAO,EAAE,CAAC;IACnD,OAAO;QACL,MAAMA,QAAQE,OAAOC,IAAI,CAACa,MAAMnD,KAAK;QACrCyD,MAAM,GAAG/C,QAAQyB,OAAO,EAAE,CAAC;QAC3BsB,MAAM,GAAG/C,QAAQyB,MAAMvB,GAAG,CAAC,CAAC8B,OAASS,MAAMnD,KAAK,CAAC0C,KAAK,GAAG,EAAE,CAAC;QAC5D3B,WAAW;QACX,WAAW,MAAM0B,OAAOY,KAAM;YAC5BI,MAAM,GAAG/C,QAAQyB,MAAMvB,GAAG,CAAC,CAAC8B,OAASD,GAAG,CAACC,KAAK,GAAG,EAAE,CAAC;YACpD3B;QACF;IACF;IACAF,aAAaC,OAAOC;AACtB;AAEA,6FAA6F;AAC7F,0EAA0E;AAC1E,MAAM+C,aAAa;AACnB,MAAMC,YAAY;AAElB,SAASC,UAAUC,MAAgB,EAAEC,KAAa;IAChD,MAAMC,OAAO,AAACF,CAAAA,OAAO7B,MAAM,GAAG,CAAA,IAAK;IACnC,MAAMgC,SAAS;WAAIH;KAAO;IAC1B,OAAS;QACP,MAAMI,QAAQD,OAAOE,MAAM,CAAC,CAACC,GAAGC,IAAMD,IAAIC,GAAG,KAAKL;QAClD,IAAIE,SAASH,OAAO,OAAOE;QAC3B,MAAMK,SAASL,OAAOM,OAAO,CAACxD,KAAKyD,GAAG,IAAIP;QAC1C,IAAIA,MAAM,CAACK,OAAO,IAAIX,YAAY,OAAOM;QACzCA,MAAM,CAACK,OAAO,GAAGvD,KAAKyD,GAAG,CAACb,YAAYM,MAAM,CAACK,OAAO,GAAGvD,KAAKyD,GAAG,CAAC,GAAGN,QAAQH;IAC7E;AACF;AAEA,8FAA8F;AAC9F,8FAA8F;AAC9F,kGAAkG;AAClG,SAASU,UAAU5E,KAAc;IAC/B,IAAIM,MAAMC,OAAO,CAACP,QAAQ,OAAOA,MAAMoC,MAAM,KAAK,IAAI;QAAC;KAAG,GAAGpC,MAAMY,GAAG,CAAC,CAACiE,IAAM9E,KAAK8E;IACnF,OAAO;QAAC9E,KAAKC;KAAO;AACtB;AAEA,SAAS8C,WAAWd,IAAc,EAAEC,MAAc,EAAE6C,QAAQpB,QAAQC,MAAM,CAACzB,OAAO;IAChF,IAAID,WAAW,QAAQ,OAAON,cAAcK,MAAM;IAClD,IAAIA,KAAKI,MAAM,KAAK,GAAG,OAAO;IAE9B,MAAMF,UAAUG,OAAOC,IAAI,CAACN,IAAI,CAAC,EAAE;IACnC,MAAM+C,cAAc/C,KAAKpB,GAAG,CAAC,CAAC6B,MAAQP,QAAQtB,GAAG,CAAC,CAACoE,MAAQJ,UAAUK,QAAQC,GAAG,CAACzC,KAAKuC;IACtF,MAAMG,UAAUJ,YAAYnE,GAAG,CAAC,CAACwE,aAAelE,KAAKyD,GAAG,CAAC,MAAMS,WAAWxE,GAAG,CAAC,CAACyE,QAAUA,MAAMjD,MAAM;IACrG,MAAMkD,UAAUpD,QAAQtB,GAAG,CAAC,CAACoE,KAAKO,IAAMrE,KAAKyD,GAAG,CAACK,IAAI5C,MAAM,KAAK2C,YAAYS,OAAO,CAAC,CAACJ,aAAeA,UAAU,CAACG,EAAE,EAAE3E,GAAG,CAAC,CAACiD,OAASA,KAAKzB,MAAM;IAC5I,MAAM6B,SAASa,SAASA,QAAQhB,aAAaE,UAAUsB,SAASR,SAASQ;IAEzE,MAAMG,OAAO,CAACzF,OAAe0F,IAAe1F,MAAMoC,MAAM,IAAIsD,IAAI1F,MAAM2F,MAAM,CAACD,KAAK,GAAG1F,MAAM4F,KAAK,CAAC,GAAGF,IAAI,KAAK3B,WAAW;IACxH,MAAM8B,YAAY,CAAClF,SACjBA,OACGC,GAAG,CAAC,CAACZ,OAAOuF,IAAME,KAAKzF,OAAOiE,MAAM,CAACsB,EAAE,GACvC/E,IAAI,CAAC,MACLsF,OAAO;IAEZ,uFAAuF;IACvF,iEAAiE;IACjE,MAAMC,OAAiB,EAAE;IACzB,IAAK,IAAIC,IAAI,GAAGA,IAAIhE,KAAKI,MAAM,EAAE4D,IAAK;QACpC,IAAK,IAAIC,KAAK,GAAGA,KAAKd,OAAO,CAACa,EAAE,EAAEC,KAAMF,KAAKvC,IAAI,CAACqC,UAAU3D,QAAQtB,GAAG,CAAC,CAACsF,GAAGC;gBAAMpB;oBAAAA,sBAAAA,WAAW,CAACiB,EAAE,CAACG,EAAE,CAACF,GAAG,cAArBlB,iCAAAA,sBAAyB;;IAC7G;IAEA,OAAO;QAACc,UAAU3D;QAAU+B,OAAOrD,GAAG,CAAC,CAAC8E,IAAM,IAAIU,MAAM,CAACV,IAAIlF,IAAI,CAAC;WAAUuF;KAAK,CAACvF,IAAI,CAAC;AACzF;AAEA,8EAA8E;AAE9E,2FAA2F;AAC3F,+CAA+C;AAC/C,OAAO,SAAS6F,eAAe3D,IAAY;IACzC,OAAO,CAAC,SAAS,EAAEA,MAAM;AAC3B;AAEA,OAAO,SAAS4D,aAAaC,QAAyC;IACpE,MAAMC,MAAMD,SAASC,GAAG,CAACpE,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAEmE,SAASC,GAAG,CAAC5F,GAAG,CAAC,CAAC8B,OAAS,GAAGA,KAAK,EAAE,EAAE2D,eAAe3D,MAAM,CAAC,CAAC,EAAElC,IAAI,CAAC,OAAO,GAAG;IAChI,OAAO,CAAC,UAAU,EAAE+F,SAASE,EAAE,CAACjG,IAAI,CAAC,QAAQgG,KAAK;AACpD;AAEA,6DAA6D;AAC7D,SAASE,aAAaC,OAA+B;IACnD,OAAOtE,OAAOuE,OAAO,CAACD,SACnB/F,GAAG,CAAC,CAAC,CAAC8B,MAAMmE,OAAO,GAAMA,WAAW,IAAInE,OAAO,GAAGA,KAAK,CAAC,EAAEmE,QAAQ,EAClErG,IAAI,CAAC;AACV;AAEA,OAAO,SAASsG,aAAaC,OAAkG;IAC7H,6FAA6F;IAC7F,8EAA8E;IAC9E,OAAO;QAAC;WAAeA,QAAQnG,GAAG,CAAC,CAACoG,IAAM,CAAC,EAAE,EAAEA,EAAEtE,IAAI,CAAC,EAAE,EAAEsE,EAAEC,KAAK,CAAC,UAAU,EAAED,EAAEE,QAAQ,CAAC,SAAS,EAAEF,EAAEL,OAAO,CAACQ,OAAO,KAAK9G,YAAY,KAAK,CAAC,WAAW,EAAEqG,aAAaM,EAAEL,OAAO,EAAE,CAAC,CAAC,EAAE;KAAE;AACzL;AAEA,OAAO,SAASS,UAAUC,MAAe;IACvC,MAAMC,QAAQ;QAAC,CAAC,MAAM,EAAED,OAAOE,IAAI,CAACC,KAAK,CAAC,EAAE,EAAEtG,KAAKC,KAAK,CAACkG,OAAOE,IAAI,CAACzG,KAAK,GAAG,MAAM,IAAI,CAAC;QAAEwF,aAAae,OAAOd,QAAQ;QAAG;WAAOO,aAAaO,OAAON,OAAO;QAAG;QAAIjE,WAAWuE,OAAOI,MAAM,EAAE;KAAS;IACrM,IAAIJ,OAAOK,WAAW,GAAGL,OAAOI,MAAM,CAACrF,MAAM,EAAEkF,MAAM9D,IAAI,CAAC,CAAC,EAAE,EAAE6D,OAAOK,WAAW,GAAGL,OAAOI,MAAM,CAACrF,MAAM,CAAC,aAAa,CAAC;IACvH,IAAIiF,OAAOM,IAAI,CAACvF,MAAM,GAAG,GAAGkF,MAAM9D,IAAI,CAAC,0BAA0BV,WAAWuE,OAAOM,IAAI,EAAE;SACpF,IAAIN,OAAOd,QAAQ,CAACC,GAAG,CAACoB,QAAQ,CAAC,SAASN,MAAM9D,IAAI,CAAC,CAAC,aAAa,EAAE6C,eAAe,QAAQ,CAAC,CAAC;IACnGiB,MAAM9D,IAAI,CAAC;IACX,IAAI6D,OAAOQ,YAAY,EAAEP,MAAM9D,IAAI,CAAC6D,OAAOQ,YAAY;IACvDP,MAAM9D,IAAI,CAACV,WAAWuE,OAAOS,MAAM,EAAE;IACrC,OAAOR,MAAM9G,IAAI,CAAC;AACpB;AAEA,OAAO,SAASuH,WAAWV,MAAY;IACrC,MAAMhC,QAAQ;QAAC,GAAGgC,OAAOW,IAAI,CAAC,IAAI,EAAEX,OAAOY,MAAM,CAAC,QAAQ,CAAC;KAAC;IAC5D,4FAA4F;IAC5F,iEAAiE;IACjE,IAAIZ,OAAOa,UAAU,EAAE7C,MAAM7B,IAAI,CAAC,CAAC,wDAAwD,EAAE6D,OAAOa,UAAU,CAAC,CAAC,CAAC;IACjH,KAAK,MAAM,CAACC,KAAKnI,MAAM,IAAIqC,OAAOuE,OAAO,CAACS,OAAOe,WAAW,EAAG/C,MAAM7B,IAAI,CAAC,CAAC,EAAE,EAAE2E,IAAI,EAAE,EAAEnI,OAAO;IAC9F,IAAIqH,OAAOgB,QAAQ,CAACjG,MAAM,GAAG,GAAG;QAC9BiD,MAAM7B,IAAI,CAAC,IAAI;QACf,KAAK,MAAM8E,KAAKjB,OAAOgB,QAAQ,CAAEhD,MAAM7B,IAAI,CAAC,CAAC,EAAE,EAAE,IAAI4C,MAAM,CAACkC,EAAEC,KAAK,EAAE,CAAC,EAAED,EAAEE,OAAO,CAAC,IAAI,EAAEF,EAAEG,UAAU,CAAC,CAAC,EAAEH,EAAEI,QAAQ,CAAC,GAAG,EAAEJ,EAAEL,MAAM,CAAC,EAAE,CAAC;QACpI,IAAIZ,OAAOsB,aAAa,GAAGtB,OAAOgB,QAAQ,CAACjG,MAAM,EAAEiD,MAAM7B,IAAI,CAAC,CAAC,IAAI,EAAE6D,OAAOsB,aAAa,GAAGtB,OAAOgB,QAAQ,CAACjG,MAAM,CAAC,iDAAiD,CAAC;IACvK,OAAO,IAAIiF,OAAOb,GAAG,CAACoB,QAAQ,CAAC,aAAavC,MAAM7B,IAAI,CAAC,IAAI;IAC3D,IAAI6D,OAAOb,GAAG,CAACoB,QAAQ,CAAC,UAAU;QAChCvC,MAAM7B,IAAI,CAAC,IAAI;IACjB,OAAO;QACL,MAAMoF,WAAW,CAACC,OAAeC,OAAiBzE;YAChD,IAAIA,QAAQ,GAAGgB,MAAM7B,IAAI,CAAC,GAAGqF,MAAM,EAAE,EAAExE,MAAM,GAAG,EAAEyE,MAAMtI,IAAI,CAAC,QAAQ6D,QAAQyE,MAAM1G,MAAM,GAAG,CAAC,GAAG,EAAEiC,QAAQyE,MAAM1G,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI;QACtI;QACA,IAAIiF,OAAO0B,aAAa,GAAG1B,OAAO2B,eAAe,GAAG3B,OAAO4B,cAAc,GAAG,GAAG5D,MAAM7B,IAAI,CAAC;QAC1FoF,SAAS,aAAavB,OAAO6B,QAAQ,EAAE7B,OAAO0B,aAAa;QAC3DH,SAAS,cAAcvB,OAAO8B,UAAU,EAAE9B,OAAO2B,eAAe;QAChEJ,SAAS,aAAavB,OAAO+B,SAAS,EAAE/B,OAAO4B,cAAc;IAC/D;IACA,OAAO5D,MAAM7E,IAAI,CAAC;AACpB"}
|
package/dist/esm/scan/pool.d.ts
CHANGED
|
@@ -10,11 +10,12 @@ export type FileResult = {
|
|
|
10
10
|
export declare class ParsePool {
|
|
11
11
|
private pool;
|
|
12
12
|
private workerData;
|
|
13
|
+
private workerCount;
|
|
13
14
|
private operation;
|
|
14
15
|
poolsCreated: number;
|
|
15
16
|
private enqueue;
|
|
16
17
|
private ensure;
|
|
17
|
-
run(files: FileStat[], features: Feature[], cfg: Config, onParsed: ((done: number) => void) | undefined,
|
|
18
|
+
run(files: FileStat[], features: Feature[], cfg: Config, onParsed: ((done: number) => void) | undefined, requestedWorkers: number): Promise<FileResult[]>;
|
|
18
19
|
private dispatch;
|
|
19
20
|
private destroy;
|
|
20
21
|
close(): Promise<void>;
|
package/dist/esm/scan/pool.js
CHANGED
|
@@ -4,6 +4,9 @@ import { resolveWorkerFile } from '../lib/worker-file.js';
|
|
|
4
4
|
import { reviveError } from './worker-error.js';
|
|
5
5
|
// Tinypool is ESM-only; our floor (>=22.20) has native require(esm), so the tier-2 house deferral reaches it.
|
|
6
6
|
const _require = typeof require === 'undefined' ? createRequire(import.meta.url) : require;
|
|
7
|
+
// Candidate queue depth gives uneven files more than one scheduling turn per worker.
|
|
8
|
+
// The bounded parser comparison decides whether this value ships.
|
|
9
|
+
const BATCHES_PER_WORKER = 4;
|
|
7
10
|
// Created lazily and reused while dispatch context stays equivalent. A builder owns one instance
|
|
8
11
|
// for its lifetime instead of paying pool startup for unchanged reconciles.
|
|
9
12
|
export class ParsePool {
|
|
@@ -12,24 +15,26 @@ export class ParsePool {
|
|
|
12
15
|
this.operation = result.then(()=>undefined, ()=>undefined);
|
|
13
16
|
return result;
|
|
14
17
|
}
|
|
15
|
-
async ensure(workerData,
|
|
16
|
-
if (this.pool && !isDeepStrictEqual(this.workerData, workerData)) await this.destroy();
|
|
18
|
+
async ensure(workerData, workerCount) {
|
|
19
|
+
if (this.pool && (!isDeepStrictEqual(this.workerData, workerData) || this.workerCount !== workerCount)) await this.destroy();
|
|
17
20
|
if (!this.pool) {
|
|
18
21
|
const { Tinypool } = _require('tinypool');
|
|
19
22
|
this.pool = new Tinypool({
|
|
20
23
|
filename: resolveWorkerFile('parse'),
|
|
21
|
-
minThreads:
|
|
22
|
-
maxThreads:
|
|
24
|
+
minThreads: workerCount,
|
|
25
|
+
maxThreads: workerCount,
|
|
23
26
|
workerData
|
|
24
27
|
});
|
|
25
28
|
this.workerData = workerData;
|
|
29
|
+
this.workerCount = workerCount;
|
|
26
30
|
this.poolsCreated++;
|
|
27
31
|
}
|
|
28
32
|
return this.pool;
|
|
29
33
|
}
|
|
30
|
-
// Never the tree: a worker task carries
|
|
31
|
-
//
|
|
32
|
-
run(files, features, cfg, onParsed,
|
|
34
|
+
// Never the tree: a worker task carries a bounded FileStat batch and returns only what
|
|
35
|
+
// parseFile returns for each file, never the token tree.
|
|
36
|
+
run(files, features, cfg, onParsed, requestedWorkers) {
|
|
37
|
+
if (files.length === 0) return Promise.resolve([]);
|
|
33
38
|
// Snapshot at invocation so caller mutation cannot change queued work or its reuse baseline.
|
|
34
39
|
const workerData = {
|
|
35
40
|
cfg: structuredClone(cfg),
|
|
@@ -37,38 +42,66 @@ export class ParsePool {
|
|
|
37
42
|
...new Set(features.map((feature)=>feature.name))
|
|
38
43
|
].sort()
|
|
39
44
|
};
|
|
40
|
-
|
|
45
|
+
const workerCount = Math.max(1, Math.min(requestedWorkers, files.length));
|
|
46
|
+
return this.enqueue(()=>this.dispatch(files, workerData, onParsed, workerCount));
|
|
41
47
|
}
|
|
42
|
-
async dispatch(files, workerData, onParsed,
|
|
43
|
-
const pool = await this.ensure(workerData,
|
|
48
|
+
async dispatch(files, workerData, onParsed, workerCount) {
|
|
49
|
+
const pool = await this.ensure(workerData, workerCount);
|
|
50
|
+
const batches = batchFiles(files, Math.min(files.length, workerCount * BATCHES_PER_WORKER));
|
|
44
51
|
let done = 0;
|
|
45
|
-
const settled = await Promise.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
const settled = await Promise.all(batches.map(async (batch)=>{
|
|
53
|
+
let taskResults;
|
|
54
|
+
try {
|
|
55
|
+
taskResults = await pool.run(batch);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
return {
|
|
58
|
+
results: [],
|
|
59
|
+
failures: [
|
|
60
|
+
err
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const results = [];
|
|
65
|
+
const failures = [];
|
|
66
|
+
for (const result of taskResults){
|
|
67
|
+
if (!result.ok) {
|
|
68
|
+
failures.push(reviveError(result.error));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
results.push({
|
|
72
|
+
doc: result.doc,
|
|
73
|
+
warnings: result.warnings,
|
|
74
|
+
parseMs: result.parseMs
|
|
75
|
+
});
|
|
76
|
+
try {
|
|
77
|
+
onParsed === null || onParsed === void 0 ? void 0 : onParsed(++done);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
failures.push(err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
49
82
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
parseMs: result.parseMs
|
|
83
|
+
results,
|
|
84
|
+
failures
|
|
53
85
|
};
|
|
54
86
|
}));
|
|
55
87
|
const results = [];
|
|
56
88
|
const failures = [];
|
|
57
|
-
for (const
|
|
58
|
-
|
|
59
|
-
|
|
89
|
+
for (const batch of settled){
|
|
90
|
+
results.push(...batch.results);
|
|
91
|
+
failures.push(...batch.failures);
|
|
60
92
|
}
|
|
61
93
|
if (failures.length === 1) throw failures[0];
|
|
62
94
|
if (failures.length > 1) throw new AggregateError(failures, `${failures.length} parse tasks failed`);
|
|
63
|
-
//
|
|
95
|
+
// Promise.all and contiguous batches preserve the input order that first-seen columns use.
|
|
64
96
|
return results;
|
|
65
97
|
}
|
|
66
98
|
async destroy() {
|
|
67
99
|
if (!this.pool) return;
|
|
68
100
|
const pool = this.pool;
|
|
101
|
+
await pool.destroy();
|
|
69
102
|
this.pool = undefined;
|
|
70
103
|
this.workerData = undefined;
|
|
71
|
-
|
|
104
|
+
this.workerCount = undefined;
|
|
72
105
|
}
|
|
73
106
|
// A pool never created costs nothing to destroy. Queuing lets active dispatches drain first.
|
|
74
107
|
close() {
|
|
@@ -77,7 +110,31 @@ export class ParsePool {
|
|
|
77
110
|
constructor(){
|
|
78
111
|
this.operation = Promise.resolve();
|
|
79
112
|
// Tinypools this instance has constructed. Repeated equivalent dispatches leave this unchanged;
|
|
80
|
-
// a context change or close followed by run constructs another pool.
|
|
113
|
+
// a context/count change or close followed by run constructs another pool.
|
|
81
114
|
this.poolsCreated = 0;
|
|
82
115
|
}
|
|
83
116
|
}
|
|
117
|
+
function batchFiles(files, batchCount) {
|
|
118
|
+
const batches = [];
|
|
119
|
+
let start = 0;
|
|
120
|
+
let remainingBytes = files.reduce((sum, file)=>sum + file.size, 0);
|
|
121
|
+
for(let batchIndex = 0; batchIndex < batchCount; batchIndex++){
|
|
122
|
+
const remainingBatches = batchCount - batchIndex;
|
|
123
|
+
if (remainingBatches === 1) {
|
|
124
|
+
batches.push(files.slice(start));
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
const targetBytes = remainingBytes / remainingBatches;
|
|
128
|
+
const lastEnd = files.length - (remainingBatches - 1);
|
|
129
|
+
let end = start;
|
|
130
|
+
let bytes = 0;
|
|
131
|
+
while(end < lastEnd && (end === start || bytes < targetBytes)){
|
|
132
|
+
bytes += files[end].size;
|
|
133
|
+
end++;
|
|
134
|
+
}
|
|
135
|
+
batches.push(files.slice(start, end));
|
|
136
|
+
start = end;
|
|
137
|
+
remainingBytes -= bytes;
|
|
138
|
+
}
|
|
139
|
+
return batches;
|
|
140
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan/pool.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { isDeepStrictEqual } from 'node:util';\n// Type-only: erased at build, but keeps depcheck's usage check satisfied for the tier-2\n// `_require` below (see coding-standards' deferral tiers).\nimport type * as TinypoolNS from 'tinypool';\nimport type { Config } from '../config/index.ts';\nimport type { Feature } from '../features/types.ts';\nimport { resolveWorkerFile } from '../lib/worker-file.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\nimport type { ParseTask, ParseTaskResult, ParseWorkerData } from '../workers/parse.ts';\nimport type { FileStat } from './list.ts';\nimport { reviveError } from './worker-error.ts';\n\n// Tinypool is ESM-only; our floor (>=22.20) has native require(esm), so the tier-2 house deferral reaches it.\nconst _require = typeof require === 'undefined' ? createRequire(import.meta.url) : require;\n\n// parseMs is worker-side only (see workers/parse.ts); absent on the serial path.\nexport type FileResult = { doc: ParsedDoc; warnings: string[]; parseMs?: number };\n\n// Created lazily and reused while dispatch context stays equivalent. A builder owns one instance\n// for its lifetime instead of paying pool startup for unchanged reconciles.\nexport class ParsePool {\n private pool: TinypoolNS.Tinypool | undefined;\n private workerData: ParseWorkerData | undefined;\n private operation: Promise<void> = Promise.resolve();\n // Tinypools this instance has constructed. Repeated equivalent dispatches leave this unchanged;\n // a context change or close followed by run constructs another pool.\n poolsCreated = 0;\n\n private enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const result = this.operation.then(operation);\n this.operation = result.then(\n () => undefined,\n () => undefined\n );\n return result;\n }\n\n private async ensure(workerData: ParseWorkerData, maxWorkers: number): Promise<TinypoolNS.Tinypool> {\n if (this.pool && !isDeepStrictEqual(this.workerData, workerData)) await this.destroy();\n if (!this.pool) {\n const { Tinypool } = _require('tinypool') as typeof TinypoolNS;\n this.pool = new Tinypool({ filename: resolveWorkerFile('parse'), minThreads: maxWorkers, maxThreads: maxWorkers, workerData });\n this.workerData = workerData;\n this.poolsCreated++;\n }\n return this.pool;\n }\n\n // Never the tree: a worker task carries one FileStat and returns only what parseFile returns --\n // extracted text and feature values, never the token tree.\n run(files: FileStat[], features: Feature[], cfg: Config, onParsed: ((done: number) => void) | undefined, maxWorkers: number): Promise<FileResult[]> {\n // Snapshot at invocation so caller mutation cannot change queued work or its reuse baseline.\n const workerData: ParseWorkerData = { cfg: structuredClone(cfg), featureNames: [...new Set(features.map((feature) => feature.name))].sort() };\n return this.enqueue(() => this.dispatch(files, workerData, onParsed, maxWorkers));\n }\n\n private async dispatch(files: FileStat[], workerData: ParseWorkerData, onParsed: ((done: number) => void) | undefined, maxWorkers: number): Promise<FileResult[]> {\n const pool = await this.ensure(workerData, maxWorkers);\n let done = 0;\n const settled = await Promise.allSettled(\n files.map(async (file): Promise<FileResult> => {\n const result = (await pool.run(file as ParseTask)) as ParseTaskResult;\n if (!result.ok) throw reviveError(result.error);\n onParsed?.(++done);\n return { doc: result.doc, warnings: result.warnings, parseMs: result.parseMs };\n })\n );\n const results: FileResult[] = [];\n const failures: unknown[] = [];\n for (const result of settled) {\n if (result.status === 'fulfilled') results.push(result.value);\n else failures.push(result.reason);\n }\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) throw new AggregateError(failures, `${failures.length} parse tasks failed`);\n // allSettled preserves input order, which downstream first-seen column order depends on.\n return results;\n }\n\n private async destroy(): Promise<void> {\n if (!this.pool) return;\n const pool = this.pool;\n this.pool = undefined;\n this.workerData = undefined;\n await pool.destroy();\n }\n\n // A pool never created costs nothing to destroy. Queuing lets active dispatches drain first.\n close(): Promise<void> {\n return this.enqueue(() => this.destroy());\n }\n}\n"],"names":["createRequire","isDeepStrictEqual","resolveWorkerFile","reviveError","_require","require","url","ParsePool","enqueue","operation","result","then","undefined","ensure","workerData","maxWorkers","pool","destroy","Tinypool","filename","minThreads","maxThreads","poolsCreated","run","files","features","cfg","onParsed","structuredClone","featureNames","Set","map","feature","name","sort","dispatch","done","settled","Promise","allSettled","file","ok","error","doc","warnings","parseMs","results","failures","status","push","value","reason","length","AggregateError","close","resolve"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan/pool.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { isDeepStrictEqual } from 'node:util';\n// Type-only: erased at build, but keeps depcheck's usage check satisfied for the tier-2\n// `_require` below (see coding-standards' deferral tiers).\nimport type * as TinypoolNS from 'tinypool';\nimport type { Config } from '../config/index.ts';\nimport type { Feature } from '../features/types.ts';\nimport { resolveWorkerFile } from '../lib/worker-file.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\nimport type { ParseTaskResult, ParseWorkerData } from '../workers/parse.ts';\nimport type { FileStat } from './list.ts';\nimport { reviveError } from './worker-error.ts';\n\n// Tinypool is ESM-only; our floor (>=22.20) has native require(esm), so the tier-2 house deferral reaches it.\nconst _require = typeof require === 'undefined' ? createRequire(import.meta.url) : require;\n// Candidate queue depth gives uneven files more than one scheduling turn per worker.\n// The bounded parser comparison decides whether this value ships.\nconst BATCHES_PER_WORKER = 4;\n\n// parseMs is worker-side only (see workers/parse.ts); absent on the serial path.\nexport type FileResult = { doc: ParsedDoc; warnings: string[]; parseMs?: number };\n\n// Created lazily and reused while dispatch context stays equivalent. A builder owns one instance\n// for its lifetime instead of paying pool startup for unchanged reconciles.\nexport class ParsePool {\n private pool: TinypoolNS.Tinypool | undefined;\n private workerData: ParseWorkerData | undefined;\n private workerCount: number | undefined;\n private operation: Promise<void> = Promise.resolve();\n // Tinypools this instance has constructed. Repeated equivalent dispatches leave this unchanged;\n // a context/count change or close followed by run constructs another pool.\n poolsCreated = 0;\n\n private enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const result = this.operation.then(operation);\n this.operation = result.then(\n () => undefined,\n () => undefined\n );\n return result;\n }\n\n private async ensure(workerData: ParseWorkerData, workerCount: number): Promise<TinypoolNS.Tinypool> {\n if (this.pool && (!isDeepStrictEqual(this.workerData, workerData) || this.workerCount !== workerCount)) await this.destroy();\n if (!this.pool) {\n const { Tinypool } = _require('tinypool') as typeof TinypoolNS;\n this.pool = new Tinypool({ filename: resolveWorkerFile('parse'), minThreads: workerCount, maxThreads: workerCount, workerData });\n this.workerData = workerData;\n this.workerCount = workerCount;\n this.poolsCreated++;\n }\n return this.pool;\n }\n\n // Never the tree: a worker task carries a bounded FileStat batch and returns only what\n // parseFile returns for each file, never the token tree.\n run(files: FileStat[], features: Feature[], cfg: Config, onParsed: ((done: number) => void) | undefined, requestedWorkers: number): Promise<FileResult[]> {\n if (files.length === 0) return Promise.resolve([]);\n // Snapshot at invocation so caller mutation cannot change queued work or its reuse baseline.\n const workerData: ParseWorkerData = { cfg: structuredClone(cfg), featureNames: [...new Set(features.map((feature) => feature.name))].sort() };\n const workerCount = Math.max(1, Math.min(requestedWorkers, files.length));\n return this.enqueue(() => this.dispatch(files, workerData, onParsed, workerCount));\n }\n\n private async dispatch(files: FileStat[], workerData: ParseWorkerData, onParsed: ((done: number) => void) | undefined, workerCount: number): Promise<FileResult[]> {\n const pool = await this.ensure(workerData, workerCount);\n const batches = batchFiles(files, Math.min(files.length, workerCount * BATCHES_PER_WORKER));\n let done = 0;\n const settled = await Promise.all(\n batches.map(async (batch): Promise<{ results: FileResult[]; failures: unknown[] }> => {\n let taskResults: ParseTaskResult;\n try {\n taskResults = (await pool.run(batch)) as ParseTaskResult;\n } catch (err) {\n return { results: [], failures: [err] };\n }\n\n const results: FileResult[] = [];\n const failures: unknown[] = [];\n for (const result of taskResults) {\n if (!result.ok) {\n failures.push(reviveError(result.error));\n continue;\n }\n results.push({ doc: result.doc, warnings: result.warnings, parseMs: result.parseMs });\n try {\n onParsed?.(++done);\n } catch (err) {\n failures.push(err);\n }\n }\n return { results, failures };\n })\n );\n const results: FileResult[] = [];\n const failures: unknown[] = [];\n for (const batch of settled) {\n results.push(...batch.results);\n failures.push(...batch.failures);\n }\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) throw new AggregateError(failures, `${failures.length} parse tasks failed`);\n // Promise.all and contiguous batches preserve the input order that first-seen columns use.\n return results;\n }\n\n private async destroy(): Promise<void> {\n if (!this.pool) return;\n const pool = this.pool;\n await pool.destroy();\n this.pool = undefined;\n this.workerData = undefined;\n this.workerCount = undefined;\n }\n\n // A pool never created costs nothing to destroy. Queuing lets active dispatches drain first.\n close(): Promise<void> {\n return this.enqueue(() => this.destroy());\n }\n}\n\nfunction batchFiles(files: FileStat[], batchCount: number): FileStat[][] {\n const batches: FileStat[][] = [];\n let start = 0;\n let remainingBytes = files.reduce((sum, file) => sum + file.size, 0);\n\n for (let batchIndex = 0; batchIndex < batchCount; batchIndex++) {\n const remainingBatches = batchCount - batchIndex;\n if (remainingBatches === 1) {\n batches.push(files.slice(start));\n break;\n }\n\n const targetBytes = remainingBytes / remainingBatches;\n const lastEnd = files.length - (remainingBatches - 1);\n let end = start;\n let bytes = 0;\n while (end < lastEnd && (end === start || bytes < targetBytes)) {\n bytes += files[end].size;\n end++;\n }\n batches.push(files.slice(start, end));\n start = end;\n remainingBytes -= bytes;\n }\n return batches;\n}\n"],"names":["createRequire","isDeepStrictEqual","resolveWorkerFile","reviveError","_require","require","url","BATCHES_PER_WORKER","ParsePool","enqueue","operation","result","then","undefined","ensure","workerData","workerCount","pool","destroy","Tinypool","filename","minThreads","maxThreads","poolsCreated","run","files","features","cfg","onParsed","requestedWorkers","length","Promise","resolve","structuredClone","featureNames","Set","map","feature","name","sort","Math","max","min","dispatch","batches","batchFiles","done","settled","all","batch","taskResults","err","results","failures","ok","push","error","doc","warnings","parseMs","AggregateError","close","batchCount","start","remainingBytes","reduce","sum","file","size","batchIndex","remainingBatches","slice","targetBytes","lastEnd","end","bytes"],"mappings":"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"}
|
|
@@ -14,5 +14,6 @@ export interface ReparseOptions {
|
|
|
14
14
|
maxWorkers?: number;
|
|
15
15
|
pool?: ParsePool;
|
|
16
16
|
}
|
|
17
|
+
export declare const PARSE_BYTES_PER_WORKER: number;
|
|
17
18
|
export declare function featuresForFile(features: Feature[], cfg: Config, file: FileStat): Feature[];
|
|
18
19
|
export declare function reparseFiles(files: FileStat[], features: Feature[], cfg: Config, knownColumns: ReadonlySet<string>, onParsed?: (done: number) => void, options?: ReparseOptions): Promise<ReparseResult>;
|
package/dist/esm/scan/reparse.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { availableParallelism } from 'node:os';
|
|
2
2
|
import { parseFile } from './index.js';
|
|
3
3
|
import { ParsePool } from './pool.js';
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
const
|
|
4
|
+
// The 2026-09-26 comparison kept ~326 KiB updates serial and 5.86 MiB work pooled.
|
|
5
|
+
// Companion byte shares prevent one dominant file from selecting workers for a tiny remainder.
|
|
6
|
+
export const PARSE_BYTES_PER_WORKER = 512 * 1024;
|
|
7
7
|
const DEFAULT_MAX_WORKERS = 8;
|
|
8
8
|
// Per-file feature filtering, shared by the serial loop (reads `features` from the caller) and
|
|
9
9
|
// the worker path (can only pass `cfg` across the thread boundary, so derives `features` itself).
|
|
@@ -25,24 +25,52 @@ function reparseSerial(files, features, cfg, onParsed) {
|
|
|
25
25
|
async function reparsePooled(files, features, cfg, onParsed, maxWorkers, pool) {
|
|
26
26
|
if (pool) return pool.run(files, features, cfg, onParsed, maxWorkers);
|
|
27
27
|
const ephemeral = new ParsePool();
|
|
28
|
+
let results;
|
|
29
|
+
let dispatchError;
|
|
30
|
+
let dispatchFailed = false;
|
|
28
31
|
try {
|
|
29
|
-
|
|
30
|
-
await ephemeral.close();
|
|
31
|
-
return results;
|
|
32
|
+
results = await ephemeral.run(files, features, cfg, onParsed, maxWorkers);
|
|
32
33
|
} catch (err) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
dispatchFailed = true;
|
|
35
|
+
dispatchError = err;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
await ephemeral.close();
|
|
39
|
+
} catch (closeError) {
|
|
40
|
+
if (dispatchFailed) throw new AggregateError([
|
|
41
|
+
dispatchError,
|
|
42
|
+
closeError
|
|
43
|
+
], 'parse dispatch and pool close failed');
|
|
44
|
+
throw closeError;
|
|
45
|
+
}
|
|
46
|
+
if (dispatchFailed) throw dispatchError;
|
|
47
|
+
if (results === undefined) throw new Error('parse dispatch finished without results');
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
function parseWorkerCount(files, bytesPerWorker, maxWorkers) {
|
|
51
|
+
if (files.length === 0) return 0;
|
|
52
|
+
const available = Math.max(1, Math.min(maxWorkers, files.length));
|
|
53
|
+
if (bytesPerWorker === 0) return available;
|
|
54
|
+
let totalBytes = 0;
|
|
55
|
+
let largestBytes = 0;
|
|
56
|
+
for (const file of files){
|
|
57
|
+
totalBytes += file.size;
|
|
58
|
+
largestBytes = Math.max(largestBytes, file.size);
|
|
37
59
|
}
|
|
60
|
+
const aggregateWorkers = Math.floor(totalBytes / bytesPerWorker);
|
|
61
|
+
// The largest file is indivisible, so extra workers need full shares among the remainder.
|
|
62
|
+
const companionWorkers = 1 + Math.floor((totalBytes - largestBytes) / bytesPerWorker);
|
|
63
|
+
return Math.min(available, aggregateWorkers, companionWorkers);
|
|
38
64
|
}
|
|
39
65
|
// `onParsed` receives the running count (1-based) after each file, mirroring a Progress.tick
|
|
40
66
|
// call; pass one in to keep progress reporting working without this module owning a reporter.
|
|
41
67
|
export async function reparseFiles(files, features, cfg, knownColumns, onParsed, options = {}) {
|
|
42
68
|
var _options_threshold, _options_maxWorkers;
|
|
43
|
-
const threshold = (_options_threshold = options.threshold) !== null && _options_threshold !== void 0 ? _options_threshold :
|
|
69
|
+
const threshold = (_options_threshold = options.threshold) !== null && _options_threshold !== void 0 ? _options_threshold : PARSE_BYTES_PER_WORKER;
|
|
44
70
|
const maxWorkers = (_options_maxWorkers = options.maxWorkers) !== null && _options_maxWorkers !== void 0 ? _options_maxWorkers : Math.min(DEFAULT_MAX_WORKERS, availableParallelism());
|
|
45
|
-
const
|
|
71
|
+
const workerCount = parseWorkerCount(files, threshold, maxWorkers);
|
|
72
|
+
const pooled = files.length > 0 && (threshold === 0 || workerCount >= 2);
|
|
73
|
+
const results = pooled ? await reparsePooled(files, features, cfg, onParsed, workerCount, options.pool) : reparseSerial(files, features, cfg, onParsed);
|
|
46
74
|
const docs = [];
|
|
47
75
|
const warnings = [];
|
|
48
76
|
const newColumns = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan/reparse.ts"],"sourcesContent":["import { availableParallelism } from 'node:os';\nimport type { Config } from '../config/index.ts';\nimport type { Feature } from '../features/types.ts';\nimport type { ParsedDoc } from './index.ts';\nimport { parseFile } from './index.ts';\nimport type { FileStat } from './list.ts';\nimport type { FileResult } from './pool.ts';\nimport { ParsePool } from './pool.ts';\n\n// Store-agnostic per-file parse pass shared by every store's reconcile(). Index-preserving:\n// one pass over `files`, pushed in order.\nexport interface ReparseResult {\n docs: ParsedDoc[];\n warnings: string[];\n // Frontmatter keys not in `knownColumns`, first-seen order -- the order callers ALTER TABLE ADD COLUMN in.\n newColumns: string[];\n // Sum of worker-side parseFile time (workers/parse.ts) across files; 0 on the serial path.\n workerParseMs: number;\n}\n\nexport interface ReparseOptions {\n // Overrides
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan/reparse.ts"],"sourcesContent":["import { availableParallelism } from 'node:os';\nimport type { Config } from '../config/index.ts';\nimport type { Feature } from '../features/types.ts';\nimport type { ParsedDoc } from './index.ts';\nimport { parseFile } from './index.ts';\nimport type { FileStat } from './list.ts';\nimport type { FileResult } from './pool.ts';\nimport { ParsePool } from './pool.ts';\n\n// Store-agnostic per-file parse pass shared by every store's reconcile(). Index-preserving:\n// one pass over `files`, pushed in order.\nexport interface ReparseResult {\n docs: ParsedDoc[];\n warnings: string[];\n // Frontmatter keys not in `knownColumns`, first-seen order -- the order callers ALTER TABLE ADD COLUMN in.\n newColumns: string[];\n // Sum of worker-side parseFile time (workers/parse.ts) across files; 0 on the serial path.\n workerParseMs: number;\n}\n\nexport interface ReparseOptions {\n // Overrides PARSE_BYTES_PER_WORKER below. Zero forces worker dispatch in tests.\n // Internal: no caller in src/ passes it.\n threshold?: number;\n // Overrides DEFAULT_MAX_WORKERS below.\n maxWorkers?: number;\n // A builder-owned pool to dispatch through instead of creating and destroying one for this\n // call alone (see store/builder.ts). Absent, reparseFiles manages its own ephemeral pool.\n pool?: ParsePool;\n}\n\n// The 2026-09-26 comparison kept ~326 KiB updates serial and 5.86 MiB work pooled.\n// Companion byte shares prevent one dominant file from selecting workers for a tiny remainder.\nexport const PARSE_BYTES_PER_WORKER = 512 * 1024;\nconst DEFAULT_MAX_WORKERS = 8;\n\n// Per-file feature filtering, shared by the serial loop (reads `features` from the caller) and\n// the worker path (can only pass `cfg` across the thread boundary, so derives `features` itself).\nexport function featuresForFile(features: Feature[], cfg: Config, file: FileStat): Feature[] {\n return features.filter((feature) => !feature.enabledForFile || feature.enabledForFile(cfg, file));\n}\n\nfunction reparseSerial(files: FileStat[], features: Feature[], cfg: Config, onParsed?: (done: number) => void): FileResult[] {\n const results: FileResult[] = [];\n let done = 0;\n for (const file of files) {\n const fileFeatures = featuresForFile(features, cfg, file);\n results.push(parseFile(file, fileFeatures, cfg));\n onParsed?.(++done);\n }\n return results;\n}\n\n// Dispatches through `pool` when the caller owns one (a builder, kept alive across calls); with\n// none given, opens an ephemeral ParsePool for this call alone and always closes it.\nasync function reparsePooled(files: FileStat[], features: Feature[], cfg: Config, onParsed: ((done: number) => void) | undefined, maxWorkers: number, pool: ParsePool | undefined): Promise<FileResult[]> {\n if (pool) return pool.run(files, features, cfg, onParsed, maxWorkers);\n const ephemeral = new ParsePool();\n let results: FileResult[] | undefined;\n let dispatchError: unknown;\n let dispatchFailed = false;\n try {\n results = await ephemeral.run(files, features, cfg, onParsed, maxWorkers);\n } catch (err) {\n dispatchFailed = true;\n dispatchError = err;\n }\n try {\n await ephemeral.close();\n } catch (closeError) {\n if (dispatchFailed) throw new AggregateError([dispatchError, closeError], 'parse dispatch and pool close failed');\n throw closeError;\n }\n if (dispatchFailed) throw dispatchError;\n if (results === undefined) throw new Error('parse dispatch finished without results');\n return results;\n}\n\nfunction parseWorkerCount(files: FileStat[], bytesPerWorker: number, maxWorkers: number): number {\n if (files.length === 0) return 0;\n const available = Math.max(1, Math.min(maxWorkers, files.length));\n if (bytesPerWorker === 0) return available;\n let totalBytes = 0;\n let largestBytes = 0;\n for (const file of files) {\n totalBytes += file.size;\n largestBytes = Math.max(largestBytes, file.size);\n }\n const aggregateWorkers = Math.floor(totalBytes / bytesPerWorker);\n // The largest file is indivisible, so extra workers need full shares among the remainder.\n const companionWorkers = 1 + Math.floor((totalBytes - largestBytes) / bytesPerWorker);\n return Math.min(available, aggregateWorkers, companionWorkers);\n}\n\n// `onParsed` receives the running count (1-based) after each file, mirroring a Progress.tick\n// call; pass one in to keep progress reporting working without this module owning a reporter.\nexport async function reparseFiles(files: FileStat[], features: Feature[], cfg: Config, knownColumns: ReadonlySet<string>, onParsed?: (done: number) => void, options: ReparseOptions = {}): Promise<ReparseResult> {\n const threshold = options.threshold ?? PARSE_BYTES_PER_WORKER;\n const maxWorkers = options.maxWorkers ?? Math.min(DEFAULT_MAX_WORKERS, availableParallelism());\n const workerCount = parseWorkerCount(files, threshold, maxWorkers);\n const pooled = files.length > 0 && (threshold === 0 || workerCount >= 2);\n const results = pooled ? await reparsePooled(files, features, cfg, onParsed, workerCount, options.pool) : reparseSerial(files, features, cfg, onParsed);\n\n const docs: ParsedDoc[] = [];\n const warnings: string[] = [];\n const newColumns: string[] = [];\n const seen = new Set(knownColumns);\n let workerParseMs = 0;\n for (const { doc, warnings: fileWarnings, parseMs } of results) {\n warnings.push(...fileWarnings);\n workerParseMs += parseMs ?? 0;\n for (const key of Object.keys(doc.data)) {\n if (!seen.has(key)) {\n seen.add(key);\n newColumns.push(key);\n }\n }\n docs.push(doc);\n }\n\n return { docs, warnings, newColumns, workerParseMs };\n}\n"],"names":["availableParallelism","parseFile","ParsePool","PARSE_BYTES_PER_WORKER","DEFAULT_MAX_WORKERS","featuresForFile","features","cfg","file","filter","feature","enabledForFile","reparseSerial","files","onParsed","results","done","fileFeatures","push","reparsePooled","maxWorkers","pool","run","ephemeral","dispatchError","dispatchFailed","err","close","closeError","AggregateError","undefined","Error","parseWorkerCount","bytesPerWorker","length","available","Math","max","min","totalBytes","largestBytes","size","aggregateWorkers","floor","companionWorkers","reparseFiles","knownColumns","options","threshold","workerCount","pooled","docs","warnings","newColumns","seen","Set","workerParseMs","doc","fileWarnings","parseMs","key","Object","keys","data","has","add"],"mappings":"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"}
|
|
@@ -1,27 +1,17 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import type { EmbedChangeKind } from './embed-scope.js';
|
|
3
|
-
import type { FeatureToggle } from './feature-scope.js';
|
|
4
|
-
import { type ReconcilePaths } from './reconcile.js';
|
|
1
|
+
import { type ResolvedConfig } from '../config/index.js';
|
|
5
2
|
import type { Stages } from './stages.js';
|
|
6
3
|
import type { Connection, ReconcileDialect } from './types.js';
|
|
4
|
+
interface BuildResult {
|
|
5
|
+
parsed: number;
|
|
6
|
+
warnings: string[];
|
|
7
|
+
stages: Stages;
|
|
8
|
+
messages: string[];
|
|
9
|
+
}
|
|
7
10
|
export interface Builder {
|
|
8
|
-
build(
|
|
9
|
-
parsed: number;
|
|
10
|
-
warnings: string[];
|
|
11
|
-
stages: Stages;
|
|
12
|
-
}>;
|
|
13
|
-
recover(): Promise<{
|
|
14
|
-
parsed: number;
|
|
15
|
-
warnings: string[];
|
|
16
|
-
stages: Stages;
|
|
17
|
-
}>;
|
|
18
|
-
invalidate(kind: EmbedChangeKind): Promise<{
|
|
19
|
-
parsed: number;
|
|
20
|
-
}>;
|
|
21
|
-
invalidateFeatures(toggles: FeatureToggle[]): Promise<{
|
|
22
|
-
parsed: number;
|
|
23
|
-
}>;
|
|
11
|
+
build(): Promise<BuildResult>;
|
|
24
12
|
close(): Promise<void>;
|
|
25
13
|
readonly poolsCreated: number;
|
|
26
14
|
}
|
|
27
|
-
|
|
15
|
+
type EnsureFeatureSchema = (cfg: ResolvedConfig) => Promise<void>;
|
|
16
|
+
export declare function createBuilder(conn: Connection, cfg: ResolvedConfig, dialect: ReconcileDialect, ensureFeatureSchema: EnsureFeatureSchema): Builder;
|
|
17
|
+
export {};
|