@utilix-tech/sdk 0.69.0 → 0.71.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,13 @@
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.71.0
6
+ - Add `readMidiInfo` (midi-file-info): parse a Standard MIDI File's header and every track chunk to report format, tick division, initial tempo, time signature, and per-track event/note-on counts, plus total duration in seconds accounting for tempo changes.
7
+
8
+ ## 0.70.0
9
+ - Add `generateHotp`, `generateTotp` (totp-hotp-generator): generate an HOTP code (RFC 4226) for a counter value, or the current TOTP code (RFC 6238) plus seconds until rotation, from a base32-encoded shared secret, supporting SHA-1/SHA-256/SHA-512 and 6-8 digit codes.
10
+ - Add `calculateTaxEquivalentYield` (tax-equivalent-yield-calculator): convert between a tax-free municipal bond yield and its taxable equivalent given a marginal federal (plus optional state) tax rate, in both directions.
11
+
5
12
  ## 0.69.0
6
13
  - Add `parseCommitTrailers` (git-commit-trailer-parser): parse a commit message's trailer block (Co-authored-by, Signed-off-by, Fixes, Reviewed-by, etc.) per git's RFC 822-style trailer convention, returning each trailer's key and value plus the body with the trailer block removed.
7
14
  - Add `extractMarkdownFootnotes` (markdown-footnote-extractor): extract every Markdown-Extra-style footnote reference and definition from a document, flagging references with no matching definition and definitions that are never referenced.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/sdk
2
2
 
3
- **688 developer utility functions for Node.js: runs locally, no API key required.**
3
+ **692 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)
@@ -876,10 +876,10 @@ analyzeString("café"); // { length: 4, codePoints: [...], ... }
876
876
 
877
877
  ---
878
878
 
879
- ### `/media`: Image Header Parsing, EXIF, PDF Metadata & Page Dimensions, WAV, ID3, ICO, FLAC, Video & GIF Metadata
879
+ ### `/media`: Image Header Parsing, EXIF, PDF Metadata & Page Dimensions, WAV, ID3, ICO, FLAC, Video, GIF & MIDI Metadata
880
880
 
881
881
  ```ts
882
- import { readImageInfo, readExifData, readPdfMetadata, readPdfPageDimensions, readWavInfo, readId3Tags, readIcoInfo, readFlacInfo, readVideoInfo, inspectGifFrames } from "@utilix-tech/sdk/media";
882
+ import { readImageInfo, readExifData, readPdfMetadata, readPdfPageDimensions, readWavInfo, readId3Tags, readIcoInfo, readFlacInfo, readVideoInfo, inspectGifFrames, readMidiInfo } from "@utilix-tech/sdk/media";
883
883
 
884
884
  // Reads format, dimensions, bit depth, and alpha channel straight from
885
885
  // file bytes: no decoding, no canvas, no image library.
@@ -945,9 +945,17 @@ readVideoInfo(new Uint8Array(videoBytes));
945
945
  const gifBytes = await fs.promises.readFile("animation.gif");
946
946
  inspectGifFrames(new Uint8Array(gifBytes));
947
947
  // { version: "89a", width: 480, height: 270, frameCount: 24, animated: true, loopCount: 0, totalDurationMs: 2400, ... }
948
+
949
+ // Parses a Standard MIDI File's header and every track chunk to report
950
+ // format, tick division, initial tempo, time signature, and per-track
951
+ // event/note-on counts, plus total duration in seconds: no MIDI playback
952
+ // or synthesis.
953
+ const midiBytes = await fs.promises.readFile("song.mid");
954
+ readMidiInfo(new Uint8Array(midiBytes));
955
+ // { format: 1, trackCount: 2, initialTempoBpm: 120, timeSignature: { numerator: 4, denominator: 4 }, durationSeconds: 4, tracks: [...] }
948
956
  ```
949
957
 
950
- `readImageInfo` supports PNG, JPEG, GIF, WebP, and BMP. `readExifData` supports JPEG only. `readWavInfo` supports the canonical RIFF/WAVE container only. `readId3Tags` decodes ID3v2.3/2.4 and ID3v1/1.1 only (ID3v2.2 is detected but not decoded). `readIcoInfo` reads .ico icon files only and rejects .cur cursor files. `readFlacInfo` reads the native FLAC container only, not FLAC-in-Ogg. `readVideoInfo` supports MP4 (ISO BMFF) and WebM (EBML/Matroska) containers only. `inspectGifFrames` reads the GIF87a/89a block structure only; it never decodes pixel data.
958
+ `readImageInfo` supports PNG, JPEG, GIF, WebP, and BMP. `readExifData` supports JPEG only. `readWavInfo` supports the canonical RIFF/WAVE container only. `readId3Tags` decodes ID3v2.3/2.4 and ID3v1/1.1 only (ID3v2.2 is detected but not decoded). `readIcoInfo` reads .ico icon files only and rejects .cur cursor files. `readFlacInfo` reads the native FLAC container only, not FLAC-in-Ogg. `readVideoInfo` supports MP4 (ISO BMFF) and WebM (EBML/Matroska) containers only. `inspectGifFrames` reads the GIF87a/89a block structure only; it never decodes pixel data. `readMidiInfo` reads Standard MIDI Files (format 0/1/2) only.
951
959
 
952
960
  ---
953
961
 
