tokentracker-cli 1.3.11 → 1.3.14
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/dashboard/dist/assets/{AchievementBadge-CjXQPC-W.js → AchievementBadge-DJK8AWBF.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-xb-g6Jws.js → AchievementsPage-Cle78oKH.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DI4NOt9D.js → AchievementsSection-BPqXDoPP.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-Cn2rV5Bk.js → BadgeDetailModal-HsH_Pwsu.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-CFb-gvO7.js → BotAnimated-BWy2-z4Q.js} +1 -1
- package/dashboard/dist/assets/{Card-CB2-QAqP.js → Card-DPGBuBDb.js} +1 -1
- package/dashboard/dist/assets/{CloudCheckoutPage-beBzOiJA.js → CloudCheckoutPage-ohIQnIhe.js} +1 -1
- package/dashboard/dist/assets/{CloudPage-COYm4wdR.js → CloudPage-OYiiZ4c4.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-kCa9Mfpj.js → CommandPalette-BmDZmpMi.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-BeUZhxxn.js → CommunityStatsModal-L86uJgyp.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-DiCm2JXX.js → ConfirmModal-COQfPXos.js} +1 -1
- package/dashboard/dist/assets/{Controls-BkRVScXl.js → Controls-BkKlT6Dz.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-DiV0ynQc.js → DashboardPage-3dlQBOjz.js} +1 -1
- package/dashboard/dist/assets/{DateRangePopover-DdmjfdFj.js → DateRangePopover-D0UeP8ig.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-D7E9uy4l.js → DevicePage-K3CdWKaf.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-VDWLqY0o.js → DialogClose-DKQ2ZVA0.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-MbuaPFlj.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DfBUUBYN.js → DialogTitle-ufFnN-8D.js} +1 -1
- package/dashboard/dist/assets/{ElectricLogo-Cr-WDplA.js → ElectricLogo-Bb79UnTo.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-DPdMW40K.js → FadeIn-DlAKIU4I.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-Ulqd6uC1.js → HeaderGithubStar-CzwSyhEY.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-By6YzN89.js → HoverTooltip-BEpwekkd.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BLvWuVmL.js → IpCheckPage-C7kpZ9JX.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-BfgBmSGV.js → LandingPage-CfYk0Jzy.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardPage-D7C91BEb.js → LeaderboardPage-XgjAHwpy.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProBadge-Mj_-HH8L.js → LeaderboardProBadge-wSlqVjBb.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfileModal-BfHTrqHf.js → LeaderboardProfileModal-DaQrNYnu.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-CggAvx-w.js → LeaderboardProfilePage-3SWjG8FQ.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-BR8hlK7t.js → LimitsPage-DvM57ri3.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-yW1orlU8.js → LocalOnlyNotice-CY6eDJEk.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-B_7mpZ6Y.js → LoginCard-4bdpPQeD.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-D1ZtIkRD.js → LoginModal-Bmw7-b3f.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-DRThDeqD.js → LoginPage-CEI4nnlT.js} +1 -1
- package/dashboard/dist/assets/{PetPage-CsIkqyiR.js → PetPage-7Udtc9GY.js} +2 -2
- package/dashboard/dist/assets/{PopoverPopup-dySzR9xR.js → PopoverPopup-r2BvRKLO.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-oBKAZ-WS.js → ProviderIcon-Cy_pKLdw.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-Bf0-zIl2.js → ResetPasswordPage-QKUaLtO3.js} +1 -1
- package/dashboard/dist/assets/{Select-xyNGq71G.js → Select-BsUrn8Ui.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-C8ZB19C7.js → SelectItemText-Nis0uGZT.js} +1 -1
- package/dashboard/dist/assets/{SelfHostPage-Cf7pLoVz.js → SelfHostPage-DF8EzBEV.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-BH3W_Wxq.js → ServiceStatusPage-BYmKzI_v.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-u8RIKjx9.js → SessionsPage-BsCVe75l.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-Bnh-AxM9.js → SettingsPage-CIZ6WmNR.js} +3 -3
- package/dashboard/dist/assets/{SkillsPage-CIP3Av7z.js → SkillsPage-cwHyK0Hp.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-DnNhsfa-.js → TokenGalaxy-2S-ud67v.js} +1 -1
- package/dashboard/dist/assets/{TooltipPopup-BstAYPIH.js → TooltipPopup-GG7xjDOP.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-D4CVzczb.js → TrendMonitor-CrbXrVMe.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-A_gMzap_.js → WidgetsPage-HrhnI3C-.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-Bmm_WjA5.js → WrappedPage-sm7UVFon.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-DS-dnBDP.js → __vite-browser-external-C7JgMYNl.js} +2 -2
- package/dashboard/dist/assets/{agent-logos-BdU0GjQq.js → agent-logos-DGrrmPth.js} +1 -1
- package/dashboard/dist/assets/{arrow-left-Cl8AYaOh.js → arrow-left-DOzsdmqR.js} +1 -1
- package/dashboard/dist/assets/{arrow-right-Cfi2pR1p.js → arrow-right-CHb0kJou.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-DH7vnAf4.js → arrow-up-right-CH8vEEN1.js} +1 -1
- package/dashboard/dist/assets/{calendar-days-BJjjx5Vu.js → calendar-days-_D5qnyH_.js} +1 -1
- package/dashboard/dist/assets/{check-DGTejNYt.js → check-DZLesnNy.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-Brz4a22U.js → chevron-down-QL4xkUtL.js} +1 -1
- package/dashboard/dist/assets/{cloud-checkout-DZbiSnVr.js → cloud-checkout-DiCcWJVl.js} +1 -1
- package/dashboard/dist/assets/{composite-rfjkpEna.js → composite-DObGcQmb.js} +1 -1
- package/dashboard/dist/assets/{download-axaJjbTW.js → download-BKp8pusR.js} +1 -1
- package/dashboard/dist/assets/{external-link-D9dvv4DN.js → external-link-B3p_TVGc.js} +1 -1
- package/dashboard/dist/assets/{flame-DuivUwnP.js → flame-Df9OTUym.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-Dg_-K3cD.js → format-tokens-DXrEDEYs.js} +1 -1
- package/dashboard/dist/assets/{gift-CgE1uDg4.js → gift-DtWubiuR.js} +1 -1
- package/dashboard/dist/assets/{host-mode-C2QCLKjw.js → host-mode-C069A_hB.js} +1 -1
- package/dashboard/dist/assets/{icons-PNlyYW8C.js → icons-lB1kfROy.js} +1 -1
- package/dashboard/dist/assets/{index-DFWE1vbu.js → index-D356L-jw.js} +1 -1
- package/dashboard/dist/assets/{index-BbroxaKr.js → index-DsXIWrJx.js} +1 -1
- package/dashboard/dist/assets/{index-PiRGMAPd.js → index-M5BiC0jx.js} +1 -1
- package/dashboard/dist/assets/{info-BjRBvuNu.js → info-CSG8OkG4.js} +1 -1
- package/dashboard/dist/assets/{link-2-CCqYoK3V.js → link-2-BkBGu7FX.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-IiB0vjDJ.js → loader-circle-BERDkbsP.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-DbjjBgLM.js → modifiers.esm-Ce5IvVw2.js} +1 -1
- package/dashboard/dist/assets/{monitor-smartphone-BUSQ3-U3.js → monitor-smartphone-zKuoJldz.js} +1 -1
- package/dashboard/dist/assets/{play-CDU4bWoE.js → play-C0x-pxft.js} +1 -1
- package/dashboard/dist/assets/{plus-BY7jwS0R.js → plus-DJvks-CX.js} +1 -1
- package/dashboard/dist/assets/{provider-display-endIONDM.js → provider-display-C0e4tAFu.js} +1 -1
- package/dashboard/dist/assets/{react-C0JpiKb9.js → react-Bg0_YfGs.js} +1 -1
- package/dashboard/dist/assets/{safePolygon-B5pqmyki.js → safePolygon-CptquMWZ.js} +1 -1
- package/dashboard/dist/assets/{search-BWQCqCrK.js → search-CLESf2Tg.js} +1 -1
- package/dashboard/dist/assets/{server-DkDn4DQU.js → server-Cdwhp-2m.js} +1 -1
- package/dashboard/dist/assets/{shield-check-BPOMBy-3.js → shield-check-8U4XqmW3.js} +1 -1
- package/dashboard/dist/assets/{skills-api-Btor2iNW.js → skills-api-ioDaxLLo.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-qA98E6si.js → sortable.esm-CEohF5EK.js} +1 -1
- package/dashboard/dist/assets/{store-DRcycxJw.js → store-CyOeiGFE.js} +1 -1
- package/dashboard/dist/assets/{terminal-BWOI_yxH.js → terminal-BjgwEAH-.js} +1 -1
- package/dashboard/dist/assets/{trash-DgWVzyKs.js → trash-Skj6z7D1.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-odyPsCDi.js → use-community-stats-Cworqjzy.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-CBocPZjh.js → use-latest-request-guard-DMVWXXHE.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-uKB639iI.js → use-limits-display-prefs-DKJZcYfE.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-EoDW6Dgk.js → use-native-settings-CQF4BLQl.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-B4Gychu4.js → use-ordered-list-B9yoUGUU.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-DPUViCfp.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-C0goPBNl.js → use-session-efficiency-pref-K00bK2sQ.js} +1 -1
- package/dashboard/dist/assets/{use-transform-hwE19i1Y.js → use-transform-Cfw11yOU.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-DnsGayPX.js → use-usage-limits-BE2dJXFd.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-DiN1Jj_t.js → useCurrency-BW7y9Tdv.js} +1 -1
- package/dashboard/dist/assets/{usePositioner-CM1Isf5e.js → usePositioner-DhtQcPOC.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-CGhMrRps.js → useTokenFormat-BnvzUc-t.js} +1 -1
- package/dashboard/dist/assets/{zap-Bc2wd9eQ.js → zap-BQ3BR9On.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/status.js +5 -1
- package/src/commands/sync.js +28 -1
- package/src/lib/async-queue-reader.js +117 -0
- package/src/lib/jsonl-lines.js +34 -4
- package/src/lib/local-api.js +88 -226
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +148 -52
- package/src/lib/session-analytics.js +11 -10
- package/src/lib/upload-queue.js +1 -1
- package/dashboard/dist/assets/DialogDescription-BQ5VSHQi.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-B-JhzSwL.js +0 -1
package/src/commands/sync.js
CHANGED
|
@@ -145,6 +145,8 @@ const {
|
|
|
145
145
|
toUtcHalfHourStart,
|
|
146
146
|
totalsKey,
|
|
147
147
|
claudeMessageDedupKey,
|
|
148
|
+
hasZcodeCursorState,
|
|
149
|
+
resolveZcodeInstallPaths,
|
|
148
150
|
} = require("../lib/rollout");
|
|
149
151
|
const { computeClaudeGroundTruthBuckets } = require("../lib/claude-categorizer");
|
|
150
152
|
const { createProgress, renderBar, formatNumber, formatBytes } = require("../lib/progress");
|
|
@@ -1733,8 +1735,23 @@ async function cmdSync(argv, context = {}) {
|
|
|
1733
1735
|
const wslZcodeDir = process.platform === "win32" && wsl.shouldProbeWsl(process.env)
|
|
1734
1736
|
? wsl.discoverWslHome(".zcode")
|
|
1735
1737
|
: null;
|
|
1736
|
-
const
|
|
1738
|
+
const zcodeWslValue = wslZcodeDir ? path.join(wslZcodeDir, "cli", "db", "db.sqlite") : null;
|
|
1739
|
+
const zcodePaths = resolveZcodeInstallPaths(
|
|
1740
|
+
{ nativeValue: zcodeNativeValue, wslValue: zcodeWslValue }, process.env,
|
|
1741
|
+
{ cursors },
|
|
1742
|
+
);
|
|
1737
1743
|
if (zcodePaths.native || zcodePaths.wsl) {
|
|
1744
|
+
const mode = wsl.getWslMode(process.env);
|
|
1745
|
+
if (process.platform === "win32" && ["native-first", "wsl-first"].includes(mode) &&
|
|
1746
|
+
!cursors.zcodeInstallSelection && hasZcodeCursorState(cursors)) {
|
|
1747
|
+
// Preserve legacy ownership before a repair can clear old buckets,
|
|
1748
|
+
// even when the selected database currently has no rows to parse.
|
|
1749
|
+
cursors.zcodeInstallSelection = {
|
|
1750
|
+
mode, zcodeHome: process.env.ZCODE_HOME?.trim() || null,
|
|
1751
|
+
dbPath: zcodePaths.native || zcodePaths.wsl,
|
|
1752
|
+
};
|
|
1753
|
+
cursorStore.requiresCommit = true;
|
|
1754
|
+
}
|
|
1738
1755
|
if (progress?.enabled) progress.start(`Parsing ZCode ${renderBar(0)} | buckets 0`);
|
|
1739
1756
|
try {
|
|
1740
1757
|
const nativeUsageAvailable = [zcodePaths.native, zcodePaths.wsl]
|
|
@@ -1761,6 +1778,16 @@ async function cmdSync(argv, context = {}) {
|
|
|
1761
1778
|
cursors, getParams: (p) => ({ dbPath: p, readFn: readZcodeDbMessages, source: "zcode", cursorKey: "zcode" }),
|
|
1762
1779
|
queuePath, projectQueuePath, onProgress: makeProviderProgress("ZCode"),
|
|
1763
1780
|
});
|
|
1781
|
+
if (zcodeResult.recordsProcessed > 0) {
|
|
1782
|
+
if (process.platform === "win32" && ["native-first", "wsl-first"].includes(mode)) {
|
|
1783
|
+
cursors.zcodeInstallSelection = {
|
|
1784
|
+
mode, zcodeHome: process.env.ZCODE_HOME?.trim() || null,
|
|
1785
|
+
dbPath: zcodePaths.native || zcodePaths.wsl,
|
|
1786
|
+
};
|
|
1787
|
+
} else {
|
|
1788
|
+
delete cursors.zcodeInstallSelection;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1764
1791
|
} catch (err) { warnProviderParseFailure("ZCode", err, opts); }
|
|
1765
1792
|
}
|
|
1766
1793
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const fs = require("node:fs/promises");
|
|
2
|
+
const { StringDecoder } = require("node:string_decoder");
|
|
3
|
+
const { setImmediate: yieldToRequests } = require("node:timers/promises");
|
|
4
|
+
|
|
5
|
+
const CHUNK_BYTES = 64 * 1024;
|
|
6
|
+
const PROBE_BYTES = 256;
|
|
7
|
+
|
|
8
|
+
function signature(stat) {
|
|
9
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// One in-flight read per path shares the dashboard's request fan-out. Parsing
|
|
13
|
+
// yields between bounded chunks so a cold/replaced queue cannot monopolize the
|
|
14
|
+
// HTTP event loop. Keep committed newline-terminated rows separate from a tail
|
|
15
|
+
// that a concurrent writer may still be completing.
|
|
16
|
+
function createAsyncQueueReader({ parseRow, label, maxEntries = 4 }) {
|
|
17
|
+
const cache = new Map();
|
|
18
|
+
const pending = new Map();
|
|
19
|
+
|
|
20
|
+
async function probe(file, position, length) {
|
|
21
|
+
const buffer = Buffer.alloc(length);
|
|
22
|
+
const { bytesRead } = await file.read(buffer, 0, length, position);
|
|
23
|
+
return buffer.subarray(0, bytesRead);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function load(queuePath) {
|
|
27
|
+
const cached = cache.get(queuePath);
|
|
28
|
+
let file;
|
|
29
|
+
try {
|
|
30
|
+
file = await fs.open(queuePath, "r");
|
|
31
|
+
// Stat the opened file, rather than a path which can be atomically replaced
|
|
32
|
+
// between stat and open. Its signature always describes the bytes we read.
|
|
33
|
+
const stat = await file.stat({ bigint: true });
|
|
34
|
+
const nextSignature = signature(stat);
|
|
35
|
+
if (cached?.signature === nextSignature) return cached.rows;
|
|
36
|
+
const identity = `${stat.dev}:${stat.ino}`;
|
|
37
|
+
const size = Number(stat.size);
|
|
38
|
+
let offset = 0;
|
|
39
|
+
let seen = new Map();
|
|
40
|
+
if (cached && cached.identity === identity && size > cached.size && cached.consumedBytes > 0) {
|
|
41
|
+
const head = await probe(file, 0, cached.head.length);
|
|
42
|
+
const anchor = await probe(file, cached.consumedBytes - cached.anchor.length, cached.anchor.length);
|
|
43
|
+
if (head.equals(cached.head) && anchor.equals(cached.anchor)) {
|
|
44
|
+
offset = cached.consumedBytes;
|
|
45
|
+
seen = cached.seen;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const changes = new Map();
|
|
50
|
+
let malformed = 0;
|
|
51
|
+
function parse(line, target) {
|
|
52
|
+
if (!line.trim()) return;
|
|
53
|
+
try {
|
|
54
|
+
const entry = parseRow(JSON.parse(line));
|
|
55
|
+
if (entry) target.set(entry.key, entry.row);
|
|
56
|
+
} catch { malformed++; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const decoder = new StringDecoder("utf8");
|
|
60
|
+
const buffer = Buffer.allocUnsafe(CHUNK_BYTES);
|
|
61
|
+
let remainder = "";
|
|
62
|
+
let position = offset;
|
|
63
|
+
let consumedBytes = offset;
|
|
64
|
+
while (position < size) {
|
|
65
|
+
const length = Math.min(CHUNK_BYTES, size - position);
|
|
66
|
+
const { bytesRead } = await file.read(buffer, 0, length, position);
|
|
67
|
+
if (!bytesRead) throw new Error("Queue truncated during read");
|
|
68
|
+
const newline = buffer.lastIndexOf(0x0a, bytesRead - 1);
|
|
69
|
+
if (newline >= 0) consumedBytes = position + newline + 1;
|
|
70
|
+
position += bytesRead;
|
|
71
|
+
remainder += decoder.write(buffer.subarray(0, bytesRead));
|
|
72
|
+
let start = 0;
|
|
73
|
+
let end;
|
|
74
|
+
while ((end = remainder.indexOf("\n", start)) !== -1) {
|
|
75
|
+
parse(remainder.slice(start, end), changes);
|
|
76
|
+
start = end + 1;
|
|
77
|
+
}
|
|
78
|
+
remainder = remainder.slice(start);
|
|
79
|
+
await yieldToRequests();
|
|
80
|
+
}
|
|
81
|
+
remainder += decoder.end();
|
|
82
|
+
// Capture probes before committing any state: a failed read must not mutate
|
|
83
|
+
// a snapshot still being used by another endpoint.
|
|
84
|
+
const head = await probe(file, 0, Math.min(PROBE_BYTES, consumedBytes));
|
|
85
|
+
const anchor = await probe(file, Math.max(0, consumedBytes - PROBE_BYTES), Math.min(PROBE_BYTES, consumedBytes));
|
|
86
|
+
for (const [key, row] of changes) seen.set(key, row);
|
|
87
|
+
const tail = new Map();
|
|
88
|
+
parse(remainder, tail);
|
|
89
|
+
// Legacy files without a final newline are readable, but their provisional
|
|
90
|
+
// key never enters the committed map (completion may change that key).
|
|
91
|
+
const rows = tail.size ? Array.from(new Map([...seen, ...tail]).values()) : Array.from(seen.values());
|
|
92
|
+
cache.delete(queuePath);
|
|
93
|
+
cache.set(queuePath, { signature: nextSignature, identity, size, consumedBytes, head, anchor, seen, rows });
|
|
94
|
+
while (cache.size > maxEntries) cache.delete(cache.keys().next().value);
|
|
95
|
+
if (malformed) console.error(`[LocalAPI] ${label}: skipped ${malformed} malformed line(s)`);
|
|
96
|
+
return rows;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error.code === "ENOENT") {
|
|
99
|
+
cache.delete(queuePath);
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
console.error(`[LocalAPI] ${label}: failed to read queue:`, error.message);
|
|
103
|
+
return cached?.rows || [];
|
|
104
|
+
} finally {
|
|
105
|
+
if (file) await file.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return function read(queuePath) {
|
|
110
|
+
if (pending.has(queuePath)) return pending.get(queuePath);
|
|
111
|
+
const task = load(queuePath).finally(() => pending.delete(queuePath));
|
|
112
|
+
pending.set(queuePath, task);
|
|
113
|
+
return task;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { createAsyncQueueReader };
|
package/src/lib/jsonl-lines.js
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* only LF as a record separator so valid U+2028/U+2029 characters inside JSON
|
|
6
6
|
* strings remain part of the record. Reading raw bytes keeps byte budgets exact
|
|
7
7
|
* and lets malformed UTF-8 fail closed.
|
|
8
|
+
* Records longer than maxRecordBytes content bytes (excluding LF) are reported
|
|
9
|
+
* as { line: null, utf8Valid: null, oversized: true } without being buffered
|
|
10
|
+
* beyond the limit or decoded.
|
|
8
11
|
*/
|
|
9
12
|
class PhysicalJsonlLimitError extends Error {
|
|
10
13
|
constructor(maxPhysicalBytes) {
|
|
@@ -26,6 +29,7 @@ function decodePhysicalLine(buffer, { stripCr = false } = {}) {
|
|
|
26
29
|
async function* physicalJsonlRecords(input, {
|
|
27
30
|
invalidUtf8 = "throw",
|
|
28
31
|
maxPhysicalBytes = Infinity,
|
|
32
|
+
maxRecordBytes = Infinity,
|
|
29
33
|
} = {}) {
|
|
30
34
|
if (invalidUtf8 !== "throw" && invalidUtf8 !== "record") {
|
|
31
35
|
throw new TypeError(`unsupported invalidUtf8 policy: ${invalidUtf8}`);
|
|
@@ -33,8 +37,12 @@ async function* physicalJsonlRecords(input, {
|
|
|
33
37
|
const byteLimit = Number.isFinite(maxPhysicalBytes)
|
|
34
38
|
? Math.max(0, Number(maxPhysicalBytes))
|
|
35
39
|
: Infinity;
|
|
40
|
+
const recordLimit = Number.isFinite(maxRecordBytes)
|
|
41
|
+
? Math.max(0, Number(maxRecordBytes))
|
|
42
|
+
: Infinity;
|
|
36
43
|
const fragments = [];
|
|
37
44
|
let fragmentsBytes = 0;
|
|
45
|
+
let oversized = false;
|
|
38
46
|
let physicalBytesRead = 0;
|
|
39
47
|
|
|
40
48
|
for await (const value of input) {
|
|
@@ -53,6 +61,17 @@ async function* physicalJsonlRecords(input, {
|
|
|
53
61
|
if (physicalBytesRead + physicalBytes > byteLimit) {
|
|
54
62
|
throw new PhysicalJsonlLimitError(byteLimit);
|
|
55
63
|
}
|
|
64
|
+
const contentBytes = fragmentsBytes + fragment.length;
|
|
65
|
+
if (oversized || contentBytes > recordLimit) {
|
|
66
|
+
fragments.length = 0;
|
|
67
|
+
fragmentsBytes = 0;
|
|
68
|
+
oversized = false;
|
|
69
|
+
physicalBytesRead += physicalBytes;
|
|
70
|
+
start = newline + 1;
|
|
71
|
+
newline = chunk.indexOf(0x0a, start);
|
|
72
|
+
yield { line: null, utf8Valid: null, oversized: true, physicalBytes, terminated: true };
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
56
75
|
const lineBuffer = fragments.length === 0
|
|
57
76
|
? fragment
|
|
58
77
|
: Buffer.concat([...fragments, fragment], fragmentsBytes + fragment.length);
|
|
@@ -66,10 +85,10 @@ async function* physicalJsonlRecords(input, {
|
|
|
66
85
|
line = decodePhysicalLine(lineBuffer, { stripCr: true });
|
|
67
86
|
} catch (error) {
|
|
68
87
|
if (invalidUtf8 === "throw") throw error;
|
|
69
|
-
yield { line: null, utf8Valid: false, physicalBytes, terminated: true };
|
|
88
|
+
yield { line: null, utf8Valid: false, oversized: false, physicalBytes, terminated: true };
|
|
70
89
|
continue;
|
|
71
90
|
}
|
|
72
|
-
yield { line, utf8Valid: true, physicalBytes, terminated: true };
|
|
91
|
+
yield { line, utf8Valid: true, oversized: false, physicalBytes, terminated: true };
|
|
73
92
|
}
|
|
74
93
|
|
|
75
94
|
if (start < chunk.length) {
|
|
@@ -77,12 +96,23 @@ async function* physicalJsonlRecords(input, {
|
|
|
77
96
|
if (physicalBytesRead + fragmentsBytes + fragment.length > byteLimit) {
|
|
78
97
|
throw new PhysicalJsonlLimitError(byteLimit);
|
|
79
98
|
}
|
|
80
|
-
|
|
99
|
+
if (!oversized) {
|
|
100
|
+
if (fragmentsBytes + fragment.length > recordLimit) {
|
|
101
|
+
fragments.length = 0;
|
|
102
|
+
oversized = true;
|
|
103
|
+
} else {
|
|
104
|
+
fragments.push(Buffer.from(fragment));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
81
107
|
fragmentsBytes += fragment.length;
|
|
82
108
|
}
|
|
83
109
|
}
|
|
84
110
|
|
|
85
111
|
if (fragmentsBytes > 0) {
|
|
112
|
+
if (oversized) {
|
|
113
|
+
yield { line: null, utf8Valid: null, oversized: true, physicalBytes: fragmentsBytes, terminated: false };
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
86
116
|
const lineBuffer = fragments.length === 1
|
|
87
117
|
? fragments[0]
|
|
88
118
|
: Buffer.concat(fragments, fragmentsBytes);
|
|
@@ -94,7 +124,7 @@ async function* physicalJsonlRecords(input, {
|
|
|
94
124
|
if (invalidUtf8 === "throw") throw error;
|
|
95
125
|
utf8Valid = false;
|
|
96
126
|
}
|
|
97
|
-
yield { line, utf8Valid, physicalBytes: fragmentsBytes, terminated: false };
|
|
127
|
+
yield { line, utf8Valid, oversized: false, physicalBytes: fragmentsBytes, terminated: false };
|
|
98
128
|
}
|
|
99
129
|
}
|
|
100
130
|
|