@sourceregistry/sveltekit-enhance 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 +201 -0
- package/README.md +112 -0
- package/dist/helpers/auth.d.ts +6 -0
- package/dist/helpers/auth.js +16 -0
- package/dist/helpers/devtools.d.ts +4 -0
- package/dist/helpers/devtools.js +10 -0
- package/dist/helpers/featureflag.d.ts +10 -0
- package/dist/helpers/featureflag.js +28 -0
- package/dist/helpers/form.d.ts +314 -0
- package/dist/helpers/form.js +530 -0
- package/dist/helpers/index.d.ts +5 -0
- package/dist/helpers/index.js +5 -0
- package/dist/helpers/request-correlation.d.ts +9 -0
- package/dist/helpers/request-correlation.js +47 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +167 -0
- package/package.json +107 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import { fail } from "../index.js";
|
|
2
|
+
const reviveMap = new Map([
|
|
3
|
+
['true', () => true],
|
|
4
|
+
['false', () => false],
|
|
5
|
+
['null', () => null],
|
|
6
|
+
['undefined', () => undefined],
|
|
7
|
+
['Infinity', () => Infinity],
|
|
8
|
+
['-Infinity', () => -Infinity],
|
|
9
|
+
['NaN', () => NaN],
|
|
10
|
+
[/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/, (v) => Number(v)],
|
|
11
|
+
[/^\s*\{[\s\S]*}\s*$/, (v) => JSON.parse(v, reviver)],
|
|
12
|
+
[/^\s*\[[\s\S]*]\s*$/, (v) => JSON.parse(v, reviver)]
|
|
13
|
+
]);
|
|
14
|
+
export const reviver = (key, value) => {
|
|
15
|
+
if (typeof value !== 'string')
|
|
16
|
+
return value;
|
|
17
|
+
for (const [pattern, converter] of reviveMap) {
|
|
18
|
+
if ((typeof pattern === 'string' && pattern === value) ||
|
|
19
|
+
(pattern instanceof RegExp && pattern.test(value))) {
|
|
20
|
+
try {
|
|
21
|
+
return converter(value);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
};
|
|
30
|
+
const createContext = (data) => {
|
|
31
|
+
return {
|
|
32
|
+
get data() {
|
|
33
|
+
return data;
|
|
34
|
+
},
|
|
35
|
+
has: (...names) => {
|
|
36
|
+
if (names.length === 0)
|
|
37
|
+
return false;
|
|
38
|
+
if (names.length === 1)
|
|
39
|
+
return data.has(names[0]);
|
|
40
|
+
else
|
|
41
|
+
return hasOneOf(data, names);
|
|
42
|
+
},
|
|
43
|
+
hasOneOf: (...name) => hasOneOf(data, name),
|
|
44
|
+
string: (name) => string(data, name) ?? undefined,
|
|
45
|
+
_string: (name) => string(data, name),
|
|
46
|
+
string$: (name) => string$(data, name),
|
|
47
|
+
pattern$: (name, pattern) => pattern$(data, name, pattern),
|
|
48
|
+
enum: (name, _enum) => Enum(data, name, _enum),
|
|
49
|
+
enum$: (name, _enum) => Enum$(data, name, _enum),
|
|
50
|
+
date: (name, parser = (data, name) => data.get(name)) => date(data, name, parser),
|
|
51
|
+
date$: (name, parser = (data, name) => data.get(name)) => date$(data, name, parser),
|
|
52
|
+
process: (name, parser, processor) => process(data, name, (formdata, name) => parser.length === 2
|
|
53
|
+
? parser(formdata, name)
|
|
54
|
+
: parser(name), processor),
|
|
55
|
+
number: (name) => number(data, name),
|
|
56
|
+
number$: (name) => number$(data, name),
|
|
57
|
+
boolean: (name) => boolean(data, name),
|
|
58
|
+
boolean$: (name) => boolean$(data, name),
|
|
59
|
+
json: (name, transformer = (val) => val) => json(data, name, transformer),
|
|
60
|
+
jsond: (options) => jsond(data, options),
|
|
61
|
+
json$: (name, transformer = (val) => val) => json$(data, name, transformer),
|
|
62
|
+
file: (name) => file(data, name),
|
|
63
|
+
file$: (name) => file$(data, name),
|
|
64
|
+
files: (name) => files(data, name),
|
|
65
|
+
fileRecord: (prefix, removePrefix = false) => fileRecord(data, prefix, removePrefix),
|
|
66
|
+
array: (name, mapper) => array(data, name, mapper),
|
|
67
|
+
array$: (name, mapper) => array$(data, name, mapper),
|
|
68
|
+
onlyIf: (condition, TRUE, FALSE = undefined) => onlyIf(condition, TRUE, FALSE),
|
|
69
|
+
onlyIfPresent: (key, TRUE, FALSE = undefined) => onlyIfPresent(data, key, TRUE, FALSE),
|
|
70
|
+
onlyIfArrayPresent: (key, TRUE, FALSE) => onlyIfArrayPresent(data, key, TRUE, FALSE),
|
|
71
|
+
selector: (cases, useArray = false) => selector(data, cases, useArray),
|
|
72
|
+
selector$: (cases, useArray = false) => selector$(data, cases, useArray),
|
|
73
|
+
basedOn: (val, processor = (val) => val) => basedOn(val, processor),
|
|
74
|
+
record: (options) => record(data, options),
|
|
75
|
+
validate: (schema, options = {
|
|
76
|
+
unpack_prefixed: true,
|
|
77
|
+
transform: (value) => value
|
|
78
|
+
}) => validate(data, schema, options)
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
export function hasOneOf(formdata, names) {
|
|
82
|
+
return names.some((name) => formdata.has(name));
|
|
83
|
+
}
|
|
84
|
+
export function string(formdata, name) {
|
|
85
|
+
if (!formdata.has(name)) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const data = formdata.get(name);
|
|
89
|
+
if (data === 'null' || data === null)
|
|
90
|
+
return null;
|
|
91
|
+
if (typeof data != 'string') {
|
|
92
|
+
fail(400, { targets: [name], message: `${name} isn't of type string` });
|
|
93
|
+
}
|
|
94
|
+
return data;
|
|
95
|
+
}
|
|
96
|
+
export function string$(formdata, name) {
|
|
97
|
+
if (!formdata.has(name)) {
|
|
98
|
+
fail(400, { targets: [name], message: `${name} is required` });
|
|
99
|
+
}
|
|
100
|
+
const data = formdata.get(name);
|
|
101
|
+
if (typeof data != 'string') {
|
|
102
|
+
fail(400, { targets: [name], message: `${name} is not correct data type` });
|
|
103
|
+
}
|
|
104
|
+
return data;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Reads a required string and validates it against a regex pattern.
|
|
108
|
+
* Accepts either a prebuilt `RegExp` or a pattern string.
|
|
109
|
+
* Throws a SvelteKit `fail(400)` callback when required checks fail.
|
|
110
|
+
*/
|
|
111
|
+
export function pattern$(formdata, name, pattern) {
|
|
112
|
+
if (!formdata.has(name))
|
|
113
|
+
fail(400, { targets: [name], message: `${name} is required` });
|
|
114
|
+
const data = formdata.get(name);
|
|
115
|
+
if (!data || typeof data != 'string')
|
|
116
|
+
return fail(400, { targets: [name], message: `${name} is not correct data type` });
|
|
117
|
+
if (typeof pattern === 'string')
|
|
118
|
+
pattern = new RegExp(pattern);
|
|
119
|
+
if (!pattern.test(data.toString()))
|
|
120
|
+
fail(400, { targets: [name], message: `${name} is not in correct format` });
|
|
121
|
+
return data;
|
|
122
|
+
}
|
|
123
|
+
export function number(formdata, name) {
|
|
124
|
+
if (!formdata.has(name)) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
const num = Number(formdata.get(name));
|
|
128
|
+
if (isNaN(num)) {
|
|
129
|
+
fail(400, { targets: [name], message: `${name} isn't of type number` });
|
|
130
|
+
}
|
|
131
|
+
return num;
|
|
132
|
+
}
|
|
133
|
+
export function number$(formdata, name) {
|
|
134
|
+
const _number = number(formdata, name);
|
|
135
|
+
if (_number === undefined) {
|
|
136
|
+
fail(400, { targets: [name], message: `${name} is required` });
|
|
137
|
+
}
|
|
138
|
+
return _number;
|
|
139
|
+
}
|
|
140
|
+
export function boolean(formdata, name) {
|
|
141
|
+
if (!formdata.has(name)) {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
const data = formdata.get(name);
|
|
145
|
+
const num = Number(data);
|
|
146
|
+
if (!isNaN(num)) {
|
|
147
|
+
return num > 0;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
return data.trim().toLowerCase().startsWith('t') || data.trim().toLowerCase() === 'on';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export function boolean$(formdata, name) {
|
|
154
|
+
if (!formdata.has(name)) {
|
|
155
|
+
fail(400, { targets: [name], message: `${name} is required` });
|
|
156
|
+
}
|
|
157
|
+
const data = formdata.get(name);
|
|
158
|
+
const num = Number(data);
|
|
159
|
+
if (!isNaN(num)) {
|
|
160
|
+
return num > 0;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
return data.trim().toLowerCase().startsWith('t') || data.trim().toLowerCase() === 'on';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export function date(formdata, name, parser = (data, name) => data.get(name)) {
|
|
167
|
+
if (!formdata.has(name)) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
const val = parser(formdata, name);
|
|
171
|
+
if (!val) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
return new Date(val);
|
|
175
|
+
}
|
|
176
|
+
export function date$(formdata, name, parser) {
|
|
177
|
+
return new Date(parser(formdata, name));
|
|
178
|
+
}
|
|
179
|
+
export function json(formdata, name, transformer = (val) => val) {
|
|
180
|
+
if (!formdata.has(name)) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
return transformer(JSON.parse(formdata.get(name), reviver));
|
|
185
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
fail(400, { targets: [name], message: `${name} isn't of json` });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
export function record(formdata, options) {
|
|
192
|
+
return Object.fromEntries(formdata
|
|
193
|
+
.keys()
|
|
194
|
+
.map((key) => {
|
|
195
|
+
let entry = [key, formdata.getAll(key)];
|
|
196
|
+
if (options && options.filter && !options.filter(entry))
|
|
197
|
+
return;
|
|
198
|
+
return entry;
|
|
199
|
+
})
|
|
200
|
+
.filter((v) => v !== undefined)
|
|
201
|
+
.map(([key, value]) => [key, Array.isArray(value) && value.length === 1 ? value[0] : value])
|
|
202
|
+
.map((entry) => {
|
|
203
|
+
if (options && options.transformer)
|
|
204
|
+
entry = options.transformer(entry);
|
|
205
|
+
return entry;
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
export function json$(formdata, name, transformer = (val) => val) {
|
|
209
|
+
if (!formdata.has(name)) {
|
|
210
|
+
fail(400, { targets: [name], message: `${name} doesn't exist` });
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
return transformer(JSON.parse(formdata.get(name), reviver));
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
throw () => fail(400, {
|
|
217
|
+
targets: [name],
|
|
218
|
+
message: `${name} isn't of type json`,
|
|
219
|
+
error: e instanceof Error ? e?.message : e?.toString()
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
export function jsond(formdata, options = {
|
|
224
|
+
unpack_prefixed: true,
|
|
225
|
+
transform: (value) => value
|
|
226
|
+
}) {
|
|
227
|
+
let result = {};
|
|
228
|
+
formdata
|
|
229
|
+
.entries()
|
|
230
|
+
.filter(([key]) => (options.prefix_name ? key.startsWith(options.prefix_name) : key))
|
|
231
|
+
.map(([key, value]) => [key, (options.transform ?? ((value) => value))(value, key, formdata)])
|
|
232
|
+
.map(([key, value]) => (options.unpack_prefixed && options.prefix_name
|
|
233
|
+
? [key.replace(options.prefix_name, ''), value]
|
|
234
|
+
: [key, value]))
|
|
235
|
+
.forEach(([key, value]) => {
|
|
236
|
+
const splits = key.split('.');
|
|
237
|
+
let context = result;
|
|
238
|
+
splits.forEach((part, index) => {
|
|
239
|
+
if (index === splits.length - 1) {
|
|
240
|
+
// If it's the last part, check if the key already exists
|
|
241
|
+
if (context[part] === undefined) {
|
|
242
|
+
context[part] = value;
|
|
243
|
+
}
|
|
244
|
+
else if (Array.isArray(context[part])) {
|
|
245
|
+
context[part].push(value);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
context[part] = [context[part], value];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
// If the key doesn't exist, initialize it as an object
|
|
253
|
+
if (!context[part])
|
|
254
|
+
context[part] = {};
|
|
255
|
+
// Move deeper into the object
|
|
256
|
+
context = context[part];
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
if (options.processor) {
|
|
261
|
+
result = options.processor(result);
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
export function file(formdata, name) {
|
|
266
|
+
if (!formdata.has(name)) {
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
const data = formdata.get(name);
|
|
270
|
+
if (data instanceof File) {
|
|
271
|
+
return formdata.get(name);
|
|
272
|
+
}
|
|
273
|
+
else if (data === null || data === 'null') {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
fail(400, { targets: [name], message: `${name} isn't of type File` });
|
|
277
|
+
}
|
|
278
|
+
export function fileRecord(formdata, prefix, removePrefix = false) {
|
|
279
|
+
const record = {};
|
|
280
|
+
formdata
|
|
281
|
+
.entries()
|
|
282
|
+
.filter(([key, value]) => key.startsWith(prefix) && value instanceof File && value.size > 0 && value.name.length > 0)
|
|
283
|
+
.map(([key, value]) => [removePrefix ? key.replace(prefix, '') : key, value])
|
|
284
|
+
.forEach(([key, value]) => {
|
|
285
|
+
if (!(key in record) || !record[key])
|
|
286
|
+
return (record[key] = [value]);
|
|
287
|
+
else
|
|
288
|
+
record[key].push(value);
|
|
289
|
+
});
|
|
290
|
+
return record;
|
|
291
|
+
}
|
|
292
|
+
export function file$(formdata, name) {
|
|
293
|
+
if (!formdata.has(name)) {
|
|
294
|
+
fail(400, { targets: [name], message: `${name} doesn't exist` });
|
|
295
|
+
}
|
|
296
|
+
const data = formdata.get(name);
|
|
297
|
+
if (data instanceof File) {
|
|
298
|
+
return formdata.get(name);
|
|
299
|
+
}
|
|
300
|
+
fail(400, { targets: [name], message: `${name} isn't of type File` });
|
|
301
|
+
}
|
|
302
|
+
export function files(formdata, name) {
|
|
303
|
+
if (!formdata.has(name)) {
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
return (formdata.getAll(name).filter((e) => e instanceof File && e.size > 0 && e.name.length > 0));
|
|
307
|
+
}
|
|
308
|
+
export function array(formdata, name, mapper) {
|
|
309
|
+
if (!formdata.has(name)) {
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
const array = formdata.getAll(name);
|
|
313
|
+
if (array.length === 1 && array[0] === '[]') {
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
if (!mapper) {
|
|
317
|
+
return array;
|
|
318
|
+
}
|
|
319
|
+
return array.map(mapper);
|
|
320
|
+
}
|
|
321
|
+
export function array$(formdata, name, mapper) {
|
|
322
|
+
if (!formdata.has(name)) {
|
|
323
|
+
fail(400, { targets: [name], message: `${name} doesn't exist` });
|
|
324
|
+
}
|
|
325
|
+
const array = formdata.getAll(name);
|
|
326
|
+
if (!mapper) {
|
|
327
|
+
return array;
|
|
328
|
+
}
|
|
329
|
+
const res = array.map(mapper);
|
|
330
|
+
if (res.length === 0) {
|
|
331
|
+
fail(400, { targets: [name], message: `${name} length of array === 0` });
|
|
332
|
+
}
|
|
333
|
+
return res;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* TODO not compliant with enum standard but works for now
|
|
337
|
+
* @param formdata
|
|
338
|
+
* @param name
|
|
339
|
+
* @param _enum
|
|
340
|
+
*/
|
|
341
|
+
export function Enum(formdata, name, _enum) {
|
|
342
|
+
if (!formdata.has(name)) {
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
const data = formdata.get(name);
|
|
346
|
+
if (typeof data != 'string') {
|
|
347
|
+
return fail(400, { targets: [name], message: `${name} isn't of type in enum` });
|
|
348
|
+
}
|
|
349
|
+
if (!(data in _enum))
|
|
350
|
+
return undefined;
|
|
351
|
+
return data;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* TODO not compliant with enum standard but works for now
|
|
355
|
+
* @param formdata
|
|
356
|
+
* @param name
|
|
357
|
+
* @param _enum
|
|
358
|
+
*/
|
|
359
|
+
export function Enum$(formdata, name, _enum) {
|
|
360
|
+
const data = Enum(formdata, name, _enum);
|
|
361
|
+
if (!data)
|
|
362
|
+
return fail(400, { targets: [name], message: `${name} isn't of type in enum` });
|
|
363
|
+
return _enum[data];
|
|
364
|
+
}
|
|
365
|
+
export function OldArray(formdata, name, parser) {
|
|
366
|
+
const data = string(formdata, name);
|
|
367
|
+
if (!data) {
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
return parser({ data });
|
|
371
|
+
}
|
|
372
|
+
export function arrayString(formdata, name, delimiter, mapper) {
|
|
373
|
+
return OldArray(formdata, name, ({ data }) => data.split(delimiter).map(mapper ?? ((i) => i)));
|
|
374
|
+
}
|
|
375
|
+
export function onlyIf(condition, TRUE, FALSE = undefined) {
|
|
376
|
+
return condition ? TRUE : FALSE;
|
|
377
|
+
}
|
|
378
|
+
export function onlyIfPresent(formdata, key, TRUE, FALSE = undefined) {
|
|
379
|
+
return formdata.has(key) ? TRUE(formdata.get(key)) : FALSE;
|
|
380
|
+
}
|
|
381
|
+
export function onlyIfArrayPresent(formdata, key, TRUE, FALSE) {
|
|
382
|
+
return formdata.has(key) && Array.isArray(formdata.getAll(key))
|
|
383
|
+
? TRUE(formdata.getAll(key))
|
|
384
|
+
: FALSE;
|
|
385
|
+
}
|
|
386
|
+
export function basedOn(val, processor = (val) => val) {
|
|
387
|
+
return processor(val);
|
|
388
|
+
}
|
|
389
|
+
export function selector(formData, cases, useArray = false) {
|
|
390
|
+
try {
|
|
391
|
+
for (const key of formData.keys()) {
|
|
392
|
+
if (key in cases) {
|
|
393
|
+
const processor = cases[key];
|
|
394
|
+
if (formData.has(key)) {
|
|
395
|
+
if (useArray) {
|
|
396
|
+
const data = formData.getAll(key);
|
|
397
|
+
if (data.length > 0) {
|
|
398
|
+
return processor(data, key);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
const data = formData.get(key);
|
|
403
|
+
if (data !== null) {
|
|
404
|
+
return processor(data, key);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if ('$default' in cases) {
|
|
411
|
+
return cases.$default(formData);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
// Call $error in case of any error
|
|
416
|
+
if ('$error' in cases) {
|
|
417
|
+
return cases.$error?.(error);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
export function selector$(formData, cases, useArray = false) {
|
|
423
|
+
const result = selector(formData, cases, useArray);
|
|
424
|
+
if (!result) {
|
|
425
|
+
if ('$error' in cases) {
|
|
426
|
+
cases?.['$error']?.('Unable to find value');
|
|
427
|
+
}
|
|
428
|
+
fail(400, { targets: Object.keys(cases), message: `Unable to find value` });
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
export async function handle(data, fn, ...errorHandlers) {
|
|
433
|
+
try {
|
|
434
|
+
if (data instanceof Request &&
|
|
435
|
+
data.headers.has('Content-Type') &&
|
|
436
|
+
data.headers.get('Content-Type')?.includes('form')) {
|
|
437
|
+
data = await data.formData();
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
return fail(400, { message: "Request doesn't contain form data" });
|
|
441
|
+
}
|
|
442
|
+
return await fn({ data: data, form: createContext(data) });
|
|
443
|
+
}
|
|
444
|
+
catch (e) {
|
|
445
|
+
for (const errorHandler of errorHandlers) {
|
|
446
|
+
try {
|
|
447
|
+
await errorHandler(e);
|
|
448
|
+
}
|
|
449
|
+
catch (e) {
|
|
450
|
+
throw e;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
throw e;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
export async function enhance(input) {
|
|
457
|
+
const data = input.request.headers.has('Content-Type') &&
|
|
458
|
+
input.request.headers.get('Content-Type')?.includes('form')
|
|
459
|
+
? await input.request.formData()
|
|
460
|
+
: new FormData();
|
|
461
|
+
return {
|
|
462
|
+
form: createContext(data)
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
export function process(formdata, name, parser, processor) {
|
|
466
|
+
return processor(parser(formdata, name), name);
|
|
467
|
+
}
|
|
468
|
+
export function validate(formdata, validator, options = {
|
|
469
|
+
unpack_prefixed: true,
|
|
470
|
+
transform: (value) => {
|
|
471
|
+
if (value instanceof File)
|
|
472
|
+
return value;
|
|
473
|
+
else
|
|
474
|
+
return reviver(undefined, value);
|
|
475
|
+
}
|
|
476
|
+
}) {
|
|
477
|
+
let value = jsond(formdata, {
|
|
478
|
+
unpack_prefixed: true,
|
|
479
|
+
...options,
|
|
480
|
+
transform: (v) => reviver(undefined, v),
|
|
481
|
+
});
|
|
482
|
+
const result = validator(value);
|
|
483
|
+
if (!result.success) {
|
|
484
|
+
return fail(400, {
|
|
485
|
+
message: "Missing or invalid form data",
|
|
486
|
+
targets: [result.errors.map(({ path }) => path.replace('$.', options.prefix_name || ''))]
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
return result.data;
|
|
490
|
+
}
|
|
491
|
+
export const schema = (validator, options = {
|
|
492
|
+
unpack_prefixed: true,
|
|
493
|
+
transform: (value) => value
|
|
494
|
+
}) => async (input) => {
|
|
495
|
+
const { form } = await enhance(input);
|
|
496
|
+
return {
|
|
497
|
+
form: {
|
|
498
|
+
...form,
|
|
499
|
+
result: form.validate(validator, options)
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
};
|
|
503
|
+
export const Form = {
|
|
504
|
+
string,
|
|
505
|
+
string$,
|
|
506
|
+
number,
|
|
507
|
+
number$,
|
|
508
|
+
boolean,
|
|
509
|
+
boolean$,
|
|
510
|
+
date,
|
|
511
|
+
date$,
|
|
512
|
+
file,
|
|
513
|
+
file$,
|
|
514
|
+
files,
|
|
515
|
+
array,
|
|
516
|
+
array$,
|
|
517
|
+
json,
|
|
518
|
+
json$,
|
|
519
|
+
jsond,
|
|
520
|
+
process,
|
|
521
|
+
validate,
|
|
522
|
+
onlyIf,
|
|
523
|
+
onlyIfPresent,
|
|
524
|
+
onlyIfArrayPresent,
|
|
525
|
+
selector,
|
|
526
|
+
selector$,
|
|
527
|
+
enhance,
|
|
528
|
+
schema,
|
|
529
|
+
handle
|
|
530
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EnhanceInput } from "../index.js";
|
|
2
|
+
export type RequestCorrelationLocals = {
|
|
3
|
+
correlation_id?: string;
|
|
4
|
+
request_started_at?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const RequestCorrelation: {
|
|
7
|
+
header: string;
|
|
8
|
+
attach: (input: EnhanceInput<"handle">) => void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
const CORRELATION_ID_HEADER = 'x-correlation-id';
|
|
3
|
+
const REQUEST_ID_HEADER = 'x-request-id';
|
|
4
|
+
const MAX_CORRELATION_ID_LENGTH = 128;
|
|
5
|
+
const SAFE_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]+$/;
|
|
6
|
+
const normalizeCorrelationId = (raw) => {
|
|
7
|
+
if (!raw)
|
|
8
|
+
return undefined;
|
|
9
|
+
const candidate = raw.trim();
|
|
10
|
+
if (!candidate)
|
|
11
|
+
return undefined;
|
|
12
|
+
if (candidate.length > MAX_CORRELATION_ID_LENGTH)
|
|
13
|
+
return undefined;
|
|
14
|
+
if (!SAFE_CORRELATION_ID_PATTERN.test(candidate))
|
|
15
|
+
return undefined;
|
|
16
|
+
return candidate;
|
|
17
|
+
};
|
|
18
|
+
const withCorrelationHeader = (response, correlationId) => {
|
|
19
|
+
try {
|
|
20
|
+
response.headers.set(CORRELATION_ID_HEADER, correlationId);
|
|
21
|
+
return response;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
const headers = new Headers(response.headers);
|
|
25
|
+
headers.set(CORRELATION_ID_HEADER, correlationId);
|
|
26
|
+
return new Response(response.body, {
|
|
27
|
+
status: response.status,
|
|
28
|
+
statusText: response.statusText,
|
|
29
|
+
headers
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
export const RequestCorrelation = {
|
|
34
|
+
header: CORRELATION_ID_HEADER,
|
|
35
|
+
attach: (input) => {
|
|
36
|
+
const correlationId = normalizeCorrelationId(input.request.headers.get(CORRELATION_ID_HEADER)) ??
|
|
37
|
+
normalizeCorrelationId(input.request.headers.get(REQUEST_ID_HEADER)) ??
|
|
38
|
+
randomUUID();
|
|
39
|
+
if (input.locals) {
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
input.locals['correlation_id'] = correlationId;
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
input.locals['request_started_at'] = Date.now();
|
|
44
|
+
}
|
|
45
|
+
input.responseHandlers.push(({ response }) => withCorrelationHeader(response, correlationId));
|
|
46
|
+
}
|
|
47
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type Action, type ActionResult, type Cookies, type Handle, type RequestEvent, type ResolveOptions, type ServerLoadEvent } from '@sveltejs/kit';
|
|
2
|
+
import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams } from '$app/types';
|
|
3
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
4
|
+
export type EnhanceErrorHandler = <T = any>(err: unknown) => MaybePromise<T> | undefined | never | void;
|
|
5
|
+
export type EnhanceResponseHandler = (input: {
|
|
6
|
+
event: RequestEvent;
|
|
7
|
+
response: Response;
|
|
8
|
+
}) => MaybePromise<unknown>;
|
|
9
|
+
export type EnhanceCallType = 'handle' | 'load' | 'method' | 'action';
|
|
10
|
+
export type EnhanceInput<CallType extends EnhanceCallType = EnhanceCallType, Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null, ParentData extends Record<string, any> = Record<string, any>> = {
|
|
11
|
+
cookies: Cookies;
|
|
12
|
+
params: Params;
|
|
13
|
+
route: {
|
|
14
|
+
id: RouteId;
|
|
15
|
+
};
|
|
16
|
+
url: URL;
|
|
17
|
+
locals: App.Locals;
|
|
18
|
+
request: Request;
|
|
19
|
+
callType: CallType;
|
|
20
|
+
fetch: typeof fetch;
|
|
21
|
+
get errorHandlers(): EnhanceErrorHandler[];
|
|
22
|
+
} & (CallType extends 'handle' ? {
|
|
23
|
+
get responseHandlers(): EnhanceResponseHandler[];
|
|
24
|
+
resolve: (event?: RequestEvent) => never;
|
|
25
|
+
readonly event: RequestEvent;
|
|
26
|
+
} : CallType extends 'load' ? {
|
|
27
|
+
parent: ServerLoadEvent<Params, ParentData, RouteId>['parent'];
|
|
28
|
+
depends: ServerLoadEvent<Params, ParentData, RouteId>['depends'];
|
|
29
|
+
untrack: ServerLoadEvent<Params, ParentData, RouteId>['untrack'];
|
|
30
|
+
} : object);
|
|
31
|
+
export type EnhanceFunction<CallType extends EnhanceCallType = EnhanceCallType, Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null, EnhanceReturn = any> = (event: EnhanceInput<CallType, Params, RouteId>) => MaybePromise<EnhanceReturn>;
|
|
32
|
+
export type EnhanceAction<Params extends AppLayoutParams<'/'>, OutputData extends Record<string, any> | void, RouteId extends AppRouteId | null, EnhanceReturn extends ActionResult | never | any = ActionResult> = (event: RequestEvent<Params, RouteId> & {
|
|
33
|
+
context: EnhanceReturn;
|
|
34
|
+
}) => MaybePromise<OutputData>;
|
|
35
|
+
export type EnhanceLoad<Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, ParentData extends Record<string, any> = Record<string, any>, OutputData extends Record<string, any> | void = Record<string, any> | void, RouteId extends AppRouteId | null = AppRouteId | null, EnhanceReturn extends never | any = any> = (event: ServerLoadEvent<Params, ParentData, RouteId> & {
|
|
36
|
+
context: EnhanceReturn;
|
|
37
|
+
}) => MaybePromise<OutputData>;
|
|
38
|
+
export type EnhanceHandle<EnhanceReturn extends never | any = any> = (input: {
|
|
39
|
+
event: RequestEvent;
|
|
40
|
+
resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
|
|
41
|
+
} & {
|
|
42
|
+
context: EnhanceReturn;
|
|
43
|
+
}) => MaybePromise<Response>;
|
|
44
|
+
export type EnhanceMethod<Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null, EnhanceReturn extends never | any = any> = (event: RequestEvent<Params, RouteId> & {
|
|
45
|
+
context: EnhanceReturn;
|
|
46
|
+
}) => MaybePromise<Response>;
|
|
47
|
+
export declare const action: <Params extends AppLayoutParams<"/"> = AppLayoutParams<"/">, OutputData extends Record<string, any> | void = Record<string, any> | void, RouteId extends AppRouteId | null = AppRouteId | null, const Enhances extends readonly EnhanceFunction<"action", Params, RouteId, object>[] = readonly EnhanceFunction<"action", Params, RouteId, object>[], EnhanceReturn extends ConcatReturnTypes<Enhances> = ConcatReturnTypes<Enhances>>(action: EnhanceAction<Params, OutputData, RouteId, EnhanceReturn>, ...enhances: [...Enhances]) => Action<Params, OutputData, RouteId>;
|
|
48
|
+
export declare const load: <Params extends AppLayoutParams<"/"> = AppLayoutParams<"/">, ParentData extends Record<string, any> = Record<string, any>, OutputData extends Record<string, any> | void = Record<string, any> | void, RouteId extends AppRouteId | null = AppRouteId | null, const Enhances extends readonly EnhanceFunction<"load", Params, RouteId, object>[] = readonly EnhanceFunction<"load", Params, RouteId, object>[], EnhanceReturn extends ConcatReturnTypes<Enhances> = ConcatReturnTypes<Enhances>>(load: EnhanceLoad<Params, ParentData, OutputData, RouteId, EnhanceReturn>, ...contexts: [...Enhances]) => (event: ServerLoadEvent<Params, ParentData, RouteId>) => Promise<OutputData>;
|
|
49
|
+
export declare const method: <Params extends AppLayoutParams<"/"> = AppLayoutParams<"/">, RouteId extends AppRouteId | null = AppRouteId | null, const Enhances extends readonly EnhanceFunction<"method", Params, RouteId, object>[] = readonly EnhanceFunction<"method", Params, RouteId, object>[], EnhanceReturn extends Awaited<ConcatReturnTypes<Enhances>> = Awaited<ConcatReturnTypes<Enhances>>>(handle: EnhanceMethod<Params, RouteId, EnhanceReturn>, ...contexts: [...Enhances]) => (event: RequestEvent<Params, RouteId>) => Promise<Response>;
|
|
50
|
+
export declare const handle: <const Enhances extends readonly EnhanceFunction<"handle">[] = readonly EnhanceFunction<"handle">[], EnhanceReturn extends Awaited<ConcatReturnTypes<Enhances>> = Awaited<ConcatReturnTypes<Enhances>>>(handle: EnhanceHandle<EnhanceReturn>, ...contexts: [...Enhances]) => Handle;
|
|
51
|
+
export declare const enhance: {
|
|
52
|
+
action: <Params extends AppLayoutParams<"/"> = Record<string, string>, OutputData extends Record<string, any> | void = void | Record<string, any>, RouteId extends AppRouteId | null = string | null, const Enhances extends readonly EnhanceFunction<"action", Params, RouteId, object>[] = readonly EnhanceFunction<"action", Params, RouteId, object>[], EnhanceReturn extends ConcatReturnTypes<Enhances> = ConcatReturnTypes<Enhances>>(action: EnhanceAction<Params, OutputData, RouteId, EnhanceReturn>, ...enhances: Enhances) => Action<Params, OutputData, RouteId>;
|
|
53
|
+
load: <Params extends AppLayoutParams<"/"> = Record<string, string>, ParentData extends Record<string, any> = Record<string, any>, OutputData extends Record<string, any> | void = void | Record<string, any>, RouteId extends AppRouteId | null = string | null, const Enhances extends readonly EnhanceFunction<"load", Params, RouteId, object>[] = readonly EnhanceFunction<"load", Params, RouteId, object>[], EnhanceReturn extends ConcatReturnTypes<Enhances> = ConcatReturnTypes<Enhances>>(load: EnhanceLoad<Params, ParentData, OutputData, RouteId, EnhanceReturn>, ...contexts: Enhances) => (event: ServerLoadEvent<Params, ParentData, RouteId>) => Promise<OutputData>;
|
|
54
|
+
method: <Params extends AppLayoutParams<"/"> = Record<string, string>, RouteId extends AppRouteId | null = string | null, const Enhances extends readonly EnhanceFunction<"method", Params, RouteId, object>[] = readonly EnhanceFunction<"method", Params, RouteId, object>[], EnhanceReturn extends Awaited<ConcatReturnTypes<Enhances>> = Awaited<ConcatReturnTypes<Enhances>>>(handle: EnhanceMethod<Params, RouteId, EnhanceReturn>, ...contexts: Enhances) => (event: RequestEvent<Params, RouteId>) => Promise<Response>;
|
|
55
|
+
handle: <const Enhances extends readonly EnhanceFunction<"handle">[] = readonly EnhanceFunction<"handle", Record<string, string>, string | null, any>[], EnhanceReturn extends Awaited<ConcatReturnTypes<Enhances>> = Awaited<ConcatReturnTypes<Enhances>>>(handle: EnhanceHandle<EnhanceReturn>, ...contexts: Enhances) => Handle;
|
|
56
|
+
};
|
|
57
|
+
export type Enhancers<Params extends AppLayoutParams<'/'> = AppLayoutParams<'/'>, RouteId extends AppRouteId | null = AppRouteId | null, EnhanceReturn extends never | any = any> = Record<string, EnhanceFunction<EnhanceCallType, Params, RouteId, EnhanceReturn>>;
|
|
58
|
+
export type Func = (...args: any[]) => any;
|
|
59
|
+
export type ConcatReturnTypes<T extends readonly Func[]> = T extends readonly [] ? Record<never, never> : T extends readonly [infer First, ...infer Rest] ? First extends Func ? Awaited<ReturnType<First>> & ConcatReturnTypes<Rest extends readonly Func[] ? Rest : []> : Record<never, never> : Record<never, never>;
|
|
60
|
+
export declare const fail: <T extends Record<string, unknown> | undefined = undefined>(status: number, data: T) => never;
|
|
61
|
+
export declare const error: (status: number, body?: {
|
|
62
|
+
message: string;
|
|
63
|
+
} extends App.Error ? App.Error | string | undefined : never) => never;
|
|
64
|
+
export declare const success: <T extends Record<string, unknown> | undefined = undefined>(data: T) => T;
|
|
65
|
+
export declare function not_good(input: {
|
|
66
|
+
callType: EnhanceInput['callType'];
|
|
67
|
+
}, status: number, arg?: App.Error | string | Record<string, unknown> | undefined): never;
|
|
68
|
+
export * from './helpers/index.js';
|