@cookbook/urlkit 1.0.0 → 1.2.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 +37 -35
- package/dist/{compile-path-wQfWAzOh.js → compile-path-oMMfpXR8.js} +339 -115
- package/dist/compile-path-oMMfpXR8.js.map +1 -0
- package/dist/{compile-static-search-Cq3uaLe8.js → compile-static-search-c2f2FB_l.js} +2 -2
- package/dist/{compile-static-search-Cq3uaLe8.js.map → compile-static-search-c2f2FB_l.js.map} +1 -1
- package/dist/{create-url-contract-BYKPM9bn.js → create-url-contract-DcYa3Jv4.js} +2 -2
- package/dist/{create-url-contract-BYKPM9bn.js.map → create-url-contract-DcYa3Jv4.js.map} +1 -1
- package/dist/date/contracts.d.ts +1 -0
- package/dist/date/date-format-string.d.ts +9 -0
- package/dist/index.js +43 -6
- package/dist/index.js.map +1 -1
- package/dist/router-runtime.js +6 -5
- package/dist/router-runtime.js.map +1 -1
- package/dist/schema/date-time.d.ts +7 -1
- package/dist/schema/date.d.ts +2 -2
- package/dist/static.js +4 -3
- package/dist/static.js.map +1 -1
- package/dist/url/contracts.d.ts +1 -1
- package/dist/url/path-param-kind.d.ts +2 -1
- package/package.json +37 -5
- package/dist/compile-path-wQfWAzOh.js.map +0 -1
- package/dist/url/register-urlkit-path-constraints.d.ts +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import compile from '@cookbook/pathkit/compile';
|
|
2
2
|
import match from '@cookbook/pathkit/match';
|
|
3
|
-
import { hasConstraint, getConstraint, registerConstraint
|
|
3
|
+
import { hasConstraint, getConstraint, registerConstraint } from '@cookbook/pathkit/constraints';
|
|
4
|
+
import tokenize from '@cookbook/pathkit/tokenize';
|
|
4
5
|
|
|
5
6
|
const defaultMessages = {
|
|
6
7
|
'invalid-url': 'Invalid URL.',
|
|
@@ -325,6 +326,209 @@ function validateBooleanDefault(value, context) {
|
|
|
325
326
|
});
|
|
326
327
|
}
|
|
327
328
|
|
|
329
|
+
const supportedTokens = ['yyyy', 'SSS', 'MM', 'dd', 'HH', 'mm', 'ss'];
|
|
330
|
+
const dateTokens = new Set(['yyyy', 'MM', 'dd']);
|
|
331
|
+
const dateTimeTokens = new Set(['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'SSS']);
|
|
332
|
+
function parseDateFormatString(input, format, mode, options = {}) {
|
|
333
|
+
const compiled = compileDateFormatString(format, mode);
|
|
334
|
+
const match = compiled.pattern.exec(input);
|
|
335
|
+
if (!match?.groups) {
|
|
336
|
+
throw createDateFormatStringError(`${getFormatLabel(mode)} value must match format "${format}".`, options);
|
|
337
|
+
}
|
|
338
|
+
const parts = readDateParts(match.groups, compiled);
|
|
339
|
+
assertValidDateParts(parts, mode, options);
|
|
340
|
+
const value = new Date(Date.UTC(0, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second, parts.millisecond));
|
|
341
|
+
value.setUTCFullYear(parts.year);
|
|
342
|
+
if (!matchesDateParts(value, parts)) {
|
|
343
|
+
throw createDateFormatStringError(`${getFormatLabel(mode)} value must be a valid UTC calendar ${mode === 'date' ? 'date' : 'instant'}.`, options);
|
|
344
|
+
}
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
347
|
+
function serializeDateFormatString(input, format, mode, options = {}) {
|
|
348
|
+
if (!(input instanceof Date) || !Number.isFinite(input.getTime())) {
|
|
349
|
+
throw createDateFormatStringError(`${getFormatLabel(mode)} value must be a valid Date.`, options);
|
|
350
|
+
}
|
|
351
|
+
const compiled = compileDateFormatString(format, mode);
|
|
352
|
+
const year = input.getUTCFullYear();
|
|
353
|
+
if (year < 0 || year > 9999) {
|
|
354
|
+
throw createDateFormatStringError(`${getFormatLabel(mode)} value year must be between 0000 and 9999 for yyyy format strings.`, options);
|
|
355
|
+
}
|
|
356
|
+
if (mode === 'date-time' && !compiled.hasMilliseconds && input.getUTCMilliseconds() !== 0) {
|
|
357
|
+
throw createDateFormatStringError('Date-time value cannot be serialized without losing milliseconds. Include SSS in the format or use a Date with zero milliseconds.', options);
|
|
358
|
+
}
|
|
359
|
+
return compiled.parts
|
|
360
|
+
.map((part) => {
|
|
361
|
+
if (part.kind === 'literal') {
|
|
362
|
+
return part.value;
|
|
363
|
+
}
|
|
364
|
+
return serializeToken(input, part.value);
|
|
365
|
+
})
|
|
366
|
+
.join('');
|
|
367
|
+
}
|
|
368
|
+
function validateDateFormatString(format, mode) {
|
|
369
|
+
compileDateFormatString(format, mode);
|
|
370
|
+
}
|
|
371
|
+
function compileDateFormatString(format, mode) {
|
|
372
|
+
const parts = tokenizeDateFormatString(format, mode);
|
|
373
|
+
validateDateFormatParts(format, parts, mode);
|
|
374
|
+
const pattern = new RegExp(`^${parts
|
|
375
|
+
.map((part) => part.kind === 'literal' ? escapeRegExp(part.value) : getTokenPattern(part.value))
|
|
376
|
+
.join('')}$`);
|
|
377
|
+
return {
|
|
378
|
+
format,
|
|
379
|
+
mode,
|
|
380
|
+
parts,
|
|
381
|
+
pattern,
|
|
382
|
+
hasMilliseconds: parts.some((part) => part.kind === 'token' && part.value === 'SSS'),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function tokenizeDateFormatString(format, mode) {
|
|
386
|
+
if (typeof format !== 'string' || format.length === 0) {
|
|
387
|
+
throw createInvalidDateFormatDescriptorError(`${getFormatLabel(mode)} format must be a non-empty string.`);
|
|
388
|
+
}
|
|
389
|
+
const parts = [];
|
|
390
|
+
let index = 0;
|
|
391
|
+
while (index < format.length) {
|
|
392
|
+
if (format[index] === "'") {
|
|
393
|
+
const literalEnd = format.indexOf("'", index + 1);
|
|
394
|
+
if (literalEnd === -1) {
|
|
395
|
+
throw createInvalidDateFormatDescriptorError(`${getFormatLabel(mode)} format contains an unterminated quoted literal.`);
|
|
396
|
+
}
|
|
397
|
+
const literal = format.slice(index + 1, literalEnd);
|
|
398
|
+
if (literal.length === 0) {
|
|
399
|
+
throw createInvalidDateFormatDescriptorError(`${getFormatLabel(mode)} format contains an empty quoted literal.`);
|
|
400
|
+
}
|
|
401
|
+
parts.push({ kind: 'literal', value: literal });
|
|
402
|
+
index = literalEnd + 1;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const token = findTokenAt(format, index);
|
|
406
|
+
if (token) {
|
|
407
|
+
parts.push({ kind: 'token', value: token });
|
|
408
|
+
index += token.length;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
const character = format[index] ?? '';
|
|
412
|
+
if (isAsciiLetter(character)) {
|
|
413
|
+
throw createInvalidDateFormatDescriptorError(`${getFormatLabel(mode)} format contains unsupported token near "${format.slice(index)}".`);
|
|
414
|
+
}
|
|
415
|
+
parts.push({ kind: 'literal', value: character });
|
|
416
|
+
index += 1;
|
|
417
|
+
}
|
|
418
|
+
return mergeAdjacentLiterals(parts);
|
|
419
|
+
}
|
|
420
|
+
function findTokenAt(format, index) {
|
|
421
|
+
return supportedTokens.find((token) => format.startsWith(token, index));
|
|
422
|
+
}
|
|
423
|
+
function mergeAdjacentLiterals(parts) {
|
|
424
|
+
const merged = [];
|
|
425
|
+
for (const part of parts) {
|
|
426
|
+
const previous = merged[merged.length - 1];
|
|
427
|
+
if (part.kind === 'literal' && previous?.kind === 'literal') {
|
|
428
|
+
merged[merged.length - 1] = { kind: 'literal', value: `${previous.value}${part.value}` };
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
merged.push(part);
|
|
432
|
+
}
|
|
433
|
+
return merged;
|
|
434
|
+
}
|
|
435
|
+
function validateDateFormatParts(format, parts, mode) {
|
|
436
|
+
const allowedTokens = mode === 'date' ? dateTokens : dateTimeTokens;
|
|
437
|
+
const requiredTokens = mode === 'date' ? ['yyyy', 'MM', 'dd'] : ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss'];
|
|
438
|
+
const seenTokens = new Set();
|
|
439
|
+
for (const part of parts) {
|
|
440
|
+
if (part.kind === 'literal') {
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (!allowedTokens.has(part.value)) {
|
|
444
|
+
throw createInvalidDateFormatDescriptorError(`${getFormatLabel(mode)} format "${format}" contains unsupported token "${part.value}".`);
|
|
445
|
+
}
|
|
446
|
+
if (seenTokens.has(part.value)) {
|
|
447
|
+
throw createInvalidDateFormatDescriptorError(`${getFormatLabel(mode)} format "${format}" contains duplicate token "${part.value}".`);
|
|
448
|
+
}
|
|
449
|
+
seenTokens.add(part.value);
|
|
450
|
+
}
|
|
451
|
+
for (const token of requiredTokens) {
|
|
452
|
+
if (!seenTokens.has(token)) {
|
|
453
|
+
throw createInvalidDateFormatDescriptorError(`${getFormatLabel(mode)} format "${format}" is missing required token "${token}".`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function readDateParts(groups, compiled) {
|
|
458
|
+
return {
|
|
459
|
+
year: Number(groups.yyyy),
|
|
460
|
+
month: Number(groups.MM),
|
|
461
|
+
day: Number(groups.dd),
|
|
462
|
+
hour: compiled.mode === 'date-time' ? Number(groups.HH) : 0,
|
|
463
|
+
minute: compiled.mode === 'date-time' ? Number(groups.mm) : 0,
|
|
464
|
+
second: compiled.mode === 'date-time' ? Number(groups.ss) : 0,
|
|
465
|
+
millisecond: compiled.hasMilliseconds ? Number(groups.SSS) : 0,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function assertValidDateParts(parts, mode, options) {
|
|
469
|
+
if (parts.month < 1 ||
|
|
470
|
+
parts.month > 12 ||
|
|
471
|
+
parts.day < 1 ||
|
|
472
|
+
parts.day > 31 ||
|
|
473
|
+
parts.hour < 0 ||
|
|
474
|
+
parts.hour > 23 ||
|
|
475
|
+
parts.minute < 0 ||
|
|
476
|
+
parts.minute > 59 ||
|
|
477
|
+
parts.second < 0 ||
|
|
478
|
+
parts.second > 59 ||
|
|
479
|
+
parts.millisecond < 0 ||
|
|
480
|
+
parts.millisecond > 999) {
|
|
481
|
+
throw createDateFormatStringError(`${getFormatLabel(mode)} value must be a valid UTC calendar ${mode === 'date' ? 'date' : 'instant'}.`, options);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function matchesDateParts(value, parts) {
|
|
485
|
+
return (value.getUTCFullYear() === parts.year &&
|
|
486
|
+
value.getUTCMonth() === parts.month - 1 &&
|
|
487
|
+
value.getUTCDate() === parts.day &&
|
|
488
|
+
value.getUTCHours() === parts.hour &&
|
|
489
|
+
value.getUTCMinutes() === parts.minute &&
|
|
490
|
+
value.getUTCSeconds() === parts.second &&
|
|
491
|
+
value.getUTCMilliseconds() === parts.millisecond);
|
|
492
|
+
}
|
|
493
|
+
function serializeToken(input, token) {
|
|
494
|
+
switch (token) {
|
|
495
|
+
case 'yyyy':
|
|
496
|
+
return String(input.getUTCFullYear()).padStart(4, '0');
|
|
497
|
+
case 'MM':
|
|
498
|
+
return String(input.getUTCMonth() + 1).padStart(2, '0');
|
|
499
|
+
case 'dd':
|
|
500
|
+
return String(input.getUTCDate()).padStart(2, '0');
|
|
501
|
+
case 'HH':
|
|
502
|
+
return String(input.getUTCHours()).padStart(2, '0');
|
|
503
|
+
case 'mm':
|
|
504
|
+
return String(input.getUTCMinutes()).padStart(2, '0');
|
|
505
|
+
case 'ss':
|
|
506
|
+
return String(input.getUTCSeconds()).padStart(2, '0');
|
|
507
|
+
case 'SSS':
|
|
508
|
+
return String(input.getUTCMilliseconds()).padStart(3, '0');
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function getTokenPattern(token) {
|
|
512
|
+
return `(?<${token}>\\d{${token === 'yyyy' || token === 'SSS' ? token.length.toString() : '2'}})`;
|
|
513
|
+
}
|
|
514
|
+
function escapeRegExp(value) {
|
|
515
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
516
|
+
}
|
|
517
|
+
function isAsciiLetter(value) {
|
|
518
|
+
return /^[A-Za-z]$/.test(value);
|
|
519
|
+
}
|
|
520
|
+
function getFormatLabel(mode) {
|
|
521
|
+
return mode === 'date' ? 'Date' : 'Date-time';
|
|
522
|
+
}
|
|
523
|
+
function createDateFormatStringError(message, options) {
|
|
524
|
+
return new UrlKitError(options.code ?? 'invalid-search', message, {
|
|
525
|
+
path: [...(options.path ?? [])],
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
function createInvalidDateFormatDescriptorError(message) {
|
|
529
|
+
return new UrlKitError('invalid-descriptor', message);
|
|
530
|
+
}
|
|
531
|
+
|
|
328
532
|
const datePattern = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
329
533
|
function parseDate(input, options = {}) {
|
|
330
534
|
const match = datePattern.exec(input);
|
|
@@ -596,21 +800,24 @@ const unixMsCodec = {
|
|
|
596
800
|
return serializeUnixMs(input, { code: context.errorCode, path: context.path });
|
|
597
801
|
},
|
|
598
802
|
};
|
|
599
|
-
function date(options = {}) {
|
|
600
|
-
const format = resolveDateFormat(options);
|
|
803
|
+
function date(options = {}, formatStringMode = 'date') {
|
|
804
|
+
const format = resolveDateFormat(options, formatStringMode);
|
|
601
805
|
return createRuntimeSchemaBuilder({
|
|
602
806
|
kind: 'date',
|
|
603
807
|
options: { format },
|
|
604
|
-
codec: getDateCodec(format),
|
|
808
|
+
codec: getDateCodec(format, formatStringMode),
|
|
605
809
|
validateDefault(value, context) {
|
|
606
810
|
validateDateDefault(value, context);
|
|
607
811
|
},
|
|
608
812
|
});
|
|
609
813
|
}
|
|
610
|
-
function getDateCodec(format) {
|
|
814
|
+
function getDateCodec(format, formatStringMode) {
|
|
611
815
|
if (isDateFormatCodec(format)) {
|
|
612
816
|
return createCustomDateCodec(format);
|
|
613
817
|
}
|
|
818
|
+
if (isDateFormatString(format) && !isBuiltInDateFormat(format)) {
|
|
819
|
+
return createDateFormatStringCodec(format, formatStringMode);
|
|
820
|
+
}
|
|
614
821
|
if (format === 'date-time') {
|
|
615
822
|
return dateTimeCodec;
|
|
616
823
|
}
|
|
@@ -622,6 +829,25 @@ function getDateCodec(format) {
|
|
|
622
829
|
}
|
|
623
830
|
return dateOnlyCodec;
|
|
624
831
|
}
|
|
832
|
+
function createDateFormatStringCodec(format, mode) {
|
|
833
|
+
return {
|
|
834
|
+
parse(input, context) {
|
|
835
|
+
return parseDateFormatString(input, format, mode, {
|
|
836
|
+
code: context.errorCode,
|
|
837
|
+
path: context.path,
|
|
838
|
+
});
|
|
839
|
+
},
|
|
840
|
+
normalize(input, context) {
|
|
841
|
+
return validateDate(input, context);
|
|
842
|
+
},
|
|
843
|
+
serialize(input, context) {
|
|
844
|
+
return serializeDateFormatString(input, format, mode, {
|
|
845
|
+
code: context.errorCode,
|
|
846
|
+
path: context.path,
|
|
847
|
+
});
|
|
848
|
+
},
|
|
849
|
+
};
|
|
850
|
+
}
|
|
625
851
|
function createCustomDateCodec(format) {
|
|
626
852
|
return {
|
|
627
853
|
parse(input, context) {
|
|
@@ -635,7 +861,7 @@ function createCustomDateCodec(format) {
|
|
|
635
861
|
},
|
|
636
862
|
};
|
|
637
863
|
}
|
|
638
|
-
function resolveDateFormat(options) {
|
|
864
|
+
function resolveDateFormat(options, formatStringMode) {
|
|
639
865
|
if (!isDateOptions(options)) {
|
|
640
866
|
throw new UrlKitError('invalid-descriptor', 'Date options must be an object.');
|
|
641
867
|
}
|
|
@@ -643,11 +869,18 @@ function resolveDateFormat(options) {
|
|
|
643
869
|
if (isBuiltInDateFormat(format) || isDateFormatCodec(format)) {
|
|
644
870
|
return format;
|
|
645
871
|
}
|
|
646
|
-
|
|
872
|
+
if (isDateFormatString(format)) {
|
|
873
|
+
validateDateFormatString(format, formatStringMode);
|
|
874
|
+
return format;
|
|
875
|
+
}
|
|
876
|
+
throw new UrlKitError('invalid-descriptor', 'Date format must be a supported built-in format, a supported format string, or an explicit codec.');
|
|
647
877
|
}
|
|
648
878
|
function isDateOptions(input) {
|
|
649
879
|
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
650
880
|
}
|
|
881
|
+
function isDateFormatString(input) {
|
|
882
|
+
return typeof input === 'string';
|
|
883
|
+
}
|
|
651
884
|
function isBuiltInDateFormat(input) {
|
|
652
885
|
return (input === 'date' || input === 'date-time' || input === 'unix-seconds' || input === 'unix-ms');
|
|
653
886
|
}
|
|
@@ -1027,19 +1260,6 @@ function isSearchSchema(input) {
|
|
|
1027
1260
|
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
1028
1261
|
}
|
|
1029
1262
|
|
|
1030
|
-
function getPathParamKind(segment) {
|
|
1031
|
-
switch (segment.constraint) {
|
|
1032
|
-
case 'int':
|
|
1033
|
-
return 'int';
|
|
1034
|
-
case 'number':
|
|
1035
|
-
return 'number';
|
|
1036
|
-
case 'regex':
|
|
1037
|
-
return 'regex';
|
|
1038
|
-
default:
|
|
1039
|
-
return 'string';
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
1263
|
function assertPathMatchFailure(pattern, pathname, segments) {
|
|
1044
1264
|
const pathnameSegments = splitPath(pathname);
|
|
1045
1265
|
if (pathnameSegments.length !== segments.length) {
|
|
@@ -1057,55 +1277,63 @@ function assertPathMatchFailure(pattern, pathname, segments) {
|
|
|
1057
1277
|
}
|
|
1058
1278
|
continue;
|
|
1059
1279
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1280
|
+
const validationFailure = getPathParamValidationFailure(segment, pathnameSegment);
|
|
1281
|
+
if (validationFailure) {
|
|
1282
|
+
throw new UrlKitError('invalid-param', validationFailure.message, {
|
|
1062
1283
|
path: ['params', segment.name],
|
|
1284
|
+
...(validationFailure.cause === undefined ? {} : { cause: validationFailure.cause }),
|
|
1063
1285
|
});
|
|
1064
1286
|
}
|
|
1065
1287
|
}
|
|
1066
1288
|
throwPathMismatch(pattern, pathname);
|
|
1067
1289
|
}
|
|
1068
1290
|
function splitPath(pathname) {
|
|
1069
|
-
if (pathname
|
|
1291
|
+
if (!pathname) {
|
|
1070
1292
|
return Object.freeze([]);
|
|
1071
1293
|
}
|
|
1072
1294
|
const normalized = pathname.startsWith('/') ? pathname.slice(1) : pathname;
|
|
1073
|
-
if (normalized
|
|
1295
|
+
if (!normalized) {
|
|
1074
1296
|
return Object.freeze([]);
|
|
1075
1297
|
}
|
|
1076
1298
|
return Object.freeze(normalized.split('/'));
|
|
1077
1299
|
}
|
|
1078
|
-
function
|
|
1079
|
-
if (value
|
|
1080
|
-
return
|
|
1300
|
+
function getPathParamValidationFailure(segment, value) {
|
|
1301
|
+
if (!value) {
|
|
1302
|
+
return {
|
|
1303
|
+
message: `Path parameter "${segment.name}" is invalid.`,
|
|
1304
|
+
};
|
|
1081
1305
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
return /^\d+$/.test(value);
|
|
1306
|
+
if (!segment.constraint) {
|
|
1307
|
+
return undefined;
|
|
1085
1308
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1309
|
+
const constraint = getConstraint(segment.constraint);
|
|
1310
|
+
if (!constraint) {
|
|
1311
|
+
return {
|
|
1312
|
+
message: `Path constraint "${segment.constraint}" is not registered.`,
|
|
1313
|
+
};
|
|
1088
1314
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
return false;
|
|
1093
|
-
}
|
|
1094
|
-
return new RegExp(`^(?:${params})$`).test(value);
|
|
1315
|
+
try {
|
|
1316
|
+
constraint(segment.name, value, segment.constraintParams ?? '');
|
|
1317
|
+
return undefined;
|
|
1095
1318
|
}
|
|
1096
|
-
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1319
|
+
catch (error) {
|
|
1320
|
+
const causeMessage = getCauseMessage(error);
|
|
1321
|
+
return {
|
|
1322
|
+
message: causeMessage
|
|
1323
|
+
? `Path parameter "${segment.name}" is invalid: ${causeMessage}`
|
|
1324
|
+
: `Path parameter "${segment.name}" is invalid.`,
|
|
1325
|
+
cause: error,
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
function getCauseMessage(error) {
|
|
1330
|
+
if (error instanceof Error && error.message) {
|
|
1331
|
+
return error.message;
|
|
1107
1332
|
}
|
|
1108
|
-
|
|
1333
|
+
if (typeof error === 'string' && error) {
|
|
1334
|
+
return error;
|
|
1335
|
+
}
|
|
1336
|
+
return undefined;
|
|
1109
1337
|
}
|
|
1110
1338
|
function throwPathMismatch(pattern, pathname) {
|
|
1111
1339
|
throw new UrlKitError('path-mismatch', `Pathname "${pathname}" does not match pattern "${pattern}".`, {
|
|
@@ -1113,6 +1341,24 @@ function throwPathMismatch(pattern, pathname) {
|
|
|
1113
1341
|
});
|
|
1114
1342
|
}
|
|
1115
1343
|
|
|
1344
|
+
function getPathParamKind(segment) {
|
|
1345
|
+
switch (segment.constraint) {
|
|
1346
|
+
case 'int':
|
|
1347
|
+
return 'int';
|
|
1348
|
+
case 'decimal':
|
|
1349
|
+
return 'decimal';
|
|
1350
|
+
case 'range':
|
|
1351
|
+
return 'range';
|
|
1352
|
+
case 'regex':
|
|
1353
|
+
return 'regex';
|
|
1354
|
+
default:
|
|
1355
|
+
return 'string';
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function isNumericPathParamKind(kind) {
|
|
1359
|
+
return kind === 'int' || kind === 'decimal' || kind === 'range';
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1116
1362
|
function coercePathParam(segment, value, paramsMode) {
|
|
1117
1363
|
if (paramsMode === 'raw') {
|
|
1118
1364
|
return value;
|
|
@@ -1127,10 +1373,10 @@ function coercePathParam(segment, value, paramsMode) {
|
|
|
1127
1373
|
}
|
|
1128
1374
|
return parsed;
|
|
1129
1375
|
}
|
|
1130
|
-
if (kind
|
|
1376
|
+
if (isNumericPathParamKind(kind)) {
|
|
1131
1377
|
const parsed = Number(value);
|
|
1132
1378
|
if (!Number.isFinite(parsed)) {
|
|
1133
|
-
throw new UrlKitError('invalid-param', `Path parameter "${segment.name}" must be a finite number.`, {
|
|
1379
|
+
throw new UrlKitError('invalid-param', `Path parameter "${segment.name}" must be a finite decimal number.`, {
|
|
1134
1380
|
path: ['params', segment.name],
|
|
1135
1381
|
});
|
|
1136
1382
|
}
|
|
@@ -1167,79 +1413,57 @@ function isRecord(input) {
|
|
|
1167
1413
|
}
|
|
1168
1414
|
|
|
1169
1415
|
function parsePathPattern(pattern) {
|
|
1170
|
-
|
|
1171
|
-
return Object.freeze(
|
|
1172
|
-
}
|
|
1173
|
-
const normalized = pattern.startsWith('/') ? pattern.slice(1) : pattern;
|
|
1174
|
-
if (normalized === '') {
|
|
1175
|
-
return Object.freeze([]);
|
|
1176
|
-
}
|
|
1177
|
-
return Object.freeze(normalized.split('/').map((segment, index) => parsePathSegment(segment, index)));
|
|
1178
|
-
}
|
|
1179
|
-
function parsePathSegment(segment, index) {
|
|
1180
|
-
if (!segment.startsWith('{') || !segment.endsWith('}')) {
|
|
1181
|
-
return Object.freeze({ kind: 'literal', value: segment });
|
|
1416
|
+
try {
|
|
1417
|
+
return Object.freeze(toParsedPathSegments(tokenize(pattern)));
|
|
1182
1418
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1419
|
+
catch (error) {
|
|
1420
|
+
if (error instanceof UrlKitError) {
|
|
1421
|
+
throw error;
|
|
1422
|
+
}
|
|
1423
|
+
const causeMessage = error instanceof Error && error.message ? `: ${error.message}` : '';
|
|
1424
|
+
throw new UrlKitError('invalid-descriptor', `Path pattern is invalid${causeMessage}.`, {
|
|
1425
|
+
path: ['path'],
|
|
1426
|
+
cause: error,
|
|
1188
1427
|
});
|
|
1189
1428
|
}
|
|
1190
|
-
return Object.freeze({ kind: 'param', ...parsed });
|
|
1191
1429
|
}
|
|
1192
|
-
function
|
|
1193
|
-
const
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
if (paramsStart === -1 || !constraintToken.endsWith(')')) {
|
|
1201
|
-
return { name, constraint: constraintToken };
|
|
1430
|
+
function toParsedPathSegments(tokens) {
|
|
1431
|
+
const segments = [];
|
|
1432
|
+
for (const token of tokens) {
|
|
1433
|
+
if (token.type === 'literal') {
|
|
1434
|
+
appendLiteralSegments(segments, token.value);
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
segments.push(toParsedParamSegment(token));
|
|
1202
1438
|
}
|
|
1203
|
-
return
|
|
1204
|
-
name,
|
|
1205
|
-
constraint: constraintToken.slice(0, paramsStart),
|
|
1206
|
-
constraintParams: constraintToken.slice(paramsStart + 1, -1),
|
|
1207
|
-
};
|
|
1439
|
+
return segments.map((segment) => Object.freeze(segment));
|
|
1208
1440
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
if (!
|
|
1215
|
-
|
|
1216
|
-
path: ['params', paramName],
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
},
|
|
1220
|
-
verify(paramName, params) {
|
|
1221
|
-
if (params.trim()) {
|
|
1222
|
-
throw new UrlKitError('invalid-descriptor', `Constraint 'number' declared for '${paramName}' parameter does not accept arguments.`, { path: ['path', paramName] });
|
|
1441
|
+
function appendLiteralSegments(segments, value) {
|
|
1442
|
+
if (!value) {
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
for (const segment of value.split('/')) {
|
|
1446
|
+
if (!segment) {
|
|
1447
|
+
continue;
|
|
1223
1448
|
}
|
|
1224
|
-
|
|
1225
|
-
toRegExp() {
|
|
1226
|
-
return '-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)';
|
|
1227
|
-
},
|
|
1228
|
-
});
|
|
1229
|
-
function registerUrlKitPathConstraints() {
|
|
1230
|
-
if (!hasConstraint('number')) {
|
|
1231
|
-
registerConstraint('number', numberConstraint);
|
|
1449
|
+
segments.push({ kind: 'literal', value: segment });
|
|
1232
1450
|
}
|
|
1233
1451
|
}
|
|
1234
|
-
function
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1452
|
+
function toParsedParamSegment(token) {
|
|
1453
|
+
const constraint = token.constraints[0];
|
|
1454
|
+
return {
|
|
1455
|
+
kind: 'param',
|
|
1456
|
+
name: token.name,
|
|
1457
|
+
...(constraint
|
|
1458
|
+
? {
|
|
1459
|
+
constraint: constraint.type,
|
|
1460
|
+
...(constraint.params ? { constraintParams: constraint.params } : {}),
|
|
1461
|
+
}
|
|
1462
|
+
: {}),
|
|
1463
|
+
};
|
|
1239
1464
|
}
|
|
1240
1465
|
|
|
1241
1466
|
function compilePath(pattern, options = {}) {
|
|
1242
|
-
registerUrlKitPathConstraints();
|
|
1243
1467
|
if (options.pathConstraints) {
|
|
1244
1468
|
registerPathConstraints(options.pathConstraints);
|
|
1245
1469
|
}
|
|
@@ -1314,5 +1538,5 @@ function compilePathPattern(pattern) {
|
|
|
1314
1538
|
}
|
|
1315
1539
|
}
|
|
1316
1540
|
|
|
1317
|
-
export { UrlKitError as U, compilePath as a, boolean as b, compileSearchSchema as c, date as d, enumOf as e, registerPathConstraints as f, handleRuntimeSchemaAbsence as g, hasPathConstraint as h, int as i, createSchemaValueError as j, createRuntimeSchemaValueContext as k, compileRuntimeSchemaValue as l, createRuntimeSchemaBuilder as m, number as n, normalizeRuntimeSchemaValue as o, getRuntimeSchemaInternals as p, runtimeSchemaSymbol as q, registerPathConstraint as r, string as s, compileRuntimeSchema as t, normalizeCompiledRuntimeSchemaValue as u,
|
|
1318
|
-
//# sourceMappingURL=compile-path-
|
|
1541
|
+
export { parseDate as A, UrlKitError as U, compilePath as a, boolean as b, compileSearchSchema as c, date as d, enumOf as e, registerPathConstraints as f, handleRuntimeSchemaAbsence as g, hasPathConstraint as h, int as i, createSchemaValueError as j, createRuntimeSchemaValueContext as k, compileRuntimeSchemaValue as l, createRuntimeSchemaBuilder as m, number as n, normalizeRuntimeSchemaValue as o, getRuntimeSchemaInternals as p, runtimeSchemaSymbol as q, registerPathConstraint as r, string as s, compileRuntimeSchema as t, normalizeCompiledRuntimeSchemaValue as u, validateDateFormatString as v, compileStaticHashDescriptor as w, parseUnixSeconds as x, parseUnixMs as y, parseDateTime as z };
|
|
1542
|
+
//# sourceMappingURL=compile-path-oMMfpXR8.js.map
|