@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.
Files changed (264) hide show
  1. package/CONTRIBUTING.md +1190 -0
  2. package/README.md +275 -0
  3. package/bin/setup.js +10 -0
  4. package/dist/api-schemas/error.schema.d.ts +20 -0
  5. package/dist/api-schemas/error.schema.d.ts.map +1 -0
  6. package/dist/api-schemas/error.schema.js +17 -0
  7. package/dist/api-schemas/health.schema.d.ts +497 -0
  8. package/dist/api-schemas/health.schema.d.ts.map +1 -0
  9. package/dist/api-schemas/health.schema.js +33 -0
  10. package/dist/api-schemas/okay.schema.d.ts +13 -0
  11. package/dist/api-schemas/okay.schema.d.ts.map +1 -0
  12. package/dist/api-schemas/okay.schema.js +5 -0
  13. package/dist/api-schemas/paginated-results.schema.d.ts +59 -0
  14. package/dist/api-schemas/paginated-results.schema.d.ts.map +1 -0
  15. package/dist/api-schemas/paginated-results.schema.js +10 -0
  16. package/dist/api-schemas/partial-results.schema.d.ts +30 -0
  17. package/dist/api-schemas/partial-results.schema.d.ts.map +1 -0
  18. package/dist/api-schemas/partial-results.schema.js +10 -0
  19. package/dist/api-schemas/result.schema.d.ts +17 -0
  20. package/dist/api-schemas/result.schema.d.ts.map +1 -0
  21. package/dist/api-schemas/result.schema.js +5 -0
  22. package/dist/api-schemas/results.schema.d.ts +21 -0
  23. package/dist/api-schemas/results.schema.d.ts.map +1 -0
  24. package/dist/api-schemas/results.schema.js +5 -0
  25. package/dist/helpers/correlation/get-correlation-id.d.ts +7 -0
  26. package/dist/helpers/correlation/get-correlation-id.d.ts.map +1 -0
  27. package/dist/helpers/correlation/get-correlation-id.js +16 -0
  28. package/dist/helpers/correlation/get-header.d.ts +7 -0
  29. package/dist/helpers/correlation/get-header.d.ts.map +1 -0
  30. package/dist/helpers/correlation/get-header.js +11 -0
  31. package/dist/helpers/detect-mime-type.d.ts +11 -0
  32. package/dist/helpers/detect-mime-type.d.ts.map +1 -0
  33. package/dist/helpers/detect-mime-type.js +40 -0
  34. package/dist/helpers/detect-suspicious-patterns.d.ts +8 -0
  35. package/dist/helpers/detect-suspicious-patterns.d.ts.map +1 -0
  36. package/dist/helpers/detect-suspicious-patterns.js +55 -0
  37. package/dist/helpers/eventify-constants.types.d.ts +32 -0
  38. package/dist/helpers/eventify-constants.types.d.ts.map +1 -0
  39. package/dist/helpers/eventify-constants.types.js +40 -0
  40. package/dist/helpers/hash-binary.d.ts +21 -0
  41. package/dist/helpers/hash-binary.d.ts.map +1 -0
  42. package/dist/helpers/hash-binary.js +28 -0
  43. package/dist/helpers/mime-types/detect-image-mime-type.d.ts +5 -0
  44. package/dist/helpers/mime-types/detect-image-mime-type.d.ts.map +1 -0
  45. package/dist/helpers/mime-types/detect-image-mime-type.js +41 -0
  46. package/dist/helpers/mime-types/detect-ole-mime-type.d.ts +6 -0
  47. package/dist/helpers/mime-types/detect-ole-mime-type.d.ts.map +1 -0
  48. package/dist/helpers/mime-types/detect-ole-mime-type.js +34 -0
  49. package/dist/helpers/mime-types/detect-pdf-mime-type.d.ts +5 -0
  50. package/dist/helpers/mime-types/detect-pdf-mime-type.d.ts.map +1 -0
  51. package/dist/helpers/mime-types/detect-pdf-mime-type.js +13 -0
  52. package/dist/helpers/mime-types/detect-zip-mime-type.d.ts +6 -0
  53. package/dist/helpers/mime-types/detect-zip-mime-type.d.ts.map +1 -0
  54. package/dist/helpers/mime-types/detect-zip-mime-type.js +23 -0
  55. package/dist/helpers/parameter-validation.d.ts +6 -0
  56. package/dist/helpers/parameter-validation.d.ts.map +1 -0
  57. package/dist/helpers/parameter-validation.js +19 -0
  58. package/dist/helpers/parameter-validation.types.d.ts +16 -0
  59. package/dist/helpers/parameter-validation.types.d.ts.map +1 -0
  60. package/dist/helpers/parameter-validation.types.js +38 -0
  61. package/dist/helpers/response-headers/build-api-unauthorized-headers.d.ts +6 -0
  62. package/dist/helpers/response-headers/build-api-unauthorized-headers.d.ts.map +1 -0
  63. package/dist/helpers/response-headers/build-api-unauthorized-headers.js +23 -0
  64. package/dist/helpers/response-headers/environment.types.d.ts +2 -0
  65. package/dist/helpers/response-headers/environment.types.d.ts.map +1 -0
  66. package/dist/helpers/response-headers/environment.types.js +1 -0
  67. package/dist/helpers/response-headers/resolve-environment.d.ts +8 -0
  68. package/dist/helpers/response-headers/resolve-environment.d.ts.map +1 -0
  69. package/dist/helpers/response-headers/resolve-environment.js +18 -0
  70. package/dist/helpers/slugify.d.ts +15 -0
  71. package/dist/helpers/slugify.d.ts.map +1 -0
  72. package/dist/helpers/slugify.js +32 -0
  73. package/dist/index.d.ts +36 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +41 -0
  76. package/dist/normalization/normalize-list.d.ts +11 -0
  77. package/dist/normalization/normalize-list.d.ts.map +1 -0
  78. package/dist/normalization/normalize-list.js +19 -0
  79. package/dist/normalization/normalize-location.d.ts +16 -0
  80. package/dist/normalization/normalize-location.d.ts.map +1 -0
  81. package/dist/normalization/normalize-location.js +26 -0
  82. package/dist/primitives/coordinate-precision.d.ts +10 -0
  83. package/dist/primitives/coordinate-precision.d.ts.map +1 -0
  84. package/dist/primitives/coordinate-precision.js +27 -0
  85. package/dist/primitives/geo-point.schema.d.ts +8 -0
  86. package/dist/primitives/geo-point.schema.d.ts.map +1 -0
  87. package/dist/primitives/geo-point.schema.js +10 -0
  88. package/dist/primitives/geoname-id.schema.d.ts +8 -0
  89. package/dist/primitives/geoname-id.schema.d.ts.map +1 -0
  90. package/dist/primitives/geoname-id.schema.js +9 -0
  91. package/dist/primitives/international-zip.schema.d.ts +76 -0
  92. package/dist/primitives/international-zip.schema.d.ts.map +1 -0
  93. package/dist/primitives/international-zip.schema.js +81 -0
  94. package/dist/primitives/latitude.schema.d.ts +9 -0
  95. package/dist/primitives/latitude.schema.d.ts.map +1 -0
  96. package/dist/primitives/latitude.schema.js +13 -0
  97. package/dist/primitives/location.schema.d.ts +8 -0
  98. package/dist/primitives/location.schema.d.ts.map +1 -0
  99. package/dist/primitives/location.schema.js +15 -0
  100. package/dist/primitives/longitude.schema.d.ts +9 -0
  101. package/dist/primitives/longitude.schema.d.ts.map +1 -0
  102. package/dist/primitives/longitude.schema.js +13 -0
  103. package/dist/primitives/numeric-id.schema.d.ts +8 -0
  104. package/dist/primitives/numeric-id.schema.d.ts.map +1 -0
  105. package/dist/primitives/numeric-id.schema.js +10 -0
  106. package/dist/primitives/slug.schema.d.ts +17 -0
  107. package/dist/primitives/slug.schema.d.ts.map +1 -0
  108. package/dist/primitives/slug.schema.js +30 -0
  109. package/dist/primitives/uuid.schema.d.ts +8 -0
  110. package/dist/primitives/uuid.schema.d.ts.map +1 -0
  111. package/dist/primitives/uuid.schema.js +9 -0
  112. package/dist/primitives/wikidata-id.schema.d.ts +9 -0
  113. package/dist/primitives/wikidata-id.schema.d.ts.map +1 -0
  114. package/dist/primitives/wikidata-id.schema.js +10 -0
  115. package/dist/time/boundary-enforcement.d.ts +11 -0
  116. package/dist/time/boundary-enforcement.d.ts.map +1 -0
  117. package/dist/time/boundary-enforcement.js +43 -0
  118. package/dist/time/bounded-time.schema.d.ts +31 -0
  119. package/dist/time/bounded-time.schema.d.ts.map +1 -0
  120. package/dist/time/bounded-time.schema.js +77 -0
  121. package/dist/time/flexible-time-parser.d.ts +12 -0
  122. package/dist/time/flexible-time-parser.d.ts.map +1 -0
  123. package/dist/time/flexible-time-parser.js +94 -0
  124. package/dist/time/flexible-time.schema.d.ts +31 -0
  125. package/dist/time/flexible-time.schema.d.ts.map +1 -0
  126. package/dist/time/flexible-time.schema.js +31 -0
  127. package/dist/time/get-week-end.d.ts +10 -0
  128. package/dist/time/get-week-end.d.ts.map +1 -0
  129. package/dist/time/get-week-end.js +25 -0
  130. package/dist/time/get-week-start.d.ts +10 -0
  131. package/dist/time/get-week-start.d.ts.map +1 -0
  132. package/dist/time/get-week-start.js +25 -0
  133. package/dist/time/is-relative-time.d.ts +8 -0
  134. package/dist/time/is-relative-time.d.ts.map +1 -0
  135. package/dist/time/is-relative-time.js +9 -0
  136. package/dist/time/iso8601.schema.d.ts +14 -0
  137. package/dist/time/iso8601.schema.d.ts.map +1 -0
  138. package/dist/time/iso8601.schema.js +17 -0
  139. package/dist/time/iso8601.types.d.ts +6 -0
  140. package/dist/time/iso8601.types.d.ts.map +1 -0
  141. package/dist/time/iso8601.types.js +11 -0
  142. package/dist/time/parse-relative-time.d.ts +9 -0
  143. package/dist/time/parse-relative-time.d.ts.map +1 -0
  144. package/dist/time/parse-relative-time.js +36 -0
  145. package/dist/time/relative-time.schema.d.ts +23 -0
  146. package/dist/time/relative-time.schema.d.ts.map +1 -0
  147. package/dist/time/relative-time.schema.js +25 -0
  148. package/dist/time/since-parameter.schema.d.ts +8 -0
  149. package/dist/time/since-parameter.schema.d.ts.map +1 -0
  150. package/dist/time/since-parameter.schema.js +56 -0
  151. package/dist/time/time-helpers.d.ts +19 -0
  152. package/dist/time/time-helpers.d.ts.map +1 -0
  153. package/dist/time/time-helpers.js +56 -0
  154. package/dist/time/time-schemas.d.ts +20 -0
  155. package/dist/time/time-schemas.d.ts.map +1 -0
  156. package/dist/time/time-schemas.js +25 -0
  157. package/dist/time/timezone.types.d.ts +17 -0
  158. package/dist/time/timezone.types.d.ts.map +1 -0
  159. package/dist/time/timezone.types.js +15 -0
  160. package/dist/validation/zod-error-handler.d.ts +3 -0
  161. package/dist/validation/zod-error-handler.d.ts.map +1 -0
  162. package/dist/validation/zod-error-handler.js +189 -0
  163. package/dist/validation/zod-utils.d.ts +9 -0
  164. package/dist/validation/zod-utils.d.ts.map +1 -0
  165. package/dist/validation/zod-utils.js +23 -0
  166. package/eslint.config.mjs +16 -0
  167. package/package.json +44 -0
  168. package/src/api-schemas/error.schema.test.ts +27 -0
  169. package/src/api-schemas/error.schema.ts +23 -0
  170. package/src/api-schemas/health.schema.test.ts +104 -0
  171. package/src/api-schemas/health.schema.ts +63 -0
  172. package/src/api-schemas/okay.schema.test.ts +15 -0
  173. package/src/api-schemas/okay.schema.ts +8 -0
  174. package/src/api-schemas/paginated-results.schema.ts +17 -0
  175. package/src/api-schemas/partial-results.schema.ts +13 -0
  176. package/src/api-schemas/result.schema.test.ts +19 -0
  177. package/src/api-schemas/result.schema.ts +9 -0
  178. package/src/api-schemas/results.schema.test.ts +15 -0
  179. package/src/api-schemas/results.schema.ts +9 -0
  180. package/src/helpers/correlation/get-correlation-id.test.ts +126 -0
  181. package/src/helpers/correlation/get-correlation-id.ts +22 -0
  182. package/src/helpers/correlation/get-header.test.ts +179 -0
  183. package/src/helpers/correlation/get-header.ts +21 -0
  184. package/src/helpers/detect-mime-type.test.ts +100 -0
  185. package/src/helpers/detect-mime-type.ts +46 -0
  186. package/src/helpers/detect-suspicious-patterns.test.ts +45 -0
  187. package/src/helpers/detect-suspicious-patterns.ts +57 -0
  188. package/src/helpers/eventify-constants.test.ts +52 -0
  189. package/src/helpers/eventify-constants.types.test.ts +52 -0
  190. package/src/helpers/eventify-constants.types.ts +51 -0
  191. package/src/helpers/hash-binary.test.ts +60 -0
  192. package/src/helpers/hash-binary.ts +30 -0
  193. package/src/helpers/mime-types/detect-image-mime-type.test.ts +73 -0
  194. package/src/helpers/mime-types/detect-image-mime-type.ts +50 -0
  195. package/src/helpers/mime-types/detect-ole-mime-type.test.ts +86 -0
  196. package/src/helpers/mime-types/detect-ole-mime-type.ts +44 -0
  197. package/src/helpers/mime-types/detect-pdf-mime-type.test.ts +39 -0
  198. package/src/helpers/mime-types/detect-pdf-mime-type.ts +15 -0
  199. package/src/helpers/mime-types/detect-zip-mime-type.test.ts +88 -0
  200. package/src/helpers/mime-types/detect-zip-mime-type.ts +28 -0
  201. package/src/helpers/parameter-validation.test.ts +35 -0
  202. package/src/helpers/parameter-validation.ts +32 -0
  203. package/src/helpers/process-eventify-request.ts +146 -0
  204. package/src/helpers/response-headers/build-api-unauthorized-headers.ts +30 -0
  205. package/src/helpers/response-headers/environment.types.ts +1 -0
  206. package/src/helpers/response-headers/resolve-environment.ts +17 -0
  207. package/src/helpers/slugify.test.ts +77 -0
  208. package/src/helpers/slugify.ts +34 -0
  209. package/src/index.ts +46 -0
  210. package/src/normalization/normalize-list.test.ts +43 -0
  211. package/src/normalization/normalize-list.ts +21 -0
  212. package/src/normalization/normalize-location.test.ts +91 -0
  213. package/src/normalization/normalize-location.ts +29 -0
  214. package/src/primitives/coordinate-precision.test.ts +46 -0
  215. package/src/primitives/coordinate-precision.ts +30 -0
  216. package/src/primitives/geo-point.schema.test.ts +70 -0
  217. package/src/primitives/geo-point.schema.ts +14 -0
  218. package/src/primitives/geoname-id.schema.test.ts +60 -0
  219. package/src/primitives/geoname-id.schema.ts +12 -0
  220. package/src/primitives/international-zip.schema.test.ts +212 -0
  221. package/src/primitives/international-zip.schema.ts +103 -0
  222. package/src/primitives/latitude.schema.test.ts +77 -0
  223. package/src/primitives/latitude.schema.ts +20 -0
  224. package/src/primitives/location.schema.test.ts +21 -0
  225. package/src/primitives/location.schema.ts +22 -0
  226. package/src/primitives/longitude.schema.test.ts +77 -0
  227. package/src/primitives/longitude.schema.ts +20 -0
  228. package/src/primitives/numeric-id.schema.test.ts +32 -0
  229. package/src/primitives/numeric-id.schema.ts +13 -0
  230. package/src/primitives/slug.schema.test.ts +101 -0
  231. package/src/primitives/slug.schema.ts +41 -0
  232. package/src/primitives/uuid.schema.test.ts +45 -0
  233. package/src/primitives/uuid.schema.ts +12 -0
  234. package/src/primitives/wikidata-id.schema.test.ts +51 -0
  235. package/src/primitives/wikidata-id.schema.ts +16 -0
  236. package/src/time/README.md +220 -0
  237. package/src/time/boundary-enforcement.test.ts +130 -0
  238. package/src/time/boundary-enforcement.ts +59 -0
  239. package/src/time/bounded-time.schema.test.ts +294 -0
  240. package/src/time/bounded-time.schema.ts +111 -0
  241. package/src/time/flexible-time-parser.test.ts +586 -0
  242. package/src/time/flexible-time-parser.ts +122 -0
  243. package/src/time/flexible-time.schema.test.ts +243 -0
  244. package/src/time/flexible-time.schema.ts +43 -0
  245. package/src/time/is-relative-time.test.ts +23 -0
  246. package/src/time/is-relative-time.ts +9 -0
  247. package/src/time/iso8601.schema.ts +29 -0
  248. package/src/time/iso8601.types.test.ts +112 -0
  249. package/src/time/iso8601.types.ts +21 -0
  250. package/src/time/parse-relative-time.test.ts +49 -0
  251. package/src/time/parse-relative-time.ts +50 -0
  252. package/src/time/relative-time.schema.test.ts +23 -0
  253. package/src/time/relative-time.schema.ts +38 -0
  254. package/src/time/since-parameter.schema.test.ts +59 -0
  255. package/src/time/since-parameter.schema.ts +69 -0
  256. package/src/time/time-helpers.test.ts +263 -0
  257. package/src/time/time-helpers.ts +78 -0
  258. package/src/time/time-schemas.test.ts +181 -0
  259. package/src/time/time-schemas.ts +42 -0
  260. package/src/time/time.schema.test.ts +237 -0
  261. package/src/time/timezone-independence.test.ts +188 -0
  262. package/src/time/timezone.types.test.ts +55 -0
  263. package/src/time/timezone.types.ts +22 -0
  264. 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
+ }