@utilix-tech/sdk 0.24.0 → 0.26.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/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.26.0
6
+ - Add `computeJwkThumbprint` (jwk-thumbprint): RFC 7638 canonical thumbprint of a single JWK, hashed with SHA-256/384/512.
7
+ - Add `calculateVideoBitrate` (video-bitrate-calculator): resulting file size from a video/audio bitrate and duration, or the required bitrate to hit a target file size.
8
+
9
+ ## 0.25.0
10
+ - Add `calculateBreakEven` (break-even-calculator): break-even unit count and revenue from fixed costs, variable cost per unit, and price per unit, plus profit and margin of safety at an optional sales volume.
11
+ - Add `calculateStringDistance` (string-distance): Levenshtein edit distance and normalized similarity between two strings, with an optional Damerau (transposition) mode.
12
+
5
13
  ## 0.24.0
6
14
  - Add `calculateDiscount` / `applySuccessiveDiscounts` (discount-calculator): solve a sale price, amount saved, or discount percent from any one of them, and stack discounts in sequence where 20% then 10% is an effective 28% off.
7
15
  - Add `parseSubtitles`, `convertSubtitles`, `shiftSubtitles`, and their timestamp/format helpers (subtitle-converter): convert between SubRip and WebVTT and shift every cue by a fixed offset.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/sdk
2
2
 
3
- **573 developer utility functions for Node.js: runs locally, no API key required.**
3
+ **576 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)
@@ -137,7 +137,7 @@ generateHtpasswdFile([{ username: "alice", password: "secret" }]);
137
137
  ```ts
138
138
  import { convertCase, slugify, countWords, generateWords, generateParagraphs,
139
139
  escapeString, htmlToMarkdown, applyOps, detectPassiveVoice, scoreReadability,
140
- detectFillerWords } from "@utilix-tech/sdk/text";
140
+ detectFillerWords, calculateStringDistance } from "@utilix-tech/sdk/text";
141
141
 
142
142
  convertCase("hello world", "camelCase"); // "helloWorld"
143
143
  convertCase("hello world", "PascalCase"); // "HelloWorld"
@@ -166,6 +166,10 @@ scoreReadability("The cat sat on the mat. It was a sunny day.");
166
166
  // Flag filler words, hedge phrases, and clichés with counts and density
167
167
  detectFillerWords("At the end of the day, I just really think we should circle back.");
168
168
  // { totalMatches: 4, fillerCount: 2, clicheCount: 2, density: 28.6, matches: [...] }
169
+
170
+ // Levenshtein edit distance + similarity ratio; pass mode: "damerau" to count a transposition as 1 edit
171
+ calculateStringDistance("kitten", "sitting");
172
+ // { distance: 3, similarity: 0.5714, maxLength: 7, mode: "levenshtein" }
169
173
  ```
170
174
 
171
175
  ---
@@ -268,7 +272,7 @@ formatIso8601Duration(90061); // { iso: "P1DT1H1M1S", ... }
268
272
  ### `/units`: Conversions
269
273
 
270
274
  ```ts
271
- import { convertBytes, pxToAll, convert, formatValue, validateCardNumber, validateIban, calculateLoan, validateBarcode, calculateTax, calculateCompoundInterest, validateIsbn, calculateTipSplit, calculateSimpleInterest, calculateTimeValue, calculatePercentageChange, convertAprApy, calculateMarginMarkup, calculateDiscount, applySuccessiveDiscounts } from "@utilix-tech/sdk/units";
275
+ import { convertBytes, pxToAll, convert, formatValue, validateCardNumber, validateIban, calculateLoan, validateBarcode, calculateTax, calculateCompoundInterest, validateIsbn, calculateTipSplit, calculateSimpleInterest, calculateTimeValue, calculatePercentageChange, convertAprApy, calculateMarginMarkup, calculateDiscount, applySuccessiveDiscounts, calculateBreakEven, calculateVideoBitrate } from "@utilix-tech/sdk/units";
272
276
 
273
277
  // File sizes
274
278
  convertBytes(1073741824, "GB"); // 1
@@ -346,6 +350,14 @@ calculateDiscount({ originalPrice: 80, discountPercent: 25 });
346
350
  // Stacked discounts: 20% then a further 10% is 28% off, not 30%
347
351
  applySuccessiveDiscounts(100, [20, 10]);
348
352
  // { finalPrice: 72, totalSaved: 28, effectiveDiscountPercent: 28, sumOfDiscountPercents: 30, ... }
353
+
354
+ // Break-even point: fixed costs, variable cost/unit, price/unit
355
+ calculateBreakEven({ fixedCosts: 10000, variableCostPerUnit: 20, pricePerUnit: 50 });
356
+ // { breakEvenUnitsRounded: 334, breakEvenRevenue: 16666.6667, contributionMarginPerUnit: 30, ... }
357
+
358
+ // Video file size from a bitrate + duration, or the bitrate to hit a target size
359
+ calculateVideoBitrate({ mode: "size-from-bitrate", durationSeconds: 600, videoBitrateKbps: 5000, audioBitrateKbps: 192 });
360
+ // { totalBitrateKbps: 5192, fileSizeBytes: 389400000, fileSizeMB: 389.4, fileSizeMiB: 371.36, ... }
349
361
  ```
350
362
 
351
363
  ---
@@ -397,7 +409,7 @@ buildCacheControl({ visibility: "public", maxAge: 3600, mustRevalidate: true });
397
409
  ### `/api`: cURL, JWT, CORS, HTTP
398
410
 
