@utilix-tech/sdk 0.19.0 → 0.21.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +22 -5
  3. package/dist/{chunk-F57WMKZO.js → chunk-2I4O5YCT.js} +100 -1
  4. package/dist/{chunk-U34B5XPK.js → chunk-TFNCFD3E.js} +25 -1
  5. package/dist/{chunk-TSAGO3XP.js → chunk-WO3LJMVV.js} +54 -1
  6. package/dist/{chunk-3QMUUTBH.js → chunk-Y5OOP6WN.js} +94 -1
  7. package/dist/index.cjs +269 -0
  8. package/dist/index.d.cts +4 -4
  9. package/dist/index.d.ts +4 -4
  10. package/dist/index.js +4 -4
  11. package/dist/{json-BjSoIS1h.d.cts → json-DEVFIpsC.d.cts} +26 -2
  12. package/dist/{json-BjSoIS1h.d.ts → json-DEVFIpsC.d.ts} +26 -2
  13. package/dist/{network-DZugNyAQ.d.cts → network-BJOjJnmY.d.cts} +45 -2
  14. package/dist/{network-DZugNyAQ.d.ts → network-BJOjJnmY.d.ts} +45 -2
  15. package/dist/{text-Bbx-tZ43.d.cts → text-DS70aMY5.d.cts} +33 -2
  16. package/dist/{text-Bbx-tZ43.d.ts → text-DS70aMY5.d.ts} +33 -2
  17. package/dist/tools/json.cjs +53 -0
  18. package/dist/tools/json.d.cts +1 -1
  19. package/dist/tools/json.d.ts +1 -1
  20. package/dist/tools/json.js +1 -1
  21. package/dist/tools/network.cjs +93 -0
  22. package/dist/tools/network.d.cts +1 -1
  23. package/dist/tools/network.d.ts +1 -1
  24. package/dist/tools/network.js +1 -1
  25. package/dist/tools/text.cjs +99 -0
  26. package/dist/tools/text.d.cts +1 -1
  27. package/dist/tools/text.d.ts +1 -1
  28. package/dist/tools/text.js +1 -1
  29. package/dist/tools/units.cjs +24 -0
  30. package/dist/tools/units.d.cts +1 -1
  31. package/dist/tools/units.d.ts +1 -1
  32. package/dist/tools/units.js +1 -1
  33. package/dist/{units-BFTQwpBu.d.cts → units-pIRkHnsU.d.cts} +24 -2
  34. package/dist/{units-BFTQwpBu.d.ts → units-pIRkHnsU.d.ts} +24 -2
  35. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  Full release notes for all Utilix surfaces (browser tools, REST API, SDKs, MCP server) live at [utilix.tech/changelog](https://utilix.tech/changelog). This file tracks just this package.
4
4
 
5
+ ## 0.21.0
6
+ - Add `calculatePercentageChange` (percentage-change-calculator): percentage change between two numbers, with absolute change, direction, and multiplier.
7
+ - Add `resolveJsonPointer` (json-pointer-resolver): resolve an RFC 6901 JSON Pointer against a document, with ~0/~1 escaping and clear not-found reasons.
8
+
9
+ ## 0.20.0
10
+ - Add `buildCacheControl` (cache-control-builder): assemble and explain a `Cache-Control` response header from directives, with warnings for combinations that cancel out.
11
+ - Add `detectFillerWords` (filler-word-detector): scan text for filler words, hedge phrases, and clichés with per-phrase counts and a density score.
12
+
5
13
  ## 0.19.0
6
14
  - Add `detectRedos` (regex-redos-detector): statically flag catastrophic-backtracking risk (nested quantifiers, overlapping alternation, adjacent overlapping quantifiers).
7
15
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/sdk
2
2
 
3
- **553 developer utility functions for Node.js: runs locally, no API key required.**
3
+ **558 developer utility functions for Node.js: runs locally, no API key required.**
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@utilix-tech/sdk?style=flat-square&color=cb3837)](https://www.npmjs.com/package/@utilix-tech/sdk)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/@utilix-tech/sdk?style=flat-square)](https://www.npmjs.com/package/@utilix-tech/sdk)
@@ -66,7 +66,7 @@ const { formatJson } = require("@utilix-tech/sdk/json");
66
66
 
67
67
  ```ts
68
68
  import { formatJson, minifyJson, diffJson, jsonToTypescript, validateJson,
69
- evaluateJsonPath, jsonToCsv, yamlToJson, jsonToYaml, getJsonStats } from "@utilix-tech/sdk/json";
69
+ evaluateJsonPath, resolveJsonPointer, jsonToCsv, yamlToJson, jsonToYaml, getJsonStats } from "@utilix-tech/sdk/json";
70
70
 
71
71
  // Format with 2-space indent
72
72
  formatJson('{"name":"alice","age":30}', { indent: 2 });
@@ -80,6 +80,10 @@ jsonToTypescript('{"id":1,"name":"Alice"}', { rootName: "User" });
80
80
  // Query with JSONPath
81
81
  evaluateJsonPath(obj, "$.users[*].name");
82
82
 
83
+ // Resolve an RFC 6901 JSON Pointer
84
+ resolveJsonPointer('{"users":[{"name":"Alice"}]}', "/users/0/name");
85
+ // { found: true, value: "Alice", tokens: ["users", "0", "name"] }
86
+
83
87
  // Convert JSON to CSV
84
88
  jsonToCsv('[{"a":1,"b":2}]');
85
89
 
@@ -132,7 +136,8 @@ generateHtpasswdFile([{ username: "alice", password: "secret" }]);
132
136
 
133
137
  ```ts
134
138
  import { convertCase, slugify, countWords, generateWords, generateParagraphs,
135
- escapeString, htmlToMarkdown, applyOps, detectPassiveVoice, scoreReadability } from "@utilix-tech/sdk/text";
139
+ escapeString, htmlToMarkdown, applyOps, detectPassiveVoice, scoreReadability,
140
+ detectFillerWords } from "@utilix-tech/sdk/text";
136
141
 
