@eco-foundation/api-schemas 0.12.0 → 0.14.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.
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MaintenanceConfigSchema = exports.MaintenanceWindowSchema = exports.MAINTENANCE_MESSAGE_MAX_LENGTH = void 0;
4
+ const zod_1 = require("zod");
5
+ const ISO_UTC_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
6
+ function isRealUtcInstant(value) {
7
+ const ms = Date.parse(value);
8
+ if (!Number.isFinite(ms))
9
+ return false;
10
+ return new Date(ms).toISOString().slice(0, 19) === value.slice(0, 19);
11
+ }
12
+ function utcMillis(value) {
13
+ return ISO_UTC_PATTERN.test(value) && isRealUtcInstant(value) ? Date.parse(value) : undefined;
14
+ }
15
+ const IsoUtcTimestamp = zod_1.z
16
+ .string({
17
+ error: 'must be an ISO-8601 UTC timestamp string; quote it in YAML',
18
+ })
19
+ .regex(ISO_UTC_PATTERN, 'must be an ISO-8601 UTC timestamp ending in Z, e.g. 2026-10-04T02:00:00Z')
20
+ .refine((value) => !ISO_UTC_PATTERN.test(value) || utcMillis(value) !== undefined, 'is not a real calendar instant');
21
+ exports.MAINTENANCE_MESSAGE_MAX_LENGTH = 200;
22
+ function rejectUnknownKeys(allowedKeys) {
23
+ const allowed = new Set(allowedKeys);
24
+ return (raw, ctx) => {
25
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
26
+ return raw;
27
+ const extra = Object.keys(raw).filter((key) => !allowed.has(key));
28
+ if (extra.length === 0)
29
+ return raw;
30
+ const quoted = extra.map((key) => `"${key}"`).join(', ');
31
+ ctx.addIssue({
32
+ code: 'custom',
33
+ message: extra.length === 1 ? `Unrecognized key: ${quoted}` : `Unrecognized keys: ${quoted}`,
34
+ });
35
+ return raw;
36
+ };
37
+ }
38
+ const MaintenanceWindowShape = zod_1.z
39
+ .object({
40
+ startsAt: IsoUtcTimestamp,
41
+ endsAt: IsoUtcTimestamp,
42
+ message: zod_1.z
43
+ .string()
44
+ .max(exports.MAINTENANCE_MESSAGE_MAX_LENGTH)
45
+ .refine((value) => value.trim().length > 0, 'must not be blank'),
46
+ })
47
+ .superRefine((window, ctx) => {
48
+ const startsMs = utcMillis(window.startsAt);
49
+ const endsMs = utcMillis(window.endsAt);
50
+ if (startsMs === undefined || endsMs === undefined)
51
+ return;
52
+ if (endsMs <= startsMs) {
53
+ ctx.addIssue({
54
+ code: 'custom',
55
+ path: ['endsAt'],
56
+ message: `endsAt ${window.endsAt} must be after startsAt ${window.startsAt}`,
57
+ });
58
+ }
59
+ });
60
+ exports.MaintenanceWindowSchema = zod_1.z.preprocess(rejectUnknownKeys(['startsAt', 'endsAt', 'message']), MaintenanceWindowShape);
61
+ const MaintenanceConfigShape = zod_1.z.object({
62
+ windows: zod_1.z.array(exports.MaintenanceWindowSchema).default([]),
63
+ });
64
+ exports.MaintenanceConfigSchema = zod_1.z.preprocess(rejectUnknownKeys(['windows']), MaintenanceConfigShape);
65
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/v1/maintenance/schema.ts"],"names":[],"mappings":"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"}
@@ -258,5 +258,10 @@
258
258
  "code": "service-unavailable",
259
259
  "status": 503,
260
260
  "legacyCodes": []
261
+ },
262
+ {
263
+ "code": "maintenance",
264
+ "status": 503,
265
+ "legacyCodes": []
261
266
  }
262
267
  ]
