@schafevormfenster/rest-commons 0.1.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/CONTRIBUTING.md +1190 -0
- package/README.md +275 -0
- package/bin/setup.js +10 -0
- package/dist/api-schemas/error.schema.d.ts +20 -0
- package/dist/api-schemas/error.schema.d.ts.map +1 -0
- package/dist/api-schemas/error.schema.js +17 -0
- package/dist/api-schemas/health.schema.d.ts +497 -0
- package/dist/api-schemas/health.schema.d.ts.map +1 -0
- package/dist/api-schemas/health.schema.js +33 -0
- package/dist/api-schemas/okay.schema.d.ts +13 -0
- package/dist/api-schemas/okay.schema.d.ts.map +1 -0
- package/dist/api-schemas/okay.schema.js +5 -0
- package/dist/api-schemas/paginated-results.schema.d.ts +59 -0
- package/dist/api-schemas/paginated-results.schema.d.ts.map +1 -0
- package/dist/api-schemas/paginated-results.schema.js +10 -0
- package/dist/api-schemas/partial-results.schema.d.ts +30 -0
- package/dist/api-schemas/partial-results.schema.d.ts.map +1 -0
- package/dist/api-schemas/partial-results.schema.js +10 -0
- package/dist/api-schemas/result.schema.d.ts +17 -0
- package/dist/api-schemas/result.schema.d.ts.map +1 -0
- package/dist/api-schemas/result.schema.js +5 -0
- package/dist/api-schemas/results.schema.d.ts +21 -0
- package/dist/api-schemas/results.schema.d.ts.map +1 -0
- package/dist/api-schemas/results.schema.js +5 -0
- package/dist/helpers/correlation/get-correlation-id.d.ts +7 -0
- package/dist/helpers/correlation/get-correlation-id.d.ts.map +1 -0
- package/dist/helpers/correlation/get-correlation-id.js +16 -0
- package/dist/helpers/correlation/get-header.d.ts +7 -0
- package/dist/helpers/correlation/get-header.d.ts.map +1 -0
- package/dist/helpers/correlation/get-header.js +11 -0
- package/dist/helpers/detect-mime-type.d.ts +11 -0
- package/dist/helpers/detect-mime-type.d.ts.map +1 -0
- package/dist/helpers/detect-mime-type.js +40 -0
- package/dist/helpers/detect-suspicious-patterns.d.ts +8 -0
- package/dist/helpers/detect-suspicious-patterns.d.ts.map +1 -0
- package/dist/helpers/detect-suspicious-patterns.js +55 -0
- package/dist/helpers/eventify-constants.types.d.ts +32 -0
- package/dist/helpers/eventify-constants.types.d.ts.map +1 -0
- package/dist/helpers/eventify-constants.types.js +40 -0
- package/dist/helpers/hash-binary.d.ts +21 -0
- package/dist/helpers/hash-binary.d.ts.map +1 -0
- package/dist/helpers/hash-binary.js +28 -0
- package/dist/helpers/mime-types/detect-image-mime-type.d.ts +5 -0
- package/dist/helpers/mime-types/detect-image-mime-type.d.ts.map +1 -0
- package/dist/helpers/mime-types/detect-image-mime-type.js +41 -0
- package/dist/helpers/mime-types/detect-ole-mime-type.d.ts +6 -0
- package/dist/helpers/mime-types/detect-ole-mime-type.d.ts.map +1 -0
- package/dist/helpers/mime-types/detect-ole-mime-type.js +34 -0
- package/dist/helpers/mime-types/detect-pdf-mime-type.d.ts +5 -0
- package/dist/helpers/mime-types/detect-pdf-mime-type.d.ts.map +1 -0
- package/dist/helpers/mime-types/detect-pdf-mime-type.js +13 -0
- package/dist/helpers/mime-types/detect-zip-mime-type.d.ts +6 -0
- package/dist/helpers/mime-types/detect-zip-mime-type.d.ts.map +1 -0
- package/dist/helpers/mime-types/detect-zip-mime-type.js +23 -0
- package/dist/helpers/parameter-validation.d.ts +6 -0
- package/dist/helpers/parameter-validation.d.ts.map +1 -0
- package/dist/helpers/parameter-validation.js +19 -0
- package/dist/helpers/parameter-validation.types.d.ts +16 -0
- package/dist/helpers/parameter-validation.types.d.ts.map +1 -0
- package/dist/helpers/parameter-validation.types.js +38 -0
- package/dist/helpers/response-headers/build-api-unauthorized-headers.d.ts +6 -0
- package/dist/helpers/response-headers/build-api-unauthorized-headers.d.ts.map +1 -0
- package/dist/helpers/response-headers/build-api-unauthorized-headers.js +23 -0
- package/dist/helpers/response-headers/environment.types.d.ts +2 -0
- package/dist/helpers/response-headers/environment.types.d.ts.map +1 -0
- package/dist/helpers/response-headers/environment.types.js +1 -0
- package/dist/helpers/response-headers/resolve-environment.d.ts +8 -0
- package/dist/helpers/response-headers/resolve-environment.d.ts.map +1 -0
- package/dist/helpers/response-headers/resolve-environment.js +18 -0
- package/dist/helpers/slugify.d.ts +15 -0
- package/dist/helpers/slugify.d.ts.map +1 -0
- package/dist/helpers/slugify.js +32 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/normalization/normalize-list.d.ts +11 -0
- package/dist/normalization/normalize-list.d.ts.map +1 -0
- package/dist/normalization/normalize-list.js +19 -0
- package/dist/normalization/normalize-location.d.ts +16 -0
- package/dist/normalization/normalize-location.d.ts.map +1 -0
- package/dist/normalization/normalize-location.js +26 -0
- package/dist/primitives/coordinate-precision.d.ts +10 -0
- package/dist/primitives/coordinate-precision.d.ts.map +1 -0
- package/dist/primitives/coordinate-precision.js +27 -0
- package/dist/primitives/geo-point.schema.d.ts +8 -0
- package/dist/primitives/geo-point.schema.d.ts.map +1 -0
- package/dist/primitives/geo-point.schema.js +10 -0
- package/dist/primitives/geoname-id.schema.d.ts +8 -0
- package/dist/primitives/geoname-id.schema.d.ts.map +1 -0
- package/dist/primitives/geoname-id.schema.js +9 -0
- package/dist/primitives/international-zip.schema.d.ts +76 -0
- package/dist/primitives/international-zip.schema.d.ts.map +1 -0
- package/dist/primitives/international-zip.schema.js +81 -0
- package/dist/primitives/latitude.schema.d.ts +9 -0
- package/dist/primitives/latitude.schema.d.ts.map +1 -0
- package/dist/primitives/latitude.schema.js +13 -0
- package/dist/primitives/location.schema.d.ts +8 -0
- package/dist/primitives/location.schema.d.ts.map +1 -0
- package/dist/primitives/location.schema.js +15 -0
- package/dist/primitives/longitude.schema.d.ts +9 -0
- package/dist/primitives/longitude.schema.d.ts.map +1 -0
- package/dist/primitives/longitude.schema.js +13 -0
- package/dist/primitives/numeric-id.schema.d.ts +8 -0
- package/dist/primitives/numeric-id.schema.d.ts.map +1 -0
- package/dist/primitives/numeric-id.schema.js +10 -0
- package/dist/primitives/slug.schema.d.ts +17 -0
- package/dist/primitives/slug.schema.d.ts.map +1 -0
- package/dist/primitives/slug.schema.js +30 -0
- package/dist/primitives/uuid.schema.d.ts +8 -0
- package/dist/primitives/uuid.schema.d.ts.map +1 -0
- package/dist/primitives/uuid.schema.js +9 -0
- package/dist/primitives/wikidata-id.schema.d.ts +9 -0
- package/dist/primitives/wikidata-id.schema.d.ts.map +1 -0
- package/dist/primitives/wikidata-id.schema.js +10 -0
- package/dist/time/boundary-enforcement.d.ts +11 -0
- package/dist/time/boundary-enforcement.d.ts.map +1 -0
- package/dist/time/boundary-enforcement.js +43 -0
- package/dist/time/bounded-time.schema.d.ts +31 -0
- package/dist/time/bounded-time.schema.d.ts.map +1 -0
- package/dist/time/bounded-time.schema.js +77 -0
- package/dist/time/flexible-time-parser.d.ts +12 -0
- package/dist/time/flexible-time-parser.d.ts.map +1 -0
- package/dist/time/flexible-time-parser.js +94 -0
- package/dist/time/flexible-time.schema.d.ts +31 -0
- package/dist/time/flexible-time.schema.d.ts.map +1 -0
- package/dist/time/flexible-time.schema.js +31 -0
- package/dist/time/get-week-end.d.ts +10 -0
- package/dist/time/get-week-end.d.ts.map +1 -0
- package/dist/time/get-week-end.js +25 -0
- package/dist/time/get-week-start.d.ts +10 -0
- package/dist/time/get-week-start.d.ts.map +1 -0
- package/dist/time/get-week-start.js +25 -0
- package/dist/time/is-relative-time.d.ts +8 -0
- package/dist/time/is-relative-time.d.ts.map +1 -0
- package/dist/time/is-relative-time.js +9 -0
- package/dist/time/iso8601.schema.d.ts +14 -0
- package/dist/time/iso8601.schema.d.ts.map +1 -0
- package/dist/time/iso8601.schema.js +17 -0
- package/dist/time/iso8601.types.d.ts +6 -0
- package/dist/time/iso8601.types.d.ts.map +1 -0
- package/dist/time/iso8601.types.js +11 -0
- package/dist/time/parse-relative-time.d.ts +9 -0
- package/dist/time/parse-relative-time.d.ts.map +1 -0
- package/dist/time/parse-relative-time.js +36 -0
- package/dist/time/relative-time.schema.d.ts +23 -0
- package/dist/time/relative-time.schema.d.ts.map +1 -0
- package/dist/time/relative-time.schema.js +25 -0
- package/dist/time/since-parameter.schema.d.ts +8 -0
- package/dist/time/since-parameter.schema.d.ts.map +1 -0
- package/dist/time/since-parameter.schema.js +56 -0
- package/dist/time/time-helpers.d.ts +19 -0
- package/dist/time/time-helpers.d.ts.map +1 -0
- package/dist/time/time-helpers.js +56 -0
- package/dist/time/time-schemas.d.ts +20 -0
- package/dist/time/time-schemas.d.ts.map +1 -0
- package/dist/time/time-schemas.js +25 -0
- package/dist/time/timezone.types.d.ts +17 -0
- package/dist/time/timezone.types.d.ts.map +1 -0
- package/dist/time/timezone.types.js +15 -0
- package/dist/validation/zod-error-handler.d.ts +3 -0
- package/dist/validation/zod-error-handler.d.ts.map +1 -0
- package/dist/validation/zod-error-handler.js +189 -0
- package/dist/validation/zod-utils.d.ts +9 -0
- package/dist/validation/zod-utils.d.ts.map +1 -0
- package/dist/validation/zod-utils.js +23 -0
- package/eslint.config.mjs +16 -0
- package/package.json +44 -0
- package/src/api-schemas/error.schema.test.ts +27 -0
- package/src/api-schemas/error.schema.ts +23 -0
- package/src/api-schemas/health.schema.test.ts +104 -0
- package/src/api-schemas/health.schema.ts +63 -0
- package/src/api-schemas/okay.schema.test.ts +15 -0
- package/src/api-schemas/okay.schema.ts +8 -0
- package/src/api-schemas/paginated-results.schema.ts +17 -0
- package/src/api-schemas/partial-results.schema.ts +13 -0
- package/src/api-schemas/result.schema.test.ts +19 -0
- package/src/api-schemas/result.schema.ts +9 -0
- package/src/api-schemas/results.schema.test.ts +15 -0
- package/src/api-schemas/results.schema.ts +9 -0
- package/src/helpers/correlation/get-correlation-id.test.ts +126 -0
- package/src/helpers/correlation/get-correlation-id.ts +22 -0
- package/src/helpers/correlation/get-header.test.ts +179 -0
- package/src/helpers/correlation/get-header.ts +21 -0
- package/src/helpers/detect-mime-type.test.ts +100 -0
- package/src/helpers/detect-mime-type.ts +46 -0
- package/src/helpers/detect-suspicious-patterns.test.ts +45 -0
- package/src/helpers/detect-suspicious-patterns.ts +57 -0
- package/src/helpers/eventify-constants.test.ts +52 -0
- package/src/helpers/eventify-constants.types.test.ts +52 -0
- package/src/helpers/eventify-constants.types.ts +51 -0
- package/src/helpers/hash-binary.test.ts +60 -0
- package/src/helpers/hash-binary.ts +30 -0
- package/src/helpers/mime-types/detect-image-mime-type.test.ts +73 -0
- package/src/helpers/mime-types/detect-image-mime-type.ts +50 -0
- package/src/helpers/mime-types/detect-ole-mime-type.test.ts +86 -0
- package/src/helpers/mime-types/detect-ole-mime-type.ts +44 -0
- package/src/helpers/mime-types/detect-pdf-mime-type.test.ts +39 -0
- package/src/helpers/mime-types/detect-pdf-mime-type.ts +15 -0
- package/src/helpers/mime-types/detect-zip-mime-type.test.ts +88 -0
- package/src/helpers/mime-types/detect-zip-mime-type.ts +28 -0
- package/src/helpers/parameter-validation.test.ts +35 -0
- package/src/helpers/parameter-validation.ts +32 -0
- package/src/helpers/process-eventify-request.ts +146 -0
- package/src/helpers/response-headers/build-api-unauthorized-headers.ts +30 -0
- package/src/helpers/response-headers/environment.types.ts +1 -0
- package/src/helpers/response-headers/resolve-environment.ts +17 -0
- package/src/helpers/slugify.test.ts +77 -0
- package/src/helpers/slugify.ts +34 -0
- package/src/index.ts +46 -0
- package/src/normalization/normalize-list.test.ts +43 -0
- package/src/normalization/normalize-list.ts +21 -0
- package/src/normalization/normalize-location.test.ts +91 -0
- package/src/normalization/normalize-location.ts +29 -0
- package/src/primitives/coordinate-precision.test.ts +46 -0
- package/src/primitives/coordinate-precision.ts +30 -0
- package/src/primitives/geo-point.schema.test.ts +70 -0
- package/src/primitives/geo-point.schema.ts +14 -0
- package/src/primitives/geoname-id.schema.test.ts +60 -0
- package/src/primitives/geoname-id.schema.ts +12 -0
- package/src/primitives/international-zip.schema.test.ts +212 -0
- package/src/primitives/international-zip.schema.ts +103 -0
- package/src/primitives/latitude.schema.test.ts +77 -0
- package/src/primitives/latitude.schema.ts +20 -0
- package/src/primitives/location.schema.test.ts +21 -0
- package/src/primitives/location.schema.ts +22 -0
- package/src/primitives/longitude.schema.test.ts +77 -0
- package/src/primitives/longitude.schema.ts +20 -0
- package/src/primitives/numeric-id.schema.test.ts +32 -0
- package/src/primitives/numeric-id.schema.ts +13 -0
- package/src/primitives/slug.schema.test.ts +101 -0
- package/src/primitives/slug.schema.ts +41 -0
- package/src/primitives/uuid.schema.test.ts +45 -0
- package/src/primitives/uuid.schema.ts +12 -0
- package/src/primitives/wikidata-id.schema.test.ts +51 -0
- package/src/primitives/wikidata-id.schema.ts +16 -0
- package/src/time/README.md +220 -0
- package/src/time/boundary-enforcement.test.ts +130 -0
- package/src/time/boundary-enforcement.ts +59 -0
- package/src/time/bounded-time.schema.test.ts +294 -0
- package/src/time/bounded-time.schema.ts +111 -0
- package/src/time/flexible-time-parser.test.ts +586 -0
- package/src/time/flexible-time-parser.ts +122 -0
- package/src/time/flexible-time.schema.test.ts +243 -0
- package/src/time/flexible-time.schema.ts +43 -0
- package/src/time/is-relative-time.test.ts +23 -0
- package/src/time/is-relative-time.ts +9 -0
- package/src/time/iso8601.schema.ts +29 -0
- package/src/time/iso8601.types.test.ts +112 -0
- package/src/time/iso8601.types.ts +21 -0
- package/src/time/parse-relative-time.test.ts +49 -0
- package/src/time/parse-relative-time.ts +50 -0
- package/src/time/relative-time.schema.test.ts +23 -0
- package/src/time/relative-time.schema.ts +38 -0
- package/src/time/since-parameter.schema.test.ts +59 -0
- package/src/time/since-parameter.schema.ts +69 -0
- package/src/time/time-helpers.test.ts +263 -0
- package/src/time/time-helpers.ts +78 -0
- package/src/time/time-schemas.test.ts +181 -0
- package/src/time/time-schemas.ts +42 -0
- package/src/time/time.schema.test.ts +237 -0
- package/src/time/timezone-independence.test.ts +188 -0
- package/src/time/timezone.types.test.ts +55 -0
- package/src/time/timezone.types.ts +22 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createBoundedFlexibleTimeSchema,
|
|
5
|
+
createBoundedRelativeTimeSchema,
|
|
6
|
+
} from "./bounded-time.schema";
|
|
7
|
+
|
|
8
|
+
describe("createBoundedFlexibleTimeSchema", () => {
|
|
9
|
+
describe("without boundaries", () => {
|
|
10
|
+
it("should accept any valid flexible time when no options provided", () => {
|
|
11
|
+
const schema = createBoundedFlexibleTimeSchema();
|
|
12
|
+
|
|
13
|
+
expect(() => schema.parse("1d")).not.toThrow();
|
|
14
|
+
expect(() => schema.parse("3h")).not.toThrow();
|
|
15
|
+
expect(() => schema.parse("2024-01-15T12:00:00Z")).not.toThrow();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("minimum boundary", () => {
|
|
20
|
+
it("should reject times older than minimum boundary", () => {
|
|
21
|
+
const schema = createBoundedFlexibleTimeSchema({ min: "1d" });
|
|
22
|
+
|
|
23
|
+
// 2 days ago should be rejected (older than 1 day minimum)
|
|
24
|
+
expect(() => schema.parse("2d")).toThrow(/must be more recent than 1d ago/);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should accept times newer than minimum boundary", () => {
|
|
28
|
+
const schema = createBoundedFlexibleTimeSchema({ min: "1w" });
|
|
29
|
+
|
|
30
|
+
// 3 days ago is more recent than 1 week ago
|
|
31
|
+
expect(() => schema.parse("3d")).not.toThrow();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should accept time at minimum boundary", () => {
|
|
35
|
+
const schema = createBoundedFlexibleTimeSchema({ min: "1d" });
|
|
36
|
+
|
|
37
|
+
// Exactly 1 day ago should be accepted
|
|
38
|
+
expect(() => schema.parse("1d")).not.toThrow();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should work with ISO8601 timestamps", () => {
|
|
42
|
+
const schema = createBoundedFlexibleTimeSchema({ min: "1w" });
|
|
43
|
+
const now = new Date();
|
|
44
|
+
const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000);
|
|
45
|
+
|
|
46
|
+
// 2 weeks ago should be rejected
|
|
47
|
+
expect(() => schema.parse(twoWeeksAgo.toISOString())).toThrow(
|
|
48
|
+
/must be more recent than/
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("maximum boundary", () => {
|
|
54
|
+
it("should reject times beyond maximum boundary", () => {
|
|
55
|
+
const schema = createBoundedFlexibleTimeSchema({ max: "1M" });
|
|
56
|
+
const now = new Date();
|
|
57
|
+
const twoMonthsFuture = new Date(now);
|
|
58
|
+
twoMonthsFuture.setMonth(twoMonthsFuture.getMonth() + 2);
|
|
59
|
+
|
|
60
|
+
expect(() => schema.parse(twoMonthsFuture.toISOString())).toThrow(
|
|
61
|
+
/must be before 1M in the future/
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should accept times within maximum boundary", () => {
|
|
66
|
+
const schema = createBoundedFlexibleTimeSchema({ max: "1M" });
|
|
67
|
+
const now = new Date();
|
|
68
|
+
const twoWeeksFuture = new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000);
|
|
69
|
+
|
|
70
|
+
expect(() => schema.parse(twoWeeksFuture.toISOString())).not.toThrow();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should accept time at maximum boundary", () => {
|
|
74
|
+
const schema = createBoundedFlexibleTimeSchema({ max: "1d" });
|
|
75
|
+
const now = new Date();
|
|
76
|
+
const oneDayFuture = new Date(now.getTime() + 24 * 60 * 60 * 1000);
|
|
77
|
+
|
|
78
|
+
expect(() => schema.parse(oneDayFuture.toISOString())).not.toThrow();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("both boundaries", () => {
|
|
83
|
+
it("should accept times within both boundaries", () => {
|
|
84
|
+
const schema = createBoundedFlexibleTimeSchema({ min: "1w", max: "1M" });
|
|
85
|
+
|
|
86
|
+
// 3 days ago is between 1 week ago and 1 month future
|
|
87
|
+
expect(() => schema.parse("3d")).not.toThrow();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should reject times outside minimum boundary", () => {
|
|
91
|
+
const schema = createBoundedFlexibleTimeSchema({ min: "1d", max: "1M" });
|
|
92
|
+
|
|
93
|
+
expect(() => schema.parse("3d")).toThrow(/must be more recent than 1d ago/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should reject times outside maximum boundary", () => {
|
|
97
|
+
const schema = createBoundedFlexibleTimeSchema({ min: "1w", max: "1M" });
|
|
98
|
+
const now = new Date();
|
|
99
|
+
const twoMonthsFuture = new Date(now);
|
|
100
|
+
twoMonthsFuture.setMonth(twoMonthsFuture.getMonth() + 2);
|
|
101
|
+
|
|
102
|
+
expect(() => schema.parse(twoMonthsFuture.toISOString())).toThrow(
|
|
103
|
+
/must be before 1M in the future/
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should handle complex boundary combinations", () => {
|
|
108
|
+
const schema = createBoundedFlexibleTimeSchema({ min: "30d", max: "6M" });
|
|
109
|
+
const now = new Date();
|
|
110
|
+
|
|
111
|
+
// 15 days ago is more recent than 30 days (should pass)
|
|
112
|
+
const fifteenDaysAgo = new Date(now.getTime() - 15 * 24 * 60 * 60 * 1000);
|
|
113
|
+
expect(() => schema.parse(fifteenDaysAgo.toISOString())).not.toThrow();
|
|
114
|
+
|
|
115
|
+
// 45 days ago is older than 30 days (should fail)
|
|
116
|
+
const fortyFiveDaysAgo = new Date(now.getTime() - 45 * 24 * 60 * 60 * 1000);
|
|
117
|
+
expect(() => schema.parse(fortyFiveDaysAgo.toISOString())).toThrow();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("relative time inputs", () => {
|
|
122
|
+
it("should handle minute-based relative times", () => {
|
|
123
|
+
const schema = createBoundedFlexibleTimeSchema({ min: "1h", max: "1d" });
|
|
124
|
+
|
|
125
|
+
expect(() => schema.parse("30m")).not.toThrow();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should handle hour-based relative times", () => {
|
|
129
|
+
const schema = createBoundedFlexibleTimeSchema({ min: "1d", max: "1w" });
|
|
130
|
+
|
|
131
|
+
expect(() => schema.parse("12h")).not.toThrow();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should handle week-based relative times", () => {
|
|
135
|
+
const schema = createBoundedFlexibleTimeSchema({ min: "1M", max: "6M" });
|
|
136
|
+
|
|
137
|
+
expect(() => schema.parse("2w")).not.toThrow();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("createBoundedRelativeTimeSchema", () => {
|
|
143
|
+
describe("without boundaries", () => {
|
|
144
|
+
it("should accept any valid relative time when no options provided", () => {
|
|
145
|
+
const schema = createBoundedRelativeTimeSchema();
|
|
146
|
+
|
|
147
|
+
expect(() => schema.parse("1d")).not.toThrow();
|
|
148
|
+
expect(() => schema.parse("3h")).not.toThrow();
|
|
149
|
+
expect(() => schema.parse("30m")).not.toThrow();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("minimum boundary (most recent allowed)", () => {
|
|
154
|
+
it("should reject times more recent than minimum", () => {
|
|
155
|
+
const schema = createBoundedRelativeTimeSchema({ min: "1h" });
|
|
156
|
+
|
|
157
|
+
// 30 minutes ago is too recent (less than 1 hour ago)
|
|
158
|
+
expect(() => schema.parse("30m")).toThrow(/must be at least 1h ago/);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should accept times older than minimum", () => {
|
|
162
|
+
const schema = createBoundedRelativeTimeSchema({ min: "1h" });
|
|
163
|
+
|
|
164
|
+
// 3 hours ago is old enough (more than 1 hour ago)
|
|
165
|
+
expect(() => schema.parse("3h")).not.toThrow();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should accept time near minimum boundary", () => {
|
|
169
|
+
const schema = createBoundedRelativeTimeSchema({ min: "1d" });
|
|
170
|
+
|
|
171
|
+
// Times older than 1 day should be accepted
|
|
172
|
+
expect(() => schema.parse("2d")).not.toThrow();
|
|
173
|
+
expect(() => schema.parse("25h")).not.toThrow();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("maximum boundary (oldest allowed)", () => {
|
|
178
|
+
it("should reject times older than maximum", () => {
|
|
179
|
+
const schema = createBoundedRelativeTimeSchema({ max: "30d" });
|
|
180
|
+
|
|
181
|
+
// 60 days ago is too old (more than 30 days ago)
|
|
182
|
+
expect(() => schema.parse("60d")).toThrow(/cannot be more than 30d ago/);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should accept times newer than maximum", () => {
|
|
186
|
+
const schema = createBoundedRelativeTimeSchema({ max: "30d" });
|
|
187
|
+
|
|
188
|
+
// 15 days ago is recent enough (less than 30 days ago)
|
|
189
|
+
expect(() => schema.parse("15d")).not.toThrow();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should accept time at maximum boundary", () => {
|
|
193
|
+
const schema = createBoundedRelativeTimeSchema({ max: "7d" });
|
|
194
|
+
|
|
195
|
+
// Exactly 7 days ago should be accepted
|
|
196
|
+
expect(() => schema.parse("7d")).not.toThrow();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("both boundaries", () => {
|
|
201
|
+
it("should accept times within both boundaries", () => {
|
|
202
|
+
const schema = createBoundedRelativeTimeSchema({ min: "1h", max: "30d" });
|
|
203
|
+
|
|
204
|
+
// 3 days ago is between 1 hour and 30 days ago
|
|
205
|
+
expect(() => schema.parse("3d")).not.toThrow();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should reject times too recent (before minimum)", () => {
|
|
209
|
+
const schema = createBoundedRelativeTimeSchema({ min: "1h", max: "30d" });
|
|
210
|
+
|
|
211
|
+
// 30 minutes ago is too recent
|
|
212
|
+
expect(() => schema.parse("30m")).toThrow(/must be at least 1h ago/);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should reject times too old (after maximum)", () => {
|
|
216
|
+
const schema = createBoundedRelativeTimeSchema({ min: "1h", max: "30d" });
|
|
217
|
+
|
|
218
|
+
// 60 days ago is too old
|
|
219
|
+
expect(() => schema.parse("60d")).toThrow(/cannot be more than 30d ago/);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should handle complex boundary combinations", () => {
|
|
223
|
+
const schema = createBoundedRelativeTimeSchema({ min: "1d", max: "7d" });
|
|
224
|
+
|
|
225
|
+
// 3 days ago is within range
|
|
226
|
+
expect(() => schema.parse("3d")).not.toThrow();
|
|
227
|
+
|
|
228
|
+
// 12 hours ago is too recent
|
|
229
|
+
expect(() => schema.parse("12h")).toThrow();
|
|
230
|
+
|
|
231
|
+
// 10 days ago is too old
|
|
232
|
+
expect(() => schema.parse("10d")).toThrow();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("various time units", () => {
|
|
237
|
+
it("should handle minute-based boundaries", () => {
|
|
238
|
+
const schema = createBoundedRelativeTimeSchema({ min: "30m", max: "2h" });
|
|
239
|
+
|
|
240
|
+
expect(() => schema.parse("1h")).not.toThrow();
|
|
241
|
+
expect(() => schema.parse("15m")).toThrow();
|
|
242
|
+
expect(() => schema.parse("3h")).toThrow();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("should handle hour-based boundaries", () => {
|
|
246
|
+
const schema = createBoundedRelativeTimeSchema({ min: "6h", max: "24h" });
|
|
247
|
+
|
|
248
|
+
expect(() => schema.parse("12h")).not.toThrow();
|
|
249
|
+
expect(() => schema.parse("3h")).toThrow();
|
|
250
|
+
expect(() => schema.parse("48h")).toThrow();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should handle day-based boundaries", () => {
|
|
254
|
+
const schema = createBoundedRelativeTimeSchema({ min: "1d", max: "14d" });
|
|
255
|
+
|
|
256
|
+
expect(() => schema.parse("7d")).not.toThrow();
|
|
257
|
+
expect(() => schema.parse("12h")).toThrow();
|
|
258
|
+
expect(() => schema.parse("30d")).toThrow();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("should handle week-based boundaries", () => {
|
|
262
|
+
const schema = createBoundedRelativeTimeSchema({ min: "1w", max: "4w" });
|
|
263
|
+
|
|
264
|
+
expect(() => schema.parse("2w")).not.toThrow();
|
|
265
|
+
expect(() => schema.parse("3d")).toThrow();
|
|
266
|
+
expect(() => schema.parse("6w")).toThrow();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("should handle month-based boundaries", () => {
|
|
270
|
+
const schema = createBoundedRelativeTimeSchema({ min: "1M", max: "6M" });
|
|
271
|
+
|
|
272
|
+
expect(() => schema.parse("3M")).not.toThrow();
|
|
273
|
+
expect(() => schema.parse("2w")).toThrow();
|
|
274
|
+
expect(() => schema.parse("12M")).toThrow();
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe("edge cases", () => {
|
|
279
|
+
it("should handle very small time ranges", () => {
|
|
280
|
+
const schema = createBoundedRelativeTimeSchema({ min: "5m", max: "15m" });
|
|
281
|
+
|
|
282
|
+
expect(() => schema.parse("10m")).not.toThrow();
|
|
283
|
+
expect(() => schema.parse("3m")).toThrow();
|
|
284
|
+
expect(() => schema.parse("20m")).toThrow();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("should handle very large time ranges", () => {
|
|
288
|
+
const schema = createBoundedRelativeTimeSchema({ min: "1M", max: "12M" });
|
|
289
|
+
|
|
290
|
+
expect(() => schema.parse("6M")).not.toThrow();
|
|
291
|
+
expect(() => schema.parse("2w")).toThrow();
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { FlexibleTimePastSchema } from "./flexible-time.schema";
|
|
2
|
+
import { RelativeTimePastSchema } from "./relative-time.schema";
|
|
3
|
+
import { parseRelativeTime } from "./time-helpers";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for creating bounded schemas
|
|
7
|
+
*/
|
|
8
|
+
export interface BoundaryOptions {
|
|
9
|
+
/** Minimum allowed time (ISO8601 or relative from now) */
|
|
10
|
+
min?: string;
|
|
11
|
+
/** Maximum allowed time (ISO8601 or relative from now) */
|
|
12
|
+
max?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a flexible time schema with min/max boundaries
|
|
17
|
+
* @param options - Boundary configuration
|
|
18
|
+
* @returns Zod schema with boundary validation
|
|
19
|
+
* @example
|
|
20
|
+
* const schema = createBoundedFlexibleTimeSchema({ min: "1w", max: "1M" });
|
|
21
|
+
* schema.parse("3d"); // valid (between 1 week ago and 1 month in future)
|
|
22
|
+
* schema.parse("2M"); // throws error (exceeds max)
|
|
23
|
+
*/
|
|
24
|
+
export function createBoundedFlexibleTimeSchema(options: BoundaryOptions = {}) {
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
let schema: any = FlexibleTimePastSchema;
|
|
27
|
+
|
|
28
|
+
if (options.min) {
|
|
29
|
+
// Min is the oldest allowed time (furthest in the past)
|
|
30
|
+
// For relative times going backwards, larger values are older
|
|
31
|
+
const minTime = parseRelativeTime(options.min, new Date(), "past");
|
|
32
|
+
const minMs = new Date(minTime).getTime();
|
|
33
|
+
|
|
34
|
+
schema = schema.refine(
|
|
35
|
+
(value: string) => {
|
|
36
|
+
const valueMs = new Date(value).getTime();
|
|
37
|
+
return valueMs >= minMs;
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
message: `Time must be more recent than ${options.min} ago (minimum: ${minTime})`,
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (options.max) {
|
|
46
|
+
// Max is the newest allowed time (can be in future for flexible time)
|
|
47
|
+
const maxTime = parseRelativeTime(options.max, new Date(), "future");
|
|
48
|
+
const maxMs = new Date(maxTime).getTime();
|
|
49
|
+
|
|
50
|
+
schema = schema.refine(
|
|
51
|
+
(value: string) => {
|
|
52
|
+
const valueMs = new Date(value).getTime();
|
|
53
|
+
return valueMs <= maxMs;
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
message: `Time must be before ${options.max} in the future (maximum: ${maxTime})`,
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return schema;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Creates a relative time schema with min/max boundaries
|
|
66
|
+
* @param options - Boundary configuration (relative times going backwards from now)
|
|
67
|
+
* @returns Zod schema with boundary validation
|
|
68
|
+
* @example
|
|
69
|
+
* const schema = createBoundedRelativeTimeSchema({ min: "1h", max: "30d" });
|
|
70
|
+
* schema.parse("3d"); // valid (3 days ago, between 1h and 30d ago)
|
|
71
|
+
* schema.parse("30m"); // throws error (too recent, less than 1h ago)
|
|
72
|
+
* schema.parse("60d"); // throws error (too old, more than 30d ago)
|
|
73
|
+
*/
|
|
74
|
+
export function createBoundedRelativeTimeSchema(options: BoundaryOptions = {}) {
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
76
|
+
let schema: any = RelativeTimePastSchema;
|
|
77
|
+
|
|
78
|
+
if (options.min) {
|
|
79
|
+
// Min is the most recent allowed (closest to now)
|
|
80
|
+
const minTime = parseRelativeTime(options.min, new Date(), "past");
|
|
81
|
+
const minMs = new Date(minTime).getTime();
|
|
82
|
+
|
|
83
|
+
schema = schema.refine(
|
|
84
|
+
(value: string) => {
|
|
85
|
+
const valueMs = new Date(value).getTime();
|
|
86
|
+
return valueMs <= minMs; // Value must be older (earlier) than min
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
message: `Time must be at least ${options.min} ago (more recent than ${minTime})`,
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (options.max) {
|
|
95
|
+
// Max is the oldest allowed (furthest from now)
|
|
96
|
+
const maxTime = parseRelativeTime(options.max, new Date(), "past");
|
|
97
|
+
const maxMs = new Date(maxTime).getTime();
|
|
98
|
+
|
|
99
|
+
schema = schema.refine(
|
|
100
|
+
(value: string) => {
|
|
101
|
+
const valueMs = new Date(value).getTime();
|
|
102
|
+
return valueMs >= maxMs; // Value must be newer (later) than max
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
message: `Time cannot be more than ${options.max} ago (older than ${maxTime})`,
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return schema;
|
|
111
|
+
}
|