@@ -962,9 +970,9 @@ inspectGifFrames(new Uint8Array(gifBytes));
962
970
  | `@utilix-tech/sdk/data` | CSV, YAML, TOML, XML, INI, NDJSON parse and convert, NDJSON record deduplicator, SRT/WebVTT subtitles, Markdown front matter parser, YAML anchor/alias resolver, CSV column type inferencer, Markdown table column aligner |
963
971
  | `@utilix-tech/sdk/generators` | UUID v4/v7, ULID, passwords, strength check, entropy estimator, fake data, CSP nonce generator |
964
972
  | `@utilix-tech/sdk/time` | Date diff, timezone convert, cron parse, relative time |
965
- | `@utilix-tech/sdk/units` | Bytes, CSS px/rem, number bases, currency format, credit card/IBAN/barcode/ISBN validation, loan calculator, EMI calculator, compound/simple interest calculators, VAT/sales tax calculator, tip/bill split calculator, break-even point calculator, Rule of 72 calculator, savings goal calculator, ROI calculator, cash denomination/till calculator, payback period calculator, CD maturity calculator, debt-to-income ratio calculator, freelancer hourly rate calculator, debt snowball/avalanche payoff planner |
973
+ | `@utilix-tech/sdk/units` | Bytes, CSS px/rem, number bases, currency format, credit card/IBAN/barcode/ISBN validation, loan calculator, EMI calculator, compound/simple interest calculators, VAT/sales tax calculator, tip/bill split calculator, break-even point calculator, Rule of 72 calculator, savings goal calculator, ROI calculator, cash denomination/till calculator, payback period calculator, CD maturity calculator, debt-to-income ratio calculator, freelancer hourly rate calculator, debt snowball/avalanche payoff planner, tax-equivalent yield calculator |
966
974
  | `@utilix-tech/sdk/network` | HTTP status codes, IP validation, country flags, geolocation URLs, HAR file parsing, User-Agent parsing, IPv6 address compress/expand, Permissions-Policy header builder, Client Hints header builder/parser, Content-Disposition header builder/parser, X-Content-Type-Options header builder/risk assessor, sitemap.xml validator, sitemap index splitter, Prefer header builder/parser (RFC 7240), Retry-After header builder/parser (RFC 9110), Priority header builder/parser (RFC 9218), WebSocket close code reference/lookup, Strict-Transport-Security (HSTS) header builder/parser, X-Robots-Tag header builder/parser, WebSocket frame opcode reference/lookup, robots meta tag builder/parser |
967
- | `@utilix-tech/sdk/api` | cURL build/parse, JWT decode/sign, JWKS parse, CORS headers, CSP header |
975
+ | `@utilix-tech/sdk/api` | cURL build/parse, JWT decode/sign, JWKS parse, CORS headers, CSP header, TOTP/HOTP code generator |
968
976
  | `@utilix-tech/sdk/code` | SQL format/minify, HTML format/minify, regex tester, GraphQL format, MIME type lookup, GitHub URL parsing, regex ReDoS detector, Conventional Commits linter, package.json exports field validator, package.json dependency range auditor, regex capture group/backreference visualizer, SVG sprite symbol extractor |
969
977
  | `@utilix-tech/sdk/color` | Hex/RGB/HSL/HSV/CMYK convert, palette generation, contrast check |
970
978
  | `@utilix-tech/sdk/css` | CSS gradient, box-shadow, border-radius, keyframe animation generators |
@@ -13,6 +13,7 @@
13
13
  * - url-parser : parseUrl, buildUrl, addParam, removeParam, getParam,
14
14
  * encodeUrlComponent, decodeUrlComponent, isValidUrl, normalizeUrl
15
15
  * - og-meta : parseMetaTags, validateOgTags, generateMetaHtml
16
+ * - totp-hotp-generator : generateHotp, generateTotp
16
17
  *
17
18
  * Node 18+ only. All crypto uses node:crypto; no browser-only APIs.
18
19
  */
@@ -287,6 +288,51 @@ declare function validateOgTags(tags: OgTags): {
287
288
  missing: string[];
288
289
  };
289
290
  declare function generateMetaHtml(tags: Partial<OgTags & TwitterTags>): string;
291
+ type OtpAlgorithm = 'SHA1' | 'SHA256' | 'SHA512';
292
+ interface OtpOptions {
293
+ digits?: number;
294
+ algorithm?: OtpAlgorithm;
295
+ }
296
+ interface HotpResult {
297
+ code: string;
298
+ counter: number;
299
+ digits: number;
300
+ algorithm: OtpAlgorithm;
301
+ }
302
+ /**
303
+ * Generates an HOTP code (RFC 4226) for a given counter value from a
304
+ * base32-encoded shared secret. Supports 6-8 digit codes and
305
+ * SHA-1/SHA-256/SHA-512 (SHA-1 is the default per the RFC and what most
306
+ * authenticator apps expect). The secret is never stored, only used for
307
+ * this one HMAC computation.
308
+ */
309
+ declare function generateHotp(secret: string, counter: number, options?: OtpOptions): HotpResult | {
310
+ error: string;
311
+ };
312
+ interface TotpOptions extends OtpOptions {
313
+ periodSeconds?: number;
314
+ /** Unix timestamp in seconds. Defaults to the current time; pass an
315
+ * explicit value for deterministic/testable output. */
316
+ timestamp?: number;
317
+ }
318
+ interface TotpResult {
319
+ code: string;
320
+ counter: number;
321
+ digits: number;
322
+ algorithm: OtpAlgorithm;
323
+ periodSeconds: number;
324
+ secondsRemaining: number;
325
+ }
326
+ /**
327
+ * Generates the current TOTP code (RFC 6238) from a base32-encoded shared
328
+ * secret, plus how many seconds remain before it rotates. TOTP is HOTP
329
+ * where the counter is the number of `periodSeconds` windows elapsed since
330
+ * the Unix epoch. Defaults (30s period, 6 digits, SHA-1) match what
331
+ * Google Authenticator and most other authenticator apps use.
332
+ */
333
+ declare function generateTotp(secret: string, options?: TotpOptions): TotpResult | {
334
+ error: string;
335
+ };
290
336
 
