@fieldia/core 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -1
- package/package.json +1 -1
- package/schema/page.schema.json +173 -1
- package/src/index.d.mts +2 -2
- package/src/index.d.ts +2 -2
- package/src/index.js +3 -1
- package/src/index.js.map +1 -1
- package/src/index.mjs +2 -2
- package/src/index.mjs.map +1 -1
- package/src/lib/expression/parser.js +4 -4
- package/src/lib/expression/parser.js.map +1 -1
- package/src/lib/expression/parser.mjs +5 -5
- package/src/lib/expression/parser.mjs.map +1 -1
- package/src/lib/expression/tokenizer.d.mts +12 -0
- package/src/lib/expression/tokenizer.d.ts +12 -0
- package/src/lib/expression/tokenizer.js +21 -12
- package/src/lib/expression/tokenizer.js.map +1 -1
- package/src/lib/expression/tokenizer.mjs +20 -12
- package/src/lib/expression/tokenizer.mjs.map +1 -1
- package/src/lib/format/check-page.d.mts +3 -2
- package/src/lib/format/check-page.d.ts +3 -2
- package/src/lib/format/check-page.js +9 -4
- package/src/lib/format/check-page.js.map +1 -1
- package/src/lib/format/check-page.mjs +10 -5
- package/src/lib/format/check-page.mjs.map +1 -1
- package/src/lib/format/field.d.mts +101 -0
- package/src/lib/format/field.d.ts +101 -0
- package/src/lib/format/field.js +25 -2
- package/src/lib/format/field.js.map +1 -1
- package/src/lib/format/field.mjs +24 -1
- package/src/lib/format/field.mjs.map +1 -1
- package/src/lib/format/layout.d.mts +142 -17
- package/src/lib/format/layout.d.ts +142 -17
- package/src/lib/format/layout.js +18 -1
- package/src/lib/format/layout.js.map +1 -1
- package/src/lib/format/layout.mjs +17 -0
- package/src/lib/format/layout.mjs.map +1 -1
- package/src/lib/format/layout.types.d.mts +6 -2
- package/src/lib/format/layout.types.d.ts +6 -2
- package/src/lib/format/page.d.mts +87 -10
- package/src/lib/format/page.d.ts +87 -10
- package/src/lib/format/page.js +5 -1
- package/src/lib/format/page.js.map +1 -1
- package/src/lib/format/page.mjs +6 -2
- package/src/lib/format/page.mjs.map +1 -1
- package/src/lib/format/pattern.d.mts +10 -0
- package/src/lib/format/pattern.d.ts +10 -0
- package/src/lib/format/pattern.js +33 -0
- package/src/lib/format/pattern.js.map +1 -0
- package/src/lib/format/pattern.mjs +30 -0
- package/src/lib/format/pattern.mjs.map +1 -0
- package/src/lib/format/references.d.mts +7 -0
- package/src/lib/format/references.d.ts +7 -0
- package/src/lib/format/references.js +54 -0
- package/src/lib/format/references.js.map +1 -1
- package/src/lib/format/references.mjs +54 -0
- package/src/lib/format/references.mjs.map +1 -1
- package/src/lib/format/translate.js +6 -4
- package/src/lib/format/translate.js.map +1 -1
- package/src/lib/format/translate.mjs +6 -4
- package/src/lib/format/translate.mjs.map +1 -1
- package/src/lib/format/upgrade.d.mts +41 -0
- package/src/lib/format/upgrade.d.ts +41 -0
- package/src/lib/format/upgrade.js +41 -0
- package/src/lib/format/upgrade.js.map +1 -0
- package/src/lib/format/upgrade.mjs +37 -0
- package/src/lib/format/upgrade.mjs.map +1 -0
- package/src/lib/format/validate.d.mts +2 -1
- package/src/lib/format/validate.d.ts +2 -1
- package/src/lib/format/validate.js +4 -2
- package/src/lib/format/validate.js.map +1 -1
- package/src/lib/format/validate.mjs +4 -2
- package/src/lib/format/validate.mjs.map +1 -1
- package/src/lib/format/version.d.mts +16 -1
- package/src/lib/format/version.d.ts +16 -1
- package/src/lib/format/version.js +39 -2
- package/src/lib/format/version.js.map +1 -1
- package/src/lib/format/version.mjs +36 -1
- package/src/lib/format/version.mjs.map +1 -1
- package/src/lib/record/check.d.mts +13 -3
- package/src/lib/record/check.d.ts +13 -3
- package/src/lib/record/check.js +87 -17
- package/src/lib/record/check.js.map +1 -1
- package/src/lib/record/check.mjs +85 -17
- package/src/lib/record/check.mjs.map +1 -1
- package/src/lib/record/data-source.d.mts +224 -10
- package/src/lib/record/data-source.d.ts +224 -10
- package/src/lib/record/data-source.js +29 -0
- package/src/lib/record/data-source.js.map +1 -1
- package/src/lib/record/data-source.mjs +25 -0
- package/src/lib/record/data-source.mjs.map +1 -1
- package/src/lib/record/filter.js +14 -2
- package/src/lib/record/filter.js.map +1 -1
- package/src/lib/record/filter.mjs +14 -2
- package/src/lib/record/filter.mjs.map +1 -1
- package/src/lib/record/form.d.mts +186 -3
- package/src/lib/record/form.d.ts +186 -3
- package/src/lib/record/form.js +1036 -57
- package/src/lib/record/form.js.map +1 -1
- package/src/lib/record/form.mjs +1037 -60
- package/src/lib/record/form.mjs.map +1 -1
- package/src/lib/record/input-rules.d.mts +277 -0
- package/src/lib/record/input-rules.d.ts +277 -0
- package/src/lib/record/input-rules.js +724 -0
- package/src/lib/record/input-rules.js.map +1 -0
- package/src/lib/record/input-rules.mjs +705 -0
- package/src/lib/record/input-rules.mjs.map +1 -0
- package/src/lib/record/inputs.d.mts +25 -0
- package/src/lib/record/inputs.d.ts +25 -0
- package/src/lib/record/inputs.js +135 -9
- package/src/lib/record/inputs.js.map +1 -1
- package/src/lib/record/inputs.mjs +131 -9
- package/src/lib/record/inputs.mjs.map +1 -1
- package/src/lib/record/locales/ar.js +23 -2
- package/src/lib/record/locales/ar.js.map +1 -1
- package/src/lib/record/locales/ar.mjs +23 -2
- package/src/lib/record/locales/ar.mjs.map +1 -1
- package/src/lib/record/locales/de.js +23 -2
- package/src/lib/record/locales/de.js.map +1 -1
- package/src/lib/record/locales/de.mjs +23 -2
- package/src/lib/record/locales/de.mjs.map +1 -1
- package/src/lib/record/locales/fr.js +23 -2
- package/src/lib/record/locales/fr.js.map +1 -1
- package/src/lib/record/locales/fr.mjs +23 -2
- package/src/lib/record/locales/fr.mjs.map +1 -1
- package/src/lib/record/memory-data-source.d.mts +40 -3
- package/src/lib/record/memory-data-source.d.ts +40 -3
- package/src/lib/record/memory-data-source.js +171 -23
- package/src/lib/record/memory-data-source.js.map +1 -1
- package/src/lib/record/memory-data-source.mjs +171 -23
- package/src/lib/record/memory-data-source.mjs.map +1 -1
- package/src/lib/record/messages.d.mts +43 -0
- package/src/lib/record/messages.d.ts +43 -0
- package/src/lib/record/messages.js +23 -2
- package/src/lib/record/messages.js.map +1 -1
- package/src/lib/record/messages.mjs +23 -2
- package/src/lib/record/messages.mjs.map +1 -1
- package/src/lib/record/rules.js +3 -1
- package/src/lib/record/rules.js.map +1 -1
- package/src/lib/record/rules.mjs +3 -1
- package/src/lib/record/rules.mjs.map +1 -1
- package/src/lib/record/run.d.mts +53 -9
- package/src/lib/record/run.d.ts +53 -9
- package/src/lib/record/run.js +68 -21
- package/src/lib/record/run.js.map +1 -1
- package/src/lib/record/run.mjs +68 -21
- package/src/lib/record/run.mjs.map +1 -1
- package/src/lib/record/search.d.mts +8 -1
- package/src/lib/record/search.d.ts +8 -1
- package/src/lib/record/search.js +28 -8
- package/src/lib/record/search.js.map +1 -1
- package/src/lib/record/search.mjs +28 -8
- package/src/lib/record/search.mjs.map +1 -1
- package/src/lib/record/structures.d.mts +8 -3
- package/src/lib/record/structures.d.ts +8 -3
- package/src/lib/record/structures.js +51 -7
- package/src/lib/record/structures.js.map +1 -1
- package/src/lib/record/structures.mjs +48 -6
- package/src/lib/record/structures.mjs.map +1 -1
- package/src/lib/record/upload-to.d.mts +50 -0
- package/src/lib/record/upload-to.d.ts +50 -0
- package/src/lib/record/upload-to.js +117 -0
- package/src/lib/record/upload-to.js.map +1 -0
- package/src/lib/record/upload-to.mjs +114 -0
- package/src/lib/record/upload-to.mjs.map +1 -0
- package/src/lib/record/uploads.d.mts +152 -0
- package/src/lib/record/uploads.d.ts +152 -0
- package/src/lib/record/uploads.js +345 -0
- package/src/lib/record/uploads.js.map +1 -0
- package/src/lib/record/uploads.mjs +337 -0
- package/src/lib/record/uploads.mjs.map +1 -0
- package/src/lib/record/values.d.mts +6 -0
- package/src/lib/record/values.d.ts +6 -0
- package/src/lib/record/values.js.map +1 -1
- package/src/lib/record/values.mjs.map +1 -1
- package/src/runtime.d.mts +15 -8
- package/src/runtime.d.ts +15 -8
- package/src/runtime.js +48 -1
- package/src/runtime.js.map +1 -1
- package/src/runtime.mjs +12 -5
- package/src/runtime.mjs.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/record/search.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"search.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/record/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,10 +5,15 @@ import type { Value } from './values.mjs';
|
|
|
5
5
|
/**
|
|
6
6
|
* What a structure's widget asks of its answer beyond Required: an address
|
|
7
7
|
* (`json.address`) its parts in `options.requiredParts` filled — the first
|
|
8
|
-
* missing one named — and
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* missing one named — and its postcode of its country's shape where that is
|
|
9
|
+
* known; and a table of lines or a repeating group as few lines as
|
|
10
|
+
* `options.min` and as many as `options.max`, once it has any. Undefined when
|
|
11
|
+
* it keeps them, or when there is nothing to check.
|
|
11
12
|
*/
|
|
12
13
|
/** An address's parts, in the order its boxes and its messages name them. */
|
|
13
14
|
export declare const ADDRESS_PARTS: readonly ["street", "line2", "city", "region", "postcode", "country"];
|
|
15
|
+
/** A postcode as it is kept: digits in any script as 0-9, no direction marks, no spaces at its ends. */
|
|
16
|
+
export declare const settledPostcode: (text: string) => string;
|
|
17
|
+
/** Whether a postcode is of its country's shape: null when the country's shape is not known, or there is no postcode. */
|
|
18
|
+
export declare function postcodeFits(country: unknown, postcode: unknown): boolean | null;
|
|
14
19
|
export declare function checkStructure(field: Field, node: FieldNode, value: Value | undefined, messages: Messages): string | undefined;
|
|
@@ -5,10 +5,15 @@ import type { Value } from './values';
|
|
|
5
5
|
/**
|
|
6
6
|
* What a structure's widget asks of its answer beyond Required: an address
|
|
7
7
|
* (`json.address`) its parts in `options.requiredParts` filled — the first
|
|
8
|
-
* missing one named — and
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* missing one named — and its postcode of its country's shape where that is
|
|
9
|
+
* known; and a table of lines or a repeating group as few lines as
|
|
10
|
+
* `options.min` and as many as `options.max`, once it has any. Undefined when
|
|
11
|
+
* it keeps them, or when there is nothing to check.
|
|
11
12
|
*/
|
|
12
13
|
/** An address's parts, in the order its boxes and its messages name them. */
|
|
13
14
|
export declare const ADDRESS_PARTS: readonly ["street", "line2", "city", "region", "postcode", "country"];
|
|
15
|
+
/** A postcode as it is kept: digits in any script as 0-9, no direction marks, no spaces at its ends. */
|
|
16
|
+
export declare const settledPostcode: (text: string) => string;
|
|
17
|
+
/** Whether a postcode is of its country's shape: null when the country's shape is not known, or there is no postcode. */
|
|
18
|
+
export declare function postcodeFits(country: unknown, postcode: unknown): boolean | null;
|
|
14
19
|
export declare function checkStructure(field: Field, node: FieldNode, value: Value | undefined, messages: Messages): string | undefined;
|
|
@@ -1,17 +1,54 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ADDRESS_PARTS = void 0;
|
|
3
|
+
exports.settledPostcode = exports.ADDRESS_PARTS = void 0;
|
|
4
|
+
exports.postcodeFits = postcodeFits;
|
|
4
5
|
exports.checkStructure = checkStructure;
|
|
6
|
+
const input_rules_1 = require("./input-rules");
|
|
5
7
|
const messages_1 = require("./messages");
|
|
6
8
|
/**
|
|
7
9
|
* What a structure's widget asks of its answer beyond Required: an address
|
|
8
10
|
* (`json.address`) its parts in `options.requiredParts` filled — the first
|
|
9
|
-
* missing one named — and
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* missing one named — and its postcode of its country's shape where that is
|
|
12
|
+
* known; and a table of lines or a repeating group as few lines as
|
|
13
|
+
* `options.min` and as many as `options.max`, once it has any. Undefined when
|
|
14
|
+
* it keeps them, or when there is nothing to check.
|
|
12
15
|
*/
|
|
13
16
|
/** An address's parts, in the order its boxes and its messages name them. */
|
|
14
17
|
exports.ADDRESS_PARTS = ['street', 'line2', 'city', 'region', 'postcode', 'country'];
|
|
18
|
+
/**
|
|
19
|
+
* A postcode's shape by its country, where it is known: how many digits, and
|
|
20
|
+
* one as the country writes it. Egypt's, Saudi Arabia's, Kuwait's and
|
|
21
|
+
* Jordan's have five; Bahrain's three or four; Oman's three. Others are not
|
|
22
|
+
* checked.
|
|
23
|
+
*/
|
|
24
|
+
const POSTCODES = {
|
|
25
|
+
EG: { least: 5, most: 5, example: '11511' },
|
|
26
|
+
SA: { least: 5, most: 5, example: '12271' },
|
|
27
|
+
KW: { least: 5, most: 5, example: '13001' },
|
|
28
|
+
JO: { least: 5, most: 5, example: '11118' },
|
|
29
|
+
BH: { least: 3, most: 4, example: '317' },
|
|
30
|
+
OM: { least: 3, most: 3, example: '112' },
|
|
31
|
+
};
|
|
32
|
+
/** A postcode as it is kept: digits in any script as 0-9, no direction marks, no spaces at its ends. */
|
|
33
|
+
const settledPostcode = (text) => (0, input_rules_1.foldDigits)((0, input_rules_1.stripMarks)(text)).trim();
|
|
34
|
+
exports.settledPostcode = settledPostcode;
|
|
35
|
+
/** Whether a postcode is of its country's shape: null when the country's shape is not known, or there is no postcode. */
|
|
36
|
+
function postcodeFits(country, postcode) {
|
|
37
|
+
const shape = typeof country === 'string' ? POSTCODES[country.toUpperCase()] : undefined;
|
|
38
|
+
if (!shape || typeof postcode !== 'string' || !postcode.trim())
|
|
39
|
+
return null;
|
|
40
|
+
const digits = (0, exports.settledPostcode)(postcode);
|
|
41
|
+
return /^[0-9]+$/.test(digits) && digits.length >= shape.least && digits.length <= shape.most;
|
|
42
|
+
}
|
|
43
|
+
/** A country's name in a language, by its code: Egypt, مصر, Égypte, Ägypten. */
|
|
44
|
+
function countryName(code, locale) {
|
|
45
|
+
try {
|
|
46
|
+
return new Intl.DisplayNames([locale], { type: 'region' }).of(code) ?? code;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return code;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
15
52
|
function checkStructure(field, node, value, messages) {
|
|
16
53
|
const options = node.options ?? {};
|
|
17
54
|
const label = node.label ?? field.label;
|
|
@@ -21,12 +58,19 @@ function checkStructure(field, node, value, messages) {
|
|
|
21
58
|
if (typeof options['max'] === 'number' && value.length > options['max'])
|
|
22
59
|
return (0, messages_1.fill)(messages.maxLines, { label, max: options['max'] });
|
|
23
60
|
}
|
|
24
|
-
const needed = options['requiredParts'];
|
|
25
|
-
if (node.widget !== 'address' || !
|
|
61
|
+
const needed = Array.isArray(options['requiredParts']) ? options['requiredParts'] : [];
|
|
62
|
+
if (node.widget !== 'address' || !value || typeof value !== 'object' || Array.isArray(value))
|
|
26
63
|
return undefined;
|
|
27
64
|
const asked = Array.isArray(options['parts']) ? options['parts'] : exports.ADDRESS_PARTS;
|
|
28
65
|
const parts = value;
|
|
29
66
|
const missing = exports.ADDRESS_PARTS.findIndex((part) => needed.includes(part) && asked.includes(part) && !String(parts[part] ?? '').trim());
|
|
30
|
-
|
|
67
|
+
if (missing >= 0)
|
|
68
|
+
return (0, messages_1.fill)(messages.required, { label: messages.addressParts.split('|')[missing] });
|
|
69
|
+
if (postcodeFits(parts['country'], parts['postcode']) !== false)
|
|
70
|
+
return undefined;
|
|
71
|
+
const country = String(parts['country']).toUpperCase();
|
|
72
|
+
const shape = POSTCODES[country];
|
|
73
|
+
const n = shape.least === shape.most ? shape.least : `${shape.least}–${shape.most}`;
|
|
74
|
+
return (0, messages_1.fill)(messages.postcode, { label, country: countryName(country, messages.locale), n, example: shape.example });
|
|
31
75
|
}
|
|
32
76
|
//# sourceMappingURL=structures.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structures.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/record/structures.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"structures.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/record/structures.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,48 @@
|
|
|
1
|
+
import { foldDigits, stripMarks } from './input-rules.mjs';
|
|
1
2
|
import { fill } from './messages.mjs';
|
|
2
3
|
/**
|
|
3
4
|
* What a structure's widget asks of its answer beyond Required: an address
|
|
4
5
|
* (`json.address`) its parts in `options.requiredParts` filled — the first
|
|
5
|
-
* missing one named — and
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* missing one named — and its postcode of its country's shape where that is
|
|
7
|
+
* known; and a table of lines or a repeating group as few lines as
|
|
8
|
+
* `options.min` and as many as `options.max`, once it has any. Undefined when
|
|
9
|
+
* it keeps them, or when there is nothing to check.
|
|
8
10
|
*/
|
|
9
11
|
/** An address's parts, in the order its boxes and its messages name them. */
|
|
10
12
|
export const ADDRESS_PARTS = ['street', 'line2', 'city', 'region', 'postcode', 'country'];
|
|
13
|
+
/**
|
|
14
|
+
* A postcode's shape by its country, where it is known: how many digits, and
|
|
15
|
+
* one as the country writes it. Egypt's, Saudi Arabia's, Kuwait's and
|
|
16
|
+
* Jordan's have five; Bahrain's three or four; Oman's three. Others are not
|
|
17
|
+
* checked.
|
|
18
|
+
*/
|
|
19
|
+
const POSTCODES = {
|
|
20
|
+
EG: { least: 5, most: 5, example: '11511' },
|
|
21
|
+
SA: { least: 5, most: 5, example: '12271' },
|
|
22
|
+
KW: { least: 5, most: 5, example: '13001' },
|
|
23
|
+
JO: { least: 5, most: 5, example: '11118' },
|
|
24
|
+
BH: { least: 3, most: 4, example: '317' },
|
|
25
|
+
OM: { least: 3, most: 3, example: '112' },
|
|
26
|
+
};
|
|
27
|
+
/** A postcode as it is kept: digits in any script as 0-9, no direction marks, no spaces at its ends. */
|
|
28
|
+
export const settledPostcode = (text) => foldDigits(stripMarks(text)).trim();
|
|
29
|
+
/** Whether a postcode is of its country's shape: null when the country's shape is not known, or there is no postcode. */
|
|
30
|
+
export function postcodeFits(country, postcode) {
|
|
31
|
+
const shape = typeof country === 'string' ? POSTCODES[country.toUpperCase()] : undefined;
|
|
32
|
+
if (!shape || typeof postcode !== 'string' || !postcode.trim())
|
|
33
|
+
return null;
|
|
34
|
+
const digits = settledPostcode(postcode);
|
|
35
|
+
return /^[0-9]+$/.test(digits) && digits.length >= shape.least && digits.length <= shape.most;
|
|
36
|
+
}
|
|
37
|
+
/** A country's name in a language, by its code: Egypt, مصر, Égypte, Ägypten. */
|
|
38
|
+
function countryName(code, locale) {
|
|
39
|
+
try {
|
|
40
|
+
return new Intl.DisplayNames([locale], { type: 'region' }).of(code) ?? code;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return code;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
11
46
|
export function checkStructure(field, node, value, messages) {
|
|
12
47
|
const options = node.options ?? {};
|
|
13
48
|
const label = node.label ?? field.label;
|
|
@@ -17,12 +52,19 @@ export function checkStructure(field, node, value, messages) {
|
|
|
17
52
|
if (typeof options['max'] === 'number' && value.length > options['max'])
|
|
18
53
|
return fill(messages.maxLines, { label, max: options['max'] });
|
|
19
54
|
}
|
|
20
|
-
const needed = options['requiredParts'];
|
|
21
|
-
if (node.widget !== 'address' || !
|
|
55
|
+
const needed = Array.isArray(options['requiredParts']) ? options['requiredParts'] : [];
|
|
56
|
+
if (node.widget !== 'address' || !value || typeof value !== 'object' || Array.isArray(value))
|
|
22
57
|
return undefined;
|
|
23
58
|
const asked = Array.isArray(options['parts']) ? options['parts'] : ADDRESS_PARTS;
|
|
24
59
|
const parts = value;
|
|
25
60
|
const missing = ADDRESS_PARTS.findIndex((part) => needed.includes(part) && asked.includes(part) && !String(parts[part] ?? '').trim());
|
|
26
|
-
|
|
61
|
+
if (missing >= 0)
|
|
62
|
+
return fill(messages.required, { label: messages.addressParts.split('|')[missing] });
|
|
63
|
+
if (postcodeFits(parts['country'], parts['postcode']) !== false)
|
|
64
|
+
return undefined;
|
|
65
|
+
const country = String(parts['country']).toUpperCase();
|
|
66
|
+
const shape = POSTCODES[country];
|
|
67
|
+
const n = shape.least === shape.most ? shape.least : `${shape.least}–${shape.most}`;
|
|
68
|
+
return fill(messages.postcode, { label, country: countryName(country, messages.locale), n, example: shape.example });
|
|
27
69
|
}
|
|
28
70
|
//# sourceMappingURL=structures.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structures.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/record/structures.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAiB,MAAM,YAAY,CAAC;AAGjD
|
|
1
|
+
{"version":3,"file":"structures.mjs","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/record/structures.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type UploadedFile, type UploadFile, type UploadRequest } from './data-source.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* A data source's `upload` for a plain endpoint, written once: a POST of a
|
|
4
|
+
* form with the file in it, or a PUT of the file itself to an address the
|
|
5
|
+
* app's server signs for it (Azure Blob's SAS, S3's presigned URL). Through
|
|
6
|
+
* XMLHttpRequest in a browser, so its progress is told; through fetch where
|
|
7
|
+
* there is none. Core names no DOM types: the few it uses are read from the
|
|
8
|
+
* host, as far as this file needs them.
|
|
9
|
+
*
|
|
10
|
+
* upload: uploadTo({ url: '/api/files', headers: { 'X-CSRF-Token': token } })
|
|
11
|
+
*/
|
|
12
|
+
/** Where one file goes, with headers of its own beside the options': a signed address's. */
|
|
13
|
+
export interface UploadTarget {
|
|
14
|
+
url: string;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
/** The server's answer, for `link` to read the stored file's address from. */
|
|
18
|
+
export interface UploadAnswer {
|
|
19
|
+
status: number;
|
|
20
|
+
/** The address the file was sent to. */
|
|
21
|
+
url: string;
|
|
22
|
+
/** The answer's body, as text. */
|
|
23
|
+
text: string;
|
|
24
|
+
/** The body read as JSON, or undefined when it is not JSON. */
|
|
25
|
+
json: unknown;
|
|
26
|
+
header(name: string): string | null;
|
|
27
|
+
}
|
|
28
|
+
type Maybe<T> = T | Promise<T>;
|
|
29
|
+
export interface UploadToOptions {
|
|
30
|
+
/** Where each file goes: an address, or a function of the file and the request giving one — an address your server signs, say. */
|
|
31
|
+
url: string | ((file: UploadFile, request: UploadRequest) => Maybe<string | UploadTarget>);
|
|
32
|
+
/** POST, the usual, sends a form with the file under `field`; PUT sends the file itself as the body, as a signed address takes it. */
|
|
33
|
+
method?: 'POST' | 'PUT';
|
|
34
|
+
/** The form's field the file goes under, with POST: `file` unless said. */
|
|
35
|
+
field?: string;
|
|
36
|
+
/** Headers for every file: a token, say. Content-Type is the file's own with PUT, and the form's with POST. */
|
|
37
|
+
headers?: Record<string, string> | ((file: UploadFile, request: UploadRequest) => Maybe<Record<string, string>>);
|
|
38
|
+
/** Send the browser's cookies to another origin too. */
|
|
39
|
+
withCredentials?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The stored file's address and id, from the answer. Without it: the JSON's
|
|
42
|
+
* `url` (or `location`) and its `fileId` (or `id`), else the Location
|
|
43
|
+
* header, else — for a PUT — the address it was put to, without its query
|
|
44
|
+
* (a signature is not a link to keep).
|
|
45
|
+
*/
|
|
46
|
+
link?: (answer: UploadAnswer, file: UploadFile) => Maybe<Partial<UploadedFile>>;
|
|
47
|
+
}
|
|
48
|
+
/** An upload to a plain endpoint, for a data source: `{ ...source, upload: uploadTo({ url: '/api/files' }) }`. */
|
|
49
|
+
export declare function uploadTo(options: UploadToOptions): (file: UploadFile, request: UploadRequest) => Promise<UploadedFile>;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type UploadedFile, type UploadFile, type UploadRequest } from './data-source';
|
|
2
|
+
/**
|
|
3
|
+
* A data source's `upload` for a plain endpoint, written once: a POST of a
|
|
4
|
+
* form with the file in it, or a PUT of the file itself to an address the
|
|
5
|
+
* app's server signs for it (Azure Blob's SAS, S3's presigned URL). Through
|
|
6
|
+
* XMLHttpRequest in a browser, so its progress is told; through fetch where
|
|
7
|
+
* there is none. Core names no DOM types: the few it uses are read from the
|
|
8
|
+
* host, as far as this file needs them.
|
|
9
|
+
*
|
|
10
|
+
* upload: uploadTo({ url: '/api/files', headers: { 'X-CSRF-Token': token } })
|
|
11
|
+
*/
|
|
12
|
+
/** Where one file goes, with headers of its own beside the options': a signed address's. */
|
|
13
|
+
export interface UploadTarget {
|
|
14
|
+
url: string;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
/** The server's answer, for `link` to read the stored file's address from. */
|
|
18
|
+
export interface UploadAnswer {
|
|
19
|
+
status: number;
|
|
20
|
+
/** The address the file was sent to. */
|
|
21
|
+
url: string;
|
|
22
|
+
/** The answer's body, as text. */
|
|
23
|
+
text: string;
|
|
24
|
+
/** The body read as JSON, or undefined when it is not JSON. */
|
|
25
|
+
json: unknown;
|
|
26
|
+
header(name: string): string | null;
|
|
27
|
+
}
|
|
28
|
+
type Maybe<T> = T | Promise<T>;
|
|
29
|
+
export interface UploadToOptions {
|
|
30
|
+
/** Where each file goes: an address, or a function of the file and the request giving one — an address your server signs, say. */
|
|
31
|
+
url: string | ((file: UploadFile, request: UploadRequest) => Maybe<string | UploadTarget>);
|
|
32
|
+
/** POST, the usual, sends a form with the file under `field`; PUT sends the file itself as the body, as a signed address takes it. */
|
|
33
|
+
method?: 'POST' | 'PUT';
|
|
34
|
+
/** The form's field the file goes under, with POST: `file` unless said. */
|
|
35
|
+
field?: string;
|
|
36
|
+
/** Headers for every file: a token, say. Content-Type is the file's own with PUT, and the form's with POST. */
|
|
37
|
+
headers?: Record<string, string> | ((file: UploadFile, request: UploadRequest) => Maybe<Record<string, string>>);
|
|
38
|
+
/** Send the browser's cookies to another origin too. */
|
|
39
|
+
withCredentials?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The stored file's address and id, from the answer. Without it: the JSON's
|
|
42
|
+
* `url` (or `location`) and its `fileId` (or `id`), else the Location
|
|
43
|
+
* header, else — for a PUT — the address it was put to, without its query
|
|
44
|
+
* (a signature is not a link to keep).
|
|
45
|
+
*/
|
|
46
|
+
link?: (answer: UploadAnswer, file: UploadFile) => Maybe<Partial<UploadedFile>>;
|
|
47
|
+
}
|
|
48
|
+
/** An upload to a plain endpoint, for a data source: `{ ...source, upload: uploadTo({ url: '/api/files' }) }`. */
|
|
49
|
+
export declare function uploadTo(options: UploadToOptions): (file: UploadFile, request: UploadRequest) => Promise<UploadedFile>;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uploadTo = uploadTo;
|
|
4
|
+
const data_source_1 = require("./data-source");
|
|
5
|
+
/** The statuses that say the store will not keep this file, whatever is tried: its size, its kind, what it holds. */
|
|
6
|
+
const REFUSALS = new Set([400, 409, 413, 415, 422]);
|
|
7
|
+
const stopped = () => Object.assign(new Error('The upload was stopped'), { name: 'AbortError' });
|
|
8
|
+
/** An upload to a plain endpoint, for a data source: `{ ...source, upload: uploadTo({ url: '/api/files' }) }`. */
|
|
9
|
+
function uploadTo(options) {
|
|
10
|
+
const method = options.method ?? 'POST';
|
|
11
|
+
return async (file, request) => {
|
|
12
|
+
if (request.signal?.aborted)
|
|
13
|
+
throw stopped();
|
|
14
|
+
const host = globalThis;
|
|
15
|
+
const given = typeof options.url === 'function' ? await options.url(file, request) : options.url;
|
|
16
|
+
const target = typeof given === 'string' ? { url: given } : given;
|
|
17
|
+
const own = typeof options.headers === 'function' ? await options.headers(file, request) : (options.headers ?? {});
|
|
18
|
+
const headers = { ...own, ...target.headers };
|
|
19
|
+
// A file the browser picked is a Blob already; one held as bytes is made one.
|
|
20
|
+
const blob = file instanceof host.Blob ? file : new host.Blob([await file.arrayBuffer()], { type: file.type });
|
|
21
|
+
let body = blob;
|
|
22
|
+
if (method === 'PUT') {
|
|
23
|
+
if (!Object.keys(headers).some((name) => name.toLowerCase() === 'content-type'))
|
|
24
|
+
headers['Content-Type'] = file.type || 'application/octet-stream';
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const form = new host.FormData();
|
|
28
|
+
form.append(options.field ?? 'file', blob, file.name);
|
|
29
|
+
body = form;
|
|
30
|
+
}
|
|
31
|
+
const answer = await (host.XMLHttpRequest ? viaXhr : viaFetch)(host, { method, url: target.url, headers, body, file, request, withCredentials: options.withCredentials === true });
|
|
32
|
+
if (answer.status < 200 || answer.status >= 300) {
|
|
33
|
+
const said = wordsOf(answer);
|
|
34
|
+
if (REFUSALS.has(answer.status))
|
|
35
|
+
throw (0, data_source_1.saveRefused)({ kind: 'rule', message: said || `The store did not take ${file.name} (${answer.status})` });
|
|
36
|
+
throw new Error(said ? `${said} (${answer.status})` : `The store answered ${answer.status}`);
|
|
37
|
+
}
|
|
38
|
+
const linked = options.link ? await options.link(answer, file) : linkOf(answer, method, host);
|
|
39
|
+
if (!linked.url)
|
|
40
|
+
throw new Error(`The store's answer named no address for ${file.name}: give uploadTo a link() that reads it`);
|
|
41
|
+
return { url: linked.url, name: linked.name ?? file.name, size: linked.size ?? file.size, type: linked.type ?? file.type, ...(linked.fileId !== undefined ? { fileId: linked.fileId } : {}) };
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function viaXhr(host, sending) {
|
|
45
|
+
const { request } = sending;
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const xhr = new host.XMLHttpRequest();
|
|
48
|
+
const stop = () => xhr.abort();
|
|
49
|
+
const done = () => request.signal?.removeEventListener('abort', stop);
|
|
50
|
+
xhr.open(sending.method, sending.url);
|
|
51
|
+
for (const [name, value] of Object.entries(sending.headers))
|
|
52
|
+
xhr.setRequestHeader(name, value);
|
|
53
|
+
if (sending.withCredentials)
|
|
54
|
+
xhr.withCredentials = true;
|
|
55
|
+
xhr.upload.onprogress = (event) => request.onProgress?.(event.loaded, event.lengthComputable ? event.total : sending.file.size);
|
|
56
|
+
xhr.onload = () => (done(), resolve(answered(sending.url, xhr.status, xhr.responseText, (name) => xhr.getResponseHeader(name))));
|
|
57
|
+
// What fetch throws when the network fails: read as the network's problem.
|
|
58
|
+
xhr.onerror = () => (done(), reject(new TypeError('Failed to fetch: the network failed during the upload')));
|
|
59
|
+
xhr.onabort = () => (done(), reject(stopped()));
|
|
60
|
+
request.signal?.addEventListener('abort', stop);
|
|
61
|
+
xhr.send(sending.body);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async function viaFetch(host, sending) {
|
|
65
|
+
if (!host.fetch)
|
|
66
|
+
throw new Error('uploadTo needs XMLHttpRequest or fetch');
|
|
67
|
+
const response = await host.fetch(sending.url, {
|
|
68
|
+
method: sending.method,
|
|
69
|
+
headers: sending.headers,
|
|
70
|
+
body: sending.body,
|
|
71
|
+
...(sending.request.signal ? { signal: sending.request.signal } : {}),
|
|
72
|
+
...(sending.withCredentials ? { credentials: 'include' } : {}),
|
|
73
|
+
});
|
|
74
|
+
const text = await response.text();
|
|
75
|
+
// No progress on the way: the whole of it, once it is there.
|
|
76
|
+
sending.request.onProgress?.(sending.file.size, sending.file.size);
|
|
77
|
+
return answered(sending.url, response.status, text, (name) => response.headers.get(name));
|
|
78
|
+
}
|
|
79
|
+
function answered(url, status, text, header) {
|
|
80
|
+
let json;
|
|
81
|
+
try {
|
|
82
|
+
json = text ? JSON.parse(text) : undefined;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
json = undefined;
|
|
86
|
+
}
|
|
87
|
+
return { status, url, text, json, header };
|
|
88
|
+
}
|
|
89
|
+
/** What a refusing server says, for the person: its JSON's message, or a short text. */
|
|
90
|
+
function wordsOf(answer) {
|
|
91
|
+
const json = answer.json;
|
|
92
|
+
const said = json?.message ?? (typeof json?.error === 'object' ? json.error?.message : json?.error);
|
|
93
|
+
if (typeof said === 'string' && said.trim())
|
|
94
|
+
return said.trim();
|
|
95
|
+
const text = answer.text.trim();
|
|
96
|
+
return text && text.length <= 200 && !/^\s*</.test(text) && answer.json === undefined ? text : '';
|
|
97
|
+
}
|
|
98
|
+
/** The stored file's address as the answer gives it: JSON, Location, or where a PUT put it. */
|
|
99
|
+
function linkOf(answer, method, host) {
|
|
100
|
+
const json = (answer.json && typeof answer.json === 'object' ? answer.json : {});
|
|
101
|
+
const id = json['fileId'] ?? json['id'];
|
|
102
|
+
const fileId = typeof id === 'string' || typeof id === 'number' ? id : undefined;
|
|
103
|
+
const said = json['url'] ?? json['location'];
|
|
104
|
+
const location = answer.header('location');
|
|
105
|
+
const url = typeof said === 'string' ? said : location ? against(location, answer.url, host) : method === 'PUT' ? answer.url.split('?')[0] : undefined;
|
|
106
|
+
return { ...(url ? { url } : {}), ...(fileId !== undefined ? { fileId } : {}) };
|
|
107
|
+
}
|
|
108
|
+
/** A Location read against the address the file was sent to. */
|
|
109
|
+
function against(location, sentTo, host) {
|
|
110
|
+
try {
|
|
111
|
+
return new host.URL(location, new host.URL(sentTo, host.location?.href).href).href;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return location;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=upload-to.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-to.js","sourceRoot":"","sources":["../../../../../../libs/core/src/lib/record/upload-to.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { saveRefused } from './data-source.mjs';
|
|
2
|
+
/** The statuses that say the store will not keep this file, whatever is tried: its size, its kind, what it holds. */
|
|
3
|
+
const REFUSALS = new Set([400, 409, 413, 415, 422]);
|
|
4
|
+
const stopped = () => Object.assign(new Error('The upload was stopped'), { name: 'AbortError' });
|
|
5
|
+
/** An upload to a plain endpoint, for a data source: `{ ...source, upload: uploadTo({ url: '/api/files' }) }`. */
|
|
6
|
+
export function uploadTo(options) {
|
|
7
|
+
const method = options.method ?? 'POST';
|
|
8
|
+
return async (file, request) => {
|
|
9
|
+
if (request.signal?.aborted)
|
|
10
|
+
throw stopped();
|
|
11
|
+
const host = globalThis;
|
|
12
|
+
const given = typeof options.url === 'function' ? await options.url(file, request) : options.url;
|
|
13
|
+
const target = typeof given === 'string' ? { url: given } : given;
|
|
14
|
+
const own = typeof options.headers === 'function' ? await options.headers(file, request) : (options.headers ?? {});
|
|
15
|
+
const headers = { ...own, ...target.headers };
|
|
16
|
+
// A file the browser picked is a Blob already; one held as bytes is made one.
|
|
17
|
+
const blob = file instanceof host.Blob ? file : new host.Blob([await file.arrayBuffer()], { type: file.type });
|
|
18
|
+
let body = blob;
|
|
19
|
+
if (method === 'PUT') {
|
|
20
|
+
if (!Object.keys(headers).some((name) => name.toLowerCase() === 'content-type'))
|
|
21
|
+
headers['Content-Type'] = file.type || 'application/octet-stream';
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const form = new host.FormData();
|
|
25
|
+
form.append(options.field ?? 'file', blob, file.name);
|
|
26
|
+
body = form;
|
|
27
|
+
}
|
|
28
|
+
const answer = await (host.XMLHttpRequest ? viaXhr : viaFetch)(host, { method, url: target.url, headers, body, file, request, withCredentials: options.withCredentials === true });
|
|
29
|
+
if (answer.status < 200 || answer.status >= 300) {
|
|
30
|
+
const said = wordsOf(answer);
|
|
31
|
+
if (REFUSALS.has(answer.status))
|
|
32
|
+
throw saveRefused({ kind: 'rule', message: said || `The store did not take ${file.name} (${answer.status})` });
|
|
33
|
+
throw new Error(said ? `${said} (${answer.status})` : `The store answered ${answer.status}`);
|
|
34
|
+
}
|
|
35
|
+
const linked = options.link ? await options.link(answer, file) : linkOf(answer, method, host);
|
|
36
|
+
if (!linked.url)
|
|
37
|
+
throw new Error(`The store's answer named no address for ${file.name}: give uploadTo a link() that reads it`);
|
|
38
|
+
return { url: linked.url, name: linked.name ?? file.name, size: linked.size ?? file.size, type: linked.type ?? file.type, ...(linked.fileId !== undefined ? { fileId: linked.fileId } : {}) };
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function viaXhr(host, sending) {
|
|
42
|
+
const { request } = sending;
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const xhr = new host.XMLHttpRequest();
|
|
45
|
+
const stop = () => xhr.abort();
|
|
46
|
+
const done = () => request.signal?.removeEventListener('abort', stop);
|
|
47
|
+
xhr.open(sending.method, sending.url);
|
|
48
|
+
for (const [name, value] of Object.entries(sending.headers))
|
|
49
|
+
xhr.setRequestHeader(name, value);
|
|
50
|
+
if (sending.withCredentials)
|
|
51
|
+
xhr.withCredentials = true;
|
|
52
|
+
xhr.upload.onprogress = (event) => request.onProgress?.(event.loaded, event.lengthComputable ? event.total : sending.file.size);
|
|
53
|
+
xhr.onload = () => (done(), resolve(answered(sending.url, xhr.status, xhr.responseText, (name) => xhr.getResponseHeader(name))));
|
|
54
|
+
// What fetch throws when the network fails: read as the network's problem.
|
|
55
|
+
xhr.onerror = () => (done(), reject(new TypeError('Failed to fetch: the network failed during the upload')));
|
|
56
|
+
xhr.onabort = () => (done(), reject(stopped()));
|
|
57
|
+
request.signal?.addEventListener('abort', stop);
|
|
58
|
+
xhr.send(sending.body);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async function viaFetch(host, sending) {
|
|
62
|
+
if (!host.fetch)
|
|
63
|
+
throw new Error('uploadTo needs XMLHttpRequest or fetch');
|
|
64
|
+
const response = await host.fetch(sending.url, {
|
|
65
|
+
method: sending.method,
|
|
66
|
+
headers: sending.headers,
|
|
67
|
+
body: sending.body,
|
|
68
|
+
...(sending.request.signal ? { signal: sending.request.signal } : {}),
|
|
69
|
+
...(sending.withCredentials ? { credentials: 'include' } : {}),
|
|
70
|
+
});
|
|
71
|
+
const text = await response.text();
|
|
72
|
+
// No progress on the way: the whole of it, once it is there.
|
|
73
|
+
sending.request.onProgress?.(sending.file.size, sending.file.size);
|
|
74
|
+
return answered(sending.url, response.status, text, (name) => response.headers.get(name));
|
|
75
|
+
}
|
|
76
|
+
function answered(url, status, text, header) {
|
|
77
|
+
let json;
|
|
78
|
+
try {
|
|
79
|
+
json = text ? JSON.parse(text) : undefined;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
json = undefined;
|
|
83
|
+
}
|
|
84
|
+
return { status, url, text, json, header };
|
|
85
|
+
}
|
|
86
|
+
/** What a refusing server says, for the person: its JSON's message, or a short text. */
|
|
87
|
+
function wordsOf(answer) {
|
|
88
|
+
const json = answer.json;
|
|
89
|
+
const said = json?.message ?? (typeof json?.error === 'object' ? json.error?.message : json?.error);
|
|
90
|
+
if (typeof said === 'string' && said.trim())
|
|
91
|
+
return said.trim();
|
|
92
|
+
const text = answer.text.trim();
|
|
93
|
+
return text && text.length <= 200 && !/^\s*</.test(text) && answer.json === undefined ? text : '';
|
|
94
|
+
}
|
|
95
|
+
/** The stored file's address as the answer gives it: JSON, Location, or where a PUT put it. */
|
|
96
|
+
function linkOf(answer, method, host) {
|
|
97
|
+
const json = (answer.json && typeof answer.json === 'object' ? answer.json : {});
|
|
98
|
+
const id = json['fileId'] ?? json['id'];
|
|
99
|
+
const fileId = typeof id === 'string' || typeof id === 'number' ? id : undefined;
|
|
100
|
+
const said = json['url'] ?? json['location'];
|
|
101
|
+
const location = answer.header('location');
|
|
102
|
+
const url = typeof said === 'string' ? said : location ? against(location, answer.url, host) : method === 'PUT' ? answer.url.split('?')[0] : undefined;
|
|
103
|
+
return { ...(url ? { url } : {}), ...(fileId !== undefined ? { fileId } : {}) };
|
|
104
|
+
}
|
|
105
|
+
/** A Location read against the address the file was sent to. */
|
|
106
|
+
function against(location, sentTo, host) {
|
|
107
|
+
try {
|
|
108
|
+
return new host.URL(location, new host.URL(sentTo, host.location?.href).href).href;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return location;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=upload-to.mjs.map
|