@cookbook/urlkit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +707 -0
- package/dist/compile-path-wQfWAzOh.js +1318 -0
- package/dist/compile-path-wQfWAzOh.js.map +1 -0
- package/dist/compile-static-search-Cq3uaLe8.js +238 -0
- package/dist/compile-static-search-Cq3uaLe8.js.map +1 -0
- package/dist/contracts.d.ts +107 -0
- package/dist/create-url-contract-BYKPM9bn.js +1751 -0
- package/dist/create-url-contract-BYKPM9bn.js.map +1 -0
- package/dist/date/contracts.d.ts +5 -0
- package/dist/date/parse-custom-date.d.ts +7 -0
- package/dist/date/parse-date-time.d.ts +6 -0
- package/dist/date/parse-date.d.ts +6 -0
- package/dist/date/parse-unix-ms.d.ts +6 -0
- package/dist/date/parse-unix-seconds.d.ts +6 -0
- package/dist/date/serialize-custom-date.d.ts +7 -0
- package/dist/date/serialize-date-time.d.ts +6 -0
- package/dist/date/serialize-date.d.ts +6 -0
- package/dist/date/serialize-unix-ms.d.ts +6 -0
- package/dist/date/serialize-unix-seconds.d.ts +6 -0
- package/dist/errors/contracts.d.ts +5 -0
- package/dist/errors/url-kit-error.d.ts +8 -0
- package/dist/hash/build-hash.d.ts +4 -0
- package/dist/hash/compile-hash-descriptor.d.ts +2 -0
- package/dist/hash/compile-normalized-hash-descriptor.d.ts +2 -0
- package/dist/hash/compile-runtime-hash-descriptor.d.ts +3 -0
- package/dist/hash/compile-static-hash-descriptor.d.ts +3 -0
- package/dist/hash/contracts.d.ts +21 -0
- package/dist/hash/copy-normalized-hash-descriptor.d.ts +2 -0
- package/dist/hash/create-hash.d.ts +5 -0
- package/dist/hash/hash-fragment.d.ts +2 -0
- package/dist/hash/is-normalized-hash-descriptor.d.ts +2 -0
- package/dist/hash/normalize-hash.d.ts +3 -0
- package/dist/hash/parse-hash.d.ts +3 -0
- package/dist/hash/validate-normalized-hash-value.d.ts +5 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/router-runtime.d.ts +12 -0
- package/dist/router-runtime.js +214 -0
- package/dist/router-runtime.js.map +1 -0
- package/dist/runtime/build-route-search.d.ts +22 -0
- package/dist/runtime/compile-cached-static-search.d.ts +3 -0
- package/dist/runtime/contracts.d.ts +7 -0
- package/dist/runtime/create-route-url-contract.d.ts +15 -0
- package/dist/runtime/parse-route-search.d.ts +12 -0
- package/dist/schema/array.d.ts +9 -0
- package/dist/schema/boolean.d.ts +4 -0
- package/dist/schema/compile-runtime-schema-value.d.ts +6 -0
- package/dist/schema/compile-runtime-schema.d.ts +2 -0
- package/dist/schema/contracts.d.ts +87 -0
- package/dist/schema/create-schema-builder.d.ts +2 -0
- package/dist/schema/create-schema-value-error.d.ts +3 -0
- package/dist/schema/date-time.d.ts +4 -0
- package/dist/schema/date.d.ts +14 -0
- package/dist/schema/enum-of.d.ts +7 -0
- package/dist/schema/get-runtime-schema-internals.d.ts +2 -0
- package/dist/schema/handle-runtime-schema-absence.d.ts +6 -0
- package/dist/schema/int.d.ts +4 -0
- package/dist/schema/is-runtime-schema-kind.d.ts +2 -0
- package/dist/schema/normalize-compiled-runtime-schema-value.d.ts +3 -0
- package/dist/schema/normalize-runtime-schema-value.d.ts +2 -0
- package/dist/schema/number.d.ts +4 -0
- package/dist/schema/object.d.ts +20 -0
- package/dist/schema/parse-compiled-runtime-schema-value.d.ts +3 -0
- package/dist/schema/parse-runtime-schema-value.d.ts +2 -0
- package/dist/schema/runtime-schema-symbol.d.ts +5 -0
- package/dist/schema/runtime-schema-value-context.d.ts +2 -0
- package/dist/schema/safe-runtime-schema-value.d.ts +4 -0
- package/dist/schema/serialize-compiled-runtime-schema-value.d.ts +3 -0
- package/dist/schema/serialize-runtime-schema-value.d.ts +2 -0
- package/dist/schema/string.d.ts +4 -0
- package/dist/search/append-object-search-entries.d.ts +4 -0
- package/dist/search/append-raw-search-value.d.ts +2 -0
- package/dist/search/append-search-entry.d.ts +3 -0
- package/dist/search/are-search-values-equal.d.ts +1 -0
- package/dist/search/assert-object-search-collisions.d.ts +2 -0
- package/dist/search/build-compiled-search.d.ts +3 -0
- package/dist/search/build-raw-search.d.ts +2 -0
- package/dist/search/build-schema-search.d.ts +3 -0
- package/dist/search/build-search.d.ts +6 -0
- package/dist/search/collect-object-search-paths.d.ts +2 -0
- package/dist/search/compile-search-schema.d.ts +2 -0
- package/dist/search/contracts.d.ts +73 -0
- package/dist/search/copy-raw-search-params.d.ts +2 -0
- package/dist/search/copy-unknown-structured-search.d.ts +2 -0
- package/dist/search/create-search-params.d.ts +1 -0
- package/dist/search/create-search.d.ts +4 -0
- package/dist/search/delete-search-field-raw-keys.d.ts +2 -0
- package/dist/search/filter-raw-search.d.ts +3 -0
- package/dist/search/find-object-search-raw-value.d.ts +2 -0
- package/dist/search/has-search-field-raw-value.d.ts +2 -0
- package/dist/search/is-runtime-search-field.d.ts +2 -0
- package/dist/search/join-search-strings.d.ts +1 -0
- package/dist/search/normalize-compiled-search.d.ts +3 -0
- package/dist/search/normalize-search-build-value.d.ts +2 -0
- package/dist/search/normalize-search-field-default.d.ts +2 -0
- package/dist/search/normalize-search-field-type.d.ts +2 -0
- package/dist/search/object-search-key.d.ts +4 -0
- package/dist/search/object-search-path-key.d.ts +2 -0
- package/dist/search/object-search-raw-key-path.d.ts +1 -0
- package/dist/search/omit-search.d.ts +1 -0
- package/dist/search/parse-array-search-value.d.ts +4 -0
- package/dist/search/parse-compiled-search.d.ts +2 -0
- package/dist/search/parse-object-search-value.d.ts +4 -0
- package/dist/search/parse-partial-compiled-search.d.ts +6 -0
- package/dist/search/parse-partial-schema-search.d.ts +6 -0
- package/dist/search/parse-raw-search.d.ts +2 -0
- package/dist/search/parse-search-field-value.d.ts +2 -0
- package/dist/search/parse-search.d.ts +5 -0
- package/dist/search/patch-search.d.ts +6 -0
- package/dist/search/pick-search.d.ts +1 -0
- package/dist/search/replace-search.d.ts +5 -0
- package/dist/search/search-array-format.d.ts +3 -0
- package/dist/search/search-entries.d.ts +4 -0
- package/dist/search/serialize-search-build-value.d.ts +2 -0
- package/dist/search/serialize-search-entries.d.ts +3 -0
- package/dist/static/compile-static-hash.d.ts +3 -0
- package/dist/static/compile-static-search.d.ts +3 -0
- package/dist/static/compile-static-url.d.ts +3 -0
- package/dist/static/contracts.d.ts +81 -0
- package/dist/static/create-static-search-schema.d.ts +3 -0
- package/dist/static/normalize-static-search-default.d.ts +2 -0
- package/dist/static/static-search-field-kind.d.ts +4 -0
- package/dist/static.d.ts +7 -0
- package/dist/static.js +55 -0
- package/dist/static.js.map +1 -0
- package/dist/url/assert-path-match-failure.d.ts +2 -0
- package/dist/url/build-compiled-url.d.ts +3 -0
- package/dist/url/build-url.d.ts +3 -0
- package/dist/url/coerce-path-param.d.ts +2 -0
- package/dist/url/compile-path.d.ts +2 -0
- package/dist/url/compile-runtime-url-descriptor.d.ts +2 -0
- package/dist/url/compile-url-descriptor.d.ts +13 -0
- package/dist/url/contracts.d.ts +91 -0
- package/dist/url/create-unsupported-url-method.d.ts +1 -0
- package/dist/url/create-url-contract.d.ts +3 -0
- package/dist/url/create-url.d.ts +2 -0
- package/dist/url/filter-compiled-url-search.d.ts +4 -0
- package/dist/url/format-parsed-url.d.ts +2 -0
- package/dist/url/match-url.d.ts +3 -0
- package/dist/url/normalize-compiled-url.d.ts +3 -0
- package/dist/url/normalize-path-build-params.d.ts +1 -0
- package/dist/url/normalize-url.d.ts +3 -0
- package/dist/url/parse-compiled-url.d.ts +3 -0
- package/dist/url/parse-path-pattern.d.ts +2 -0
- package/dist/url/parse-request.d.ts +2 -0
- package/dist/url/parse-url.d.ts +6 -0
- package/dist/url/patch-compiled-url-search.d.ts +3 -0
- package/dist/url/path-constraints.d.ts +5 -0
- package/dist/url/path-param-kind.d.ts +3 -0
- package/dist/url/path-segment.d.ts +11 -0
- package/dist/url/register-urlkit-path-constraints.d.ts +1 -0
- package/dist/url/replace-compiled-url-search.d.ts +3 -0
- package/dist/url/resolve-url-unknown-search.d.ts +3 -0
- package/dist/url/url-state-brand.d.ts +7 -0
- package/package.json +73 -0
|
@@ -0,0 +1,1318 @@
|
|
|
1
|
+
import compile from '@cookbook/pathkit/compile';
|
|
2
|
+
import match from '@cookbook/pathkit/match';
|
|
3
|
+
import { hasConstraint, getConstraint, registerConstraint, createConstraint } from '@cookbook/pathkit/constraints';
|
|
4
|
+
|
|
5
|
+
const defaultMessages = {
|
|
6
|
+
'invalid-url': 'Invalid URL.',
|
|
7
|
+
'path-mismatch': 'Pathname does not match the URL pattern.',
|
|
8
|
+
'missing-param': 'Required path parameter is missing.',
|
|
9
|
+
'invalid-param': 'Path parameter is invalid.',
|
|
10
|
+
'missing-search': 'Required search parameter is missing.',
|
|
11
|
+
'invalid-search': 'Search parameter is invalid.',
|
|
12
|
+
'invalid-hash': 'Hash fragment is invalid.',
|
|
13
|
+
'invalid-descriptor': 'URL descriptor is invalid.',
|
|
14
|
+
};
|
|
15
|
+
function resolveMessage(code, messageOrOptions) {
|
|
16
|
+
if (typeof messageOrOptions === 'string') {
|
|
17
|
+
return messageOrOptions;
|
|
18
|
+
}
|
|
19
|
+
return defaultMessages[code];
|
|
20
|
+
}
|
|
21
|
+
function resolveOptions(messageOrOptions, options) {
|
|
22
|
+
if (typeof messageOrOptions === 'string') {
|
|
23
|
+
return options;
|
|
24
|
+
}
|
|
25
|
+
return messageOrOptions;
|
|
26
|
+
}
|
|
27
|
+
function hasCause(options) {
|
|
28
|
+
return Boolean(options && 'cause' in options);
|
|
29
|
+
}
|
|
30
|
+
class UrlKitError extends Error {
|
|
31
|
+
code;
|
|
32
|
+
path;
|
|
33
|
+
cause;
|
|
34
|
+
constructor(code, messageOrOptions, options) {
|
|
35
|
+
const resolvedOptions = resolveOptions(messageOrOptions, options);
|
|
36
|
+
super(resolveMessage(code, messageOrOptions), hasCause(resolvedOptions) ? { cause: resolvedOptions.cause } : undefined);
|
|
37
|
+
this.name = 'UrlKitError';
|
|
38
|
+
this.code = code;
|
|
39
|
+
if (resolvedOptions?.path) {
|
|
40
|
+
this.path = [...resolvedOptions.path];
|
|
41
|
+
}
|
|
42
|
+
if (hasCause(resolvedOptions)) {
|
|
43
|
+
this.cause = resolvedOptions.cause;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function registerPathConstraint(name, constraint, options = {}) {
|
|
49
|
+
assertPathConstraintName(name);
|
|
50
|
+
assertPathConstraint(name, constraint);
|
|
51
|
+
const existing = getConstraint(name);
|
|
52
|
+
if (existing && existing !== constraint && !options.overwrite) {
|
|
53
|
+
throw new UrlKitError('invalid-descriptor', `Path constraint "${name}" is already registered.`, {
|
|
54
|
+
path: ['pathConstraints', name],
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (existing === constraint && !options.overwrite) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
registerConstraint(name, constraint);
|
|
61
|
+
}
|
|
62
|
+
function registerPathConstraints(constraints, options = {}) {
|
|
63
|
+
assertPathConstraintMap(constraints);
|
|
64
|
+
for (const [name, constraint] of Object.entries(constraints)) {
|
|
65
|
+
registerPathConstraint(name, constraint, options);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function hasPathConstraint(name) {
|
|
69
|
+
return hasConstraint(name);
|
|
70
|
+
}
|
|
71
|
+
function assertPathConstraintName(name) {
|
|
72
|
+
if (typeof name === 'string' && name.trim()) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
throw new UrlKitError('invalid-descriptor', 'Path constraint name must be a non-empty string.', {
|
|
76
|
+
path: ['pathConstraints'],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function assertPathConstraintMap(input) {
|
|
80
|
+
if (isRecord$2(input)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
throw new UrlKitError('invalid-descriptor', 'Path constraints must be an object.', {
|
|
84
|
+
path: ['pathConstraints'],
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function assertPathConstraint(name, constraint) {
|
|
88
|
+
if (typeof constraint === 'function' &&
|
|
89
|
+
typeof constraint.verify === 'function' &&
|
|
90
|
+
typeof constraint.toRegExp === 'function') {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
throw new UrlKitError('invalid-descriptor', `Path constraint "${name}" must be created with createConstraint().`, {
|
|
94
|
+
path: ['pathConstraints', name],
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function isRecord$2(input) {
|
|
98
|
+
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const runtimeSchemaSymbol = Symbol('urlkit.runtime-schema');
|
|
102
|
+
|
|
103
|
+
function getRuntimeSchemaInternals(schema) {
|
|
104
|
+
const candidate = schema;
|
|
105
|
+
const internals = candidate[runtimeSchemaSymbol];
|
|
106
|
+
if (!internals) {
|
|
107
|
+
throw new UrlKitError('invalid-descriptor', 'Expected a runtime schema builder.');
|
|
108
|
+
}
|
|
109
|
+
return internals;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function compileRuntimeSchema(schema, options = {}) {
|
|
113
|
+
const internals = getRuntimeSchemaInternals(schema);
|
|
114
|
+
const descriptor = internals.toDescriptor();
|
|
115
|
+
if (internals.validateDescriptor) {
|
|
116
|
+
internals.validateDescriptor({
|
|
117
|
+
kind: descriptor.kind,
|
|
118
|
+
path: [...(options.path ?? [])],
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (descriptor.presence === 'defaulted' && internals.validateDefault) {
|
|
122
|
+
internals.validateDefault(descriptor.defaultValue, {
|
|
123
|
+
kind: descriptor.kind,
|
|
124
|
+
path: [...(options.path ?? [])],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return descriptor;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createRuntimeSchemaBuilder(definition) {
|
|
131
|
+
return createBuilder({
|
|
132
|
+
kind: definition.kind,
|
|
133
|
+
presence: 'required',
|
|
134
|
+
options: copyOptions(definition.options),
|
|
135
|
+
...(definition.codec ? { codec: definition.codec } : {}),
|
|
136
|
+
...(definition.validateDefault ? { validateDefault: definition.validateDefault } : {}),
|
|
137
|
+
...(definition.validateDescriptor ? { validateDescriptor: definition.validateDescriptor } : {}),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function createBuilder(state) {
|
|
141
|
+
const internals = Object.freeze({
|
|
142
|
+
kind: state.kind,
|
|
143
|
+
presence: state.presence,
|
|
144
|
+
options: state.options,
|
|
145
|
+
...(state.defaultValue !== undefined ? { defaultValue: state.defaultValue } : {}),
|
|
146
|
+
...(state.codec ? { codec: state.codec } : {}),
|
|
147
|
+
...(state.validateDefault ? { validateDefault: state.validateDefault } : {}),
|
|
148
|
+
...(state.validateDescriptor ? { validateDescriptor: state.validateDescriptor } : {}),
|
|
149
|
+
toDescriptor: () => createDescriptor(state),
|
|
150
|
+
});
|
|
151
|
+
const builder = {
|
|
152
|
+
[runtimeSchemaSymbol]: internals,
|
|
153
|
+
optional: () => {
|
|
154
|
+
const nextState = {
|
|
155
|
+
kind: state.kind,
|
|
156
|
+
presence: 'optional',
|
|
157
|
+
options: state.options,
|
|
158
|
+
...(state.codec ? { codec: state.codec } : {}),
|
|
159
|
+
...(state.validateDefault
|
|
160
|
+
? { validateDefault: state.validateDefault }
|
|
161
|
+
: {}),
|
|
162
|
+
...(state.validateDescriptor ? { validateDescriptor: state.validateDescriptor } : {}),
|
|
163
|
+
};
|
|
164
|
+
return createBuilder(nextState);
|
|
165
|
+
},
|
|
166
|
+
required: () => {
|
|
167
|
+
const nextState = {
|
|
168
|
+
kind: state.kind,
|
|
169
|
+
presence: 'required',
|
|
170
|
+
options: state.options,
|
|
171
|
+
...(state.codec ? { codec: state.codec } : {}),
|
|
172
|
+
...(state.validateDefault
|
|
173
|
+
? {
|
|
174
|
+
validateDefault: state.validateDefault,
|
|
175
|
+
}
|
|
176
|
+
: {}),
|
|
177
|
+
...(state.validateDescriptor ? { validateDescriptor: state.validateDescriptor } : {}),
|
|
178
|
+
};
|
|
179
|
+
return createBuilder(nextState);
|
|
180
|
+
},
|
|
181
|
+
default: (value) => {
|
|
182
|
+
const nextState = {
|
|
183
|
+
kind: state.kind,
|
|
184
|
+
presence: 'defaulted',
|
|
185
|
+
options: state.options,
|
|
186
|
+
defaultValue: value,
|
|
187
|
+
...(state.codec ? { codec: state.codec } : {}),
|
|
188
|
+
...(state.validateDefault
|
|
189
|
+
? {
|
|
190
|
+
validateDefault: state.validateDefault,
|
|
191
|
+
}
|
|
192
|
+
: {}),
|
|
193
|
+
...(state.validateDescriptor ? { validateDescriptor: state.validateDescriptor } : {}),
|
|
194
|
+
};
|
|
195
|
+
return createBuilder(nextState);
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
return Object.freeze(builder);
|
|
199
|
+
}
|
|
200
|
+
function createDescriptor(state) {
|
|
201
|
+
const base = {
|
|
202
|
+
kind: state.kind,
|
|
203
|
+
presence: state.presence,
|
|
204
|
+
options: copyOptions(state.options),
|
|
205
|
+
};
|
|
206
|
+
if (state.presence === 'defaulted') {
|
|
207
|
+
return Object.freeze({
|
|
208
|
+
...base,
|
|
209
|
+
presence: 'defaulted',
|
|
210
|
+
defaultValue: state.defaultValue,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return Object.freeze(base);
|
|
214
|
+
}
|
|
215
|
+
function copyOptions(options) {
|
|
216
|
+
return Object.freeze({ ...(options ?? {}) });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function createSchemaValueError(code, message, path, cause) {
|
|
220
|
+
return new UrlKitError(code, message, {
|
|
221
|
+
path,
|
|
222
|
+
...(cause !== undefined ? { cause } : {}),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function compileRuntimeSchemaValue(schema, options = {}) {
|
|
227
|
+
const descriptor = compileRuntimeSchema(schema, options);
|
|
228
|
+
const internals = getRuntimeSchemaInternals(schema);
|
|
229
|
+
return Object.freeze({
|
|
230
|
+
descriptor: descriptor,
|
|
231
|
+
...(internals.codec ? { codec: internals.codec } : {}),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function handleRuntimeSchemaAbsence(descriptor, input, options = {}) {
|
|
236
|
+
if (input !== undefined && input !== null) {
|
|
237
|
+
return { handled: false };
|
|
238
|
+
}
|
|
239
|
+
if (descriptor.presence === 'optional') {
|
|
240
|
+
return { handled: true, value: undefined };
|
|
241
|
+
}
|
|
242
|
+
if (descriptor.presence === 'defaulted') {
|
|
243
|
+
return { handled: true, value: descriptor.defaultValue };
|
|
244
|
+
}
|
|
245
|
+
const path = [...(options.path ?? [])];
|
|
246
|
+
if (input === null) {
|
|
247
|
+
throw new UrlKitError(options.errorCode ?? 'invalid-search', 'Required value cannot be null.', {
|
|
248
|
+
path,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
throw new UrlKitError(options.missingCode ?? 'missing-search', 'Required value is missing.', {
|
|
252
|
+
path,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function createRuntimeSchemaValueContext(kind, options = {}) {
|
|
257
|
+
return {
|
|
258
|
+
kind,
|
|
259
|
+
path: [...(options.path ?? [])],
|
|
260
|
+
errorCode: options.errorCode ?? 'invalid-search',
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function normalizeCompiledRuntimeSchemaValue(compiled, input, options = {}) {
|
|
265
|
+
const absence = handleRuntimeSchemaAbsence(compiled.descriptor, input, options);
|
|
266
|
+
if (absence.handled) {
|
|
267
|
+
return absence.value;
|
|
268
|
+
}
|
|
269
|
+
if (!compiled.codec) {
|
|
270
|
+
throw new UrlKitError('invalid-descriptor', `Runtime schema "${compiled.descriptor.kind}" does not define a normalizer.`, options.path ? { path: options.path } : undefined);
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
return compiled.codec.normalize(input, createRuntimeSchemaValueContext(compiled.descriptor.kind, options));
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (error instanceof UrlKitError) {
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
throw createSchemaValueError(options.errorCode ?? 'invalid-search', 'Schema value is invalid.', options.path ?? [], error);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function normalizeRuntimeSchemaValue(schema, input, options = {}) {
|
|
284
|
+
return normalizeCompiledRuntimeSchemaValue(compileRuntimeSchemaValue(schema, options), input, options);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const booleanCodec = {
|
|
288
|
+
parse(input, context) {
|
|
289
|
+
if (input === 'true') {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
if (input === 'false') {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
throw createSchemaValueError(context.errorCode, 'Expected "true" or "false".', context.path);
|
|
296
|
+
},
|
|
297
|
+
normalize(input, context) {
|
|
298
|
+
return validateBoolean(input, context);
|
|
299
|
+
},
|
|
300
|
+
serialize(input, context) {
|
|
301
|
+
return validateBoolean(input, context) ? 'true' : 'false';
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
function boolean() {
|
|
305
|
+
return createRuntimeSchemaBuilder({
|
|
306
|
+
kind: 'boolean',
|
|
307
|
+
codec: booleanCodec,
|
|
308
|
+
validateDefault(value, context) {
|
|
309
|
+
validateBooleanDefault(value, context);
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
function validateBoolean(input, context) {
|
|
314
|
+
if (typeof input === 'boolean') {
|
|
315
|
+
return input;
|
|
316
|
+
}
|
|
317
|
+
throw createSchemaValueError(context.errorCode, 'Expected a boolean value.', context.path);
|
|
318
|
+
}
|
|
319
|
+
function validateBooleanDefault(value, context) {
|
|
320
|
+
if (typeof value === 'boolean') {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
throw new UrlKitError('invalid-descriptor', 'Boolean schema default must be a boolean.', {
|
|
324
|
+
path: context.path,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const datePattern = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
329
|
+
function parseDate(input, options = {}) {
|
|
330
|
+
const match = datePattern.exec(input);
|
|
331
|
+
if (!match) {
|
|
332
|
+
throw createInvalidDateError('Date value must use YYYY-MM-DD format.', options);
|
|
333
|
+
}
|
|
334
|
+
const [, yearText, monthText, dayText] = match;
|
|
335
|
+
const year = Number(yearText);
|
|
336
|
+
const month = Number(monthText);
|
|
337
|
+
const day = Number(dayText);
|
|
338
|
+
if (month < 1 || month > 12 || day < 1 || day > 31) {
|
|
339
|
+
throw createInvalidDateError('Date value must be a valid calendar date.', options);
|
|
340
|
+
}
|
|
341
|
+
const value = new Date(Date.UTC(0, month - 1, day));
|
|
342
|
+
value.setUTCFullYear(year);
|
|
343
|
+
if (value.getUTCFullYear() !== year ||
|
|
344
|
+
value.getUTCMonth() !== month - 1 ||
|
|
345
|
+
value.getUTCDate() !== day ||
|
|
346
|
+
value.getUTCHours() !== 0 ||
|
|
347
|
+
value.getUTCMinutes() !== 0 ||
|
|
348
|
+
value.getUTCSeconds() !== 0 ||
|
|
349
|
+
value.getUTCMilliseconds() !== 0) {
|
|
350
|
+
throw createInvalidDateError('Date value must be a valid calendar date.', options);
|
|
351
|
+
}
|
|
352
|
+
return value;
|
|
353
|
+
}
|
|
354
|
+
function createInvalidDateError(message, options) {
|
|
355
|
+
return new UrlKitError(options.code ?? 'invalid-search', message, {
|
|
356
|
+
path: [...(options.path ?? [])],
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function parseCustomDate(input, format, options = {}) {
|
|
361
|
+
let value;
|
|
362
|
+
try {
|
|
363
|
+
value = format.parse(input);
|
|
364
|
+
}
|
|
365
|
+
catch (cause) {
|
|
366
|
+
throw createInvalidCustomDateError$1('Custom date format parser failed.', options, cause);
|
|
367
|
+
}
|
|
368
|
+
if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
|
|
369
|
+
throw createInvalidCustomDateError$1('Custom date format parser must return a valid Date.', options);
|
|
370
|
+
}
|
|
371
|
+
return value;
|
|
372
|
+
}
|
|
373
|
+
function createInvalidCustomDateError$1(message, options, cause) {
|
|
374
|
+
return new UrlKitError(options.code ?? 'invalid-search', message, {
|
|
375
|
+
path: [...(options.path ?? [])],
|
|
376
|
+
...(cause !== undefined ? { cause } : {}),
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const dateTimePattern = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})Z$/;
|
|
381
|
+
function parseDateTime(input, options = {}) {
|
|
382
|
+
const match = dateTimePattern.exec(input);
|
|
383
|
+
if (!match) {
|
|
384
|
+
throw createInvalidDateTimeError$1('Date-time value must use UTC YYYY-MM-DDTHH:mm:ss.sssZ format.', options);
|
|
385
|
+
}
|
|
386
|
+
const [, yearText, monthText, dayText, hourText, minuteText, secondText, millisecondText] = match;
|
|
387
|
+
const year = Number(yearText);
|
|
388
|
+
const month = Number(monthText);
|
|
389
|
+
const day = Number(dayText);
|
|
390
|
+
const hour = Number(hourText);
|
|
391
|
+
const minute = Number(minuteText);
|
|
392
|
+
const second = Number(secondText);
|
|
393
|
+
const millisecond = Number(millisecondText);
|
|
394
|
+
if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59) {
|
|
395
|
+
throw createInvalidDateTimeError$1('Date-time value must be a valid UTC calendar instant.', options);
|
|
396
|
+
}
|
|
397
|
+
const value = new Date(Date.UTC(0, month - 1, day, hour, minute, second, millisecond));
|
|
398
|
+
value.setUTCFullYear(year);
|
|
399
|
+
if (value.getUTCFullYear() !== year ||
|
|
400
|
+
value.getUTCMonth() !== month - 1 ||
|
|
401
|
+
value.getUTCDate() !== day ||
|
|
402
|
+
value.getUTCHours() !== hour ||
|
|
403
|
+
value.getUTCMinutes() !== minute ||
|
|
404
|
+
value.getUTCSeconds() !== second ||
|
|
405
|
+
value.getUTCMilliseconds() !== millisecond) {
|
|
406
|
+
throw createInvalidDateTimeError$1('Date-time value must be a valid UTC calendar instant.', options);
|
|
407
|
+
}
|
|
408
|
+
return value;
|
|
409
|
+
}
|
|
410
|
+
function createInvalidDateTimeError$1(message, options) {
|
|
411
|
+
return new UrlKitError(options.code ?? 'invalid-search', message, {
|
|
412
|
+
path: [...(options.path ?? [])],
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const unixIntegerPattern$1 = /^-?\d+$/;
|
|
417
|
+
function parseUnixMs(input, options = {}) {
|
|
418
|
+
const milliseconds = parseUnixInteger$1(input, 'Unix milliseconds value must be a finite integer.', options);
|
|
419
|
+
const value = new Date(milliseconds);
|
|
420
|
+
if (!Number.isFinite(value.getTime())) {
|
|
421
|
+
throw createInvalidUnixMsError$1('Unix milliseconds value must be a valid Date instant.', options);
|
|
422
|
+
}
|
|
423
|
+
return value;
|
|
424
|
+
}
|
|
425
|
+
function parseUnixInteger$1(input, message, options) {
|
|
426
|
+
if (!unixIntegerPattern$1.test(input)) {
|
|
427
|
+
throw createInvalidUnixMsError$1(message, options);
|
|
428
|
+
}
|
|
429
|
+
const value = Number(input);
|
|
430
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) {
|
|
431
|
+
throw createInvalidUnixMsError$1(message, options);
|
|
432
|
+
}
|
|
433
|
+
return value;
|
|
434
|
+
}
|
|
435
|
+
function createInvalidUnixMsError$1(message, options) {
|
|
436
|
+
return new UrlKitError(options.code ?? 'invalid-search', message, {
|
|
437
|
+
path: [...(options.path ?? [])],
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const unixIntegerPattern = /^-?\d+$/;
|
|
442
|
+
function parseUnixSeconds(input, options = {}) {
|
|
443
|
+
const seconds = parseUnixInteger(input, 'Unix seconds value must be a finite integer.', options);
|
|
444
|
+
const milliseconds = seconds * 1000;
|
|
445
|
+
const value = new Date(milliseconds);
|
|
446
|
+
if (!Number.isFinite(milliseconds) || !Number.isFinite(value.getTime())) {
|
|
447
|
+
throw createInvalidUnixSecondsError$1('Unix seconds value must be a valid Date instant.', options);
|
|
448
|
+
}
|
|
449
|
+
return value;
|
|
450
|
+
}
|
|
451
|
+
function parseUnixInteger(input, message, options) {
|
|
452
|
+
if (!unixIntegerPattern.test(input)) {
|
|
453
|
+
throw createInvalidUnixSecondsError$1(message, options);
|
|
454
|
+
}
|
|
455
|
+
const value = Number(input);
|
|
456
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) {
|
|
457
|
+
throw createInvalidUnixSecondsError$1(message, options);
|
|
458
|
+
}
|
|
459
|
+
return value;
|
|
460
|
+
}
|
|
461
|
+
function createInvalidUnixSecondsError$1(message, options) {
|
|
462
|
+
return new UrlKitError(options.code ?? 'invalid-search', message, {
|
|
463
|
+
path: [...(options.path ?? [])],
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function serializeDate(input, options = {}) {
|
|
468
|
+
if (!(input instanceof Date) || !Number.isFinite(input.getTime())) {
|
|
469
|
+
throw new UrlKitError(options.code ?? 'invalid-search', 'Date value must be a valid Date.', {
|
|
470
|
+
path: [...(options.path ?? [])],
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
const year = String(input.getUTCFullYear()).padStart(4, '0');
|
|
474
|
+
const month = String(input.getUTCMonth() + 1).padStart(2, '0');
|
|
475
|
+
const day = String(input.getUTCDate()).padStart(2, '0');
|
|
476
|
+
return `${year}-${month}-${day}`;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function serializeCustomDate(input, format, options = {}) {
|
|
480
|
+
if (!(input instanceof Date) || !Number.isFinite(input.getTime())) {
|
|
481
|
+
throw createInvalidCustomDateError('Custom date format value must be a valid Date.', options);
|
|
482
|
+
}
|
|
483
|
+
let value;
|
|
484
|
+
try {
|
|
485
|
+
value = format.serialize(input);
|
|
486
|
+
}
|
|
487
|
+
catch (cause) {
|
|
488
|
+
throw createInvalidCustomDateError('Custom date format serializer failed.', options, cause);
|
|
489
|
+
}
|
|
490
|
+
if (typeof value !== 'string' || value === '') {
|
|
491
|
+
throw createInvalidCustomDateError('Custom date format serializer must return a non-empty string.', options);
|
|
492
|
+
}
|
|
493
|
+
return value;
|
|
494
|
+
}
|
|
495
|
+
function createInvalidCustomDateError(message, options, cause) {
|
|
496
|
+
return new UrlKitError(options.code ?? 'invalid-search', message, {
|
|
497
|
+
path: [...(options.path ?? [])],
|
|
498
|
+
...(cause !== undefined ? { cause } : {}),
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const serializedDateTimePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
503
|
+
function serializeDateTime(input, options = {}) {
|
|
504
|
+
if (!(input instanceof Date) || !Number.isFinite(input.getTime())) {
|
|
505
|
+
throw createInvalidDateTimeError('Date-time value must be a valid Date.', options);
|
|
506
|
+
}
|
|
507
|
+
const value = input.toISOString();
|
|
508
|
+
if (!serializedDateTimePattern.test(value)) {
|
|
509
|
+
throw createInvalidDateTimeError('Date-time value must serialize to YYYY-MM-DDTHH:mm:ss.sssZ.', options);
|
|
510
|
+
}
|
|
511
|
+
return value;
|
|
512
|
+
}
|
|
513
|
+
function createInvalidDateTimeError(message, options) {
|
|
514
|
+
return new UrlKitError(options.code ?? 'invalid-search', message, {
|
|
515
|
+
path: [...(options.path ?? [])],
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function serializeUnixMs(input, options = {}) {
|
|
520
|
+
if (!(input instanceof Date) || !Number.isFinite(input.getTime())) {
|
|
521
|
+
throw createInvalidUnixMsError('Unix milliseconds value must be a valid Date.', options);
|
|
522
|
+
}
|
|
523
|
+
const milliseconds = input.getTime();
|
|
524
|
+
if (!Number.isFinite(milliseconds) || !Number.isInteger(milliseconds)) {
|
|
525
|
+
throw createInvalidUnixMsError('Unix milliseconds value must serialize to finite integer milliseconds.', options);
|
|
526
|
+
}
|
|
527
|
+
return String(milliseconds);
|
|
528
|
+
}
|
|
529
|
+
function createInvalidUnixMsError(message, options) {
|
|
530
|
+
return new UrlKitError(options.code ?? 'invalid-search', message, {
|
|
531
|
+
path: [...(options.path ?? [])],
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function serializeUnixSeconds(input, options = {}) {
|
|
536
|
+
if (!(input instanceof Date) || !Number.isFinite(input.getTime())) {
|
|
537
|
+
throw createInvalidUnixSecondsError('Unix seconds value must be a valid Date.', options);
|
|
538
|
+
}
|
|
539
|
+
const milliseconds = input.getTime();
|
|
540
|
+
if (!Number.isInteger(milliseconds) || milliseconds % 1000 !== 0) {
|
|
541
|
+
throw createInvalidUnixSecondsError('Unix seconds value must serialize to finite integer seconds.', options);
|
|
542
|
+
}
|
|
543
|
+
const seconds = milliseconds / 1000;
|
|
544
|
+
if (!Number.isFinite(seconds) || !Number.isInteger(seconds)) {
|
|
545
|
+
throw createInvalidUnixSecondsError('Unix seconds value must serialize to finite integer seconds.', options);
|
|
546
|
+
}
|
|
547
|
+
return String(seconds);
|
|
548
|
+
}
|
|
549
|
+
function createInvalidUnixSecondsError(message, options) {
|
|
550
|
+
return new UrlKitError(options.code ?? 'invalid-search', message, {
|
|
551
|
+
path: [...(options.path ?? [])],
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const dateOnlyCodec = {
|
|
556
|
+
parse(input, context) {
|
|
557
|
+
return parseDate(input, { code: context.errorCode, path: context.path });
|
|
558
|
+
},
|
|
559
|
+
normalize(input, context) {
|
|
560
|
+
return validateDate(input, context);
|
|
561
|
+
},
|
|
562
|
+
serialize(input, context) {
|
|
563
|
+
return serializeDate(input, { code: context.errorCode, path: context.path });
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
const dateTimeCodec = {
|
|
567
|
+
parse(input, context) {
|
|
568
|
+
return parseDateTime(input, { code: context.errorCode, path: context.path });
|
|
569
|
+
},
|
|
570
|
+
normalize(input, context) {
|
|
571
|
+
return validateDate(input, context);
|
|
572
|
+
},
|
|
573
|
+
serialize(input, context) {
|
|
574
|
+
return serializeDateTime(input, { code: context.errorCode, path: context.path });
|
|
575
|
+
},
|
|
576
|
+
};
|
|
577
|
+
const unixSecondsCodec = {
|
|
578
|
+
parse(input, context) {
|
|
579
|
+
return parseUnixSeconds(input, { code: context.errorCode, path: context.path });
|
|
580
|
+
},
|
|
581
|
+
normalize(input, context) {
|
|
582
|
+
return validateDate(input, context);
|
|
583
|
+
},
|
|
584
|
+
serialize(input, context) {
|
|
585
|
+
return serializeUnixSeconds(input, { code: context.errorCode, path: context.path });
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
const unixMsCodec = {
|
|
589
|
+
parse(input, context) {
|
|
590
|
+
return parseUnixMs(input, { code: context.errorCode, path: context.path });
|
|
591
|
+
},
|
|
592
|
+
normalize(input, context) {
|
|
593
|
+
return validateDate(input, context);
|
|
594
|
+
},
|
|
595
|
+
serialize(input, context) {
|
|
596
|
+
return serializeUnixMs(input, { code: context.errorCode, path: context.path });
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
function date(options = {}) {
|
|
600
|
+
const format = resolveDateFormat(options);
|
|
601
|
+
return createRuntimeSchemaBuilder({
|
|
602
|
+
kind: 'date',
|
|
603
|
+
options: { format },
|
|
604
|
+
codec: getDateCodec(format),
|
|
605
|
+
validateDefault(value, context) {
|
|
606
|
+
validateDateDefault(value, context);
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
function getDateCodec(format) {
|
|
611
|
+
if (isDateFormatCodec(format)) {
|
|
612
|
+
return createCustomDateCodec(format);
|
|
613
|
+
}
|
|
614
|
+
if (format === 'date-time') {
|
|
615
|
+
return dateTimeCodec;
|
|
616
|
+
}
|
|
617
|
+
if (format === 'unix-seconds') {
|
|
618
|
+
return unixSecondsCodec;
|
|
619
|
+
}
|
|
620
|
+
if (format === 'unix-ms') {
|
|
621
|
+
return unixMsCodec;
|
|
622
|
+
}
|
|
623
|
+
return dateOnlyCodec;
|
|
624
|
+
}
|
|
625
|
+
function createCustomDateCodec(format) {
|
|
626
|
+
return {
|
|
627
|
+
parse(input, context) {
|
|
628
|
+
return parseCustomDate(input, format, { code: context.errorCode, path: context.path });
|
|
629
|
+
},
|
|
630
|
+
normalize(input, context) {
|
|
631
|
+
return validateDate(input, context);
|
|
632
|
+
},
|
|
633
|
+
serialize(input, context) {
|
|
634
|
+
return serializeCustomDate(input, format, { code: context.errorCode, path: context.path });
|
|
635
|
+
},
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function resolveDateFormat(options) {
|
|
639
|
+
if (!isDateOptions(options)) {
|
|
640
|
+
throw new UrlKitError('invalid-descriptor', 'Date options must be an object.');
|
|
641
|
+
}
|
|
642
|
+
const format = options.format ?? 'date';
|
|
643
|
+
if (isBuiltInDateFormat(format) || isDateFormatCodec(format)) {
|
|
644
|
+
return format;
|
|
645
|
+
}
|
|
646
|
+
throw new UrlKitError('invalid-descriptor', 'Date format must be a supported built-in format or an explicit codec.');
|
|
647
|
+
}
|
|
648
|
+
function isDateOptions(input) {
|
|
649
|
+
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
650
|
+
}
|
|
651
|
+
function isBuiltInDateFormat(input) {
|
|
652
|
+
return (input === 'date' || input === 'date-time' || input === 'unix-seconds' || input === 'unix-ms');
|
|
653
|
+
}
|
|
654
|
+
function isDateFormatCodec(input) {
|
|
655
|
+
return (typeof input === 'object' &&
|
|
656
|
+
input !== null &&
|
|
657
|
+
typeof input.parse === 'function' &&
|
|
658
|
+
typeof input.serialize === 'function');
|
|
659
|
+
}
|
|
660
|
+
function validateDate(input, context) {
|
|
661
|
+
if (input instanceof Date && Number.isFinite(input.getTime())) {
|
|
662
|
+
return input;
|
|
663
|
+
}
|
|
664
|
+
throw createSchemaValueError(context.errorCode, 'Expected a valid Date value.', context.path);
|
|
665
|
+
}
|
|
666
|
+
function validateDateDefault(value, context) {
|
|
667
|
+
if (value instanceof Date && Number.isFinite(value.getTime())) {
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
throw new UrlKitError('invalid-descriptor', 'Date schema default must be a valid Date.', {
|
|
671
|
+
path: context.path,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function enumOf(values) {
|
|
676
|
+
const normalizedValues = normalizeEnumValues$1(values);
|
|
677
|
+
const allowedValues = new Set(normalizedValues);
|
|
678
|
+
const expectedMessage = createExpectedEnumMessage(normalizedValues);
|
|
679
|
+
const enumCodec = {
|
|
680
|
+
parse(input, context) {
|
|
681
|
+
return validateEnumValue(input, allowedValues, expectedMessage, context);
|
|
682
|
+
},
|
|
683
|
+
normalize(input, context) {
|
|
684
|
+
return validateEnumValue(input, allowedValues, expectedMessage, context);
|
|
685
|
+
},
|
|
686
|
+
serialize(input, context) {
|
|
687
|
+
return validateEnumValue(input, allowedValues, expectedMessage, context);
|
|
688
|
+
},
|
|
689
|
+
};
|
|
690
|
+
return createRuntimeSchemaBuilder({
|
|
691
|
+
kind: 'enum',
|
|
692
|
+
options: {
|
|
693
|
+
values: normalizedValues,
|
|
694
|
+
},
|
|
695
|
+
codec: enumCodec,
|
|
696
|
+
validateDefault(value, context) {
|
|
697
|
+
validateEnumDefault(value, allowedValues, expectedMessage, context);
|
|
698
|
+
},
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
function normalizeEnumValues$1(values) {
|
|
702
|
+
if (!Array.isArray(values)) {
|
|
703
|
+
throw new UrlKitError('invalid-descriptor', 'Enum schema values must be an array.');
|
|
704
|
+
}
|
|
705
|
+
if (!values.length) {
|
|
706
|
+
throw new UrlKitError('invalid-descriptor', 'Enum schema values must not be empty.');
|
|
707
|
+
}
|
|
708
|
+
for (const value of values) {
|
|
709
|
+
if (typeof value !== 'string') {
|
|
710
|
+
throw new UrlKitError('invalid-descriptor', 'Enum schema values must be strings.');
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return Object.freeze([...values]);
|
|
714
|
+
}
|
|
715
|
+
function validateEnumValue(input, allowedValues, message, context) {
|
|
716
|
+
if (typeof input === 'string' && allowedValues.has(input)) {
|
|
717
|
+
return input;
|
|
718
|
+
}
|
|
719
|
+
throw createSchemaValueError(context.errorCode, message, context.path);
|
|
720
|
+
}
|
|
721
|
+
function validateEnumDefault(value, allowedValues, message, context) {
|
|
722
|
+
if (typeof value === 'string' && allowedValues.has(value)) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
throw new UrlKitError('invalid-descriptor', `Enum schema default is invalid. ${message}`, {
|
|
726
|
+
path: context.path,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
function createExpectedEnumMessage(values) {
|
|
730
|
+
return `Expected one of: ${values.join(', ')}.`;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const intCodec = {
|
|
734
|
+
parse(input, context) {
|
|
735
|
+
return parseFiniteInteger(input, context, 'Expected a finite integer value.');
|
|
736
|
+
},
|
|
737
|
+
normalize(input, context) {
|
|
738
|
+
return validateFiniteInteger(input, context, 'Expected a finite integer value.');
|
|
739
|
+
},
|
|
740
|
+
serialize(input, context) {
|
|
741
|
+
return String(validateFiniteInteger(input, context, 'Expected a finite integer value.'));
|
|
742
|
+
},
|
|
743
|
+
};
|
|
744
|
+
function int() {
|
|
745
|
+
return createRuntimeSchemaBuilder({
|
|
746
|
+
kind: 'int',
|
|
747
|
+
codec: intCodec,
|
|
748
|
+
validateDefault(value, context) {
|
|
749
|
+
validateIntDefault(value, context);
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
function parseFiniteInteger(input, context, message) {
|
|
754
|
+
if (!input.trim()) {
|
|
755
|
+
throw createSchemaValueError(context.errorCode, message, context.path);
|
|
756
|
+
}
|
|
757
|
+
return validateFiniteInteger(Number(input), context, message);
|
|
758
|
+
}
|
|
759
|
+
function validateFiniteInteger(input, context, message) {
|
|
760
|
+
if (typeof input === 'number' && Number.isInteger(input)) {
|
|
761
|
+
return input;
|
|
762
|
+
}
|
|
763
|
+
throw createSchemaValueError(context.errorCode, message, context.path);
|
|
764
|
+
}
|
|
765
|
+
function validateIntDefault(value, context) {
|
|
766
|
+
if (typeof value === 'number' && Number.isInteger(value)) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
throw new UrlKitError('invalid-descriptor', 'Integer schema default must be a finite integer.', {
|
|
770
|
+
path: context.path,
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const numberCodec = {
|
|
775
|
+
parse(input, context) {
|
|
776
|
+
return parseFiniteNumber(input, context, 'Expected a finite number value.');
|
|
777
|
+
},
|
|
778
|
+
normalize(input, context) {
|
|
779
|
+
return validateFiniteNumber(input, context, 'Expected a finite number value.');
|
|
780
|
+
},
|
|
781
|
+
serialize(input, context) {
|
|
782
|
+
return String(validateFiniteNumber(input, context, 'Expected a finite number value.'));
|
|
783
|
+
},
|
|
784
|
+
};
|
|
785
|
+
function number() {
|
|
786
|
+
return createRuntimeSchemaBuilder({
|
|
787
|
+
kind: 'number',
|
|
788
|
+
codec: numberCodec,
|
|
789
|
+
validateDefault(value, context) {
|
|
790
|
+
validateNumberDefault(value, context);
|
|
791
|
+
},
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
function parseFiniteNumber(input, context, message) {
|
|
795
|
+
if (!input.trim()) {
|
|
796
|
+
throw createSchemaValueError(context.errorCode, message, context.path);
|
|
797
|
+
}
|
|
798
|
+
return validateFiniteNumber(Number(input), context, message);
|
|
799
|
+
}
|
|
800
|
+
function validateFiniteNumber(input, context, message) {
|
|
801
|
+
if (typeof input === 'number' && Number.isFinite(input)) {
|
|
802
|
+
return input;
|
|
803
|
+
}
|
|
804
|
+
throw createSchemaValueError(context.errorCode, message, context.path);
|
|
805
|
+
}
|
|
806
|
+
function validateNumberDefault(value, context) {
|
|
807
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
throw new UrlKitError('invalid-descriptor', 'Number schema default must be a finite number.', {
|
|
811
|
+
path: context.path,
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const stringCodec = {
|
|
816
|
+
parse(input, context) {
|
|
817
|
+
return validateString(input, context, 'Expected a string value.');
|
|
818
|
+
},
|
|
819
|
+
normalize(input, context) {
|
|
820
|
+
return validateString(input, context, 'Expected a string value.');
|
|
821
|
+
},
|
|
822
|
+
serialize(input, context) {
|
|
823
|
+
return validateString(input, context, 'Expected a string value.');
|
|
824
|
+
},
|
|
825
|
+
};
|
|
826
|
+
function string() {
|
|
827
|
+
return createRuntimeSchemaBuilder({
|
|
828
|
+
kind: 'string',
|
|
829
|
+
codec: stringCodec,
|
|
830
|
+
validateDefault(value, context) {
|
|
831
|
+
validateStringDefault(value, context);
|
|
832
|
+
},
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
function validateString(input, context, message) {
|
|
836
|
+
if (typeof input === 'string') {
|
|
837
|
+
return input;
|
|
838
|
+
}
|
|
839
|
+
throw createSchemaValueError(context.errorCode, message, context.path);
|
|
840
|
+
}
|
|
841
|
+
function validateStringDefault(value, context) {
|
|
842
|
+
if (typeof value === 'string') {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
throw new UrlKitError('invalid-descriptor', 'String schema default must be a string.', {
|
|
846
|
+
path: context.path,
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function compileStaticHashDescriptor(descriptor) {
|
|
851
|
+
if (Array.isArray(descriptor)) {
|
|
852
|
+
return Object.freeze({
|
|
853
|
+
kind: 'enum',
|
|
854
|
+
presence: 'optional',
|
|
855
|
+
values: normalizeEnumValues(descriptor),
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
if (isStaticStringHashDescriptor(descriptor)) {
|
|
859
|
+
return compileStaticStringHashDescriptor(descriptor);
|
|
860
|
+
}
|
|
861
|
+
if (isStaticEnumHashDescriptor(descriptor)) {
|
|
862
|
+
return compileStaticEnumHashDescriptor(descriptor);
|
|
863
|
+
}
|
|
864
|
+
throw new UrlKitError('invalid-descriptor', 'Hash descriptor must be a string, enum, or enum shorthand descriptor.', {
|
|
865
|
+
path: ['hash'],
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
function compileStaticStringHashDescriptor(descriptor) {
|
|
869
|
+
assertOptionalFlag(descriptor.optional);
|
|
870
|
+
if ('default' in descriptor &&
|
|
871
|
+
descriptor.default !== undefined &&
|
|
872
|
+
typeof descriptor.default !== 'string') {
|
|
873
|
+
throw new UrlKitError('invalid-descriptor', 'String hash default must be a string.', {
|
|
874
|
+
path: ['hash'],
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
if (descriptor.default !== undefined) {
|
|
878
|
+
return Object.freeze({
|
|
879
|
+
kind: 'string',
|
|
880
|
+
presence: 'defaulted',
|
|
881
|
+
defaultValue: descriptor.default,
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
return Object.freeze({ kind: 'string', presence: descriptor.optional ? 'optional' : 'required' });
|
|
885
|
+
}
|
|
886
|
+
function compileStaticEnumHashDescriptor(descriptor) {
|
|
887
|
+
assertOptionalFlag(descriptor.optional);
|
|
888
|
+
const values = normalizeEnumValues(descriptor.values);
|
|
889
|
+
if (descriptor.default !== undefined && !values.includes(descriptor.default)) {
|
|
890
|
+
throw new UrlKitError('invalid-descriptor', 'Enum hash default must be one of the declared values.', { path: ['hash'] });
|
|
891
|
+
}
|
|
892
|
+
if (descriptor.default !== undefined) {
|
|
893
|
+
return Object.freeze({
|
|
894
|
+
kind: 'enum',
|
|
895
|
+
presence: 'defaulted',
|
|
896
|
+
values,
|
|
897
|
+
defaultValue: descriptor.default,
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
return Object.freeze({
|
|
901
|
+
kind: 'enum',
|
|
902
|
+
presence: descriptor.optional ? 'optional' : 'required',
|
|
903
|
+
values,
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
function normalizeEnumValues(values) {
|
|
907
|
+
if (!Array.isArray(values) || !values.length) {
|
|
908
|
+
throw new UrlKitError('invalid-descriptor', 'Enum hash values must be a non-empty array.', {
|
|
909
|
+
path: ['hash'],
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
for (const value of values) {
|
|
913
|
+
if (typeof value !== 'string') {
|
|
914
|
+
throw new UrlKitError('invalid-descriptor', 'Enum hash values must be strings.', {
|
|
915
|
+
path: ['hash'],
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return Object.freeze(values.map((value) => value));
|
|
920
|
+
}
|
|
921
|
+
function assertOptionalFlag(optional) {
|
|
922
|
+
if (optional !== undefined && typeof optional !== 'boolean') {
|
|
923
|
+
throw new UrlKitError('invalid-descriptor', 'Hash optional flag must be a boolean.', {
|
|
924
|
+
path: ['hash'],
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function isStaticStringHashDescriptor(input) {
|
|
929
|
+
return isRecord$1(input) && input.type === 'string';
|
|
930
|
+
}
|
|
931
|
+
function isStaticEnumHashDescriptor(input) {
|
|
932
|
+
return isRecord$1(input) && input.type === 'enum';
|
|
933
|
+
}
|
|
934
|
+
function isRecord$1(input) {
|
|
935
|
+
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function isRuntimeSearchField(input) {
|
|
939
|
+
return typeof input === 'object' && input !== null && 'value' in input;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function normalizeSearchFieldDefault(fieldType, schema, value, path) {
|
|
943
|
+
if (fieldType === 'many') {
|
|
944
|
+
if (!Array.isArray(value)) {
|
|
945
|
+
throw new UrlKitError('invalid-descriptor', 'Many search field default must be an array.', {
|
|
946
|
+
path,
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
return Object.freeze(value.map((item) => normalizeRuntimeSchemaValue(schema, item, {
|
|
950
|
+
path,
|
|
951
|
+
errorCode: 'invalid-descriptor',
|
|
952
|
+
missingCode: 'invalid-descriptor',
|
|
953
|
+
})));
|
|
954
|
+
}
|
|
955
|
+
return normalizeRuntimeSchemaValue(schema, value, {
|
|
956
|
+
path,
|
|
957
|
+
errorCode: 'invalid-descriptor',
|
|
958
|
+
missingCode: 'invalid-descriptor',
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function normalizeSearchFieldType(input) {
|
|
963
|
+
if (input === undefined || input === 'one') {
|
|
964
|
+
return 'one';
|
|
965
|
+
}
|
|
966
|
+
if (input === 'many') {
|
|
967
|
+
return 'many';
|
|
968
|
+
}
|
|
969
|
+
throw new UrlKitError('invalid-descriptor', 'Search field type must be "one" or "many".');
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function compileSearchSchema(schema) {
|
|
973
|
+
if (!isSearchSchema(schema)) {
|
|
974
|
+
throw new UrlKitError('invalid-descriptor', 'Search schema must be an object.');
|
|
975
|
+
}
|
|
976
|
+
const fields = Object.freeze(Object.entries(schema).map(([key, field]) => compileSearchField(key, field)));
|
|
977
|
+
const keys = new Set(fields.map((field) => field.key));
|
|
978
|
+
return Object.freeze({ fields, keys });
|
|
979
|
+
}
|
|
980
|
+
function compileSearchField(key, field) {
|
|
981
|
+
if (isRuntimeSearchField(field)) {
|
|
982
|
+
return compileSearchFieldObject(key, field);
|
|
983
|
+
}
|
|
984
|
+
return compileSearchRuntimeSchemaField(key, field);
|
|
985
|
+
}
|
|
986
|
+
function compileSearchRuntimeSchemaField(key, schema) {
|
|
987
|
+
const compiledSchema = compileRuntimeSchemaValue(schema, { path: [key] });
|
|
988
|
+
const descriptor = compiledSchema.descriptor;
|
|
989
|
+
return Object.freeze({
|
|
990
|
+
key,
|
|
991
|
+
type: 'one',
|
|
992
|
+
schema,
|
|
993
|
+
compiledSchema,
|
|
994
|
+
presence: descriptor.presence,
|
|
995
|
+
...(descriptor.presence === 'defaulted' ? { defaultValue: descriptor.defaultValue } : {}),
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
function compileSearchFieldObject(key, field) {
|
|
999
|
+
const path = [key];
|
|
1000
|
+
const fieldType = normalizeSearchFieldType(field.type);
|
|
1001
|
+
getRuntimeSchemaInternals(field.value);
|
|
1002
|
+
const compiledSchema = compileRuntimeSchemaValue(field.value, { path });
|
|
1003
|
+
const descriptor = compiledSchema.descriptor;
|
|
1004
|
+
const hasDefault = Object.prototype.hasOwnProperty.call(field, 'default');
|
|
1005
|
+
const presence = hasDefault
|
|
1006
|
+
? 'defaulted'
|
|
1007
|
+
: field.optional
|
|
1008
|
+
? 'optional'
|
|
1009
|
+
: fieldType === 'many'
|
|
1010
|
+
? 'required'
|
|
1011
|
+
: descriptor.presence;
|
|
1012
|
+
const defaultValue = hasDefault
|
|
1013
|
+
? normalizeSearchFieldDefault(fieldType, field.value, field.default, path)
|
|
1014
|
+
: descriptor.presence === 'defaulted' && fieldType === 'one'
|
|
1015
|
+
? descriptor.defaultValue
|
|
1016
|
+
: undefined;
|
|
1017
|
+
return Object.freeze({
|
|
1018
|
+
key,
|
|
1019
|
+
type: fieldType,
|
|
1020
|
+
schema: field.value,
|
|
1021
|
+
compiledSchema,
|
|
1022
|
+
presence,
|
|
1023
|
+
...(presence === 'defaulted' ? { defaultValue } : {}),
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
function isSearchSchema(input) {
|
|
1027
|
+
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
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
|
+
function assertPathMatchFailure(pattern, pathname, segments) {
|
|
1044
|
+
const pathnameSegments = splitPath(pathname);
|
|
1045
|
+
if (pathnameSegments.length !== segments.length) {
|
|
1046
|
+
throwPathMismatch(pattern, pathname);
|
|
1047
|
+
}
|
|
1048
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
1049
|
+
const segment = segments[index];
|
|
1050
|
+
const pathnameSegment = pathnameSegments[index];
|
|
1051
|
+
if (!segment || pathnameSegment === undefined) {
|
|
1052
|
+
throwPathMismatch(pattern, pathname);
|
|
1053
|
+
}
|
|
1054
|
+
if (segment.kind === 'literal') {
|
|
1055
|
+
if (segment.value !== pathnameSegment) {
|
|
1056
|
+
throwPathMismatch(pattern, pathname);
|
|
1057
|
+
}
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (!isValidPathParamSegment(segment, pathnameSegment)) {
|
|
1061
|
+
throw new UrlKitError('invalid-param', `Path parameter "${segment.name}" is invalid.`, {
|
|
1062
|
+
path: ['params', segment.name],
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
throwPathMismatch(pattern, pathname);
|
|
1067
|
+
}
|
|
1068
|
+
function splitPath(pathname) {
|
|
1069
|
+
if (pathname === '') {
|
|
1070
|
+
return Object.freeze([]);
|
|
1071
|
+
}
|
|
1072
|
+
const normalized = pathname.startsWith('/') ? pathname.slice(1) : pathname;
|
|
1073
|
+
if (normalized === '') {
|
|
1074
|
+
return Object.freeze([]);
|
|
1075
|
+
}
|
|
1076
|
+
return Object.freeze(normalized.split('/'));
|
|
1077
|
+
}
|
|
1078
|
+
function isValidPathParamSegment(segment, value) {
|
|
1079
|
+
if (value === '') {
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
const kind = getPathParamKind(segment);
|
|
1083
|
+
if (kind === 'int') {
|
|
1084
|
+
return /^\d+$/.test(value);
|
|
1085
|
+
}
|
|
1086
|
+
if (kind === 'number') {
|
|
1087
|
+
return /^-?(?:\d+(?:\.\d+)?|\.\d+)$/.test(value) && Number.isFinite(Number(value));
|
|
1088
|
+
}
|
|
1089
|
+
if (kind === 'regex') {
|
|
1090
|
+
const params = segment.constraintParams;
|
|
1091
|
+
if (!params) {
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
return new RegExp(`^(?:${params})$`).test(value);
|
|
1095
|
+
}
|
|
1096
|
+
if (segment.constraint) {
|
|
1097
|
+
const constraint = getConstraint(segment.constraint);
|
|
1098
|
+
if (!constraint) {
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1101
|
+
try {
|
|
1102
|
+
return new RegExp(`^(?:${constraint.toRegExp(segment.constraintParams ?? '')})$`).test(value);
|
|
1103
|
+
}
|
|
1104
|
+
catch {
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return true;
|
|
1109
|
+
}
|
|
1110
|
+
function throwPathMismatch(pattern, pathname) {
|
|
1111
|
+
throw new UrlKitError('path-mismatch', `Pathname "${pathname}" does not match pattern "${pattern}".`, {
|
|
1112
|
+
path: ['pathname'],
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
function coercePathParam(segment, value, paramsMode) {
|
|
1117
|
+
if (paramsMode === 'raw') {
|
|
1118
|
+
return value;
|
|
1119
|
+
}
|
|
1120
|
+
const kind = getPathParamKind(segment);
|
|
1121
|
+
if (kind === 'int') {
|
|
1122
|
+
const parsed = Number(value);
|
|
1123
|
+
if (!Number.isInteger(parsed)) {
|
|
1124
|
+
throw new UrlKitError('invalid-param', `Path parameter "${segment.name}" must be an integer.`, {
|
|
1125
|
+
path: ['params', segment.name],
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
return parsed;
|
|
1129
|
+
}
|
|
1130
|
+
if (kind === 'number') {
|
|
1131
|
+
const parsed = Number(value);
|
|
1132
|
+
if (!Number.isFinite(parsed)) {
|
|
1133
|
+
throw new UrlKitError('invalid-param', `Path parameter "${segment.name}" must be a finite number.`, {
|
|
1134
|
+
path: ['params', segment.name],
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
return parsed;
|
|
1138
|
+
}
|
|
1139
|
+
return value;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function normalizePathBuildParams(params) {
|
|
1143
|
+
if (params === undefined) {
|
|
1144
|
+
return {};
|
|
1145
|
+
}
|
|
1146
|
+
if (!isRecord(params)) {
|
|
1147
|
+
throw new UrlKitError('invalid-param', 'Path params must be an object.', { path: ['params'] });
|
|
1148
|
+
}
|
|
1149
|
+
const normalized = {};
|
|
1150
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1151
|
+
if (value === undefined || value === null) {
|
|
1152
|
+
throw new UrlKitError('missing-param', `Path parameter "${key}" is required.`, {
|
|
1153
|
+
path: ['params', key],
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {
|
|
1157
|
+
throw new UrlKitError('invalid-param', `Path parameter "${key}" must be a string, number, or boolean.`, {
|
|
1158
|
+
path: ['params', key],
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
normalized[key] = value;
|
|
1162
|
+
}
|
|
1163
|
+
return normalized;
|
|
1164
|
+
}
|
|
1165
|
+
function isRecord(input) {
|
|
1166
|
+
return typeof input === 'object' && input !== null && !Array.isArray(input);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function parsePathPattern(pattern) {
|
|
1170
|
+
if (pattern === '') {
|
|
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 });
|
|
1182
|
+
}
|
|
1183
|
+
const token = segment.slice(1, -1);
|
|
1184
|
+
const parsed = parseParamToken(token);
|
|
1185
|
+
if (!parsed.name) {
|
|
1186
|
+
throw new UrlKitError('invalid-descriptor', 'Path parameter name is required.', {
|
|
1187
|
+
path: ['path', String(index)],
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
return Object.freeze({ kind: 'param', ...parsed });
|
|
1191
|
+
}
|
|
1192
|
+
function parseParamToken(token) {
|
|
1193
|
+
const colonIndex = token.indexOf(':');
|
|
1194
|
+
if (colonIndex === -1) {
|
|
1195
|
+
return { name: token };
|
|
1196
|
+
}
|
|
1197
|
+
const name = token.slice(0, colonIndex);
|
|
1198
|
+
const constraintToken = token.slice(colonIndex + 1);
|
|
1199
|
+
const paramsStart = constraintToken.indexOf('(');
|
|
1200
|
+
if (paramsStart === -1 || !constraintToken.endsWith(')')) {
|
|
1201
|
+
return { name, constraint: constraintToken };
|
|
1202
|
+
}
|
|
1203
|
+
return {
|
|
1204
|
+
name,
|
|
1205
|
+
constraint: constraintToken.slice(0, paramsStart),
|
|
1206
|
+
constraintParams: constraintToken.slice(paramsStart + 1, -1),
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
const numberConstraint = createConstraint({
|
|
1211
|
+
parse(paramName, value, params) {
|
|
1212
|
+
numberConstraint.verify(paramName, params);
|
|
1213
|
+
const serialized = String(value);
|
|
1214
|
+
if (!isFinitePathNumber(serialized)) {
|
|
1215
|
+
throw new UrlKitError('invalid-param', `Path parameter "${paramName}" must be a finite number.`, {
|
|
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] });
|
|
1223
|
+
}
|
|
1224
|
+
},
|
|
1225
|
+
toRegExp() {
|
|
1226
|
+
return '-?(?:\\d+(?:\\.\\d+)?|\\.\\d+)';
|
|
1227
|
+
},
|
|
1228
|
+
});
|
|
1229
|
+
function registerUrlKitPathConstraints() {
|
|
1230
|
+
if (!hasConstraint('number')) {
|
|
1231
|
+
registerConstraint('number', numberConstraint);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
function isFinitePathNumber(value) {
|
|
1235
|
+
if (!/^-?(?:\d+(?:\.\d+)?|\.\d+)$/.test(value)) {
|
|
1236
|
+
return false;
|
|
1237
|
+
}
|
|
1238
|
+
return Number.isFinite(Number(value));
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
function compilePath(pattern, options = {}) {
|
|
1242
|
+
registerUrlKitPathConstraints();
|
|
1243
|
+
if (options.pathConstraints) {
|
|
1244
|
+
registerPathConstraints(options.pathConstraints);
|
|
1245
|
+
}
|
|
1246
|
+
const paramsMode = options.params ?? 'parsed';
|
|
1247
|
+
const { segments, matcher, builder } = compilePathPattern(pattern);
|
|
1248
|
+
return Object.freeze({
|
|
1249
|
+
pattern,
|
|
1250
|
+
parsePathname(pathname) {
|
|
1251
|
+
const result = matcher(pathname);
|
|
1252
|
+
if (!result.match || !result.params) {
|
|
1253
|
+
assertPathMatchFailure(pattern, pathname, segments);
|
|
1254
|
+
}
|
|
1255
|
+
return Object.freeze(coercePathParams(result.params, segments, paramsMode));
|
|
1256
|
+
},
|
|
1257
|
+
buildPath(params) {
|
|
1258
|
+
try {
|
|
1259
|
+
return builder(normalizePathBuildParams(params));
|
|
1260
|
+
}
|
|
1261
|
+
catch (error) {
|
|
1262
|
+
throw mapBuildPathError(error);
|
|
1263
|
+
}
|
|
1264
|
+
},
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
function coercePathParams(matchedParams, segments, paramsMode) {
|
|
1268
|
+
const params = {};
|
|
1269
|
+
for (const segment of segments) {
|
|
1270
|
+
if (segment.kind !== 'param') {
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
const value = matchedParams[segment.name];
|
|
1274
|
+
if (typeof value !== 'string') {
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
params[segment.name] = coercePathParam(segment, value, paramsMode);
|
|
1278
|
+
}
|
|
1279
|
+
return params;
|
|
1280
|
+
}
|
|
1281
|
+
function mapBuildPathError(error) {
|
|
1282
|
+
if (error instanceof UrlKitError) {
|
|
1283
|
+
return error;
|
|
1284
|
+
}
|
|
1285
|
+
const message = error instanceof Error ? error.message : 'Failed to build pathname.';
|
|
1286
|
+
const missingParamMatch = /Missing required parameter: ([^\s]+)/.exec(message);
|
|
1287
|
+
if (missingParamMatch?.[1]) {
|
|
1288
|
+
return new UrlKitError('missing-param', `Path parameter "${missingParamMatch[1]}" is required.`, {
|
|
1289
|
+
path: ['params', missingParamMatch[1]],
|
|
1290
|
+
cause: error,
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
return new UrlKitError('invalid-param', message, {
|
|
1294
|
+
path: ['params'],
|
|
1295
|
+
cause: error,
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
function compilePathPattern(pattern) {
|
|
1299
|
+
try {
|
|
1300
|
+
return Object.freeze({
|
|
1301
|
+
segments: parsePathPattern(pattern),
|
|
1302
|
+
matcher: match(pattern),
|
|
1303
|
+
builder: compile(pattern),
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
catch (error) {
|
|
1307
|
+
if (error instanceof UrlKitError) {
|
|
1308
|
+
throw error;
|
|
1309
|
+
}
|
|
1310
|
+
throw new UrlKitError('invalid-descriptor', 'Path pattern is invalid.', {
|
|
1311
|
+
path: ['path'],
|
|
1312
|
+
cause: error,
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
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, compileStaticHashDescriptor as v, parseUnixSeconds as w, parseUnixMs as x, parseDateTime as y, parseDate as z };
|
|
1318
|
+
//# sourceMappingURL=compile-path-wQfWAzOh.js.map
|