291
337
  declare const api_COMMON_HEADERS: typeof COMMON_HEADERS;
292
338
  declare const api_COMMON_PRESETS: typeof COMMON_PRESETS;
@@ -304,6 +350,7 @@ type api_DirectiveConfig = DirectiveConfig;
304
350
  type api_FormatResult = FormatResult;
305
351
  declare const api_HTTP_METHODS: typeof HTTP_METHODS;
306
352
  type api_Header = Header;
353
+ type api_HotpResult = HotpResult;
307
354
  type api_HttpMethod = HttpMethod;
308
355
  type api_JwkSummary = JwkSummary;
309
356
  type api_JwkThumbprintErrorResult = JwkThumbprintErrorResult;
@@ -322,10 +369,14 @@ type api_JwtResult = JwtResult;
322
369
  type api_JwtSignOptions = JwtSignOptions;
323
370
  type api_MetaTags = MetaTags;
324
371
  type api_OgTags = OgTags;
372
+ type api_OtpAlgorithm = OtpAlgorithm;
373
+ type api_OtpOptions = OtpOptions;
325
374
  type api_ParsedUrl = ParsedUrl;
326
375
  type api_ResponseFormat = ResponseFormat;
327
376
  declare const api_SUPPORTED_LANGUAGES: typeof SUPPORTED_LANGUAGES;
328
377
  type api_TargetLanguage = TargetLanguage;
378
+ type api_TotpOptions = TotpOptions;
379
+ type api_TotpResult = TotpResult;
329
380
  type api_TwitterTags = TwitterTags;
330
381
  declare const api_addParam: typeof addParam;
331
382
  declare const api_base64urlDecode: typeof base64urlDecode;
@@ -345,7 +396,9 @@ declare const api_formatResponse: typeof formatResponse;
345
396
  declare const api_generateCors: typeof generateCors;
346
397
  declare const api_generateCspHeader: typeof generateCspHeader;
347
398
  declare const api_generateFullHeader: typeof generateFullHeader;
399
+ declare const api_generateHotp: typeof generateHotp;
348
400
  declare const api_generateMetaHtml: typeof generateMetaHtml;
401
+ declare const api_generateTotp: typeof generateTotp;
349
402
  declare const api_getParam: typeof getParam;
350
403
  declare const api_getStats: typeof getStats;
351
404
  declare const api_isValidUrl: typeof isValidUrl;
@@ -362,7 +415,7 @@ declare const api_validateCorsConfig: typeof validateCorsConfig;
362
415
  declare const api_validateCspConfig: typeof validateCspConfig;
363
416
  declare const api_validateOgTags: typeof validateOgTags;
364
417
  declare namespace api {
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 };
418
+ 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_HotpResult as HotpResult, 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_OtpAlgorithm as OtpAlgorithm, type api_OtpOptions as OtpOptions, type api_ParsedUrl as ParsedUrl, type api_ResponseFormat as ResponseFormat, api_SUPPORTED_LANGUAGES as SUPPORTED_LANGUAGES, type api_TargetLanguage as TargetLanguage, type api_TotpOptions as TotpOptions, type api_TotpResult as TotpResult, 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_generateHotp as generateHotp, api_generateMetaHtml as generateMetaHtml, api_generateTotp as generateTotp, 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 };
366
419
  }
367
420
 
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 };
421
+ export { decodeJwt as $, type JwtPart as A, type JwtResult as B, COMMON_HEADERS as C, DEFAULT_CORS_CONFIG as D, type JwtSignOptions as E, type FormatResult as F, type OtpAlgorithm as G, HTTP_METHODS as H, type OtpOptions as I, type JwkSummary as J, type TotpOptions as K, type TotpResult as L, type MetaTags as M, type TwitterTags as N, type OgTags as O, type ParsedUrl as P, addParam as Q, type ResponseFormat as R, SUPPORTED_LANGUAGES as S, type TargetLanguage as T, base64urlDecode as U, base64urlEncode as V, buildCurl as W, buildCurlMultiline as X, buildUrl as Y, computeJwkThumbprint as Z, convertToCode as _, api as a, decodeUrlComponent as a0, detectFormat as a1, encodeUrlComponent as a2, extractJsonPaths as a3, formatJwtJson as a4, formatResponse as a5, generateCors as a6, generateCspHeader as a7, generateFullHeader as a8, generateHotp as a9, generateMetaHtml as aa, generateTotp as ab, getParam as ac, getStats as ad, isValidUrl as ae, normalizeUrl as af, parseCspHeader as ag, parseCurl as ah, parseCurlCommand as ai, parseJwks as aj, parseMetaTags as ak, parseUrl as al, removeParam as am, signJwt as an, validateCorsConfig as ao, validateCspConfig as ap, validateOgTags as aq, 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 HotpResult as n, type HttpMethod as o, type JwkThumbprintErrorResult as p, type JwkThumbprintHash as q, type JwkThumbprintOutcome as r, type JwkThumbprintResult as s, type JwksError as t, type JwksParseResult as u, type JwksResult as v, type JwtAlgorithm as w, type JwtDecodeError as x, type JwtDecodeResult as y, type JwtExpiry as z };
@@ -13,6 +13,7 @@
13
13
  * - url-parser : parseUrl, buildUrl, addParam, removeParam, getParam,