399
411
  ```ts
400
- import { buildCurl, parseCurlCommand, decodeJwt, signJwt, parseJwks,
412
+ import { buildCurl, parseCurlCommand, decodeJwt, signJwt, parseJwks, computeJwkThumbprint,
401
413
  generateCors, generateCspHeader } from "@utilix-tech/sdk/api";
402
414
 
403
415
  buildCurl({
@@ -421,6 +433,10 @@ decodeJwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...");
421
433
  parseJwks('{"keys":[{"kty":"RSA","use":"sig","kid":"1","n":"...","e":"AQAB"}]}');
422
434
  // { ok: true, keys: [{ kty: "RSA", kid: "1", keySize: 2048, ... }], count: 1, duplicateKids: [] }
423
435
 
436
+ // JWK thumbprint (RFC 7638): canonical fingerprint of a single JWK
437
+ computeJwkThumbprint('{"kty":"oct","k":"c2VjcmV0"}');
438
+ // { ok: true, kty: "oct", hashAlg: "SHA-256", thumbprint: "DWBh0SEIAPYh1x5uvot4z3AhaikHkxNJa3Ada2fT-Cg", ... }
439
+
424
440
  // Generate CORS headers
425
441
  generateCors({ origins: ["https://myapp.com"], methods: ["GET", "POST"] });
426
442
 
@@ -595,11 +611,11 @@ readIcoInfo(new Uint8Array(icoBytes));
595
611
  | `@utilix-tech/sdk/json` | Format, minify, diff, validate, JSONPath, CSV↔JSON, YAML↔JSON, TS types |
596
612
  | `@utilix-tech/sdk/encoding` | Base64, URL, HTML entities, Base32/Base58/Base62 encode/decode |
597
613
  | `@utilix-tech/sdk/hashing` | MD5, SHA-1/256/512, bcrypt, HMAC, .htpasswd |
598
- | `@utilix-tech/sdk/text` | Case convert, slugify, word count, lorem ipsum, line ops, HTML→Markdown |
614
+ | `@utilix-tech/sdk/text` | Case convert, slugify, word count, lorem ipsum, line ops, HTML→Markdown, Levenshtein/Damerau string distance |
599
615
  | `@utilix-tech/sdk/data` | CSV, YAML, TOML, XML, INI, NDJSON parse and convert, SRT/WebVTT subtitles |
600
616
  | `@utilix-tech/sdk/generators` | UUID v4/v7, ULID, passwords, strength check, fake data |
601
617
  | `@utilix-tech/sdk/time` | Date diff, timezone convert, cron parse, relative time |
602
- | `@utilix-tech/sdk/units` | Bytes, CSS px/rem, number bases, currency format, credit card/IBAN/barcode/ISBN validation, loan calculator, compound/simple interest calculators, VAT/sales tax calculator, tip/bill split calculator |
618
+ | `@utilix-tech/sdk/units` | Bytes, CSS px/rem, number bases, currency format, credit card/IBAN/barcode/ISBN validation, loan calculator, compound/simple interest calculators, VAT/sales tax calculator, tip/bill split calculator, break-even point calculator |
603
619
  | `@utilix-tech/sdk/network` | HTTP status codes, IP validation, country flags, geolocation URLs, HAR file parsing, User-Agent parsing |
604
620
  | `@utilix-tech/sdk/api` | cURL build/parse, JWT decode/sign, JWKS parse, CORS headers, CSP header |
605
621
  | `@utilix-tech/sdk/code` | SQL format/minify, HTML format/minify, regex tester, GraphQL format, MIME type lookup, GitHub URL parsing |
@@ -5,6 +5,7 @@
5
5
  * - curl-to-code : parseCurlCommand, convertToCode
6
6
  * - jwt-decoder : decodeJwt, formatJwtJson
7
7
  * - jwk-viewer : parseJwks
8
+ * - jwk-thumbprint : computeJwkThumbprint
8
9
  * - jwt-signer : base64urlEncode, base64urlDecode, signJwt
9
10
  * - cors-builder : validateCorsConfig, generateCors
10
11
  * - csp-builder : generateCspHeader, generateFullHeader, parseCspHeader, validateCspConfig
@@ -106,6 +107,27 @@ type JwksParseResult = JwksResult | JwksError;
106
107
  * use the keys cryptographically: this is inspection only.
107
108
  */
108
109
  declare function parseJwks(input: string): JwksParseResult;
110
+ type JwkThumbprintHash = 'SHA-256' | 'SHA-384' | 'SHA-512';
111
+ interface JwkThumbprintResult {
112
+ ok: true;
113
+ kty: string;
114
+ hashAlg: JwkThumbprintHash;
115
+ members: string[];
116
+ canonicalJson: string;
117
+ thumbprint: string;
118
+ }
119
+ interface JwkThumbprintErrorResult {
120
+ ok: false;
121
+ error: string;
122
+ }
123
+ type JwkThumbprintOutcome = JwkThumbprintResult | JwkThumbprintErrorResult;
124
+ /**
125
+ * Compute the RFC 7638 thumbprint of a single JWK: build the canonical JSON
126
+ * of its required members (lexicographic key order, no whitespace) and hash
127
+ * it. This is a fingerprint for identifying/matching a key, not a signature
128
+ * or verification, and it only accepts a bare JWK, not a JWKS set.
129
+ */
130
+ declare function computeJwkThumbprint(input: string, hashAlg?: JwkThumbprintHash): JwkThumbprintOutcome;
109
131
  type JwtAlgorithm = 'HS256' | 'HS384' | 'HS512';
110
132
  interface JwtSignOptions {
111
133
  algorithm?: JwtAlgorithm;
@@ -284,6 +306,10 @@ declare const api_HTTP_METHODS: typeof HTTP_METHODS;
284
306
  type api_Header = Header;
285
307
  type api_HttpMethod = HttpMethod;
286
308
  type api_JwkSummary = JwkSummary;
309
+ type api_JwkThumbprintErrorResult = JwkThumbprintErrorResult;
310
+ type api_JwkThumbprintHash = JwkThumbprintHash;
311
+ type api_JwkThumbprintOutcome = JwkThumbprintOutcome;
312
+ type api_JwkThumbprintResult = JwkThumbprintResult;
287
313
  type api_JwksError = JwksError;
288
314
  type api_JwksParseResult = JwksParseResult;
289
315
  type api_JwksResult = JwksResult;
@@ -307,6 +333,7 @@ declare const api_base64urlEncode: typeof base64urlEncode;
307
333
  declare const api_buildCurl: typeof buildCurl;
308
334
  declare const api_buildCurlMultiline: typeof buildCurlMultiline;
309
335
  declare const api_buildUrl: typeof buildUrl;
336
+ declare const api_computeJwkThumbprint: typeof computeJwkThumbprint;
310
337
  declare const api_convertToCode: typeof convertToCode;
311
338
  declare const api_decodeJwt: typeof decodeJwt;
312
339
  declare const api_decodeUrlComponent: typeof decodeUrlComponent;
@@ -335,7 +362,7 @@ declare const api_validateCorsConfig: typeof validateCorsConfig;
335
362
  declare const api_validateCspConfig: typeof validateCspConfig;
336
363
  declare const api_validateOgTags: typeof validateOgTags;
337
364
  declare namespace api {
338
- export { api_COMMON_HEADERS as COMMON_HEADERS, api_COMMON_PRESETS as COMMON_PRESETS, api_CORS_COMMON_HEADERS as CORS_COMMON_HEADERS, type api_CorsConfig as CorsConfig, type api_CorsOutput as CorsOutput, type api_CspConfig as CspConfig, type api_CspDirective as CspDirective, type api_CspSource as CspSource, type api_CurlCommand as CurlCommand, type api_CurlConfig as CurlConfig, api_DEFAULT_CORS_CONFIG as DEFAULT_CORS_CONFIG, api_DIRECTIVE_DESCRIPTIONS as DIRECTIVE_DESCRIPTIONS, type api_DirectiveConfig as DirectiveConfig, type api_FormatResult as FormatResult, api_HTTP_METHODS as HTTP_METHODS, type api_Header as Header, type api_HttpMethod as HttpMethod, type api_JwkSummary as JwkSummary, type api_JwksError as JwksError, type api_JwksParseResult as JwksParseResult, type api_JwksResult as JwksResult, type api_JwtAlgorithm as JwtAlgorithm, type api_JwtDecodeError as JwtDecodeError, type api_JwtDecodeResult as JwtDecodeResult, type api_JwtExpiry as JwtExpiry, type api_JwtPart as JwtPart, type api_JwtResult as JwtResult, type api_JwtSignOptions as JwtSignOptions, type api_MetaTags as MetaTags, type api_OgTags as OgTags, type api_ParsedUrl as ParsedUrl, type api_ResponseFormat as ResponseFormat, api_SUPPORTED_LANGUAGES as SUPPORTED_LANGUAGES, type api_TargetLanguage as TargetLanguage, type api_TwitterTags as TwitterTags, api_addParam as addParam, api_base64urlDecode as base64urlDecode, api_base64urlEncode as base64urlEncode, api_buildCurl as buildCurl, api_buildCurlMultiline as buildCurlMultiline, api_buildUrl as buildUrl, api_convertToCode as convertToCode, api_decodeJwt as decodeJwt, api_decodeUrlComponent as decodeUrlComponent, api_detectFormat as detectFormat, api_encodeUrlComponent as encodeUrlComponent, api_extractJsonPaths as extractJsonPaths, api_formatJwtJson as formatJwtJson, api_formatResponse as formatResponse, api_generateCors as generateCors, api_generateCspHeader as generateCspHeader, api_generateFullHeader as generateFullHeader, api_generateMetaHtml as generateMetaHtml, api_getParam as getParam, api_getStats as getStats, api_isValidUrl as isValidUrl, api_normalizeUrl as normalizeUrl, api_parseCspHeader as parseCspHeader, api_parseCurl as parseCurl, api_parseCurlCommand as parseCurlCommand, api_parseJwks as parseJwks, api_parseMetaTags as parseMetaTags, api_parseUrl as parseUrl, api_removeParam as removeParam, api_signJwt as signJwt, api_validateCorsConfig as validateCorsConfig, api_validateCspConfig as validateCspConfig, api_validateOgTags as validateOgTags };
365
+ export { api_COMMON_HEADERS as COMMON_HEADERS, api_COMMON_PRESETS as COMMON_PRESETS, api_CORS_COMMON_HEADERS as CORS_COMMON_HEADERS, type api_CorsConfig as CorsConfig, type api_CorsOutput as CorsOutput, type api_CspConfig as CspConfig, type api_CspDirective as CspDirective, type api_CspSource as CspSource, type api_CurlCommand as CurlCommand, type api_CurlConfig as CurlConfig, api_DEFAULT_CORS_CONFIG as DEFAULT_CORS_CONFIG, api_DIRECTIVE_DESCRIPTIONS as DIRECTIVE_DESCRIPTIONS, type api_DirectiveConfig as DirectiveConfig, type api_FormatResult as FormatResult, api_HTTP_METHODS as HTTP_METHODS, type api_Header as Header, type api_HttpMethod as HttpMethod, type api_JwkSummary as JwkSummary, type api_JwkThumbprintErrorResult as JwkThumbprintErrorResult, type api_JwkThumbprintHash as JwkThumbprintHash, type api_JwkThumbprintOutcome as JwkThumbprintOutcome, type api_JwkThumbprintResult as JwkThumbprintResult, type api_JwksError as JwksError, type api_JwksParseResult as JwksParseResult, type api_JwksResult as JwksResult, type api_JwtAlgorithm as JwtAlgorithm, type api_JwtDecodeError as JwtDecodeError, type api_JwtDecodeResult as JwtDecodeResult, type api_JwtExpiry as JwtExpiry, type api_JwtPart as JwtPart, type api_JwtResult as JwtResult, type api_JwtSignOptions as JwtSignOptions, type api_MetaTags as MetaTags, type api_OgTags as OgTags, type api_ParsedUrl as ParsedUrl, type api_ResponseFormat as ResponseFormat, api_SUPPORTED_LANGUAGES as SUPPORTED_LANGUAGES, type api_TargetLanguage as TargetLanguage, type api_TwitterTags as TwitterTags, api_addParam as addParam, api_base64urlDecode as base64urlDecode, api_base64urlEncode as base64urlEncode, api_buildCurl as buildCurl, api_buildCurlMultiline as buildCurlMultiline, api_buildUrl as buildUrl, api_computeJwkThumbprint as computeJwkThumbprint, api_convertToCode as convertToCode, api_decodeJwt as decodeJwt, api_decodeUrlComponent as decodeUrlComponent, api_detectFormat as detectFormat, api_encodeUrlComponent as encodeUrlComponent, api_extractJsonPaths as extractJsonPaths, api_formatJwtJson as formatJwtJson, api_formatResponse as formatResponse, api_generateCors as generateCors, api_generateCspHeader as generateCspHeader, api_generateFullHeader as generateFullHeader, api_generateMetaHtml as generateMetaHtml, api_getParam as getParam, api_getStats as getStats, api_isValidUrl as isValidUrl, api_normalizeUrl as normalizeUrl, api_parseCspHeader as parseCspHeader, api_parseCurl as parseCurl, api_parseCurlCommand as parseCurlCommand, api_parseJwks as parseJwks, api_parseMetaTags as parseMetaTags, api_parseUrl as parseUrl, api_removeParam as removeParam, api_signJwt as signJwt, api_validateCorsConfig as validateCorsConfig, api_validateCspConfig as validateCspConfig, api_validateOgTags as validateOgTags };
339
366
  }
340
367
 
341
- export { generateMetaHtml as $, base64urlDecode as A, base64urlEncode as B, COMMON_HEADERS as C, DEFAULT_CORS_CONFIG as D, buildCurl as E, type FormatResult as F, buildCurlMultiline as G, HTTP_METHODS as H, buildUrl as I, type JwkSummary as J, convertToCode as K, decodeJwt as L, type MetaTags as M, decodeUrlComponent as N, type OgTags as O, type ParsedUrl as P, detectFormat as Q, type ResponseFormat as R, SUPPORTED_LANGUAGES as S, type TargetLanguage as T, encodeUrlComponent as U, extractJsonPaths as V, formatJwtJson as W, formatResponse as X, generateCors as Y, generateCspHeader as Z, generateFullHeader as _, api as a, getParam as a0, getStats as a1, isValidUrl as a2, normalizeUrl as a3, parseCspHeader as a4, parseCurl as a5, parseCurlCommand as a6, parseJwks as a7, parseMetaTags as a8, parseUrl as a9, removeParam as aa, signJwt as ab, validateCorsConfig as ac, validateCspConfig as ad, validateOgTags as ae, COMMON_PRESETS as b, CORS_COMMON_HEADERS as c, type CorsConfig as d, type CorsOutput as e, type CspConfig as f, type CspDirective as g, type CspSource as h, type CurlCommand as i, type CurlConfig as j, DIRECTIVE_DESCRIPTIONS as k, type DirectiveConfig as l, type Header as m, type HttpMethod as n, type JwksError as o, type JwksParseResult as p, type JwksResult as q, type JwtAlgorithm as r, type JwtDecodeError as s, type JwtDecodeResult as t, type JwtExpiry as u, type JwtPart as v, type JwtResult as w, type JwtSignOptions as x, type TwitterTags as y, addParam as z };
368
+ export { formatJwtJson as $, type JwtResult as A, type JwtSignOptions as B, COMMON_HEADERS as C, DEFAULT_CORS_CONFIG as D, type TwitterTags as E, type FormatResult as F, addParam as G, HTTP_METHODS as H, base64urlDecode as I, type JwkSummary as J, base64urlEncode as K, buildCurl as L, type MetaTags as M, buildCurlMultiline as N, type OgTags as O, type ParsedUrl as P, buildUrl as Q, type ResponseFormat as R, SUPPORTED_LANGUAGES as S, type TargetLanguage as T, computeJwkThumbprint as U, convertToCode as V, decodeJwt as W, decodeUrlComponent as X, detectFormat as Y, encodeUrlComponent as Z, extractJsonPaths as _, api as a, formatResponse as a0, generateCors as a1, generateCspHeader as a2, generateFullHeader as a3, generateMetaHtml as a4, getParam as a5, getStats as a6, isValidUrl as a7, normalizeUrl as a8, parseCspHeader as a9, parseCurl as aa, parseCurlCommand as ab, parseJwks as ac, parseMetaTags as ad, parseUrl as ae, removeParam as af, signJwt as ag, validateCorsConfig as ah, validateCspConfig as ai, validateOgTags as aj, COMMON_PRESETS as b, CORS_COMMON_HEADERS as c, type CorsConfig as d, type CorsOutput as e, type CspConfig as f, type CspDirective as g, type CspSource as h, type CurlCommand as i, type CurlConfig as j, DIRECTIVE_DESCRIPTIONS as k, type DirectiveConfig as l, type Header as m, type HttpMethod as n, type JwkThumbprintErrorResult as o, type JwkThumbprintHash as p, type JwkThumbprintOutcome as q, type JwkThumbprintResult as r, type JwksError as s, type JwksParseResult as t, type JwksResult as u, type JwtAlgorithm as v, type JwtDecodeError as w, type JwtDecodeResult as x, type JwtExpiry as y, type JwtPart as z };
@@ -5,6 +5,7 @@
5
5
  * - curl-to-code : parseCurlCommand, convertToCode
6
6
  * - jwt-decoder : decodeJwt, formatJwtJson
7
7
  * - jwk-viewer : parseJwks
8
+ * - jwk-thumbprint : computeJwkThumbprint
8
9
  * - jwt-signer : base64urlEncode, base64urlDecode, signJwt
9
10
  * - cors-builder : validateCorsConfig, generateCors
10
11
  * - csp-builder : generateCspHeader, generateFullHeader, parseCspHeader, validateCspConfig
@@ -106,6 +107,27 @@ type JwksParseResult = JwksResult | JwksError;
106
107
  * use the keys cryptographically: this is inspection only.
107
108
  */
108
109
  declare function parseJwks(input: string): JwksParseResult;
110
+ type JwkThumbprintHash = 'SHA-256' | 'SHA-384' | 'SHA-512';
111
+ interface JwkThumbprintResult {
112
+ ok: true;
113
+ kty: string;
114
+ hashAlg: JwkThumbprintHash;
115
+ members: string[];
116
+ canonicalJson: string;
117
+ thumbprint: string;
118
+ }
119
+ interface JwkThumbprintErrorResult {
120
+ ok: false;
121
+ error: string;
122
+ }
123
+ type JwkThumbprintOutcome = JwkThumbprintResult | JwkThumbprintErrorResult;
124
+ /**
125
+ * Compute the RFC 7638 thumbprint of a single JWK: build the canonical JSON
126
+ * of its required members (lexicographic key order, no whitespace) and hash
127
+ * it. This is a fingerprint for identifying/matching a key, not a signature
128
+ * or verification, and it only accepts a bare JWK, not a JWKS set.
129
+ */
130
+ declare function computeJwkThumbprint(input: string, hashAlg?: JwkThumbprintHash): JwkThumbprintOutcome;
109
131
  type JwtAlgorithm = 'HS256' | 'HS384' | 'HS512';
110
132
  interface JwtSignOptions {
111
133
  algorithm?: JwtAlgorithm;
@@ -284,6 +306,10 @@ declare const api_HTTP_METHODS: typeof HTTP_METHODS;
284
306
  type api_Header = Header;
285
307
  type api_HttpMethod = HttpMethod;
286
308
  type api_JwkSummary = JwkSummary;
309
+ type api_JwkThumbprintErrorResult = JwkThumbprintErrorResult;
310
+ type api_JwkThumbprintHash = JwkThumbprintHash;
311
+ type api_JwkThumbprintOutcome = JwkThumbprintOutcome;
312
+ type api_JwkThumbprintResult = JwkThumbprintResult;
287
313
  type api_JwksError = JwksError;
288
314
  type api_JwksParseResult = JwksParseResult;
289
315
  type api_JwksResult = JwksResult;
@@ -307,6 +333,7 @@ declare const api_base64urlEncode: typeof base64urlEncode;
307
333
  declare const api_buildCurl: typeof buildCurl;
308
334
  declare const api_buildCurlMultiline: typeof buildCurlMultiline;
309
335
  declare const api_buildUrl: typeof buildUrl;
336
+ declare const api_computeJwkThumbprint: typeof computeJwkThumbprint;
310
337
  declare const api_convertToCode: typeof convertToCode;
311
338
  declare const api_decodeJwt: typeof decodeJwt;
312
339
  declare const api_decodeUrlComponent: typeof decodeUrlComponent;
@@ -335,7 +362,7 @@ declare const api_validateCorsConfig: typeof validateCorsConfig;
335
362
  declare const api_validateCspConfig: typeof validateCspConfig;
336
363
  declare const api_validateOgTags: typeof validateOgTags;
337
364
  declare namespace api {
338
- export { api_COMMON_HEADERS as COMMON_HEADERS, api_COMMON_PRESETS as COMMON_PRESETS, api_CORS_COMMON_HEADERS as CORS_COMMON_HEADERS, type api_CorsConfig as CorsConfig, type api_CorsOutput as CorsOutput, type api_CspConfig as CspConfig, type api_CspDirective as CspDirective, type api_CspSource as CspSource, type api_CurlCommand as CurlCommand, type api_CurlConfig as CurlConfig, api_DEFAULT_CORS_CONFIG as DEFAULT_CORS_CONFIG, api_DIRECTIVE_DESCRIPTIONS as DIRECTIVE_DESCRIPTIONS, type api_DirectiveConfig as DirectiveConfig, type api_FormatResult as FormatResult, api_HTTP_METHODS as HTTP_METHODS, type api_Header as Header, type api_HttpMethod as HttpMethod, type api_JwkSummary as JwkSummary, type api_JwksError as JwksError, type api_JwksParseResult as JwksParseResult, type api_JwksResult as JwksResult, type api_JwtAlgorithm as JwtAlgorithm, type api_JwtDecodeError as JwtDecodeError, type api_JwtDecodeResult as JwtDecodeResult, type api_JwtExpiry as JwtExpiry, type api_JwtPart as JwtPart, type api_JwtResult as JwtResult, type api_JwtSignOptions as JwtSignOptions, type api_MetaTags as MetaTags, type api_OgTags as OgTags, type api_ParsedUrl as ParsedUrl, type api_ResponseFormat as ResponseFormat, api_SUPPORTED_LANGUAGES as SUPPORTED_LANGUAGES, type api_TargetLanguage as TargetLanguage, type api_TwitterTags as TwitterTags, api_addParam as addParam, api_base64urlDecode as base64urlDecode, api_base64urlEncode as base64urlEncode, api_buildCurl as buildCurl, api_buildCurlMultiline as buildCurlMultiline, api_buildUrl as buildUrl, api_convertToCode as convertToCode, api_decodeJwt as decodeJwt, api_decodeUrlComponent as decodeUrlComponent, api_detectFormat as detectFormat, api_encodeUrlComponent as encodeUrlComponent, api_extractJsonPaths as extractJsonPaths, api_formatJwtJson as formatJwtJson, api_formatResponse as formatResponse, api_generateCors as generateCors, api_generateCspHeader as generateCspHeader, api_generateFullHeader as generateFullHeader, api_generateMetaHtml as generateMetaHtml, api_getParam as getParam, api_getStats as getStats, api_isValidUrl as isValidUrl, api_normalizeUrl as normalizeUrl, api_parseCspHeader as parseCspHeader, api_parseCurl as parseCurl, api_parseCurlCommand as parseCurlCommand, api_parseJwks as parseJwks, api_parseMetaTags as parseMetaTags, api_parseUrl as parseUrl, api_removeParam as removeParam, api_signJwt as signJwt, api_validateCorsConfig as validateCorsConfig, api_validateCspConfig as validateCspConfig, api_validateOgTags as validateOgTags };
365
+ export { api_COMMON_HEADERS as COMMON_HEADERS, api_COMMON_PRESETS as COMMON_PRESETS, api_CORS_COMMON_HEADERS as CORS_COMMON_HEADERS, type api_CorsConfig as CorsConfig, type api_CorsOutput as CorsOutput, type api_CspConfig as CspConfig, type api_CspDirective as CspDirective, type api_CspSource as CspSource, type api_CurlCommand as CurlCommand, type api_CurlConfig as CurlConfig, api_DEFAULT_CORS_CONFIG as DEFAULT_CORS_CONFIG, api_DIRECTIVE_DESCRIPTIONS as DIRECTIVE_DESCRIPTIONS, type api_DirectiveConfig as DirectiveConfig, type api_FormatResult as FormatResult, api_HTTP_METHODS as HTTP_METHODS, type api_Header as Header, type api_HttpMethod as HttpMethod, type api_JwkSummary as JwkSummary, type api_JwkThumbprintErrorResult as JwkThumbprintErrorResult, type api_JwkThumbprintHash as JwkThumbprintHash, type api_JwkThumbprintOutcome as JwkThumbprintOutcome, type api_JwkThumbprintResult as JwkThumbprintResult, type api_JwksError as JwksError, type api_JwksParseResult as JwksParseResult, type api_JwksResult as JwksResult, type api_JwtAlgorithm as JwtAlgorithm, type api_JwtDecodeError as JwtDecodeError, type api_JwtDecodeResult as JwtDecodeResult, type api_JwtExpiry as JwtExpiry, type api_JwtPart as JwtPart, type api_JwtResult as JwtResult, type api_JwtSignOptions as JwtSignOptions, type api_MetaTags as MetaTags, type api_OgTags as OgTags, type api_ParsedUrl as ParsedUrl, type api_ResponseFormat as ResponseFormat, api_SUPPORTED_LANGUAGES as SUPPORTED_LANGUAGES, type api_TargetLanguage as TargetLanguage, type api_TwitterTags as TwitterTags, api_addParam as addParam, api_base64urlDecode as base64urlDecode, api_base64urlEncode as base64urlEncode, api_buildCurl as buildCurl, api_buildCurlMultiline as buildCurlMultiline, api_buildUrl as buildUrl, api_computeJwkThumbprint as computeJwkThumbprint, api_convertToCode as convertToCode, api_decodeJwt as decodeJwt, api_decodeUrlComponent as decodeUrlComponent, api_detectFormat as detectFormat, api_encodeUrlComponent as encodeUrlComponent, api_extractJsonPaths as extractJsonPaths, api_formatJwtJson as formatJwtJson, api_formatResponse as formatResponse, api_generateCors as generateCors, api_generateCspHeader as generateCspHeader, api_generateFullHeader as generateFullHeader, api_generateMetaHtml as generateMetaHtml, api_getParam as getParam, api_getStats as getStats, api_isValidUrl as isValidUrl, api_normalizeUrl as normalizeUrl, api_parseCspHeader as parseCspHeader, api_parseCurl as parseCurl, api_parseCurlCommand as parseCurlCommand, api_parseJwks as parseJwks, api_parseMetaTags as parseMetaTags, api_parseUrl as parseUrl, api_removeParam as removeParam, api_signJwt as signJwt, api_validateCorsConfig as validateCorsConfig, api_validateCspConfig as validateCspConfig, api_validateOgTags as validateOgTags };
339
366
  }
340
367
 
341
- export { generateMetaHtml as $, base64urlDecode as A, base64urlEncode as B, COMMON_HEADERS as C, DEFAULT_CORS_CONFIG as D, buildCurl as E, type FormatResult as F, buildCurlMultiline as G, HTTP_METHODS as H, buildUrl as I, type JwkSummary as J, convertToCode as K, decodeJwt as L, type MetaTags as M, decodeUrlComponent as N, type OgTags as O, type ParsedUrl as P, detectFormat as Q, type ResponseFormat as R, SUPPORTED_LANGUAGES as S, type TargetLanguage as T, encodeUrlComponent as U, extractJsonPaths as V, formatJwtJson as W, formatResponse as X, generateCors as Y, generateCspHeader as Z, generateFullHeader as _, api as a, getParam as a0, getStats as a1, isValidUrl as a2, normalizeUrl as a3, parseCspHeader as a4, parseCurl as a5, parseCurlCommand as a6, parseJwks as a7, parseMetaTags as a8, parseUrl as a9, removeParam as aa, signJwt as ab, validateCorsConfig as ac, validateCspConfig as ad, validateOgTags as ae, COMMON_PRESETS as b, CORS_COMMON_HEADERS as c, type CorsConfig as d, type CorsOutput as e, type CspConfig as f, type CspDirective as g, type CspSource as h, type CurlCommand as i, type CurlConfig as j, DIRECTIVE_DESCRIPTIONS as k, type DirectiveConfig as l, type Header as m, type HttpMethod as n, type JwksError as o, type JwksParseResult as p, type JwksResult as q, type JwtAlgorithm as r, type JwtDecodeError as s, type JwtDecodeResult as t, type JwtExpiry as u, type JwtPart as v, type JwtResult as w, type JwtSignOptions as x, type TwitterTags as y, addParam as z };
368
+ export { formatJwtJson as $, type JwtResult as A, type JwtSignOptions as B, COMMON_HEADERS as C, DEFAULT_CORS_CONFIG as D, type TwitterTags as E, type FormatResult as F, addParam as G, HTTP_METHODS as H, base64urlDecode as I, type JwkSummary as J, base64urlEncode as K, buildCurl as L, type MetaTags as M, buildCurlMultiline as N, type OgTags as O, type ParsedUrl as P, buildUrl as Q, type ResponseFormat as R, SUPPORTED_LANGUAGES as S, type TargetLanguage as T, computeJwkThumbprint as U, convertToCode as V, decodeJwt as W, decodeUrlComponent as X, detectFormat as Y, encodeUrlComponent as Z, extractJsonPaths as _, api as a, formatResponse as a0, generateCors as a1, generateCspHeader as a2, generateFullHeader as a3, generateMetaHtml as a4, getParam as a5, getStats as a6, isValidUrl as a7, normalizeUrl as a8, parseCspHeader as a9, parseCurl as aa, parseCurlCommand as ab, parseJwks as ac, parseMetaTags as ad, parseUrl as ae, removeParam as af, signJwt as ag, validateCorsConfig as ah, validateCspConfig as ai, validateOgTags as aj, COMMON_PRESETS as b, CORS_COMMON_HEADERS as c, type CorsConfig as d, type CorsOutput as e, type CspConfig as f, type CspDirective as g, type CspSource as h, type CurlCommand as i, type CurlConfig as j, DIRECTIVE_DESCRIPTIONS as k, type DirectiveConfig as l, type Header as m, type HttpMethod as n, type JwkThumbprintErrorResult as o, type JwkThumbprintHash as p, type JwkThumbprintOutcome as q, type JwkThumbprintResult as r, type JwksError as s, type JwksParseResult as t, type JwksResult as u, type JwtAlgorithm as v, type JwtDecodeError as w, type JwtDecodeResult as x, type JwtExpiry as y, type JwtPart as z };
@@ -1,5 +1,5 @@
1
1
  import { __export } from './chunk-MLKGABMK.js';
2
- import { createHmac } from 'crypto';
2
+ import { createHash, createHmac } from 'crypto';
3
3
 
4
4
  // src/tools/api.ts
5
5
  var api_exports = {};
@@ -17,6 +17,7 @@ __export(api_exports, {
17
17
  buildCurl: () => buildCurl,
18
18
  buildCurlMultiline: () => buildCurlMultiline,
19
19
  buildUrl: () => buildUrl,
20
+ computeJwkThumbprint: () => computeJwkThumbprint,
20
21
  convertToCode: () => convertToCode,
21
22
  decodeJwt: () => decodeJwt,
22
23
  decodeUrlComponent: () => decodeUrlComponent,
@@ -535,6 +536,48 @@ function parseJwks(input) {
535
536
  const duplicateKids = [...kidCounts.entries()].filter(([, c]) => c > 1).map(([kid]) => kid);
536
537
  return { ok: true, keys, count: keys.length, duplicateKids };
537
538
  }
539
+ var JWK_THUMBPRINT_MEMBERS = {
540
+ oct: ["k", "kty"],
541
+ RSA: ["e", "kty", "n"],
542
+ EC: ["crv", "kty", "x", "y"],
543
+ OKP: ["crv", "kty", "x"]
544
+ };
545
+ var JWK_THUMBPRINT_NODE_HASH = {
546
+ "SHA-256": "sha256",
547
+ "SHA-384": "sha384",
548
+ "SHA-512": "sha512"
549
+ };
550
+ function computeJwkThumbprint(input, hashAlg = "SHA-256") {
551
+ const trimmed = input.trim();
552
+ if (!trimmed) return { ok: false, error: "No input provided" };
553
+ const nodeHash = JWK_THUMBPRINT_NODE_HASH[hashAlg];
554
+ if (!nodeHash) return { ok: false, error: 'hashAlg must be one of "SHA-256", "SHA-384", "SHA-512"' };
555
+ let jwk;
556
+ try {
557
+ jwk = JSON.parse(trimmed);
558
+ } catch (e) {
559
+ return { ok: false, error: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}` };
560
+ }
561
+ if (jwk === null || typeof jwk !== "object" || Array.isArray(jwk)) {
562
+ return { ok: false, error: "Input must be a single JWK object, not a JWKS set or array" };
563
+ }
564
+ const obj = jwk;
565
+ const kty = obj.kty;
566
+ if (typeof kty !== "string") return { ok: false, error: 'JWK must have a "kty" (key type) member' };
567
+ const members = JWK_THUMBPRINT_MEMBERS[kty];
568
+ if (!members) return { ok: false, error: `Unsupported key type "${kty}": supported types are RSA, EC, oct, OKP` };
569
+ const canonical = {};
570
+ for (const m of members) {
571
+ const v = obj[m];
572
+ if (typeof v !== "string" || v.length === 0) {
573
+ return { ok: false, error: `JWK is missing required member "${m}" for key type "${kty}"` };
574
+ }
575
+ canonical[m] = v;
576
+ }
577
+ const canonicalJson = "{" + members.map((m) => `${JSON.stringify(m)}:${JSON.stringify(canonical[m])}`).join(",") + "}";
578
+ const thumbprint = createHash(nodeHash).update(canonicalJson, "utf8").digest("base64url");
579
+ return { ok: true, kty, hashAlg, members, canonicalJson, thumbprint };
580
+ }
538
581
  var ALG_TO_NODE_HASH = {
539
582
  HS256: "sha256",
540
583
  HS384: "sha384",
@@ -1266,4 +1309,4 @@ function generateMetaHtml(tags) {
1266
1309
  return lines.join("\n");
1267
1310
  }
1268
1311
 
1269
- export { COMMON_HEADERS, COMMON_PRESETS, CORS_COMMON_HEADERS, DEFAULT_CORS_CONFIG, DIRECTIVE_DESCRIPTIONS, HTTP_METHODS, SUPPORTED_LANGUAGES, addParam, api_exports, base64urlDecode, base64urlEncode, buildCurl, buildCurlMultiline, buildUrl, convertToCode, decodeJwt, decodeUrlComponent, detectFormat, encodeUrlComponent, extractJsonPaths, formatJwtJson, formatResponse, generateCors, generateCspHeader, generateFullHeader, generateMetaHtml, getParam, getStats, isValidUrl, normalizeUrl, parseCspHeader, parseCurl, parseCurlCommand, parseJwks, parseMetaTags, parseUrl, removeParam, signJwt, validateCorsConfig, validateCspConfig, validateOgTags };
1312
+ export { COMMON_HEADERS, COMMON_PRESETS, CORS_COMMON_HEADERS, DEFAULT_CORS_CONFIG, DIRECTIVE_DESCRIPTIONS, HTTP_METHODS, SUPPORTED_LANGUAGES, addParam, api_exports, base64urlDecode, base64urlEncode, buildCurl, buildCurlMultiline, buildUrl, computeJwkThumbprint, convertToCode, decodeJwt, decodeUrlComponent, detectFormat, encodeUrlComponent, extractJsonPaths, formatJwtJson, formatResponse, generateCors, generateCspHeader, generateFullHeader, generateMetaHtml, getParam, getStats, isValidUrl, normalizeUrl, parseCspHeader, parseCurl, parseCurlCommand, parseJwks, parseMetaTags, parseUrl, removeParam, signJwt, validateCorsConfig, validateCspConfig, validateOgTags };
@@ -13,6 +13,7 @@ __export(text_exports, {
13
13
  analyzeText: () => analyzeText,
14
14
  applyOp: () => applyOp,
15
15
  applyOps: () => applyOps,
16
+ calculateStringDistance: () => calculateStringDistance,
16
17
  centerText: () => centerText,
17
18
  convertAll: () => convertAll,
18
19
  convertCase: () => convertCase,
@@ -2556,5 +2557,46 @@ function detectFillerWords(text) {
2556
2557
  const density = totalWords > 0 ? Math.round(totalMatches / totalWords * 1e3) / 10 : 0;
2557
2558
  return { totalWords, totalMatches, fillerCount, hedgeCount, clicheCount, density, matches };
2558
2559
  }
2560
+ var MAX_STRING_DISTANCE_LENGTH = 5e3;
2561
+ function calculateStringDistance(a, b, options) {
2562
+ if (typeof a !== "string" || typeof b !== "string") {
2563
+ return { error: "Both a and b must be strings" };
2564
+ }
2565
+ if (a.length > MAX_STRING_DISTANCE_LENGTH || b.length > MAX_STRING_DISTANCE_LENGTH) {
2566
+ return { error: `Strings must be at most ${MAX_STRING_DISTANCE_LENGTH} characters` };
2567
+ }
2568
+ const mode = options?.mode ?? "levenshtein";
2569
+ const caseSensitive = options?.caseSensitive ?? true;
2570
+ const s1 = caseSensitive ? a : a.toLowerCase();
2571
+ const s2 = caseSensitive ? b : b.toLowerCase();
2572
+ const m = s1.length;
2573
+ const n = s2.length;
2574
+ const maxLength = Math.max(m, n);
2575
+ if (m === 0 || n === 0) {
2576
+ return { a, b, distance: maxLength, similarity: maxLength === 0 ? 1 : 0, maxLength, mode };
2577
+ }
2578
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
2579
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
2580
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
2581
+ for (let i = 1; i <= m; i++) {
2582
+ for (let j = 1; j <= n; j++) {
2583
+ const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
2584
+ dp[i][j] = Math.min(
2585
+ dp[i - 1][j] + 1,
2586
+ // deletion
2587
+ dp[i][j - 1] + 1,
2588
+ // insertion
2589
+ dp[i - 1][j - 1] + cost
2590
+ // substitution
2591
+ );
2592
+ if (mode === "damerau" && i > 1 && j > 1 && s1[i - 1] === s2[j - 2] && s1[i - 2] === s2[j - 1]) {
2593
+ dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + 1);
2594
+ }
2595
+ }
2596
+ }
2597
+ const distance = dp[m][n];
2598
+ const similarity = maxLength === 0 ? 1 : Math.round((1 - distance / maxLength) * 1e4) / 1e4;
2599
+ return { a, b, distance, similarity, maxLength, mode };
2600
+ }
2559
2601
 
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 };
2602
+ export { CASE_FORMATS, DEFAULT_CONVERT_OPTIONS, ESCAPE_MODES, LINE_OPS, MORSE_CODE, addBorder, analyzeText, applyOp, applyOps, calculateStringDistance, 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 };
@@ -19,6 +19,7 @@ __export(units_exports, {
19
19
  bumpVersion: () => bumpVersion,
20
20
  calcAspectRatio: () => calcAspectRatio,
21
21
  calcSubnet: () => calcSubnet,
22
+ calculateBreakEven: () => calculateBreakEven,
22
23
  calculateCompoundInterest: () => calculateCompoundInterest,
23
24
  calculateDiscount: () => calculateDiscount,
24
25
  calculateLoan: () => calculateLoan,
@@ -28,6 +29,7 @@ __export(units_exports, {
28
29
  calculateTax: () => calculateTax,
29
30
  calculateTimeValue: () => calculateTimeValue,
30
31
  calculateTipSplit: () => calculateTipSplit,
32
+ calculateVideoBitrate: () => calculateVideoBitrate,
31
33
  compareSemver: () => compareSemver,
32
34
  convert: () => convert,
33
35
  convertAprApy: () => convertAprApy,
@@ -1548,5 +1550,97 @@ function applySuccessiveDiscounts(originalPrice, discountPercents) {
1548
1550
  steps
1549
1551
  };
1550
1552
  }
1553
+ function calculateBreakEven(input) {
1554
+ const { fixedCosts, variableCostPerUnit, pricePerUnit, salesVolume } = input;
1555
+ if (!Number.isFinite(fixedCosts)) return { error: "fixedCosts must be a finite number" };
1556
+ if (!Number.isFinite(variableCostPerUnit)) return { error: "variableCostPerUnit must be a finite number" };
1557
+ if (!Number.isFinite(pricePerUnit)) return { error: "pricePerUnit must be a finite number" };
1558
+ if (fixedCosts < 0) return { error: "fixedCosts cannot be negative" };
1559
+ if (variableCostPerUnit < 0) return { error: "variableCostPerUnit cannot be negative" };
1560
+ if (pricePerUnit <= 0) return { error: "pricePerUnit must be greater than zero" };
1561
+ if (pricePerUnit <= variableCostPerUnit) {
1562
+ return { error: "pricePerUnit must be greater than variableCostPerUnit, otherwise every unit sold loses money and break-even is never reached" };
1563
+ }
1564
+ const round4 = (n) => Math.round(n * 1e4) / 1e4;
1565
+ const contributionMarginPerUnit = pricePerUnit - variableCostPerUnit;
1566
+ const contributionMarginRatio = contributionMarginPerUnit / pricePerUnit * 100;
1567
+ const breakEvenUnits = fixedCosts / contributionMarginPerUnit;
1568
+ const breakEvenUnitsRounded = Math.ceil(breakEvenUnits);
1569
+ const breakEvenRevenue = breakEvenUnits * pricePerUnit;
1570
+ const result = {
1571
+ fixedCosts: round4(fixedCosts),
1572
+ variableCostPerUnit: round4(variableCostPerUnit),
1573
+ pricePerUnit: round4(pricePerUnit),
1574
+ contributionMarginPerUnit: round4(contributionMarginPerUnit),
1575
+ contributionMarginRatio: round4(contributionMarginRatio),
1576
+ breakEvenUnits: round4(breakEvenUnits),
1577
+ breakEvenUnitsRounded,
1578
+ breakEvenRevenue: round4(breakEvenRevenue)
1579
+ };
1580
+ if (typeof salesVolume === "number" && Number.isFinite(salesVolume)) {
1581
+ if (salesVolume < 0) return { error: "salesVolume cannot be negative" };
1582
+ const revenueAtSalesVolume = salesVolume * pricePerUnit;
1583
+ const profitAtSalesVolume = salesVolume * contributionMarginPerUnit - fixedCosts;
1584
+ const marginOfSafetyUnits = salesVolume - breakEvenUnits;
1585
+ result.salesVolume = round4(salesVolume);
1586
+ result.revenueAtSalesVolume = round4(revenueAtSalesVolume);
1587
+ result.profitAtSalesVolume = round4(profitAtSalesVolume);
1588
+ result.marginOfSafetyUnits = round4(marginOfSafetyUnits);
1589
+ result.marginOfSafetyPercent = salesVolume > 0 ? round4(marginOfSafetyUnits / salesVolume * 100) : 0;
1590
+ }
1591
+ return result;
1592
+ }
1593
+ function calculateVideoBitrate(input) {
1594
+ const { mode, durationSeconds, videoBitrateKbps, targetSizeMB } = input;
1595
+ const audioBitrateKbps = input.audioBitrateKbps ?? 0;
1596
+ if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) {
1597
+ return { error: "durationSeconds must be a positive number" };
1598
+ }
1599
+ if (!Number.isFinite(audioBitrateKbps) || audioBitrateKbps < 0) {
1600
+ return { error: "audioBitrateKbps must be zero or a positive number" };
1601
+ }
1602
+ const round22 = (n) => Math.round(n * 100) / 100;
1603
+ if (mode === "size-from-bitrate") {
1604
+ if (typeof videoBitrateKbps !== "number" || !Number.isFinite(videoBitrateKbps) || videoBitrateKbps <= 0) {
1605
+ return { error: 'videoBitrateKbps must be a positive number for mode "size-from-bitrate"' };
1606
+ }
1607
+ const totalBitrateKbps = videoBitrateKbps + audioBitrateKbps;
1608
+ const fileSizeBytes = totalBitrateKbps * 1e3 * durationSeconds / 8;
1609
+ return {
1610
+ mode,
1611
+ durationSeconds,
1612
+ videoBitrateKbps: round22(videoBitrateKbps),
1613
+ audioBitrateKbps: round22(audioBitrateKbps),
1614
+ totalBitrateKbps: round22(totalBitrateKbps),
1615
+ fileSizeBytes: Math.round(fileSizeBytes),
1616
+ fileSizeMB: round22(fileSizeBytes / 1e6),
1617
+ fileSizeMiB: round22(fileSizeBytes / 1048576)
1618
+ };
1619
+ }
1620
+ if (mode === "bitrate-from-size") {
1621
+ if (typeof targetSizeMB !== "number" || !Number.isFinite(targetSizeMB) || targetSizeMB <= 0) {
1622
+ return { error: 'targetSizeMB must be a positive number for mode "bitrate-from-size"' };
1623
+ }
1624
+ const fileSizeBytes = targetSizeMB * 1e6;
1625
+ const totalBitrateKbps = fileSizeBytes * 8 / (1e3 * durationSeconds);
1626
+ const videoKbps = totalBitrateKbps - audioBitrateKbps;
1627
+ if (videoKbps <= 0) {
1628
+ return {
1629
+ error: "audioBitrateKbps exceeds the total bitrate available at this target size and duration; lower the audio bitrate or raise the target size"
1630
+ };
1631
+ }
1632
+ return {
1633
+ mode,
1634
+ durationSeconds,
1635
+ videoBitrateKbps: round22(videoKbps),
1636
+ audioBitrateKbps: round22(audioBitrateKbps),
1637
+ totalBitrateKbps: round22(totalBitrateKbps),
1638
+ fileSizeBytes: Math.round(fileSizeBytes),
1639
+ fileSizeMB: round22(targetSizeMB),
1640
+ fileSizeMiB: round22(fileSizeBytes / 1048576)
1641
+ };
1642
+ }
1643
+ return { error: 'mode must be "size-from-bitrate" or "bitrate-from-size"' };
1644
+ }
1551
1645
 
1552
- 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, applySuccessiveDiscounts, autoFormat, bumpVersion, calcAspectRatio, calcSubnet, calculateCompoundInterest, calculateDiscount, calculateLoan, calculateMarginMarkup, calculatePercentageChange, calculateSimpleInterest, calculateTax, calculateTimeValue, calculateTipSplit, compareSemver, convert, convertAprApy, convertBytes, convertIp, coverBox, decimalToIp, decimalToIpCidr, describePermissions, fitIntoBox, formatNumber, formatValue, formatVwValue, formatWithSeparator, gcd, ipFromBinary, ipFromDecimal, ipFromHex, ipToBinary, ipToDecimal, ipToDecimalCidr, ipToHex, ipToOctal, isValid, isValidCidr, isValidIp, isValidIpv4, marginToMarkup, markupToMargin, 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 };
1646
+ 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, applySuccessiveDiscounts, autoFormat, bumpVersion, calcAspectRatio, calcSubnet, calculateBreakEven, calculateCompoundInterest, calculateDiscount, calculateLoan, calculateMarginMarkup, calculatePercentageChange, calculateSimpleInterest, calculateTax, calculateTimeValue, calculateTipSplit, calculateVideoBitrate, compareSemver, convert, convertAprApy, convertBytes, convertIp, coverBox, decimalToIp, decimalToIpCidr, describePermissions, fitIntoBox, formatNumber, formatValue, formatVwValue, formatWithSeparator, gcd, ipFromBinary, ipFromDecimal, ipFromHex, ipToBinary, ipToDecimal, ipToDecimalCidr, ipToHex, ipToOctal, isValid, isValidCidr, isValidIp, isValidIpv4, marginToMarkup, markupToMargin, 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 };