@padmaj/duration 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,6 +77,11 @@ try {
77
77
  }
78
78
  ```
79
79
 
80
+ ## Notes
81
+
82
+ - Input strings longer than 100 characters are rejected with a `DurationError` to prevent abuse.
83
+ - `formatDuration` throws `RangeError` for negative, `NaN`, or `Infinity` values.
84
+
80
85
  ## License
81
86
 
82
87
  MIT
package/dist/index.js CHANGED
@@ -43,6 +43,7 @@ var DurationError = class extends Error {
43
43
  function parseDuration(input) {
44
44
  const str = input.trim();
45
45
  if (!str) throw new DurationError(input);
46
+ if (str.length > 100) throw new DurationError(input);
46
47
  let total = 0;
47
48
  let matched = false;
48
49
  PARSE_RE.lastIndex = 0;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["const UNITS: Record<string, number> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n w: 604_800_000,\n}\n\nconst PARSE_RE = /(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h|d|w)/g\n\nexport class DurationError extends Error {\n constructor(input: string) {\n super(`Invalid duration string: \"${input}\"`)\n this.name = 'DurationError'\n }\n}\n\nexport function parseDuration(input: string): number {\n const str = input.trim()\n if (!str) throw new DurationError(input)\n\n let total = 0\n let matched = false\n\n PARSE_RE.lastIndex = 0\n for (const match of str.matchAll(PARSE_RE)) {\n total += parseFloat(match[1]) * UNITS[match[2]]\n matched = true\n }\n\n if (!matched) throw new DurationError(input)\n return Math.round(total)\n}\n\nexport function formatDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new RangeError(`formatDuration expects a non-negative finite number, got ${ms}`)\n }\n\n if (ms === 0) return '0ms'\n\n const units: [string, number][] = [\n ['w', UNITS.w],\n ['d', UNITS.d],\n ['h', UNITS.h],\n ['m', UNITS.m],\n ['s', UNITS.s],\n ['ms', UNITS.ms],\n ]\n\n const parts: string[] = []\n let remaining = Math.round(ms)\n\n for (const [unit, value] of units) {\n if (remaining >= value) {\n const count = Math.floor(remaining / value)\n parts.push(`${count}${unit}`)\n remaining %= value\n }\n }\n\n return parts.join(' ')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAM,QAAgC;AAAA,EACpC,IAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AACN;AAEA,IAAM,WAAW;AAEV,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC,YAAY,OAAe;AACzB,UAAM,6BAA6B,KAAK,GAAG;AAC3C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,cAAc,OAAuB;AACnD,QAAM,MAAM,MAAM,KAAK;AACvB,MAAI,CAAC,IAAK,OAAM,IAAI,cAAc,KAAK;AAEvC,MAAI,QAAQ;AACZ,MAAI,UAAU;AAEd,WAAS,YAAY;AACrB,aAAW,SAAS,IAAI,SAAS,QAAQ,GAAG;AAC1C,aAAS,WAAW,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC;AAC9C,cAAU;AAAA,EACZ;AAEA,MAAI,CAAC,QAAS,OAAM,IAAI,cAAc,KAAK;AAC3C,SAAO,KAAK,MAAM,KAAK;AACzB;AAEO,SAAS,eAAe,IAAoB;AACjD,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,KAAK,GAAG;AAClC,UAAM,IAAI,WAAW,4DAA4D,EAAE,EAAE;AAAA,EACvF;AAEA,MAAI,OAAO,EAAG,QAAO;AAErB,QAAM,QAA4B;AAAA,IAChC,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,MAAM,MAAM,EAAE;AAAA,EACjB;AAEA,QAAM,QAAkB,CAAC;AACzB,MAAI,YAAY,KAAK,MAAM,EAAE;AAE7B,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO;AACjC,QAAI,aAAa,OAAO;AACtB,YAAM,QAAQ,KAAK,MAAM,YAAY,KAAK;AAC1C,YAAM,KAAK,GAAG,KAAK,GAAG,IAAI,EAAE;AAC5B,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,GAAG;AACvB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["const UNITS: Record<string, number> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n w: 604_800_000,\n}\n\nconst PARSE_RE = /(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h|d|w)/g\n\nexport class DurationError extends Error {\n constructor(input: string) {\n super(`Invalid duration string: \"${input}\"`)\n this.name = 'DurationError'\n }\n}\n\nexport function parseDuration(input: string): number {\n const str = input.trim()\n if (!str) throw new DurationError(input)\n if (str.length > 100) throw new DurationError(input)\n\n let total = 0\n let matched = false\n\n PARSE_RE.lastIndex = 0\n for (const match of str.matchAll(PARSE_RE)) {\n total += parseFloat(match[1]) * UNITS[match[2]]\n matched = true\n }\n\n if (!matched) throw new DurationError(input)\n return Math.round(total)\n}\n\nexport function formatDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new RangeError(`formatDuration expects a non-negative finite number, got ${ms}`)\n }\n\n if (ms === 0) return '0ms'\n\n const units: [string, number][] = [\n ['w', UNITS.w],\n ['d', UNITS.d],\n ['h', UNITS.h],\n ['m', UNITS.m],\n ['s', UNITS.s],\n ['ms', UNITS.ms],\n ]\n\n const parts: string[] = []\n let remaining = Math.round(ms)\n\n for (const [unit, value] of units) {\n if (remaining >= value) {\n const count = Math.floor(remaining / value)\n parts.push(`${count}${unit}`)\n remaining %= value\n }\n }\n\n return parts.join(' ')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAM,QAAgC;AAAA,EACpC,IAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AACN;AAEA,IAAM,WAAW;AAEV,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC,YAAY,OAAe;AACzB,UAAM,6BAA6B,KAAK,GAAG;AAC3C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,cAAc,OAAuB;AACnD,QAAM,MAAM,MAAM,KAAK;AACvB,MAAI,CAAC,IAAK,OAAM,IAAI,cAAc,KAAK;AACvC,MAAI,IAAI,SAAS,IAAK,OAAM,IAAI,cAAc,KAAK;AAEnD,MAAI,QAAQ;AACZ,MAAI,UAAU;AAEd,WAAS,YAAY;AACrB,aAAW,SAAS,IAAI,SAAS,QAAQ,GAAG;AAC1C,aAAS,WAAW,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC;AAC9C,cAAU;AAAA,EACZ;AAEA,MAAI,CAAC,QAAS,OAAM,IAAI,cAAc,KAAK;AAC3C,SAAO,KAAK,MAAM,KAAK;AACzB;AAEO,SAAS,eAAe,IAAoB;AACjD,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,KAAK,GAAG;AAClC,UAAM,IAAI,WAAW,4DAA4D,EAAE,EAAE;AAAA,EACvF;AAEA,MAAI,OAAO,EAAG,QAAO;AAErB,QAAM,QAA4B;AAAA,IAChC,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,MAAM,MAAM,EAAE;AAAA,EACjB;AAEA,QAAM,QAAkB,CAAC;AACzB,MAAI,YAAY,KAAK,MAAM,EAAE;AAE7B,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO;AACjC,QAAI,aAAa,OAAO;AACtB,YAAM,QAAQ,KAAK,MAAM,YAAY,KAAK;AAC1C,YAAM,KAAK,GAAG,KAAK,GAAG,IAAI,EAAE;AAC5B,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,GAAG;AACvB;","names":[]}