14
14
  * encodeUrlComponent, decodeUrlComponent, isValidUrl, normalizeUrl
15
15
  * - og-meta : parseMetaTags, validateOgTags, generateMetaHtml
16
+ * - totp-hotp-generator : generateHotp, generateTotp
16
17
  *
17
18
  * Node 18+ only. All crypto uses node:crypto; no browser-only APIs.
18
19
  */
@@ -287,6 +288,51 @@ declare function validateOgTags(tags: OgTags): {
287
288
  missing: string[];
288
289
  };
289
290
  declare function generateMetaHtml(tags: Partial<OgTags & TwitterTags>): string;
291
+ type OtpAlgorithm = 'SHA1' | 'SHA256' | 'SHA512';
292
+ interface OtpOptions {
293
+ digits?: number;
294
+ algorithm?: OtpAlgorithm;
295
+ }
296
+ interface HotpResult {
297
+ code: string;
298
+ counter: number;
299
+ digits: number;
300
+ algorithm: OtpAlgorithm;
301
+ }
302
+ /**
303
+ * Generates an HOTP code (RFC 4226) for a given counter value from a
304
+ * base32-encoded shared secret. Supports 6-8 digit codes and
305
+ * SHA-1/SHA-256/SHA-512 (SHA-1 is the default per the RFC and what most
306
+ * authenticator apps expect). The secret is never stored, only used for
307
+ * this one HMAC computation.
308
+ */
309
+ declare function generateHotp(secret: string, counter: number, options?: OtpOptions): HotpResult | {
310
+ error: string;
311
+ };
312
+ interface TotpOptions extends OtpOptions {
313
+ periodSeconds?: number;
314
+ /** Unix timestamp in seconds. Defaults to the current time; pass an
315
+ * explicit value for deterministic/testable output. */
316
+ timestamp?: number;
317
+ }
318
+ interface TotpResult {
319
+ code: string;
320
+ counter: number;
321
+ digits: number;
322
+ algorithm: OtpAlgorithm;
323
+ periodSeconds: number;
324
+ secondsRemaining: number;
325
+ }
326
+ /**
327
+ * Generates the current TOTP code (RFC 6238) from a base32-encoded shared
328
+ * secret, plus how many seconds remain before it rotates. TOTP is HOTP
329
+ * where the counter is the number of `periodSeconds` windows elapsed since
330
+ * the Unix epoch. Defaults (30s period, 6 digits, SHA-1) match what
331
+ * Google Authenticator and most other authenticator apps use.
332
+ */
333
+ declare function generateTotp(secret: string, options?: TotpOptions): TotpResult | {
334
+ error: string;
335
+ };
290
336
 
291
337
  declare const api_COMMON_HEADERS: typeof COMMON_HEADERS;
292
338
  declare const api_COMMON_PRESETS: typeof COMMON_PRESETS;
@@ -304,6 +350,7 @@ type api_DirectiveConfig = DirectiveConfig;
304
350
  type api_FormatResult = FormatResult;
305
351
  declare const api_HTTP_METHODS: typeof HTTP_METHODS;
306
352
  type api_Header = Header;
353
+ type api_HotpResult = HotpResult;
307
354
  type api_HttpMethod = HttpMethod;
308
355
  type api_JwkSummary = JwkSummary;
309
356
  type api_JwkThumbprintErrorResult = JwkThumbprintErrorResult;
@@ -322,10 +369,14 @@ type api_JwtResult = JwtResult;
322
369
  type api_JwtSignOptions = JwtSignOptions;
323
370
  type api_MetaTags = MetaTags;
324
371
  type api_OgTags = OgTags;
372
+ type api_OtpAlgorithm = OtpAlgorithm;
373
+ type api_OtpOptions = OtpOptions;
325
374
  type api_ParsedUrl = ParsedUrl;
326
375
  type api_ResponseFormat = ResponseFormat;
327
376
  declare const api_SUPPORTED_LANGUAGES: typeof SUPPORTED_LANGUAGES;
328
377
  type api_TargetLanguage = TargetLanguage;
378
+ type api_TotpOptions = TotpOptions;
379
+ type api_TotpResult = TotpResult;
329
380
  type api_TwitterTags = TwitterTags;
330
381
  declare const api_addParam: typeof addParam;
331
382
  declare const api_base64urlDecode: typeof base64urlDecode;
@@ -345,7 +396,9 @@ declare const api_formatResponse: typeof formatResponse;
345
396
  declare const api_generateCors: typeof generateCors;
346
397
  declare const api_generateCspHeader: typeof generateCspHeader;
347
398
  declare const api_generateFullHeader: typeof generateFullHeader;
399
+ declare const api_generateHotp: typeof generateHotp;
348
400
  declare const api_generateMetaHtml: typeof generateMetaHtml;
401
+ declare const api_generateTotp: typeof generateTotp;
349
402
  declare const api_getParam: typeof getParam;
350
403
  declare const api_getStats: typeof getStats;
351
404
  declare const api_isValidUrl: typeof isValidUrl;