137
142
  convertCase("hello world", "camelCase"); // "helloWorld"
138
143
  convertCase("hello world", "PascalCase"); // "HelloWorld"
@@ -157,6 +162,10 @@ detectPassiveVoice("The report was reviewed by the committee. They approved it."
157
162
  // Flesch Reading Ease, Flesch-Kincaid Grade Level, and Gunning Fog Index
158
163
  scoreReadability("The cat sat on the mat. It was a sunny day.");
159
164
  // { fleschReadingEase: 109, fleschKincaidGrade: -0.6, gunningFog: 2.2, readingLevel: "Very easy (5th grade)", ... }
165
+
166
+ // Flag filler words, hedge phrases, and clichés with counts and density
167
+ detectFillerWords("At the end of the day, I just really think we should circle back.");
168
+ // { totalMatches: 4, fillerCount: 2, clicheCount: 2, density: 28.6, matches: [...] }
160
169
  ```
161
170
 
162
171
  ---
@@ -247,7 +256,7 @@ getNextRuns("*/5 * * * *", 5); // next 5 run timestamps
247
256
  ### `/units`: Conversions
248
257
 
249
258
  ```ts
250
- import { convertBytes, pxToAll, convert, formatValue, validateCardNumber, validateIban, calculateLoan, validateBarcode, calculateTax, calculateCompoundInterest, validateIsbn, calculateTipSplit, calculateSimpleInterest, calculateTimeValue } from "@utilix-tech/sdk/units";
259
+ import { convertBytes, pxToAll, convert, formatValue, validateCardNumber, validateIban, calculateLoan, validateBarcode, calculateTax, calculateCompoundInterest, validateIsbn, calculateTipSplit, calculateSimpleInterest, calculateTimeValue, calculatePercentageChange } from "@utilix-tech/sdk/units";
251
260
 
252
261
  // File sizes
253
262
  convertBytes(1073741824, "GB"); // 1
@@ -305,6 +314,10 @@ calculateSimpleInterest(1000, 5, 2);
305
314
  // Present / future value (time value of money), either direction
306
315
  calculateTimeValue(1000, 5, 10, "future");
307
316
  // { presentValue: 1000, futureValue: 1628.89, growthFactor: 1.628895, ... }
317
+
318
+ // Percentage change between two numbers
319
+ calculatePercentageChange(200, 250);
320
+ // { percentChange: 25, absoluteChange: 50, direction: "increase", multiplier: 1.25 }
308
321
  ```
309
322
 
310
323
  ---
@@ -315,7 +328,7 @@ calculateTimeValue(1000, 5, 10, "future");
315
328
  import { getStatusCode, isValidIp, isValidIpv4, isValidIpv6,
316
329
  searchStatusCodes, buildGeoUrl, countryCodeToFlag,
317
330
  parseHar, validateSitemap, validateRobotsTxt,
318
- parseUserAgent } from "@utilix-tech/sdk/network";
331
+ parseUserAgent, buildCacheControl } from "@utilix-tech/sdk/network";
319
332
 
320
333
  getStatusCode(404);
321
334
  // { code: 404, text: "Not Found", description: "...", category: "Client Error" }
@@ -345,6 +358,10 @@ validateSitemap('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><ur
345
358
  // Validate a robots.txt file: User-agent groups, rules, and syntax mistakes
346
359
  validateRobotsTxt("User-agent: *\nDisallow: /admin\nSitemap: https://example.com/sitemap.xml");
347
360
  // { groups: [...], sitemaps: [...], issues: [...], valid: true }
361
+
362
+ // Build and explain a Cache-Control response header from directives
363
+ buildCacheControl({ visibility: "public", maxAge: 3600, mustRevalidate: true });
364
+ // { header: "public, max-age=3600, must-revalidate", directives: [...], warnings: [] }
348
365
  ```
349
366
 
350
367
  ---
@@ -20,6 +20,7 @@ __export(text_exports, {
20
20
  csvToHtml: () => csvToHtml,
21
21
  csvToMarkdown: () => csvToMarkdown,
22
22
  detectDelimiter: () => detectDelimiter,
23
+ detectFillerWords: () => detectFillerWords,
23
24
  detectPassiveVoice: () => detectPassiveVoice,
24
25
  diffLines: () => diffLines,
25
26
  diffLinesSimple: () => diffLinesSimple,
@@ -2457,5 +2458,103 @@ function scoreReadability(text) {
2457
2458
  readingLevel: readabilityGradeLabel(fleschReadingEase)
2458
2459
  };
2459
2460
  }
2461
+ var FILLER_WORDS = [
2462
+ "very",
2463
+ "really",
2464
+ "just",
2465
+ "actually",
2466
+ "basically",
2467
+ "literally",
2468
+ "simply",
2469
+ "totally",
2470
+ "definitely",
2471
+ "certainly",
2472
+ "essentially",
2473
+ "absolutely",
2474
+ "completely",
2475
+ "extremely",
2476
+ "truly",
2477
+ "honestly",
2478
+ "seriously",
2479
+ "obviously",
2480
+ "clearly",
2481
+ "quite",
2482
+ "rather",
2483
+ "somewhat",
2484
+ "fairly",
2485
+ "virtually"
2486
+ ];
2487
+ var HEDGE_PHRASES = [
2488
+ "maybe",
2489
+ "perhaps",
2490
+ "probably",
2491
+ "possibly",
2492
+ "arguably",
2493
+ "kind of",
2494
+ "sort of",
2495
+ "a bit",
2496
+ "a little",
2497
+ "more or less",
2498
+ "in my opinion",
2499
+ "i think",
2500
+ "i believe",
2501
+ "to some extent"
2502
+ ];
2503
+ var CLICHE_PHRASES = [
2504
+ "at the end of the day",
2505
+ "think outside the box",
2506
+ "low-hanging fruit",
2507
+ "move the needle",
2508
+ "circle back",
2509
+ "touch base",
2510
+ "on the same page",
2511
+ "hit the ground running",
2512
+ "paradigm shift",
2513
+ "win-win",
2514
+ "game changer",
2515
+ "best of both worlds",
2516
+ "only time will tell",
2517
+ "back to the drawing board",
2518
+ "the tip of the iceberg",
2519
+ "a perfect storm",
2520
+ "needle in a haystack",
2521
+ "raise the bar",
2522
+ "push the envelope",
2523
+ "the elephant in the room",
2524
+ "boil the ocean",
2525
+ "when it rains it pours"
2526
+ ];
2527
+ var FILLER_DICTIONARY = [
2528
+ ...FILLER_WORDS.map((phrase) => ({ phrase, category: "filler" })),
2529
+ ...HEDGE_PHRASES.map((phrase) => ({ phrase, category: "hedge" })),
2530
+ ...CLICHE_PHRASES.map((phrase) => ({ phrase, category: "cliche" }))
2531
+ ];
2532
+ function _fillerRegex(phrase) {
2533
+ const tokens = phrase.split(/\s+/).map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
2534
+ return new RegExp("\\b" + tokens.join("\\s+") + "\\b", "gi");
2535
+ }
2536
+ function detectFillerWords(text) {
2537
+ const totalWords = (text.match(/[A-Za-z0-9']+/g) ?? []).length;
2538
+ const matches = [];
2539
+ for (const entry of FILLER_DICTIONARY) {
2540
+ const re = _fillerRegex(entry.phrase);
2541
+ const indices = [];
2542
+ let m;
2543
+ while ((m = re.exec(text)) !== null) {
2544
+ indices.push(m.index);
2545
+ if (m.index === re.lastIndex) re.lastIndex++;
2546
+ }
2547
+ if (indices.length > 0) {
2548
+ matches.push({ phrase: entry.phrase, category: entry.category, count: indices.length, indices });
2549
+ }
2550
+ }
2551
+ matches.sort((a, b) => b.count - a.count || a.category.localeCompare(b.category) || a.phrase.localeCompare(b.phrase));
2552
+ const fillerCount = matches.filter((x) => x.category === "filler").reduce((s, x) => s + x.count, 0);
2553
+ const hedgeCount = matches.filter((x) => x.category === "hedge").reduce((s, x) => s + x.count, 0);
2554
+ const clicheCount = matches.filter((x) => x.category === "cliche").reduce((s, x) => s + x.count, 0);
2555
+ const totalMatches = fillerCount + hedgeCount + clicheCount;
2556
+ const density = totalWords > 0 ? Math.round(totalMatches / totalWords * 1e3) / 10 : 0;
2557
+ return { totalWords, totalMatches, fillerCount, hedgeCount, clicheCount, density, matches };
2558
+ }
2460
2559
 
2461
- export { CASE_FORMATS, DEFAULT_CONVERT_OPTIONS, ESCAPE_MODES, LINE_OPS, MORSE_CODE, addBorder, analyzeText, applyOp, applyOps, centerText, convertAll, convertCase, countWords, csvToHtml, csvToMarkdown, detectDelimiter, detectPassiveVoice, diffLines, diffLinesSimple, diffStats, escapeAll, escapeString, extractHeadings, formatReadingTime, generate, generateDiff, generateHtmlTable, generateMarkdownTable, generateParagraphs, generateSentences, generateWords, getAvailableFonts, getMorseForChar, getMorseTiming, htmlToMarkdown, isValidMorse, markdownToHtml, morseToText, numberToOrdinal, numberToRoman, numberToWords, ordinalWords, parseDiff, parseTableData, readabilityScore, renderMarkdown, romanToNumber, scoreReadability, sentenceCount, slugify, slugifyAll, syllableCount, textToAscii, textToMorse, text_exports, unescapeString, wordFrequency };
2560
+ export { CASE_FORMATS, DEFAULT_CONVERT_OPTIONS, ESCAPE_MODES, LINE_OPS, MORSE_CODE, addBorder, analyzeText, applyOp, applyOps, centerText, convertAll, convertCase, countWords, csvToHtml, csvToMarkdown, detectDelimiter, detectFillerWords, detectPassiveVoice, diffLines, diffLinesSimple, diffStats, escapeAll, escapeString, extractHeadings, formatReadingTime, generate, generateDiff, generateHtmlTable, generateMarkdownTable, generateParagraphs, generateSentences, generateWords, getAvailableFonts, getMorseForChar, getMorseTiming, htmlToMarkdown, isValidMorse, markdownToHtml, morseToText, numberToOrdinal, numberToRoman, numberToWords, ordinalWords, parseDiff, parseTableData, readabilityScore, renderMarkdown, romanToNumber, scoreReadability, sentenceCount, slugify, slugifyAll, syllableCount, textToAscii, textToMorse, text_exports, unescapeString, wordFrequency };
@@ -20,6 +20,7 @@ __export(units_exports, {
20
20
  calcSubnet: () => calcSubnet,
21
21
  calculateCompoundInterest: () => calculateCompoundInterest,
22
22
  calculateLoan: () => calculateLoan,
23
+ calculatePercentageChange: () => calculatePercentageChange,
23
24
  calculateSimpleInterest: () => calculateSimpleInterest,
24
25
  calculateTax: () => calculateTax,
25
26
  calculateTimeValue: () => calculateTimeValue,
@@ -1337,5 +1338,28 @@ function calculateTimeValue(amount, annualRatePercent, years, direction, compoun
1337
1338
  interest: round22(futureValue - presentValue)
1338
1339
  };
1339
1340
  }
1341
+ function calculatePercentageChange(oldValue, newValue) {
1342
+ if (!Number.isFinite(oldValue)) return { error: "Original value must be a finite number" };
1343
+ if (!Number.isFinite(newValue)) return { error: "New value must be a finite number" };
1344
+ if (oldValue === 0 && newValue !== 0) {
1345
+ return { error: "Percentage change from zero is undefined (division by zero); use a nonzero original value" };
1346
+ }
1347
+ const round4 = (n) => Math.round(n * 1e4) / 1e4;
1348
+ const absoluteChange = newValue - oldValue;
1349
+ const percentChange = oldValue === 0 ? 0 : absoluteChange / Math.abs(oldValue) * 100;
1350
+ const multiplier = oldValue === 0 ? 1 : newValue / oldValue;
1351
+ let direction;
1352
+ if (absoluteChange > 0) direction = "increase";
1353
+ else if (absoluteChange < 0) direction = "decrease";
1354
+ else direction = "no change";
1355
+ return {
1356
+ oldValue,
1357
+ newValue,
1358
+ absoluteChange: round4(absoluteChange),
1359
+ percentChange: round4(percentChange),
1360
+ direction,
1361
+ multiplier: round4(multiplier)
1362
+ };
1363
+ }
1340
1364
 
1341
- export { BASE_LABELS, BASE_PREFIXES, BYTE_UNITS, COMMON_CURRENCIES, COMMON_LOCALES, COMMON_PERMISSIONS, COMMON_RATIOS, COMMON_SIZES, COMMON_TAX_RATES, COMMON_TIP_PERCENTAGES, COMMON_VIEWPORTS, autoFormat, bumpVersion, calcAspectRatio, calcSubnet, calculateCompoundInterest, calculateLoan, calculateSimpleInterest, calculateTax, calculateTimeValue, calculateTipSplit, compareSemver, convert, convertBytes, convertIp, coverBox, decimalToIp, decimalToIpCidr, describePermissions, fitIntoBox, formatNumber, formatValue, formatVwValue, formatWithSeparator, gcd, ipFromBinary, ipFromDecimal, ipFromHex, ipToBinary, ipToDecimal, ipToDecimalCidr, ipToHex, ipToOctal, isValid, isValidCidr, isValidIp, isValidIpv4, octalToPermissions, parseFileSize, parseInput, parseNumberString, parseSemver, permissionsToChmod, permissionsToOctal, permissionsToSymbolic, ptToPx, pxToAll, pxToViewport, remToPx, satisfiesRange, scaleByHeight, scaleByWidth, sortVersions, splitSubnet, symbolicToPermissions, units_exports, validateBarcode, validateCardNumber, validateIban, validateIsbn, viewportToPx };
1365
+ export { BASE_LABELS, BASE_PREFIXES, BYTE_UNITS, COMMON_CURRENCIES, COMMON_LOCALES, COMMON_PERMISSIONS, COMMON_RATIOS, COMMON_SIZES, COMMON_TAX_RATES, COMMON_TIP_PERCENTAGES, COMMON_VIEWPORTS, autoFormat, bumpVersion, calcAspectRatio, calcSubnet, calculateCompoundInterest, calculateLoan, calculatePercentageChange, calculateSimpleInterest, calculateTax, calculateTimeValue, calculateTipSplit, compareSemver, convert, convertBytes, convertIp, coverBox, decimalToIp, decimalToIpCidr, describePermissions, fitIntoBox, formatNumber, formatValue, formatVwValue, formatWithSeparator, gcd, ipFromBinary, ipFromDecimal, ipFromHex, ipToBinary, ipToDecimal, ipToDecimalCidr, ipToHex, ipToOctal, isValid, isValidCidr, isValidIp, isValidIpv4, octalToPermissions, parseFileSize, parseInput, parseNumberString, parseSemver, permissionsToChmod, permissionsToOctal, permissionsToSymbolic, ptToPx, pxToAll, pxToViewport, remToPx, satisfiesRange, scaleByHeight, scaleByWidth, sortVersions, splitSubnet, symbolicToPermissions, units_exports, validateBarcode, validateCardNumber, validateIban, validateIsbn, viewportToPx };
@@ -24,6 +24,7 @@ __export(json_exports, {
24
24
  minifyJson: () => minifyJson,
25
25
  parseJson: () => parseJson,
26
26
  parseStructs: () => parseStructs,
27
+ resolveJsonPointer: () => resolveJsonPointer,
27
28
  toPascalCase: () => toPascalCase,
28
29
  toSnakeCase: () => toSnakeCase,
29
30
  validateJson: () => validateJson,
@@ -1249,5 +1250,57 @@ function jsonToZod(json, options) {
1249
1250
 
1250
1251
  export const ${rootName}Schema = ${schema}`;
1251
1252
  }
1253
+ function _unescapePointerToken(token) {
1254
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
1255
+ }
1256
+ function resolveJsonPointer(json, pointer) {
1257
+ let parsed;
1258
+ try {
1259
+ parsed = JSON.parse(json);
1260
+ } catch {
1261
+ return { pointer, found: false, tokens: [], error: "Invalid JSON input" };
1262
+ }
1263
+ let ptr = pointer;
1264
+ if (ptr.startsWith("#")) {
1265
+ ptr = ptr.slice(1);
1266
+ try {
1267
+ ptr = decodeURIComponent(ptr);
1268
+ } catch {
1269
+ return { pointer, found: false, tokens: [], error: "Invalid percent-encoding in URI-fragment pointer" };
1270
+ }
1271
+ }
1272
+ if (ptr === "") {
1273
+ return { pointer, found: true, value: parsed, tokens: [] };
1274
+ }
1275
+ if (!ptr.startsWith("/")) {
1276
+ return { pointer, found: false, tokens: [], error: "Pointer must be empty or start with '/' (RFC 6901)" };
1277
+ }
1278
+ const tokens = ptr.split("/").slice(1).map(_unescapePointerToken);
1279
+ let current = parsed;
1280
+ for (const token of tokens) {
1281
+ if (Array.isArray(current)) {
1282
+ if (token === "-") {
1283
+ return { pointer, found: false, tokens, failedAt: token, reason: '"-" refers to the nonexistent element after the end of the array' };
1284
+ }
1285
+ if (!/^(0|[1-9][0-9]*)$/.test(token)) {
1286
+ return { pointer, found: false, tokens, failedAt: token, reason: `"${token}" is not a valid array index` };
1287
+ }
1288
+ const idx = Number(token);
1289
+ if (idx >= current.length) {
1290
+ return { pointer, found: false, tokens, failedAt: token, reason: `array index ${idx} is out of bounds (length ${current.length})` };
1291
+ }
1292
+ current = current[idx];
1293
+ } else if (current !== null && typeof current === "object") {
1294
+ const obj = current;
1295
+ if (!Object.prototype.hasOwnProperty.call(obj, token)) {
1296
+ return { pointer, found: false, tokens, failedAt: token, reason: `key "${token}" not found` };
1297
+ }
1298
+ current = obj[token];
1299
+ } else {
1300
+ return { pointer, found: false, tokens, failedAt: token, reason: `cannot descend into ${current === null ? "null" : typeof current} at "${token}"` };
1301
+ }
1302
+ }
1303
+ return { pointer, found: true, value: current, tokens };
1304
+ }
1252
1305
 
1253
- export { csvToJson, detectFormat, diffJson, evaluateJsonPath, formatDiffLine, formatJson, generateExample, getJsonStats, inferGoType, inferPythonType, inferType, jsonToCsv, jsonToGo, jsonToPython, jsonToTypescript, jsonToYaml, jsonToZod, json_exports, minifyJson, parseJson, parseStructs, toPascalCase, toSnakeCase, validateJson, validateJsonPath, validateJsonString, yamlToJson };
1306
+ export { csvToJson, detectFormat, diffJson, evaluateJsonPath, formatDiffLine, formatJson, generateExample, getJsonStats, inferGoType, inferPythonType, inferType, jsonToCsv, jsonToGo, jsonToPython, jsonToTypescript, jsonToYaml, jsonToZod, json_exports, minifyJson, parseJson, parseStructs, resolveJsonPointer, toPascalCase, toSnakeCase, validateJson, validateJsonPath, validateJsonString, yamlToJson };
@@ -6,6 +6,7 @@ __export(network_exports, {
6
6
  DNS_RECORD_TYPES: () => DNS_RECORD_TYPES,
7
7
  HTTP_HEADERS: () => HTTP_HEADERS,
8
8
  STATUS_CODES: () => STATUS_CODES,
9
+ buildCacheControl: () => buildCacheControl,
9
10
  buildDnsQueryUrl: () => buildDnsQueryUrl,
10
11
  buildGeoUrl: () => buildGeoUrl,
11
12
  buildMapUrl: () => buildMapUrl,
@@ -1721,5 +1722,97 @@ function parseUserAgent(ua) {
1721
1722
  const device = { type: _detectUaDeviceType(s) };
1722
1723
  return { browser, engine, os, device, raw: s };
1723
1724
  }
1725
+ var _CACHE_NUM_FIELDS = ["maxAge", "sMaxAge", "staleWhileRevalidate", "staleIfError"];
1726
+ function _humanizeSeconds(s) {
1727
+ if (s === 0) return "0 seconds";
1728
+ const units = [
1729
+ [86400, "day"],
1730
+ [3600, "hour"],
1731
+ [60, "minute"],
1732
+ [1, "second"]
1733
+ ];
1734
+ const parts = [];
1735
+ let rem = s;
1736
+ for (const [size, label] of units) {
1737
+ if (rem >= size) {
1738
+ const n = Math.floor(rem / size);
1739
+ rem -= n * size;
1740
+ parts.push(`${n} ${label}${n === 1 ? "" : "s"}`);
1741
+ }
1742
+ }
1743
+ return parts.slice(0, 2).join(" ");
1744
+ }
1745
+ function buildCacheControl(options = {}) {
1746
+ const nums = {
1747
+ maxAge: null,
1748
+ sMaxAge: null,
1749
+ staleWhileRevalidate: null,
1750
+ staleIfError: null
1751
+ };
1752
+ for (const field of _CACHE_NUM_FIELDS) {
1753
+ const v = options[field];
1754
+ if (v === void 0 || v === null) continue;
1755
+ if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v) || v < 0) {
1756
+ return { error: `${field} must be a non-negative integer number of seconds` };
1757
+ }
1758
+ nums[field] = v;
1759
+ }
1760
+ const directives = [];
1761
+ const warnings = [];
1762
+ if (options.visibility === "public") {
1763
+ directives.push({ name: "public", value: null, explanation: "Any cache, including shared caches and CDNs, may store this response." });
1764
+ }
1765
+ if (options.visibility === "private") {
1766
+ directives.push({ name: "private", value: null, explanation: "Only the user's browser may cache this response; shared caches and CDNs must not." });
1767
+ }
1768
+ if (options.noCache) {
1769
+ directives.push({ name: "no-cache", value: null, explanation: "A cache may store the response but must revalidate it with the origin before every reuse." });
1770
+ }
1771
+ if (options.noStore) {
1772
+ directives.push({ name: "no-store", value: null, explanation: "No cache may store any part of this request or response." });
1773
+ }
1774
+ if (nums.maxAge !== null) {
1775
+ directives.push({ name: "max-age", value: String(nums.maxAge), explanation: `Considered fresh for ${_humanizeSeconds(nums.maxAge)} (${nums.maxAge}s), after which it becomes stale.` });
1776
+ }
1777
+ if (nums.sMaxAge !== null) {
1778
+ directives.push({ name: "s-maxage", value: String(nums.sMaxAge), explanation: `For shared caches (CDNs, proxies) it is fresh for ${_humanizeSeconds(nums.sMaxAge)} (${nums.sMaxAge}s), overriding max-age there.` });
1779
+ }
1780
+ if (options.mustRevalidate) {
1781
+ directives.push({ name: "must-revalidate", value: null, explanation: "Once stale, a cache must revalidate with the origin and never serve the stale copy." });
1782
+ }
1783
+ if (options.proxyRevalidate) {
1784
+ directives.push({ name: "proxy-revalidate", value: null, explanation: "Like must-revalidate, but the requirement applies only to shared caches." });
1785
+ }
1786
+ if (options.immutable) {
1787
+ directives.push({ name: "immutable", value: null, explanation: "The body will not change while fresh, so browsers should not revalidate it on reload." });
1788
+ }
1789
+ if (options.noTransform) {
1790
+ directives.push({ name: "no-transform", value: null, explanation: "Intermediaries must not modify the response body (for example, recompressing images)." });
1791
+ }
1792
+ if (nums.staleWhileRevalidate !== null) {
1793
+ directives.push({ name: "stale-while-revalidate", value: String(nums.staleWhileRevalidate), explanation: `A cache may serve the stale response for up to ${_humanizeSeconds(nums.staleWhileRevalidate)} (${nums.staleWhileRevalidate}s) while it revalidates in the background.` });
1794
+ }
1795
+ if (nums.staleIfError !== null) {
1796
+ directives.push({ name: "stale-if-error", value: String(nums.staleIfError), explanation: `A cache may serve the stale response for up to ${_humanizeSeconds(nums.staleIfError)} (${nums.staleIfError}s) if the origin returns an error.` });
1797
+ }
1798
+ if (options.mustUnderstand) {
1799
+ directives.push({ name: "must-understand", value: null, explanation: "A cache should store the response only if it understands the caching requirements of the response's status code." });
1800
+ }
1801
+ if (options.noStore && directives.length > 1) {
1802
+ warnings.push("no-store means nothing is cached, so the other directives have no effect.");
1803
+ }
1804
+ if (options.noCache && nums.maxAge !== null) {
1805
+ warnings.push("no-cache forces revalidation on every use, so max-age has little practical effect alongside it.");
1806
+ }
1807
+ if (options.immutable && nums.maxAge === null) {
1808
+ warnings.push("immutable only applies while the response is fresh \u2014 add a max-age so it has a freshness lifetime.");
1809
+ }
1810
+ if (directives.length === 0) {
1811
+ warnings.push("No directives set \u2014 an empty Cache-Control header has no effect.");
1812
+ }
1813
+ const header = directives.map((d) => d.value === null ? d.name : `${d.name}=${d.value}`).join(", ");
1814
+ const snippet = `Cache-Control: ${header}`;
1815
+ return { header, headerName: "Cache-Control", snippet, directives, warnings };
1816
+ }
1724
1817
 
1725
- export { DNS_RECORD_TYPES, HTTP_HEADERS, STATUS_CODES, buildDnsQueryUrl, buildGeoUrl, buildMapUrl, countryCodeToFlag, formatCoordinates, formatTtl, getByCategory, getHeader, getHeadersByCategory, getStatusByCategory, getStatusCode, isClientError, isServerError, isSuccess, isValidIp, isValidIpv4, isValidIpv6, network_exports, parseDnsResponse, parseGeoResponse, parseHar, parseUserAgent, searchHeaders, searchStatusCodes, statusCodeToText, typeNumberToLabel, validateDomain, validateHeaderName, validateHeaderValue, validateRobotsTxt, validateSitemap };
1818
+ export { DNS_RECORD_TYPES, HTTP_HEADERS, STATUS_CODES, buildCacheControl, buildDnsQueryUrl, buildGeoUrl, buildMapUrl, countryCodeToFlag, formatCoordinates, formatTtl, getByCategory, getHeader, getHeadersByCategory, getStatusByCategory, getStatusCode, isClientError, isServerError, isSuccess, isValidIp, isValidIpv4, isValidIpv6, network_exports, parseDnsResponse, parseGeoResponse, parseHar, parseUserAgent, searchHeaders, searchStatusCodes, statusCodeToText, typeNumberToLabel, validateDomain, validateHeaderName, validateHeaderValue, validateRobotsTxt, validateSitemap };