package/dist/index.mjs CHANGED
@@ -17,6 +17,7 @@ var DurationError = class extends Error {
17
17
  function parseDuration(input) {
18
18
  const str = input.trim();
19
19
  if (!str) throw new DurationError(input);
20
+ if (str.length > 100) throw new DurationError(input);
20
21
  let total = 0;
21
22
  let matched = false;
22
23
  PARSE_RE.lastIndex = 0;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["const UNITS: Record<string, number> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n w: 604_800_000,\n}\n\nconst PARSE_RE = /(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h|d|w)/g\n\nexport class DurationError extends Error {\n constructor(input: string) {\n super(`Invalid duration string: \"${input}\"`)\n this.name = 'DurationError'\n }\n}\n\nexport function parseDuration(input: string): number {\n const str = input.trim()\n if (!str) throw new DurationError(input)\n\n let total = 0\n let matched = false\n\n PARSE_RE.lastIndex = 0\n for (const match of str.matchAll(PARSE_RE)) {\n total += parseFloat(match[1]) * UNITS[match[2]]\n matched = true\n }\n\n if (!matched) throw new DurationError(input)\n return Math.round(total)\n}\n\nexport function formatDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new RangeError(`formatDuration expects a non-negative finite number, got ${ms}`)\n }\n\n if (ms === 0) return '0ms'\n\n const units: [string, number][] = [\n ['w', UNITS.w],\n ['d', UNITS.d],\n ['h', UNITS.h],\n ['m', UNITS.m],\n ['s', UNITS.s],\n ['ms', UNITS.ms],\n ]\n\n const parts: string[] = []\n let remaining = Math.round(ms)\n\n for (const [unit, value] of units) {\n if (remaining >= value) {\n const count = Math.floor(remaining / value)\n parts.push(`${count}${unit}`)\n remaining %= value\n }\n }\n\n return parts.join(' ')\n}\n"],"mappings":";AAAA,IAAM,QAAgC;AAAA,EACpC,IAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AACN;AAEA,IAAM,WAAW;AAEV,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC,YAAY,OAAe;AACzB,UAAM,6BAA6B,KAAK,GAAG;AAC3C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,cAAc,OAAuB;AACnD,QAAM,MAAM,MAAM,KAAK;AACvB,MAAI,CAAC,IAAK,OAAM,IAAI,cAAc,KAAK;AAEvC,MAAI,QAAQ;AACZ,MAAI,UAAU;AAEd,WAAS,YAAY;AACrB,aAAW,SAAS,IAAI,SAAS,QAAQ,GAAG;AAC1C,aAAS,WAAW,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC;AAC9C,cAAU;AAAA,EACZ;AAEA,MAAI,CAAC,QAAS,OAAM,IAAI,cAAc,KAAK;AAC3C,SAAO,KAAK,MAAM,KAAK;AACzB;AAEO,SAAS,eAAe,IAAoB;AACjD,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,KAAK,GAAG;AAClC,UAAM,IAAI,WAAW,4DAA4D,EAAE,EAAE;AAAA,EACvF;AAEA,MAAI,OAAO,EAAG,QAAO;AAErB,QAAM,QAA4B;AAAA,IAChC,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,MAAM,MAAM,EAAE;AAAA,EACjB;AAEA,QAAM,QAAkB,CAAC;AACzB,MAAI,YAAY,KAAK,MAAM,EAAE;AAE7B,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO;AACjC,QAAI,aAAa,OAAO;AACtB,YAAM,QAAQ,KAAK,MAAM,YAAY,KAAK;AAC1C,YAAM,KAAK,GAAG,KAAK,GAAG,IAAI,EAAE;AAC5B,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,GAAG;AACvB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["const UNITS: Record<string, number> = {\n ms: 1,\n s: 1_000,\n m: 60_000,\n h: 3_600_000,\n d: 86_400_000,\n w: 604_800_000,\n}\n\nconst PARSE_RE = /(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h|d|w)/g\n\nexport class DurationError extends Error {\n constructor(input: string) {\n super(`Invalid duration string: \"${input}\"`)\n this.name = 'DurationError'\n }\n}\n\nexport function parseDuration(input: string): number {\n const str = input.trim()\n if (!str) throw new DurationError(input)\n if (str.length > 100) throw new DurationError(input)\n\n let total = 0\n let matched = false\n\n PARSE_RE.lastIndex = 0\n for (const match of str.matchAll(PARSE_RE)) {\n total += parseFloat(match[1]) * UNITS[match[2]]\n matched = true\n }\n\n if (!matched) throw new DurationError(input)\n return Math.round(total)\n}\n\nexport function formatDuration(ms: number): string {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new RangeError(`formatDuration expects a non-negative finite number, got ${ms}`)\n }\n\n if (ms === 0) return '0ms'\n\n const units: [string, number][] = [\n ['w', UNITS.w],\n ['d', UNITS.d],\n ['h', UNITS.h],\n ['m', UNITS.m],\n ['s', UNITS.s],\n ['ms', UNITS.ms],\n ]\n\n const parts: string[] = []\n let remaining = Math.round(ms)\n\n for (const [unit, value] of units) {\n if (remaining >= value) {\n const count = Math.floor(remaining / value)\n parts.push(`${count}${unit}`)\n remaining %= value\n }\n }\n\n return parts.join(' ')\n}\n"],"mappings":";AAAA,IAAM,QAAgC;AAAA,EACpC,IAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AAAA,EACJ,GAAI;AACN;AAEA,IAAM,WAAW;AAEV,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC,YAAY,OAAe;AACzB,UAAM,6BAA6B,KAAK,GAAG;AAC3C,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,cAAc,OAAuB;AACnD,QAAM,MAAM,MAAM,KAAK;AACvB,MAAI,CAAC,IAAK,OAAM,IAAI,cAAc,KAAK;AACvC,MAAI,IAAI,SAAS,IAAK,OAAM,IAAI,cAAc,KAAK;AAEnD,MAAI,QAAQ;AACZ,MAAI,UAAU;AAEd,WAAS,YAAY;AACrB,aAAW,SAAS,IAAI,SAAS,QAAQ,GAAG;AAC1C,aAAS,WAAW,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC;AAC9C,cAAU;AAAA,EACZ;AAEA,MAAI,CAAC,QAAS,OAAM,IAAI,cAAc,KAAK;AAC3C,SAAO,KAAK,MAAM,KAAK;AACzB;AAEO,SAAS,eAAe,IAAoB;AACjD,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,KAAK,GAAG;AAClC,UAAM,IAAI,WAAW,4DAA4D,EAAE,EAAE;AAAA,EACvF;AAEA,MAAI,OAAO,EAAG,QAAO;AAErB,QAAM,QAA4B;AAAA,IAChC,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,KAAK,MAAM,CAAC;AAAA,IACb,CAAC,MAAM,MAAM,EAAE;AAAA,EACjB;AAEA,QAAM,QAAkB,CAAC;AACzB,MAAI,YAAY,KAAK,MAAM,EAAE;AAE7B,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO;AACjC,QAAI,aAAa,OAAO;AACtB,YAAM,QAAQ,KAAK,MAAM,YAAY,KAAK;AAC1C,YAAM,KAAK,GAAG,KAAK,GAAG,IAAI,EAAE;AAC5B,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,GAAG;AACvB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@padmaj/duration",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Parse and format duration strings. '2h30m' ↔ 9000000ms. Zero dependencies. TypeScript-first.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -12,7 +12,9 @@
12
12
  "require": "./dist/index.js"
13
13
  }
14
14
  },
15
- "files": ["dist"],
15
+ "files": [
16
+ "dist"
17
+ ],
16
18
  "repository": {
17
19
  "type": "git",
18
20
  "url": "https://github.com/padmajp4/duration.git"
@@ -23,7 +25,14 @@
23
25
  "test:watch": "vitest",
24
26
  "prepublishOnly": "npm run build && npm test"
25
27
  },
26
- "keywords": ["duration", "time", "parse", "format", "ms", "human-readable"],
28
+ "keywords": [
29
+ "duration",
30
+ "time",
31
+ "parse",
32
+ "format",
33
+ "ms",
34
+ "human-readable"
35
+ ],
27
36
  "author": "Padmaj P Kumar",
28
37
  "license": "MIT",
29
38
  "devDependencies": {