@@ -362,7 +415,7 @@ declare const api_validateCorsConfig: typeof validateCorsConfig;
362
415
  declare const api_validateCspConfig: typeof validateCspConfig;
363
416
  declare const api_validateOgTags: typeof validateOgTags;
364
417
  declare namespace api {
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 };
418
+ 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_HotpResult as HotpResult, 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_OtpAlgorithm as OtpAlgorithm, type api_OtpOptions as OtpOptions, type api_ParsedUrl as ParsedUrl, type api_ResponseFormat as ResponseFormat, api_SUPPORTED_LANGUAGES as SUPPORTED_LANGUAGES, type api_TargetLanguage as TargetLanguage, type api_TotpOptions as TotpOptions, type api_TotpResult as TotpResult, 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_generateHotp as generateHotp, api_generateMetaHtml as generateMetaHtml, api_generateTotp as generateTotp, 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 };
366
419
  }
367
420
 
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 };
421
+ export { decodeJwt as $, type JwtPart as A, type JwtResult as B, COMMON_HEADERS as C, DEFAULT_CORS_CONFIG as D, type JwtSignOptions as E, type FormatResult as F, type OtpAlgorithm as G, HTTP_METHODS as H, type OtpOptions as I, type JwkSummary as J, type TotpOptions as K, type TotpResult as L, type MetaTags as M, type TwitterTags as N, type OgTags as O, type ParsedUrl as P, addParam as Q, type ResponseFormat as R, SUPPORTED_LANGUAGES as S, type TargetLanguage as T, base64urlDecode as U, base64urlEncode as V, buildCurl as W, buildCurlMultiline as X, buildUrl as Y, computeJwkThumbprint as Z, convertToCode as _, api as a, decodeUrlComponent as a0, detectFormat as a1, encodeUrlComponent as a2, extractJsonPaths as a3, formatJwtJson as a4, formatResponse as a5, generateCors as a6, generateCspHeader as a7, generateFullHeader as a8, generateHotp as a9, generateMetaHtml as aa, generateTotp as ab, getParam as ac, getStats as ad, isValidUrl as ae, normalizeUrl as af, parseCspHeader as ag, parseCurl as ah, parseCurlCommand as ai, parseJwks as aj, parseMetaTags as ak, parseUrl as al, removeParam as am, signJwt as an, validateCorsConfig as ao, validateCspConfig as ap, validateOgTags as aq, 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 HotpResult as n, type HttpMethod as o, type JwkThumbprintErrorResult as p, type JwkThumbprintHash as q, type JwkThumbprintOutcome as r, type JwkThumbprintResult as s, type JwksError as t, type JwksParseResult as u, type JwksResult as v, type JwtAlgorithm as w, type JwtDecodeError as x, type JwtDecodeResult as y, type JwtExpiry as z };
@@ -29,7 +29,9 @@ __export(api_exports, {
29
29
  generateCors: () => generateCors,
30
30
  generateCspHeader: () => generateCspHeader,
31
31
  generateFullHeader: () => generateFullHeader,
32
+ generateHotp: () => generateHotp,
32
33
  generateMetaHtml: () => generateMetaHtml,
34
+ generateTotp: () => generateTotp,
33
35
  getParam: () => getParam,
34
36
  getStats: () => getStats,
35
37
  isValidUrl: () => isValidUrl,
@@ -1308,5 +1310,74 @@ function generateMetaHtml(tags) {
1308
1310
  }
1309
1311
  return lines.join("\n");
1310
1312
  }
1313
+ var OTP_ALG_TO_NODE_HASH = {
1314
+ SHA1: "sha1",
1315
+ SHA256: "sha256",
1316
+ SHA512: "sha512"
1317
+ };
1318
+ function decodeBase32Secret(secret) {
1319
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
1320
+ const cleaned = secret.toUpperCase().replace(/[\s=]/g, "");
1321
+ if (!cleaned) {
1322
+ return { error: "Secret must not be empty" };
1323
+ }
1324
+ const bytes = [];
1325
+ let buffer = 0;
1326
+ let bitsLeft = 0;
1327
+ for (const char of cleaned) {
1328
+ const val = alphabet.indexOf(char);
1329
+ if (val === -1) {
1330
+ return { error: `Invalid base32 character '${char}' in secret` };
1331
+ }
1332
+ buffer = buffer << 5 | val;
1333
+ bitsLeft += 5;
1334
+ if (bitsLeft >= 8) {
1335
+ bitsLeft -= 8;
1336
+ bytes.push(buffer >> bitsLeft & 255);
1337
+ }
1338
+ }
1339
+ return Buffer.from(bytes);
1340
+ }
1341
+ function hotpFromCounter(keyBytes, counter, digits, algorithm) {
1342
+ const counterBuffer = Buffer.alloc(8);
1343
+ counterBuffer.writeUInt32BE(Math.floor(counter / 4294967296), 0);
1344
+ counterBuffer.writeUInt32BE(counter % 4294967296, 4);
1345
+ const hmac = createHmac(OTP_ALG_TO_NODE_HASH[algorithm], keyBytes).update(counterBuffer).digest();
1346
+ const offset = hmac[hmac.length - 1] & 15;
1347
+ const binCode = (hmac[offset] & 127) << 24 | (hmac[offset + 1] & 255) << 16 | (hmac[offset + 2] & 255) << 8 | hmac[offset + 3] & 255;
1348
+ const code = (binCode % 10 ** digits).toString().padStart(digits, "0");
1349
+ return code;
1350
+ }
1351
+ function generateHotp(secret, counter, options = {}) {
1352
+ const digits = options.digits ?? 6;
1353
+ const algorithm = options.algorithm ?? "SHA1";
1354
+ if (!Number.isInteger(counter) || counter < 0) {
1355
+ return { error: "Counter must be a non-negative whole number" };
1356
+ }
1357
+ if (!Number.isInteger(digits) || digits < 6 || digits > 8) {
1358
+ return { error: "Digits must be an integer between 6 and 8" };
1359
+ }
1360
+ if (!(algorithm in OTP_ALG_TO_NODE_HASH)) {
1361
+ return { error: "Algorithm must be one of SHA1, SHA256, SHA512" };
1362
+ }
1363
+ const keyBytes = decodeBase32Secret(secret);
1364
+ if ("error" in keyBytes) return keyBytes;
1365
+ return { code: hotpFromCounter(keyBytes, counter, digits, algorithm), counter, digits, algorithm };
1366
+ }
1367
+ function generateTotp(secret, options = {}) {
1368
+ const periodSeconds = options.periodSeconds ?? 30;
1369
+ const timestamp = options.timestamp ?? Math.floor(Date.now() / 1e3);
1370
+ if (!Number.isFinite(periodSeconds) || periodSeconds <= 0) {
1371
+ return { error: "Period must be a positive number of seconds" };
1372
+ }
1373
+ if (!Number.isFinite(timestamp) || timestamp < 0) {
1374
+ return { error: "Timestamp must be a non-negative number of seconds" };
1375
+ }
1376
+ const counter = Math.floor(timestamp / periodSeconds);
1377
+ const hotp = generateHotp(secret, counter, options);
1378
+ if ("error" in hotp) return hotp;
1379
+ const secondsRemaining = periodSeconds - timestamp % periodSeconds;
1380
+ return { ...hotp, periodSeconds, secondsRemaining };
1381
+ }
1311
1382
 
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 };
1383
+ 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, generateHotp, generateMetaHtml, generateTotp, getParam, getStats, isValidUrl, normalizeUrl, parseCspHeader, parseCurl, parseCurlCommand, parseJwks, parseMetaTags, parseUrl, removeParam, signJwt, validateCorsConfig, validateCspConfig, validateOgTags };
@@ -47,6 +47,7 @@ __export(units_exports, {
47
47
  calculateSavingsGoal: () => calculateSavingsGoal,
48
48
  calculateSimpleInterest: () => calculateSimpleInterest,
49
49
  calculateTax: () => calculateTax,
50
+ calculateTaxEquivalentYield: () => calculateTaxEquivalentYield,
50
51
  calculateTimeValue: () => calculateTimeValue,
51
52
  calculateTipSplit: () => calculateTipSplit,
52
53
  calculateVideoBitrate: () => calculateVideoBitrate,
@@ -2503,5 +2504,44 @@ function calculateLoanRefinance(currentBalance, currentRatePercent, currentRemai
2503
2504
  totalInterestSavings: round2(current.totalInterest - refinanced.totalInterest)
2504
2505
  };
2505
2506
  }
2507
+ function calculateTaxEquivalentYield(inputYieldPercent, federalTaxRatePercent, stateTaxRatePercent = 0, direction) {
2508
+ if (!Number.isFinite(inputYieldPercent) || inputYieldPercent < 0) {
2509
+ return { error: "Yield must be zero or a positive number" };
2510
+ }
2511
+ if (!Number.isFinite(federalTaxRatePercent) || federalTaxRatePercent < 0 || federalTaxRatePercent >= 100) {
2512
+ return { error: "Federal tax rate must be a percentage between 0 and 100 (exclusive of 100)" };
2513
+ }
2514
+ if (!Number.isFinite(stateTaxRatePercent) || stateTaxRatePercent < 0 || stateTaxRatePercent >= 100) {
2515
+ return { error: "State tax rate must be a percentage between 0 and 100 (exclusive of 100)" };
2516
+ }
2517
+ if (direction !== "tax-free-to-taxable" && direction !== "taxable-to-tax-free") {
2518
+ return { error: 'Direction must be "tax-free-to-taxable" or "taxable-to-tax-free"' };
2519
+ }
2520
+ const combinedTaxRatePercent = federalTaxRatePercent + stateTaxRatePercent;
2521
+ if (combinedTaxRatePercent >= 100) {
2522
+ return { error: "Combined federal and state tax rate must be less than 100%" };
2523
+ }
2524
+ const round6 = (n) => Math.round(n * 1e6) / 1e6;
2525
+ const combinedRateDecimal = combinedTaxRatePercent / 100;
2526
+ let taxFreeYield;
2527
+ let taxableYield;
2528
+ if (direction === "tax-free-to-taxable") {
2529
+ taxFreeYield = inputYieldPercent;
2530
+ taxableYield = taxFreeYield / (1 - combinedRateDecimal);
2531
+ } else {
2532
+ taxableYield = inputYieldPercent;
2533
+ taxFreeYield = taxableYield * (1 - combinedRateDecimal);
2534
+ }
2535
+ return {
2536
+ direction,
2537
+ inputYield: round6(inputYieldPercent),
2538
+ federalTaxRatePercent: round6(federalTaxRatePercent),
2539
+ stateTaxRatePercent: round6(stateTaxRatePercent),
2540
+ combinedTaxRatePercent: round6(combinedTaxRatePercent),
2541
+ taxFreeYield: round6(taxFreeYield),
2542
+ taxableYield: round6(taxableYield),
2543
+ yieldDifference: round6(taxableYield - taxFreeYield)
2544
+ };
2545
+ }
2506
2546
 
2507
- 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, GPA_GRADE_POINTS, USD_DENOMINATIONS, applySuccessiveDiscounts, autoFormat, bumpVersion, calcAspectRatio, calcSubnet, calculateBlendedLoanRate, calculateBreakEven, calculateCashBreakdown, calculateCashRounding, calculateCdMaturity, calculateChange, calculateCompoundInterest, calculateCreditUtilization, calculateDebtToIncomeRatio, calculateDepreciationSchedule, calculateDiscount, calculateEmi, calculateExtraPaymentPayoff, calculateFreelancerRate, calculateGpa, calculateLoan, calculateLoanRefinance, calculateMarginMarkup, calculateMovingAverages, calculatePaybackPeriod, calculatePercentageChange, calculateRoi, calculateRuleOf72, calculateSavingsGoal, calculateSimpleInterest, calculateTax, calculateTimeValue, calculateTipSplit, calculateVideoBitrate, compareSemver, compareUnitPrices, 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, planDebtPayoff, ptToPx, pxToAll, pxToViewport, remToPx, satisfiesRange, scaleByHeight, scaleByWidth, sortVersions, splitSubnet, symbolicToPermissions, units_exports, validateBarcode, validateCardNumber, validateIban, validateIsbn, viewportToPx };
2547
+ 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, GPA_GRADE_POINTS, USD_DENOMINATIONS, applySuccessiveDiscounts, autoFormat, bumpVersion, calcAspectRatio, calcSubnet, calculateBlendedLoanRate, calculateBreakEven, calculateCashBreakdown, calculateCashRounding, calculateCdMaturity, calculateChange, calculateCompoundInterest, calculateCreditUtilization, calculateDebtToIncomeRatio, calculateDepreciationSchedule, calculateDiscount, calculateEmi, calculateExtraPaymentPayoff, calculateFreelancerRate, calculateGpa, calculateLoan, calculateLoanRefinance, calculateMarginMarkup, calculateMovingAverages, calculatePaybackPeriod, calculatePercentageChange, calculateRoi, calculateRuleOf72, calculateSavingsGoal, calculateSimpleInterest, calculateTax, calculateTaxEquivalentYield, calculateTimeValue, calculateTipSplit, calculateVideoBitrate, compareSemver, compareUnitPrices, 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, planDebtPayoff, ptToPx, pxToAll, pxToViewport, remToPx, satisfiesRange, scaleByHeight, scaleByWidth, sortVersions, splitSubnet, symbolicToPermissions, units_exports, validateBarcode, validateCardNumber, validateIban, validateIsbn, viewportToPx };
@@ -9,6 +9,7 @@ __export(media_exports, {
9
9
  readIcoInfo: () => readIcoInfo,
10
10
  readId3Tags: () => readId3Tags,
11
11
  readImageInfo: () => readImageInfo,
12
+ readMidiInfo: () => readMidiInfo,
12
13
  readPdfMetadata: () => readPdfMetadata,
13
14
  readPdfPageDimensions: () => readPdfPageDimensions,
14
15
  readVideoInfo: () => readVideoInfo,
@@ -1306,5 +1307,135 @@ function inspectGifFrames(bytes) {
1306
1307
  hasTransparency
1307
1308
  };
1308
1309
  }
1310
+ function readU16BE(bytes, offset) {
1311
+ return bytes[offset] << 8 | bytes[offset + 1];
1312
+ }
1313
+ function readMidiVarLen(bytes, pos) {
1314
+ let value = 0;
1315
+ let i = pos;
1316
+ for (let n = 0; n < 4; n++) {
1317
+ if (i >= bytes.length) return null;
1318
+ const b = bytes[i];
1319
+ value = value << 7 | b & 127;
1320
+ i++;
1321
+ if ((b & 128) === 0) return { value, next: i };
1322
+ }
1323
+ return null;
1324
+ }
1325
+ function readMidiInfo(bytes) {
1326
+ if (bytes.length < 14) return { error: "File is too small to be a valid MIDI file" };
1327
+ if (Buffer.from(bytes.subarray(0, 4)).toString("ascii") !== "MThd") {
1328
+ return { error: 'Not a valid MIDI file (missing "MThd" header chunk)' };
1329
+ }
1330
+ const headerLength = readU32BE(bytes, 4);
1331
+ if (headerLength < 6 || 8 + headerLength > bytes.length) return { error: "Truncated or invalid MThd header chunk" };
1332
+ const format = readU16BE(bytes, 8);
1333
+ if (format !== 0 && format !== 1 && format !== 2) return { error: `Unsupported MIDI format: ${format} (expected 0, 1, or 2)` };
1334
+ const trackCount = readU16BE(bytes, 10);
1335
+ if (trackCount < 1) return { error: "MIDI file declares zero tracks" };
1336
+ const divisionRaw = readU16BE(bytes, 12);
1337
+ const division = (divisionRaw & 32768) !== 0 ? { type: "smpte", framesPerSecond: -(divisionRaw >> 8 << 24 >> 24), ticksPerFrame: divisionRaw & 255 } : { type: "ppq", ticksPerQuarterNote: divisionRaw & 32767 };
1338
+ const tracks = [];
1339
+ const tempoChanges = [];
1340
+ let timeSignature = null;
1341
+ let maxEndTick = 0;
1342
+ let offset = 8 + headerLength;
1343
+ let tracksParsed = 0;
1344
+ while (tracksParsed < trackCount && offset + 8 <= bytes.length) {
1345
+ const chunkId = Buffer.from(bytes.subarray(offset, offset + 4)).toString("ascii");
1346
+ const chunkLength = readU32BE(bytes, offset + 4);
1347
+ const dataStart = offset + 8;
1348
+ const dataEnd = Math.min(dataStart + chunkLength, bytes.length);
1349
+ if (chunkId !== "MTrk") {
1350
+ offset = dataEnd;
1351
+ continue;
1352
+ }
1353
+ let pos = dataStart;
1354
+ let runningStatus = 0;
1355
+ let absoluteTick = 0;
1356
+ let eventCount = 0;
1357
+ let noteOnCount = 0;
1358
+ let name = null;
1359
+ while (pos < dataEnd) {
1360
+ const delta = readMidiVarLen(bytes, pos);
1361
+ if (!delta) break;
1362
+ pos = delta.next;
1363
+ absoluteTick += delta.value;
1364
+ if (pos >= dataEnd) break;
1365
+ let statusByte = bytes[pos];
1366
+ if (statusByte & 128) {
1367
+ runningStatus = statusByte;
1368
+ pos++;
1369
+ }
1370
+ statusByte = runningStatus;
1371
+ eventCount++;
1372
+ if (statusByte === 255) {
1373
+ const metaType = bytes[pos];
1374
+ pos++;
1375
+ const len = readMidiVarLen(bytes, pos);
1376
+ if (!len) break;
1377
+ pos = len.next;
1378
+ const data = bytes.subarray(pos, Math.min(pos + len.value, dataEnd));
1379
+ pos += len.value;
1380
+ if (metaType === 81 && data.length >= 3) {
1381
+ tempoChanges.push({ tick: absoluteTick, usPerQuarter: data[0] << 16 | data[1] << 8 | data[2] });
1382
+ } else if (metaType === 88 && data.length >= 2) {
1383
+ if (!timeSignature) timeSignature = { numerator: data[0], denominator: 2 ** data[1] };
1384
+ } else if (metaType === 3 && name === null) {
1385
+ name = Buffer.from(data).toString("utf8").trim() || null;
1386
+ } else if (metaType === 47) {
1387
+ break;
1388
+ }
1389
+ } else if (statusByte === 240 || statusByte === 247) {
1390
+ const len = readMidiVarLen(bytes, pos);
1391
+ if (!len) break;
1392
+ pos = len.next + len.value;
1393
+ } else if (statusByte >= 128) {
1394
+ const type = statusByte & 240;
1395
+ if (type === 192 || type === 208) {
1396
+ pos += 1;
1397
+ } else {
1398
+ if (type === 144 && (bytes[pos + 1] ?? 0) > 0) noteOnCount++;
1399
+ pos += 2;
1400
+ }
1401
+ } else {
1402
+ break;
1403
+ }
1404
+ }
1405
+ tracks.push({ index: tracksParsed, name, eventCount, noteOnCount });
1406
+ maxEndTick = Math.max(maxEndTick, absoluteTick);
1407
+ offset = dataEnd;
1408
+ tracksParsed++;
1409
+ }
1410
+ if (tracks.length === 0) return { error: "No MTrk track chunks found in this MIDI file" };
1411
+ const DEFAULT_US_PER_QUARTER = 5e5;
1412
+ const initialTempoBpm = Math.round(6e7 / (tempoChanges[0]?.usPerQuarter ?? DEFAULT_US_PER_QUARTER) * 100) / 100;
1413
+ let durationSeconds = null;
1414
+ if (division.type === "smpte") {
1415
+ const ticksPerSecond = division.framesPerSecond * division.ticksPerFrame;
1416
+ durationSeconds = ticksPerSecond > 0 ? Math.round(maxEndTick / ticksPerSecond * 1e3) / 1e3 : null;
1417
+ } else {
1418
+ const sorted = [...tempoChanges].sort((a, b) => a.tick - b.tick);
1419
+ let seconds = 0;
1420
+ let prevTick = 0;
1421
+ let currentUsPerQuarter = DEFAULT_US_PER_QUARTER;
1422
+ for (const change of sorted) {
1423
+ seconds += (change.tick - prevTick) / division.ticksPerQuarterNote * (currentUsPerQuarter / 1e6);
1424
+ prevTick = change.tick;
1425
+ currentUsPerQuarter = change.usPerQuarter;
1426
+ }
1427
+ seconds += (maxEndTick - prevTick) / division.ticksPerQuarterNote * (currentUsPerQuarter / 1e6);
1428
+ durationSeconds = Math.round(seconds * 1e3) / 1e3;
1429
+ }
1430
+ return {
1431
+ format,
1432
+ trackCount: tracks.length,
1433
+ division,
1434
+ initialTempoBpm,
1435
+ timeSignature,
1436
+ durationSeconds,
1437
+ tracks
1438
+ };
1439
+ }
1309
1440
 
1310
- export { inspectGifFrames, media_exports, readExifData, readFlacInfo, readIcoInfo, readId3Tags, readImageInfo, readPdfMetadata, readPdfPageDimensions, readVideoInfo, readWavInfo };
1441
+ export { inspectGifFrames, media_exports, readExifData, readFlacInfo, readIcoInfo, readId3Tags, readImageInfo, readMidiInfo, readPdfMetadata, readPdfPageDimensions, readVideoInfo, readWavInfo };