chrono-age 1.0.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/README.md +99 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +38 -0
- package/verify.ts +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# age-calc
|
|
2
|
+
|
|
3
|
+
**"Bulletproof" Age Calculation for High-Scale Backend & Frontend**
|
|
4
|
+
|
|
5
|
+
`age-calc` is a zero-dependency, environment-agnostic TypeScript library designed for precision age calculation. It is optimized for correctness, memory safety, and performance.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Environment Agnostic**: Works perfectly in Node.js, Bun, Deno, and Browsers.
|
|
10
|
+
- **Zero Dependencies**: Lightweight and secure.
|
|
11
|
+
- **Type-Safe**: Written in TypeScript with strict types and `Readonly` interfaces.
|
|
12
|
+
- **Defensive**: Validates inputs rigorously to prevent runtime crashes (no `undefined` errors).
|
|
13
|
+
- **Correctness**: Handles leap years and edge cases accurately.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install age-calc
|
|
19
|
+
# or
|
|
20
|
+
bun add age-calc
|
|
21
|
+
# or
|
|
22
|
+
yarn add age-calc
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Basic Usage
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { calculateAge, calculateAgeDetail } from 'age-calc';
|
|
31
|
+
|
|
32
|
+
const birthDate = new Date('1990-01-01');
|
|
33
|
+
const age = calculateAge({ birthDate });
|
|
34
|
+
|
|
35
|
+
console.log(age); // e.g., 33 (depending on today's date)
|
|
36
|
+
|
|
37
|
+
// Get detailed breakdown
|
|
38
|
+
const detail = calculateAgeDetail({
|
|
39
|
+
birthDate,
|
|
40
|
+
targetDate: new Date('2023-05-20')
|
|
41
|
+
});
|
|
42
|
+
console.log(detail); // { years: 33, months: 4, days: 19 }
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Backend Integration (Express / ElysiaJS)
|
|
46
|
+
|
|
47
|
+
`age-calc` throws typed errors (`InvalidDateError`, `FutureDateError`) allowing you to return proper HTTP 400 responses.
|
|
48
|
+
|
|
49
|
+
**Express & TypeScript Example:**
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import express from 'express';
|
|
53
|
+
import { calculateAge, InvalidDateError, FutureDateError } from 'age-calc';
|
|
54
|
+
|
|
55
|
+
const app = express();
|
|
56
|
+
app.use(express.json());
|
|
57
|
+
|
|
58
|
+
app.post('/check-age', (req, res) => {
|
|
59
|
+
try {
|
|
60
|
+
const { birthDateStr } = req.body;
|
|
61
|
+
const birthDate = new Date(birthDateStr);
|
|
62
|
+
|
|
63
|
+
// Calculate age (defaults targetDate to now)
|
|
64
|
+
const age = calculateAge({ birthDate });
|
|
65
|
+
|
|
66
|
+
res.json({ age });
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error instanceof InvalidDateError || error instanceof FutureDateError) {
|
|
69
|
+
// 400 Bad Request
|
|
70
|
+
res.status(400).json({ error: error.message });
|
|
71
|
+
} else {
|
|
72
|
+
// 500 Internal Server Error
|
|
73
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## API Reference
|
|
80
|
+
|
|
81
|
+
### `calculateAge(input: AgeInput): number`
|
|
82
|
+
Calculates the age in full years.
|
|
83
|
+
|
|
84
|
+
### `calculateAgeDetail(input: AgeInput): AgeDetail`
|
|
85
|
+
Calculates age in years, months, and days.
|
|
86
|
+
|
|
87
|
+
### `validateInput(input: AgeInput): void`
|
|
88
|
+
Runs validation logic explicitly. Throws errors if invalid.
|
|
89
|
+
|
|
90
|
+
### Types
|
|
91
|
+
- `AgeInput`: `{ readonly birthDate: Date; readonly targetDate?: Date; }`
|
|
92
|
+
- `AgeDetail`: `{ years: number; months: number; days: number; }`
|
|
93
|
+
|
|
94
|
+
## Benchmarks
|
|
95
|
+
Designed for low memory footprint. Allocates minimal objects.
|
|
96
|
+
Zero external dependencies ensures fast install and startup.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var{defineProperty:L,getOwnPropertyNames:S,getOwnPropertyDescriptor:U}=Object,V=Object.prototype.hasOwnProperty;var O=new WeakMap,W=(x)=>{var A=O.get(x),j;if(A)return A;if(A=L({},"__esModule",{value:!0}),x&&typeof x==="object"||typeof x==="function")S(x).map((q)=>!V.call(A,q)&&L(A,q,{get:()=>x[q],enumerable:!(j=U(x,q))||j.enumerable}));return O.set(x,A),A};var X=(x,A)=>{for(var j in A)L(x,j,{get:A[j],enumerable:!0,configurable:!0,set:(q)=>A[j]=()=>q})};var Y={};X(Y,{validateInput:()=>J,calculateAgeDetail:()=>Q,calculateAge:()=>P,InvalidDateError:()=>B,FutureDateError:()=>H,AgeCalcError:()=>F});module.exports=W(Y);class F extends Error{constructor(x){super(x);this.name="AgeCalcError"}}class B extends F{constructor(x="Invalid date provided"){super(x);this.name="InvalidDateError"}}class H extends F{constructor(x="Birth date cannot be in the future"){super(x);this.name="FutureDateError"}}function J(x){if(!x)throw new B("Input object is required");let{birthDate:A,targetDate:j=new Date}=x;if(!(A instanceof Date)||isNaN(A.getTime()))throw new B("Invalid birth date");if(!(j instanceof Date)||isNaN(j.getTime()))throw new B("Invalid target date");if(A.getTime()>j.getTime())throw new H(`Birth date ${A.toISOString()} is after target date ${j.toISOString()}`)}function P(x){J(x);let{birthDate:A,targetDate:j=new Date}=x,q=j.getFullYear()-A.getFullYear(),z=j.getMonth()-A.getMonth();if(z<0||z===0&&j.getDate()<A.getDate())q--;return q}function Q(x){J(x);let{birthDate:A,targetDate:j=new Date}=x,q=j.getFullYear()-A.getFullYear(),z=j.getMonth()-A.getMonth(),K=j.getDate()-A.getDate();if(K<0){z--;let R=new Date(j.getFullYear(),j.getMonth(),0);K+=R.getDate()}if(z<0)q--,z+=12;return{years:q,months:z,days:K}}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class F extends Error{constructor(x){super(x);this.name="AgeCalcError"}}class z extends F{constructor(x="Invalid date provided"){super(x);this.name="InvalidDateError"}}class H extends F{constructor(x="Birth date cannot be in the future"){super(x);this.name="FutureDateError"}}function J(x){if(!x)throw new z("Input object is required");let{birthDate:j,targetDate:A=new Date}=x;if(!(j instanceof Date)||isNaN(j.getTime()))throw new z("Invalid birth date");if(!(A instanceof Date)||isNaN(A.getTime()))throw new z("Invalid target date");if(j.getTime()>A.getTime())throw new H(`Birth date ${j.toISOString()} is after target date ${A.toISOString()}`)}function O(x){J(x);let{birthDate:j,targetDate:A=new Date}=x,B=A.getFullYear()-j.getFullYear(),q=A.getMonth()-j.getMonth();if(q<0||q===0&&A.getDate()<j.getDate())B--;return B}function P(x){J(x);let{birthDate:j,targetDate:A=new Date}=x,B=A.getFullYear()-j.getFullYear(),q=A.getMonth()-j.getMonth(),K=A.getDate()-j.getDate();if(K<0){q--;let L=new Date(A.getFullYear(),A.getMonth(),0);K+=L.getDate()}if(q<0)B--,q+=12;return{years:B,months:q,days:K}}export{J as validateInput,P as calculateAgeDetail,O as calculateAge,z as InvalidDateError,H as FutureDateError,F as AgeCalcError};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chrono-age",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A bulletproof, environment-agnostic age calculator for high-scale backends and frontends.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "bun build ./src/index.ts --outfile ./dist/index.js --target node --format cjs --minify && bun build ./src/index.ts --outfile ./dist/index.mjs --target node --format esm --minify",
|
|
17
|
+
"test": "bun test",
|
|
18
|
+
"lint": "bun x eslint src/**/*.ts",
|
|
19
|
+
"prepublishOnly": "bun run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"age",
|
|
23
|
+
"calculator",
|
|
24
|
+
"date",
|
|
25
|
+
"validator",
|
|
26
|
+
"typescript",
|
|
27
|
+
"zero-dependency"
|
|
28
|
+
],
|
|
29
|
+
"author": "",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"bun-types": "latest",
|
|
33
|
+
"typescript": "^5.0.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"typescript": "^5.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/verify.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { calculateAge, calculateAgeDetail, validateInput, InvalidDateError } from "./dist/index";
|
|
2
|
+
|
|
3
|
+
console.log("Starting verification...");
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
// Test 1: Basic Age
|
|
7
|
+
const age = calculateAge({ birthDate: new Date("1990-01-01"), targetDate: new Date("2023-01-01") });
|
|
8
|
+
if (age !== 33) throw new Error(`Test 1 Failed: Expected 33, got ${age}`);
|
|
9
|
+
console.log("Test 1 Passed: Basic Age");
|
|
10
|
+
|
|
11
|
+
// Test 2: Leap Year
|
|
12
|
+
const ageLeap = calculateAge({ birthDate: new Date("2000-02-29"), targetDate: new Date("2001-02-28") });
|
|
13
|
+
if (ageLeap !== 0) throw new Error(`Test 2 Failed: Expected 0, got ${ageLeap}`);
|
|
14
|
+
console.log("Test 2 Passed: Leap Year boundary");
|
|
15
|
+
|
|
16
|
+
// Test 3: Validation
|
|
17
|
+
try {
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
validateInput(null);
|
|
20
|
+
throw new Error("Test 3 Failed: Should have thrown validation error");
|
|
21
|
+
} catch (e) {
|
|
22
|
+
if (e instanceof InvalidDateError) {
|
|
23
|
+
console.log("Test 3 Passed: Validation caught null");
|
|
24
|
+
} else {
|
|
25
|
+
throw e;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Test 4: Details
|
|
30
|
+
const details = calculateAgeDetail({ birthDate: new Date("1990-01-01"), targetDate: new Date("1991-02-05") });
|
|
31
|
+
if (details.years !== 1 || details.months !== 1 || details.days !== 4) {
|
|
32
|
+
throw new Error(`Test 4 Failed: Details mismatch ${JSON.stringify(details)}`);
|
|
33
|
+
}
|
|
34
|
+
console.log("Test 4 Passed: details");
|
|
35
|
+
|
|
36
|
+
console.log("ALL TESTS PASSED MANUALLY");
|
|
37
|
+
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error(e);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|