@@ -0,0 +1,405 @@
1
+ [
2
+ {
3
+ "name": "empty window list",
4
+ "windows": [],
5
+ "nowIso": "2026-10-04T03:00:00.000Z",
6
+ "expected": {
7
+ "active": false
8
+ }
9
+ },
10
+ {
11
+ "name": "one millisecond before startsAt",
12
+ "windows": [
13
+ {
14
+ "startsAt": "2026-10-04T02:00:00Z",
15
+ "endsAt": "2026-10-04T04:00:00Z",
16
+ "message": "Scheduled maintenance: database upgrade"
17
+ }
18
+ ],
19
+ "nowIso": "2026-10-04T01:59:59.999Z",
20
+ "expected": {
21
+ "active": false
22
+ }
23
+ },
24
+ {
25
+ "name": "exactly startsAt (start is inclusive)",
26
+ "windows": [
27
+ {
28
+ "startsAt": "2026-10-04T02:00:00Z",
29
+ "endsAt": "2026-10-04T04:00:00Z",
30
+ "message": "Scheduled maintenance: database upgrade"
31
+ }
32
+ ],
33
+ "nowIso": "2026-10-04T02:00:00.000Z",
34
+ "expected": {
35
+ "active": true,
36
+ "startsAt": "2026-10-04T02:00:00Z",
37
+ "endsAt": "2026-10-04T04:00:00Z",
38
+ "message": "Scheduled maintenance: database upgrade",
39
+ "retryAfterSeconds": 7200
40
+ }
41
+ },
42
+ {
43
+ "name": "mid-window",
44
+ "windows": [
45
+ {
46
+ "startsAt": "2026-10-04T02:00:00Z",
47
+ "endsAt": "2026-10-04T04:00:00Z",
48
+ "message": "Scheduled maintenance: database upgrade"
49
+ }
50
+ ],
51
+ "nowIso": "2026-10-04T03:00:00.000Z",
52
+ "expected": {
53
+ "active": true,
54
+ "startsAt": "2026-10-04T02:00:00Z",
55
+ "endsAt": "2026-10-04T04:00:00Z",
56
+ "message": "Scheduled maintenance: database upgrade",
57
+ "retryAfterSeconds": 3600
58
+ }
59
+ },
60
+ {
61
+ "name": "one millisecond before endsAt",
62
+ "windows": [
63
+ {
64
+ "startsAt": "2026-10-04T02:00:00Z",
65
+ "endsAt": "2026-10-04T04:00:00Z",
66
+ "message": "Scheduled maintenance: database upgrade"
67
+ }
68
+ ],
69
+ "nowIso": "2026-10-04T03:59:59.999Z",
70
+ "expected": {
71
+ "active": true,
72
+ "startsAt": "2026-10-04T02:00:00Z",
73
+ "endsAt": "2026-10-04T04:00:00Z",
74
+ "message": "Scheduled maintenance: database upgrade",
75
+ "retryAfterSeconds": 1
76
+ }
77
+ },
78
+ {
79
+ "name": "exactly endsAt (end is exclusive)",
80
+ "windows": [
81
+ {
82
+ "startsAt": "2026-10-04T02:00:00Z",
83
+ "endsAt": "2026-10-04T04:00:00Z",
84
+ "message": "Scheduled maintenance: database upgrade"
85
+ }
86
+ ],
87
+ "nowIso": "2026-10-04T04:00:00.000Z",
88
+ "expected": {
89
+ "active": false
90
+ }
91
+ },
92
+ {
93
+ "name": "fractional remainder rounds up: 1.5 s before endsAt",
94
+ "windows": [
95
+ {
96
+ "startsAt": "2026-10-04T02:00:00Z",
97
+ "endsAt": "2026-10-04T04:00:00Z",
98
+ "message": "Scheduled maintenance: database upgrade"
99
+ }
100
+ ],
101
+ "nowIso": "2026-10-04T03:59:58.500Z",
102
+ "expected": {
103
+ "active": true,
104
+ "startsAt": "2026-10-04T02:00:00Z",
105
+ "endsAt": "2026-10-04T04:00:00Z",
106
+ "message": "Scheduled maintenance: database upgrade",
107
+ "retryAfterSeconds": 2
108
+ }
109
+ },
110
+ {
111
+ "name": "whole-second remainder does not round up: 2 s before endsAt",
112
+ "windows": [
113
+ {
114
+ "startsAt": "2026-10-04T02:00:00Z",
115
+ "endsAt": "2026-10-04T04:00:00Z",
116
+ "message": "Scheduled maintenance: database upgrade"
117
+ }
118
+ ],
119
+ "nowIso": "2026-10-04T03:59:58.000Z",
120
+ "expected": {
121
+ "active": true,
122
+ "startsAt": "2026-10-04T02:00:00Z",
123
+ "endsAt": "2026-10-04T04:00:00Z",
124
+ "message": "Scheduled maintenance: database upgrade",
125
+ "retryAfterSeconds": 2
126
+ }
127
+ },
128
+ {
129
+ "name": "past-only window",
130
+ "windows": [
131
+ {
132
+ "startsAt": "2026-10-01T02:00:00Z",
133
+ "endsAt": "2026-10-01T04:00:00Z",
134
+ "message": "Last week's maintenance"
135
+ }
136
+ ],
137
+ "nowIso": "2026-10-04T03:00:00.000Z",
138
+ "expected": {
139
+ "active": false
140
+ }
141
+ },
142
+ {
143
+ "name": "future-only window",
144
+ "windows": [
145
+ {
146
+ "startsAt": "2026-10-11T02:00:00Z",
147
+ "endsAt": "2026-10-11T04:00:00Z",
148
+ "message": "Next week's maintenance"
149
+ }
150
+ ],
151
+ "nowIso": "2026-10-04T03:00:00.000Z",
152
+ "expected": {
153
+ "active": false
154
+ }
155
+ },
156
+ {
157
+ "name": "past and future windows, now between them",
158
+ "windows": [
159
+ {
160
+ "startsAt": "2026-10-01T02:00:00Z",
161
+ "endsAt": "2026-10-01T04:00:00Z",
162
+ "message": "Last week's maintenance"
163
+ },
164
+ {
165
+ "startsAt": "2026-10-11T02:00:00Z",
166
+ "endsAt": "2026-10-11T04:00:00Z",
167
+ "message": "Next week's maintenance"
168
+ }
169
+ ],
170
+ "nowIso": "2026-10-04T03:00:00.000Z",
171
+ "expected": {
172
+ "active": false
173
+ }
174
+ },
175
+ {
176
+ "name": "overlapping windows, only the first active",
177
+ "windows": [
178
+ {
179
+ "startsAt": "2026-10-04T02:00:00Z",
180
+ "endsAt": "2026-10-04T04:00:00Z",
181
+ "message": "A: database upgrade"
182
+ },
183
+ {
184
+ "startsAt": "2026-10-04T03:00:00Z",
185
+ "endsAt": "2026-10-04T05:00:00Z",
186
+ "message": "B: key rotation"
187
+ }
188
+ ],
189
+ "nowIso": "2026-10-04T02:30:00.000Z",
190
+ "expected": {
191
+ "active": true,
192
+ "startsAt": "2026-10-04T02:00:00Z",
193
+ "endsAt": "2026-10-04T04:00:00Z",
194
+ "message": "A: database upgrade",
195
+ "retryAfterSeconds": 5400
196
+ }
197
+ },
198
+ {
199
+ "name": "overlapping windows, both active: latest endsAt wins",
200
+ "windows": [
201
+ {
202
+ "startsAt": "2026-10-04T02:00:00Z",
203
+ "endsAt": "2026-10-04T04:00:00Z",
204
+ "message": "A: database upgrade"
205
+ },
206
+ {
207
+ "startsAt": "2026-10-04T03:00:00Z",
208
+ "endsAt": "2026-10-04T05:00:00Z",
209
+ "message": "B: key rotation"
210
+ }
211
+ ],
212
+ "nowIso": "2026-10-04T03:30:00.000Z",
213
+ "expected": {
214
+ "active": true,
215
+ "startsAt": "2026-10-04T03:00:00Z",
216
+ "endsAt": "2026-10-04T05:00:00Z",
217
+ "message": "B: key rotation",
218
+ "retryAfterSeconds": 5400
219
+ }
220
+ },
221
+ {
222
+ "name": "overlapping windows, both active, list reversed: latest endsAt still wins",
223
+ "windows": [
224
+ {
225
+ "startsAt": "2026-10-04T03:00:00Z",
226
+ "endsAt": "2026-10-04T05:00:00Z",
227
+ "message": "B: key rotation"
228
+ },
229
+ {
230
+ "startsAt": "2026-10-04T02:00:00Z",
231
+ "endsAt": "2026-10-04T04:00:00Z",
232
+ "message": "A: database upgrade"
233
+ }
234
+ ],
235
+ "nowIso": "2026-10-04T03:30:00.000Z",
236
+ "expected": {
237
+ "active": true,
238
+ "startsAt": "2026-10-04T03:00:00Z",
239
+ "endsAt": "2026-10-04T05:00:00Z",
240
+ "message": "B: key rotation",
241
+ "retryAfterSeconds": 5400
242
+ }
243
+ },
244
+ {
245
+ "name": "overlapping windows, after the first ends",
246
+ "windows": [
247
+ {
248
+ "startsAt": "2026-10-04T02:00:00Z",
249
+ "endsAt": "2026-10-04T04:00:00Z",
250
+ "message": "A: database upgrade"
251
+ },
252
+ {
253
+ "startsAt": "2026-10-04T03:00:00Z",
254
+ "endsAt": "2026-10-04T05:00:00Z",
255
+ "message": "B: key rotation"
256
+ }
257
+ ],
258
+ "nowIso": "2026-10-04T04:00:00.000Z",
259
+ "expected": {
260
+ "active": true,
261
+ "startsAt": "2026-10-04T03:00:00Z",
262
+ "endsAt": "2026-10-04T05:00:00Z",
263
+ "message": "B: key rotation",
264
+ "retryAfterSeconds": 3600
265
+ }
266
+ },
267
+ {
268
+ "name": "overlapping windows with equal endsAt: first in list wins",
269
+ "windows": [
270
+ {
271
+ "startsAt": "2026-10-04T02:00:00Z",
272
+ "endsAt": "2026-10-04T04:00:00Z",
273
+ "message": "A: database upgrade"
274
+ },
275
+ {
276
+ "startsAt": "2026-10-04T03:00:00Z",
277
+ "endsAt": "2026-10-04T04:00:00Z",
278
+ "message": "B: key rotation"
279
+ }
280
+ ],
281
+ "nowIso": "2026-10-04T03:30:00.000Z",
282
+ "expected": {
283
+ "active": true,
284
+ "startsAt": "2026-10-04T02:00:00Z",
285
+ "endsAt": "2026-10-04T04:00:00Z",
286
+ "message": "A: database upgrade",
287
+ "retryAfterSeconds": 1800
288
+ }
289
+ },
290
+ {
291
+ "name": "overlapping windows with equal endsAt, list reversed: first in list wins",
292
+ "windows": [
293
+ {
294
+ "startsAt": "2026-10-04T03:00:00Z",
295
+ "endsAt": "2026-10-04T04:00:00Z",
296
+ "message": "B: key rotation"
297
+ },
298
+ {
299
+ "startsAt": "2026-10-04T02:00:00Z",
300
+ "endsAt": "2026-10-04T04:00:00Z",
301
+ "message": "A: database upgrade"
302
+ }
303
+ ],
304
+ "nowIso": "2026-10-04T03:30:00.000Z",
305
+ "expected": {
306
+ "active": true,
307
+ "startsAt": "2026-10-04T03:00:00Z",
308
+ "endsAt": "2026-10-04T04:00:00Z",
309
+ "message": "B: key rotation",
310
+ "retryAfterSeconds": 1800
311
+ }
312
+ },
313
+ {
314
+ "name": "adjacent windows, one millisecond before the handoff",
315
+ "windows": [
316
+ {
317
+ "startsAt": "2026-10-04T02:00:00Z",
318
+ "endsAt": "2026-10-04T03:00:00Z",
319
+ "message": "A: database upgrade"
320
+ },
321
+ {
322
+ "startsAt": "2026-10-04T03:00:00Z",
323
+ "endsAt": "2026-10-04T04:00:00Z",
324
+ "message": "B: key rotation"
325
+ }
326
+ ],
327
+ "nowIso": "2026-10-04T02:59:59.999Z",
328
+ "expected": {
329
+ "active": true,
330
+ "startsAt": "2026-10-04T02:00:00Z",
331
+ "endsAt": "2026-10-04T03:00:00Z",
332
+ "message": "A: database upgrade",
333
+ "retryAfterSeconds": 1
334
+ }
335
+ },
336
+ {
337
+ "name": "adjacent windows at the handoff instant: the second window",
338
+ "windows": [
339
+ {
340
+ "startsAt": "2026-10-04T02:00:00Z",
341
+ "endsAt": "2026-10-04T03:00:00Z",
342
+ "message": "A: database upgrade"
343
+ },
344
+ {
345
+ "startsAt": "2026-10-04T03:00:00Z",
346
+ "endsAt": "2026-10-04T04:00:00Z",
347
+ "message": "B: key rotation"
348
+ }
349
+ ],
350
+ "nowIso": "2026-10-04T03:00:00.000Z",
351
+ "expected": {
352
+ "active": true,
353
+ "startsAt": "2026-10-04T03:00:00Z",
354
+ "endsAt": "2026-10-04T04:00:00Z",
355
+ "message": "B: key rotation",
356
+ "retryAfterSeconds": 3600
357
+ }
358
+ },
359
+ {
360
+ "name": "sub-second bounds, one millisecond before startsAt",
361
+ "windows": [
362
+ {
363
+ "startsAt": "2026-10-04T02:00:00.250Z",
364
+ "endsAt": "2026-10-04T02:00:01.750Z",
365
+ "message": "Sub-second window"
366
+ }
367
+ ],
368
+ "nowIso": "2026-10-04T02:00:00.249Z",
369
+ "expected": {
370
+ "active": false
371
+ }
372
+ },
373
+ {
374
+ "name": "sub-second bounds, exactly startsAt",
375
+ "windows": [
376
+ {
377
+ "startsAt": "2026-10-04T02:00:00.250Z",
378
+ "endsAt": "2026-10-04T02:00:01.750Z",
379
+ "message": "Sub-second window"
380
+ }
381
+ ],
382
+ "nowIso": "2026-10-04T02:00:00.250Z",
383
+ "expected": {
384
+ "active": true,
385
+ "startsAt": "2026-10-04T02:00:00.250Z",
386
+ "endsAt": "2026-10-04T02:00:01.750Z",
387
+ "message": "Sub-second window",
388
+ "retryAfterSeconds": 2
389
+ }
390
+ },
391
+ {
392
+ "name": "sub-second bounds, exactly endsAt",
393
+ "windows": [
394
+ {
395
+ "startsAt": "2026-10-04T02:00:00.250Z",
396
+ "endsAt": "2026-10-04T02:00:01.750Z",
397
+ "message": "Sub-second window"
398
+ }
399
+ ],
400
+ "nowIso": "2026-10-04T02:00:01.750Z",
401
+ "expected": {
402
+ "active": false
403
+ }
404
+ }
405
+ ]