@dereekb/model 13.0.7 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +745 -421
- package/index.cjs.js.map +1 -1
- package/index.esm.js +730 -402
- package/index.esm.js.map +1 -1
- package/package.json +3 -6
- package/src/lib/data/address/address.d.ts +36 -15
- package/src/lib/data/website/link.d.ts +38 -9
- package/src/lib/data/website/link.file.d.ts +97 -18
- package/src/lib/data/website/link.website.d.ts +254 -3
- package/src/lib/index.d.ts +1 -0
- package/src/lib/service/permission/permission.d.ts +37 -1
- package/src/lib/service/permission/role.d.ts +71 -9
- package/src/lib/service/sync/sync.entity.d.ts +32 -3
- package/src/lib/service/sync/sync.entity.synchronizer.basic.d.ts +15 -0
- package/src/lib/service/sync/sync.entity.synchronizer.d.ts +20 -0
- package/src/lib/transform/index.d.ts +0 -2
- package/src/lib/transform/transform.d.ts +54 -25
- package/src/lib/transform/transform.function.d.ts +22 -2
- package/src/lib/transform/transform.result.d.ts +10 -2
- package/src/lib/type/index.d.ts +2 -0
- package/src/lib/type/model.d.ts +20 -0
- package/src/lib/type/type.d.ts +21 -0
- package/src/lib/validator/date.d.ts +2 -4
- package/src/lib/validator/number.d.ts +2 -4
- package/src/lib/validator/phone.d.ts +6 -14
- package/src/lib/validator/unique.d.ts +11 -4
- package/src/lib/validator/url.d.ts +4 -6
- package/src/lib/transform/type.annotation.d.ts +0 -5
- package/src/lib/transform/type.d.ts +0 -6
package/index.esm.js
CHANGED
|
@@ -1,237 +1,215 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { IsString, IsNotEmpty, MaxLength, IsOptional, Matches, MinLength, validate, registerDecorator, buildMessage } from 'class-validator';
|
|
1
|
+
import { US_STATE_CODE_STRING_REGEX, splitJoinRemainder, filterFalsyAndEmptyValues, isolateWebsitePathFunction, toRelativeSlashPathStartType, addPrefix, hasWebsiteDomain, toAbsoluteSlashPathStartType, removeHttpFromUrl, forEachKeyValue, iterableToArray, arrayToObject, MAP_IDENTITY, makeValuesGroupMap, cachedGetter, filterMaybeArrayValues, sortByNumberFunction, performAsyncTasks, pushArrayItemsIntoArray, mapPromiseOrValue, isISO8601DayString, isMinuteOfDay, isE164PhoneNumber, isE164PhoneNumberWithExtension, isUniqueKeyedFunction, isWebsiteUrl, isWebsiteUrlWithPrefix } from '@dereekb/util';
|
|
2
|
+
import { type } from 'arktype';
|
|
4
3
|
import { BaseError } from 'make-error';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
10
|
-
purpose with or without fee is hereby granted.
|
|
11
|
-
|
|
12
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
13
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
14
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
15
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
16
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
17
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
18
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
19
|
-
***************************************************************************** */
|
|
20
|
-
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
function __decorate(decorators, target, key, desc) {
|
|
24
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
25
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
26
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
27
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function __metadata(metadataKey, metadataValue) {
|
|
31
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
35
|
-
var e = new Error(message);
|
|
36
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
37
|
-
};
|
|
38
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Maximum character length for address line fields (line1, line2).
|
|
7
|
+
*/
|
|
39
8
|
const ADDRESS_LINE_MAX_LENGTH = 50;
|
|
9
|
+
/**
|
|
10
|
+
* Maximum character length for city names.
|
|
11
|
+
*/
|
|
40
12
|
const ADDRESS_CITY_MAX_LENGTH = 80;
|
|
13
|
+
/**
|
|
14
|
+
* Maximum character length for full state names (e.g., "Texas").
|
|
15
|
+
*/
|
|
41
16
|
const ADDRESS_STATE_MAX_LENGTH = 30;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum character length for two-letter state codes (e.g., "TX").
|
|
19
|
+
*/
|
|
42
20
|
const ADDRESS_STATE_CODE_MAX_LENGTH = 2;
|
|
21
|
+
/**
|
|
22
|
+
* Maximum character length for ZIP codes, accommodating ZIP+4 format (e.g., "77834-1234").
|
|
23
|
+
*/
|
|
43
24
|
const ADDRESS_ZIP_MAX_LENGTH = 11;
|
|
25
|
+
/**
|
|
26
|
+
* Maximum character length for country names.
|
|
27
|
+
*/
|
|
44
28
|
const ADDRESS_COUNTRY_MAX_LENGTH = 80;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
Expose(),
|
|
67
|
-
IsString(),
|
|
68
|
-
IsNotEmpty(),
|
|
69
|
-
MaxLength(ADDRESS_CITY_MAX_LENGTH),
|
|
70
|
-
__metadata("design:type", String)
|
|
71
|
-
], AbstractUnitedStatesAddressWithoutStateParams.prototype, "city", void 0);
|
|
72
|
-
__decorate([
|
|
73
|
-
Expose(),
|
|
74
|
-
IsString(),
|
|
75
|
-
IsNotEmpty(),
|
|
76
|
-
Matches(ZIP_CODE_STRING_REGEX),
|
|
77
|
-
MaxLength(ADDRESS_ZIP_MAX_LENGTH),
|
|
78
|
-
__metadata("design:type", String)
|
|
79
|
-
], AbstractUnitedStatesAddressWithoutStateParams.prototype, "zip", void 0);
|
|
80
|
-
/**
|
|
81
|
-
* UnitedStatesAddress that enforces a StateCode for the state value.
|
|
82
|
-
*/
|
|
83
|
-
class UnitedStatesAddressWithStateCodeParams extends AbstractUnitedStatesAddressWithoutStateParams {
|
|
84
|
-
state;
|
|
85
|
-
}
|
|
86
|
-
__decorate([
|
|
87
|
-
Expose(),
|
|
88
|
-
IsString(),
|
|
89
|
-
Matches(US_STATE_CODE_STRING_REGEX),
|
|
90
|
-
MinLength(ADDRESS_STATE_CODE_MAX_LENGTH),
|
|
91
|
-
MaxLength(ADDRESS_STATE_CODE_MAX_LENGTH),
|
|
92
|
-
__metadata("design:type", String)
|
|
93
|
-
], UnitedStatesAddressWithStateCodeParams.prototype, "state", void 0);
|
|
94
|
-
/**
|
|
95
|
-
* UnitedStatesAddress that enforces a State for the state value.
|
|
96
|
-
*/
|
|
97
|
-
class UnitedStatesAddressWithStateStringParams extends AbstractUnitedStatesAddressWithoutStateParams {
|
|
98
|
-
state;
|
|
99
|
-
}
|
|
100
|
-
__decorate([
|
|
101
|
-
Expose(),
|
|
102
|
-
IsString(),
|
|
103
|
-
IsNotEmpty(),
|
|
104
|
-
MaxLength(ADDRESS_STATE_MAX_LENGTH),
|
|
105
|
-
__metadata("design:type", String)
|
|
106
|
-
], UnitedStatesAddressWithStateStringParams.prototype, "state", void 0);
|
|
29
|
+
/**
|
|
30
|
+
* Base ArkType schema for United States address fields without the state.
|
|
31
|
+
*/
|
|
32
|
+
const baseUnitedStatesAddressType = type({
|
|
33
|
+
line1: `0 < string <= ${ADDRESS_LINE_MAX_LENGTH}`,
|
|
34
|
+
'line2?': `string <= ${ADDRESS_LINE_MAX_LENGTH}`,
|
|
35
|
+
city: `0 < string <= ${ADDRESS_CITY_MAX_LENGTH}`,
|
|
36
|
+
zip: [/^\d{5}(-\d{4})?$/, '&', `string <= ${ADDRESS_ZIP_MAX_LENGTH}`]
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* ArkType schema for a United States address with a two-letter state code (e.g., "TX").
|
|
40
|
+
*/
|
|
41
|
+
const unitedStatesAddressWithStateCodeType = baseUnitedStatesAddressType.merge({
|
|
42
|
+
state: [US_STATE_CODE_STRING_REGEX, '&', `${ADDRESS_STATE_CODE_MAX_LENGTH} <= string <= ${ADDRESS_STATE_CODE_MAX_LENGTH}`]
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* ArkType schema for a United States address with a full state name (e.g., "Texas").
|
|
46
|
+
*/
|
|
47
|
+
const unitedStatesAddressWithStateStringType = baseUnitedStatesAddressType.merge({
|
|
48
|
+
state: `0 < string <= ${ADDRESS_STATE_MAX_LENGTH}`
|
|
49
|
+
});
|
|
107
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Fallback link type used when the actual type is not known.
|
|
53
|
+
*/
|
|
108
54
|
const UNKNOWN_WEBSITE_LINK_TYPE = 'u';
|
|
109
55
|
/**
|
|
110
|
-
*
|
|
56
|
+
* Maximum character length for a {@link WebsiteLinkType} string.
|
|
111
57
|
*/
|
|
112
58
|
const WEBSITE_LINK_TYPE_MAX_LENGTH = 32;
|
|
113
59
|
/**
|
|
114
|
-
*
|
|
60
|
+
* Regex pattern that validates a {@link WebsiteLinkType} as 1-32 alphanumeric characters.
|
|
115
61
|
*/
|
|
116
62
|
const WEBSITE_LINK_TYPE_REGEX = /^[a-zA-Z0-9]{1,32}$/;
|
|
63
|
+
/**
|
|
64
|
+
* Checks whether the given string is a valid {@link WebsiteLinkType}.
|
|
65
|
+
*
|
|
66
|
+
* @param input - the string to validate
|
|
67
|
+
* @returns true if it matches the alphanumeric 1-32 character pattern
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* isValidWebsiteLinkType('fb'); // true
|
|
72
|
+
* isValidWebsiteLinkType(''); // false
|
|
73
|
+
* isValidWebsiteLinkType('a-b'); // false (hyphen not allowed)
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
117
76
|
function isValidWebsiteLinkType(input) {
|
|
118
77
|
return WEBSITE_LINK_TYPE_REGEX.test(input);
|
|
119
78
|
}
|
|
120
79
|
/**
|
|
121
|
-
*
|
|
80
|
+
* Maximum character length for the encoded data string in a {@link WebsiteLink}.
|
|
122
81
|
*/
|
|
123
82
|
const WEBSITE_LINK_ENCODED_DATA_MAX_LENGTH = 1000;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
__decorate([
|
|
135
|
-
Expose(),
|
|
136
|
-
IsString(),
|
|
137
|
-
IsNotEmpty(),
|
|
138
|
-
Matches(WEBSITE_LINK_TYPE_REGEX),
|
|
139
|
-
MaxLength(WEBSITE_LINK_TYPE_MAX_LENGTH),
|
|
140
|
-
__metadata("design:type", String)
|
|
141
|
-
], WebsiteLink.prototype, "t", void 0);
|
|
142
|
-
__decorate([
|
|
143
|
-
Expose(),
|
|
144
|
-
IsString(),
|
|
145
|
-
IsNotEmpty(),
|
|
146
|
-
MaxLength(WEBSITE_LINK_ENCODED_DATA_MAX_LENGTH),
|
|
147
|
-
__metadata("design:type", String)
|
|
148
|
-
], WebsiteLink.prototype, "d", void 0);
|
|
83
|
+
/**
|
|
84
|
+
* ArkType schema for a {@link WebsiteLink}.
|
|
85
|
+
*/
|
|
86
|
+
const websiteLinkType = type({
|
|
87
|
+
t: [WEBSITE_LINK_TYPE_REGEX, '&', `0 < string <= ${WEBSITE_LINK_TYPE_MAX_LENGTH}`],
|
|
88
|
+
d: `0 < string <= ${WEBSITE_LINK_ENCODED_DATA_MAX_LENGTH}`
|
|
89
|
+
});
|
|
149
90
|
|
|
150
91
|
/**
|
|
151
|
-
*
|
|
92
|
+
* Maximum character length for a {@link WebsiteFileLinkType}. Matches {@link WEBSITE_LINK_TYPE_MAX_LENGTH}.
|
|
152
93
|
*/
|
|
153
94
|
const WEBSITE_FILE_LINK_TYPE_MAX_LENGTH = WEBSITE_LINK_TYPE_MAX_LENGTH;
|
|
154
95
|
/**
|
|
155
|
-
*
|
|
96
|
+
* Validation regex for {@link WebsiteFileLinkType}. Matches {@link WEBSITE_LINK_TYPE_REGEX}.
|
|
156
97
|
*/
|
|
157
98
|
const WEBSITE_FILE_LINK_TYPE_REGEX = WEBSITE_LINK_TYPE_REGEX;
|
|
158
99
|
/**
|
|
159
|
-
*
|
|
100
|
+
* Maximum character length for a {@link WebsiteFileLinkMimeType}.
|
|
160
101
|
*/
|
|
161
102
|
const WEBSITE_FILE_LINK_MIME_TYPE_MAX_LENGTH = 128;
|
|
162
103
|
/**
|
|
163
|
-
*
|
|
104
|
+
* Regex pattern that validates a MIME type string (e.g., "text/plain", "application/vnd.api+json").
|
|
164
105
|
*/
|
|
165
106
|
const WEBSITE_FILE_LINK_MIME_TYPE_REGEX = /^\w+\/[-+.\w]+$/;
|
|
166
107
|
/**
|
|
167
|
-
*
|
|
108
|
+
* Maximum character length for a {@link WebsiteFileLinkName}.
|
|
168
109
|
*/
|
|
169
110
|
const WEBSITE_FILE_LINK_NAME_MAX_LENGTH = 128;
|
|
170
111
|
/**
|
|
171
|
-
*
|
|
112
|
+
* Maximum character length for {@link WebsiteFileLinkData}, derived from the total encoded data budget
|
|
113
|
+
* minus the separator characters, type, MIME type, and name fields.
|
|
172
114
|
*/
|
|
173
115
|
const WEBSITE_FILE_LINK_DATA_MAX_LENGTH = WEBSITE_LINK_ENCODED_DATA_MAX_LENGTH - 3 - WEBSITE_FILE_LINK_TYPE_MAX_LENGTH - WEBSITE_FILE_LINK_MIME_TYPE_MAX_LENGTH - WEBSITE_FILE_LINK_NAME_MAX_LENGTH;
|
|
116
|
+
/**
|
|
117
|
+
* Regex pattern for file link data — disallows the pipe character since it is used as the encoding separator.
|
|
118
|
+
*/
|
|
174
119
|
const WEBSITE_FILE_LINK_DATA_REGEX = /^[^|]+$/;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
__decorate([
|
|
190
|
-
Expose(),
|
|
191
|
-
IsOptional(),
|
|
192
|
-
IsString(),
|
|
193
|
-
Matches(WEBSITE_FILE_LINK_TYPE_REGEX),
|
|
194
|
-
MaxLength(WEBSITE_LINK_TYPE_MAX_LENGTH),
|
|
195
|
-
__metadata("design:type", String)
|
|
196
|
-
], WebsiteFileLink.prototype, "type", void 0);
|
|
197
|
-
__decorate([
|
|
198
|
-
Expose(),
|
|
199
|
-
IsOptional(),
|
|
200
|
-
IsString(),
|
|
201
|
-
Matches(WEBSITE_FILE_LINK_MIME_TYPE_REGEX),
|
|
202
|
-
MaxLength(WEBSITE_FILE_LINK_MIME_TYPE_MAX_LENGTH),
|
|
203
|
-
__metadata("design:type", String)
|
|
204
|
-
], WebsiteFileLink.prototype, "mime", void 0);
|
|
205
|
-
__decorate([
|
|
206
|
-
Expose(),
|
|
207
|
-
IsOptional(),
|
|
208
|
-
IsString(),
|
|
209
|
-
MaxLength(WEBSITE_FILE_LINK_NAME_MAX_LENGTH),
|
|
210
|
-
__metadata("design:type", String)
|
|
211
|
-
], WebsiteFileLink.prototype, "name", void 0);
|
|
212
|
-
__decorate([
|
|
213
|
-
IsString(),
|
|
214
|
-
IsNotEmpty(),
|
|
215
|
-
Matches(WEBSITE_FILE_LINK_DATA_REGEX),
|
|
216
|
-
MaxLength(WEBSITE_FILE_LINK_DATA_MAX_LENGTH),
|
|
217
|
-
__metadata("design:type", String)
|
|
218
|
-
], WebsiteFileLink.prototype, "data", void 0);
|
|
120
|
+
/**
|
|
121
|
+
* ArkType schema for a {@link WebsiteFileLink}.
|
|
122
|
+
*/
|
|
123
|
+
const websiteFileLinkType = type({
|
|
124
|
+
'type?': [WEBSITE_FILE_LINK_TYPE_REGEX, '&', `string <= ${WEBSITE_LINK_TYPE_MAX_LENGTH}`],
|
|
125
|
+
'mime?': [WEBSITE_FILE_LINK_MIME_TYPE_REGEX, '&', `string <= ${WEBSITE_FILE_LINK_MIME_TYPE_MAX_LENGTH}`],
|
|
126
|
+
'name?': `string <= ${WEBSITE_FILE_LINK_NAME_MAX_LENGTH}`,
|
|
127
|
+
data: [WEBSITE_FILE_LINK_DATA_REGEX, '&', `0 < string <= ${WEBSITE_FILE_LINK_DATA_MAX_LENGTH}`]
|
|
128
|
+
});
|
|
129
|
+
/**
|
|
130
|
+
* The {@link WebsiteLinkType} code used to identify a {@link WebsiteLink} as a file link.
|
|
131
|
+
*/
|
|
219
132
|
const WEBSITE_FILE_LINK_WEBSITE_LINK_TYPE = 'f';
|
|
133
|
+
/**
|
|
134
|
+
* Converts a {@link WebsiteFileLink} to a {@link WebsiteLink} by encoding its fields into the data string.
|
|
135
|
+
*
|
|
136
|
+
* @param input - the file link to convert
|
|
137
|
+
* @returns a WebsiteLink with type "f" and pipe-encoded data
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const fileLink: WebsiteFileLink = { type: 't', mime: 'text/plain', data: 'https://example.com/file.txt', name: 'file' };
|
|
142
|
+
* const link = websiteFileLinkToWebsiteLink(fileLink);
|
|
143
|
+
* // link.t === 'f'
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
220
146
|
function websiteFileLinkToWebsiteLink(input) {
|
|
221
147
|
return {
|
|
222
148
|
t: WEBSITE_FILE_LINK_WEBSITE_LINK_TYPE,
|
|
223
149
|
d: encodeWebsiteFileLinkToWebsiteLinkEncodedData(input)
|
|
224
150
|
};
|
|
225
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Converts a {@link WebsiteLink} back to a {@link WebsiteFileLink} by decoding the pipe-separated data string.
|
|
154
|
+
*
|
|
155
|
+
* @param input - a WebsiteLink whose data field contains an encoded file link
|
|
156
|
+
* @returns the decoded file link with type, MIME, name, and data fields
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* const link: WebsiteLink = { t: 'f', d: 't|text/plain|https://example.com/file.txt|file' };
|
|
161
|
+
* const fileLink = websiteLinkToWebsiteLinkFile(link);
|
|
162
|
+
* // fileLink.data === 'https://example.com/file.txt'
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
226
165
|
function websiteLinkToWebsiteLinkFile(input) {
|
|
227
166
|
const encodedData = input.d;
|
|
228
167
|
return decodeWebsiteLinkEncodedDataToWebsiteFileLink(encodedData);
|
|
229
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Separator character used when encoding/decoding file link fields into a single string.
|
|
171
|
+
*/
|
|
230
172
|
const WEBSITE_FILE_LINK_ENCODE_SEPARATOR = '|';
|
|
173
|
+
/**
|
|
174
|
+
* Encodes a {@link WebsiteFileLink} into a pipe-separated string suitable for storage in a {@link WebsiteLink}'s data field.
|
|
175
|
+
*
|
|
176
|
+
* Fields are encoded in order: type, MIME, data, name. Empty/undefined fields become empty strings.
|
|
177
|
+
*
|
|
178
|
+
* @param input - the file link to encode
|
|
179
|
+
* @returns a pipe-separated encoded string
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* const encoded = encodeWebsiteFileLinkToWebsiteLinkEncodedData({
|
|
184
|
+
* type: 't',
|
|
185
|
+
* mime: 'test/test',
|
|
186
|
+
* name: 'test-name',
|
|
187
|
+
* data: 'https://example.com/'
|
|
188
|
+
* });
|
|
189
|
+
* // encoded === 't|test/test|https://example.com/|test-name'
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
231
192
|
function encodeWebsiteFileLinkToWebsiteLinkEncodedData(input) {
|
|
232
193
|
const encoded = [input.type, input.mime, input.data, input.name].map((x) => x || '').join(WEBSITE_FILE_LINK_ENCODE_SEPARATOR);
|
|
233
194
|
return encoded;
|
|
234
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Decodes a pipe-separated encoded string back into a {@link WebsiteFileLink}.
|
|
198
|
+
*
|
|
199
|
+
* Empty fields in the encoded string are omitted from the result (falsy values are filtered out).
|
|
200
|
+
*
|
|
201
|
+
* @param input - the pipe-separated encoded string
|
|
202
|
+
* @returns the decoded file link
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* const fileLink = decodeWebsiteLinkEncodedDataToWebsiteFileLink('t|test/test|https://example.com/|test-name');
|
|
207
|
+
* // fileLink.type === 't'
|
|
208
|
+
* // fileLink.mime === 'test/test'
|
|
209
|
+
* // fileLink.data === 'https://example.com/'
|
|
210
|
+
* // fileLink.name === 'test-name'
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
235
213
|
function decodeWebsiteLinkEncodedDataToWebsiteFileLink(input) {
|
|
236
214
|
const [type, mime, data, name] = splitJoinRemainder(input, WEBSITE_FILE_LINK_ENCODE_SEPARATOR, 4);
|
|
237
215
|
return filterFalsyAndEmptyValues({
|
|
@@ -243,16 +221,40 @@ function decodeWebsiteLinkEncodedDataToWebsiteFileLink(input) {
|
|
|
243
221
|
}
|
|
244
222
|
|
|
245
223
|
/**
|
|
246
|
-
*
|
|
224
|
+
* Isolates a username from a URL where the username is the first path segment after the domain.
|
|
225
|
+
*
|
|
226
|
+
* Strips trailing slashes and query parameters. Used by social platforms like Facebook, Instagram, and Twitter
|
|
227
|
+
* where the profile URL pattern is `https://domain.com/{username}`.
|
|
247
228
|
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* WEBSITE_LINK_ISOLATE_BASE_URL_PROFILE_ID('https://www.facebook.com/myuser');
|
|
232
|
+
* // returns 'myuser'
|
|
233
|
+
* ```
|
|
250
234
|
*/
|
|
251
235
|
const WEBSITE_LINK_ISOLATE_BASE_URL_PROFILE_ID = isolateWebsitePathFunction({
|
|
252
236
|
isolatePathComponents: 0,
|
|
253
237
|
removeTrailingSlash: true,
|
|
254
238
|
removeQueryParameters: true
|
|
255
239
|
});
|
|
240
|
+
/**
|
|
241
|
+
* Extracts a username from either a raw username string or a full profile URL where the username is the first path segment.
|
|
242
|
+
*
|
|
243
|
+
* Optionally prepends a prefix (e.g., "@" for TikTok, "$" for Cash App).
|
|
244
|
+
*
|
|
245
|
+
* @param input - a username or full profile URL
|
|
246
|
+
* @param prefix - optional prefix to prepend to the extracted username
|
|
247
|
+
* @returns the isolated username, optionally prefixed
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```typescript
|
|
251
|
+
* usernameFromUsernameOrWebsiteWithBaseUrlUsername('https://facebook.com/myuser');
|
|
252
|
+
* // returns 'myuser'
|
|
253
|
+
*
|
|
254
|
+
* usernameFromUsernameOrWebsiteWithBaseUrlUsername('myuser', '@');
|
|
255
|
+
* // returns '@myuser'
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
256
258
|
function usernameFromUsernameOrWebsiteWithBaseUrlUsername(input, prefix) {
|
|
257
259
|
const username = toRelativeSlashPathStartType(WEBSITE_LINK_ISOLATE_BASE_URL_PROFILE_ID(usernameOrWebsiteUrlToWebsiteUrl(input)));
|
|
258
260
|
if (prefix) {
|
|
@@ -262,14 +264,46 @@ function usernameFromUsernameOrWebsiteWithBaseUrlUsername(input, prefix) {
|
|
|
262
264
|
return username;
|
|
263
265
|
}
|
|
264
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Extracts a username from either a raw username string or a full profile URL using a custom path isolation function.
|
|
269
|
+
*
|
|
270
|
+
* Used for platforms with non-standard URL patterns (e.g., Snapchat's `/add/{username}`, YouTube's `/c/{username}`).
|
|
271
|
+
*
|
|
272
|
+
* @param input - a username or full profile URL
|
|
273
|
+
* @param isolateFn - custom function that extracts the relevant path segment from the URL
|
|
274
|
+
* @returns the isolated username
|
|
275
|
+
*/
|
|
265
276
|
function usernameFromUsernameOrWebsiteWithOneOffBaseUrlUsername(input, isolateFn) {
|
|
266
277
|
return toRelativeSlashPathStartType(isolateFn(usernameOrWebsiteUrlToWebsiteUrl(input)));
|
|
267
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Normalizes a string that may be either a plain username or a full website URL into a consistent URL format.
|
|
281
|
+
*
|
|
282
|
+
* If the input has a website domain, it is returned as-is. Otherwise, it is treated as a path and converted to an absolute slash path.
|
|
283
|
+
*
|
|
284
|
+
* @param input - a username or website URL
|
|
285
|
+
* @returns a normalized website URL
|
|
286
|
+
*/
|
|
268
287
|
function usernameOrWebsiteUrlToWebsiteUrl(input) {
|
|
269
288
|
return hasWebsiteDomain(input) ? input : toAbsoluteSlashPathStartType(removeHttpFromUrl(input));
|
|
270
289
|
}
|
|
271
290
|
// MARK: Website
|
|
291
|
+
/**
|
|
292
|
+
* {@link WebsiteLinkType} code for generic website URLs.
|
|
293
|
+
*/
|
|
272
294
|
const WEBSITE_URL_WEBSITE_LINK_TYPE = 'w';
|
|
295
|
+
/**
|
|
296
|
+
* Converts a generic website URL into a {@link WebsiteLink}, stripping the HTTP/HTTPS protocol.
|
|
297
|
+
*
|
|
298
|
+
* @param input - the full website URL
|
|
299
|
+
* @returns a WebsiteLink with the protocol removed from the data
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* const link = websiteUrlToWebsiteLink('https://example.com/page');
|
|
304
|
+
* // link.t === 'w', link.d === 'example.com/page'
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
273
307
|
function websiteUrlToWebsiteLink(input) {
|
|
274
308
|
return {
|
|
275
309
|
t: WEBSITE_URL_WEBSITE_LINK_TYPE,
|
|
@@ -277,7 +311,16 @@ function websiteUrlToWebsiteLink(input) {
|
|
|
277
311
|
};
|
|
278
312
|
}
|
|
279
313
|
// MARK: Email
|
|
314
|
+
/**
|
|
315
|
+
* {@link WebsiteLinkType} code for email addresses.
|
|
316
|
+
*/
|
|
280
317
|
const EMAIL_URL_WEBSITE_LINK_TYPE = 'e';
|
|
318
|
+
/**
|
|
319
|
+
* Converts an email address into a {@link WebsiteLink}.
|
|
320
|
+
*
|
|
321
|
+
* @param input - the email address
|
|
322
|
+
* @returns a WebsiteLink storing the email as data
|
|
323
|
+
*/
|
|
281
324
|
function emailAddressToWebsiteLink(input) {
|
|
282
325
|
return {
|
|
283
326
|
t: EMAIL_URL_WEBSITE_LINK_TYPE,
|
|
@@ -285,7 +328,16 @@ function emailAddressToWebsiteLink(input) {
|
|
|
285
328
|
};
|
|
286
329
|
}
|
|
287
330
|
// MARK: Phone
|
|
331
|
+
/**
|
|
332
|
+
* {@link WebsiteLinkType} code for phone numbers.
|
|
333
|
+
*/
|
|
288
334
|
const PHONE_URL_WEBSITE_LINK_TYPE = 'p';
|
|
335
|
+
/**
|
|
336
|
+
* Converts an E.164 phone number into a {@link WebsiteLink}.
|
|
337
|
+
*
|
|
338
|
+
* @param input - the phone number in E.164 format
|
|
339
|
+
* @returns a WebsiteLink storing the phone number as data
|
|
340
|
+
*/
|
|
289
341
|
function phoneNumberToWebsiteLink(input) {
|
|
290
342
|
return {
|
|
291
343
|
t: PHONE_URL_WEBSITE_LINK_TYPE,
|
|
@@ -293,148 +345,325 @@ function phoneNumberToWebsiteLink(input) {
|
|
|
293
345
|
};
|
|
294
346
|
}
|
|
295
347
|
// MARK: Facebook
|
|
348
|
+
/** Base URL for Facebook profiles. */
|
|
296
349
|
const FACEBOOK_BASE_URL = `https://www.facebook.com`;
|
|
350
|
+
/** {@link WebsiteLinkType} code for Facebook. */
|
|
297
351
|
const FACEBOOK_WEBSITE_LINK_TYPE = 'fb';
|
|
352
|
+
/**
|
|
353
|
+
* Converts a Facebook profile ID or URL into a {@link WebsiteLink}.
|
|
354
|
+
*
|
|
355
|
+
* Accepts either a raw username or a full Facebook profile URL.
|
|
356
|
+
*
|
|
357
|
+
* @param input - a Facebook profile ID or full profile URL
|
|
358
|
+
* @returns a WebsiteLink with the isolated username as data
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```typescript
|
|
362
|
+
* facebookProfileUrlToWebsiteLink('https://www.facebook.com/myuser');
|
|
363
|
+
* // { t: 'fb', d: 'myuser' }
|
|
364
|
+
*
|
|
365
|
+
* facebookProfileUrlToWebsiteLink('myuser');
|
|
366
|
+
* // { t: 'fb', d: 'myuser' }
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
298
369
|
function facebookProfileUrlToWebsiteLink(input) {
|
|
299
370
|
return {
|
|
300
371
|
t: FACEBOOK_WEBSITE_LINK_TYPE,
|
|
301
372
|
d: usernameFromUsernameOrWebsiteWithBaseUrlUsername(input)
|
|
302
373
|
};
|
|
303
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Constructs a full Facebook profile URL from a profile ID.
|
|
377
|
+
*
|
|
378
|
+
* @param profileId - the Facebook profile ID or username
|
|
379
|
+
* @returns the full profile URL
|
|
380
|
+
*/
|
|
304
381
|
function facebookProfileUrl(profileId) {
|
|
305
382
|
return `${FACEBOOK_BASE_URL}/${profileId}`;
|
|
306
383
|
}
|
|
307
384
|
// MARK: Instagram
|
|
385
|
+
/** Base URL for Instagram profiles. */
|
|
308
386
|
const INSTAGRAM_BASE_URL = `https://www.instagram.com`;
|
|
387
|
+
/** {@link WebsiteLinkType} code for Instagram. */
|
|
309
388
|
const INSTAGRAM_WEBSITE_LINK_TYPE = 'ig';
|
|
389
|
+
/**
|
|
390
|
+
* Converts an Instagram profile ID or URL into a {@link WebsiteLink}.
|
|
391
|
+
*
|
|
392
|
+
* @param input - an Instagram username or full profile URL
|
|
393
|
+
* @returns a WebsiteLink with the isolated username as data
|
|
394
|
+
*/
|
|
310
395
|
function instagramProfileUrlToWebsiteLink(input) {
|
|
311
396
|
return {
|
|
312
397
|
t: INSTAGRAM_WEBSITE_LINK_TYPE,
|
|
313
398
|
d: usernameFromUsernameOrWebsiteWithBaseUrlUsername(input)
|
|
314
399
|
};
|
|
315
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Constructs a full Instagram profile URL from a profile ID.
|
|
403
|
+
*
|
|
404
|
+
* @param profileId - the Instagram username
|
|
405
|
+
* @returns the full profile URL
|
|
406
|
+
*/
|
|
316
407
|
function instagramProfileUrl(profileId) {
|
|
317
408
|
return `${INSTAGRAM_BASE_URL}/${profileId}`;
|
|
318
409
|
}
|
|
319
410
|
// MARK: Twitter
|
|
411
|
+
/** Base URL for Twitter profiles. */
|
|
320
412
|
const TWITTER_BASE_URL = `https://www.twitter.com`;
|
|
413
|
+
/** {@link WebsiteLinkType} code for Twitter. */
|
|
321
414
|
const TWITTER_WEBSITE_LINK_TYPE = 'tw';
|
|
415
|
+
/**
|
|
416
|
+
* Converts a Twitter profile ID or URL into a {@link WebsiteLink}.
|
|
417
|
+
*
|
|
418
|
+
* @param input - a Twitter username or full profile URL
|
|
419
|
+
* @returns a WebsiteLink with the isolated username as data
|
|
420
|
+
*/
|
|
322
421
|
function twitterProfileUrlToWebsiteLink(input) {
|
|
323
422
|
return {
|
|
324
423
|
t: TWITTER_WEBSITE_LINK_TYPE,
|
|
325
424
|
d: usernameFromUsernameOrWebsiteWithBaseUrlUsername(input)
|
|
326
425
|
};
|
|
327
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Constructs a full Twitter profile URL from a profile ID.
|
|
429
|
+
*
|
|
430
|
+
* @param profileId - the Twitter username
|
|
431
|
+
* @returns the full profile URL
|
|
432
|
+
*/
|
|
328
433
|
function twitterProfileUrl(profileId) {
|
|
329
434
|
return `${TWITTER_BASE_URL}/${profileId}`;
|
|
330
435
|
}
|
|
331
436
|
// MARK: Tiktok
|
|
437
|
+
/** Base URL for TikTok profiles. */
|
|
332
438
|
const TIKTOK_BASE_URL = `https://tiktok.com`;
|
|
439
|
+
/** TikTok usernames are prefixed with "@" in URLs and stored data. */
|
|
333
440
|
const TIKTOK_USERNAME_PREFIX = '@';
|
|
441
|
+
/** {@link WebsiteLinkType} code for TikTok. */
|
|
334
442
|
const TIKTOK_WEBSITE_LINK_TYPE = 'tt';
|
|
443
|
+
/**
|
|
444
|
+
* Converts a TikTok profile ID or URL into a {@link WebsiteLink}.
|
|
445
|
+
*
|
|
446
|
+
* Automatically prepends the "@" prefix if not already present.
|
|
447
|
+
*
|
|
448
|
+
* @param input - a TikTok username (with or without "@") or full profile URL
|
|
449
|
+
* @returns a WebsiteLink with the "@"-prefixed username as data
|
|
450
|
+
*/
|
|
335
451
|
function tiktokProfileUrlToWebsiteLink(input) {
|
|
336
452
|
return {
|
|
337
453
|
t: TIKTOK_WEBSITE_LINK_TYPE,
|
|
338
454
|
d: usernameFromUsernameOrWebsiteWithBaseUrlUsername(input, TIKTOK_USERNAME_PREFIX)
|
|
339
455
|
};
|
|
340
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* Constructs a full TikTok profile URL from a profile ID.
|
|
459
|
+
*
|
|
460
|
+
* @param profileId - the TikTok username (without "@" prefix)
|
|
461
|
+
* @returns the full profile URL with "@" prefix
|
|
462
|
+
*/
|
|
341
463
|
function tiktokProfileUrl(profileId) {
|
|
342
464
|
return `${TIKTOK_BASE_URL}/@${profileId}`;
|
|
343
465
|
}
|
|
344
466
|
// MARK: Snapchat
|
|
467
|
+
/** Base URL for Snapchat profiles. */
|
|
345
468
|
const SNAPCHAT_BASE_URL = `https://snapchat.com`;
|
|
469
|
+
/** {@link WebsiteLinkType} code for Snapchat. */
|
|
346
470
|
const SNAPCHAT_WEBSITE_LINK_TYPE = 'sc';
|
|
471
|
+
/**
|
|
472
|
+
* Isolates a Snapchat username from a URL, ignoring the `/add/` base path segment.
|
|
473
|
+
*/
|
|
347
474
|
const SNAPCHAT_WEBSITE_LINK_ISOLATE_PROFILE_ID = isolateWebsitePathFunction({
|
|
348
475
|
ignoredBasePath: 'add',
|
|
349
476
|
isolatePathComponents: 0,
|
|
350
477
|
removeTrailingSlash: true,
|
|
351
478
|
removeQueryParameters: true
|
|
352
479
|
});
|
|
480
|
+
/**
|
|
481
|
+
* Converts a Snapchat profile ID or URL into a {@link WebsiteLink}.
|
|
482
|
+
*
|
|
483
|
+
* Handles Snapchat's `/add/{username}` URL pattern.
|
|
484
|
+
*
|
|
485
|
+
* @param input - a Snapchat username or full profile URL
|
|
486
|
+
* @returns a WebsiteLink with the isolated username as data
|
|
487
|
+
*/
|
|
353
488
|
function snapchatProfileUrlToWebsiteLink(input) {
|
|
354
489
|
return {
|
|
355
490
|
t: SNAPCHAT_WEBSITE_LINK_TYPE,
|
|
356
491
|
d: usernameFromUsernameOrWebsiteWithOneOffBaseUrlUsername(input, SNAPCHAT_WEBSITE_LINK_ISOLATE_PROFILE_ID)
|
|
357
492
|
};
|
|
358
493
|
}
|
|
494
|
+
/**
|
|
495
|
+
* Constructs a full Snapchat profile URL from a profile ID.
|
|
496
|
+
*
|
|
497
|
+
* @param profileId - the Snapchat username
|
|
498
|
+
* @returns the full profile URL with `/add/` path
|
|
499
|
+
*/
|
|
359
500
|
function snapchatProfileUrl(profileId) {
|
|
360
501
|
return `${SNAPCHAT_BASE_URL}/add/${profileId}`;
|
|
361
502
|
}
|
|
362
503
|
// MARK: YouTube
|
|
504
|
+
/** Base URL for YouTube channels. */
|
|
363
505
|
const YOUTUBE_BASE_URL = `https://youtube.com`;
|
|
506
|
+
/** {@link WebsiteLinkType} code for YouTube. */
|
|
364
507
|
const YOUTUBE_WEBSITE_LINK_TYPE = 'yt';
|
|
508
|
+
/**
|
|
509
|
+
* Isolates a YouTube channel name from a URL, ignoring the `/c/` base path segment.
|
|
510
|
+
*/
|
|
365
511
|
const YOUTUBE_WEBSITE_LINK_ISOLATE_PROFILE_ID = isolateWebsitePathFunction({
|
|
366
512
|
ignoredBasePath: 'c',
|
|
367
513
|
isolatePathComponents: 0,
|
|
368
514
|
removeTrailingSlash: true,
|
|
369
515
|
removeQueryParameters: true
|
|
370
516
|
});
|
|
517
|
+
/**
|
|
518
|
+
* Converts a YouTube channel ID or URL into a {@link WebsiteLink}.
|
|
519
|
+
*
|
|
520
|
+
* Handles YouTube's `/c/{channel}` URL pattern.
|
|
521
|
+
*
|
|
522
|
+
* @param input - a YouTube channel name or full channel URL
|
|
523
|
+
* @returns a WebsiteLink with the isolated channel name as data
|
|
524
|
+
*/
|
|
371
525
|
function youtubeProfileUrlToWebsiteLink(input) {
|
|
372
526
|
return {
|
|
373
527
|
t: YOUTUBE_WEBSITE_LINK_TYPE,
|
|
374
528
|
d: usernameFromUsernameOrWebsiteWithOneOffBaseUrlUsername(input, YOUTUBE_WEBSITE_LINK_ISOLATE_PROFILE_ID)
|
|
375
529
|
};
|
|
376
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* Constructs a full YouTube channel URL from a profile ID.
|
|
533
|
+
*
|
|
534
|
+
* @param profileId - the YouTube channel name
|
|
535
|
+
* @returns the full channel URL with `/c/` path
|
|
536
|
+
*/
|
|
377
537
|
function youtubeProfileUrl(profileId) {
|
|
378
538
|
return `${YOUTUBE_BASE_URL}/c/${profileId}`;
|
|
379
539
|
}
|
|
380
540
|
// MARK: PayPal
|
|
541
|
+
/** Base URL for PayPal.me profiles. */
|
|
381
542
|
const PAYPAL_BASE_URL = `https://paypal.me`;
|
|
543
|
+
/** {@link WebsiteLinkType} code for PayPal. */
|
|
382
544
|
const PAYPAL_WEBSITE_LINK_TYPE = 'pp';
|
|
545
|
+
/**
|
|
546
|
+
* Converts a PayPal profile ID or URL into a {@link WebsiteLink}.
|
|
547
|
+
*
|
|
548
|
+
* @param input - a PayPal username or full PayPal.me URL
|
|
549
|
+
* @returns a WebsiteLink with the isolated username as data
|
|
550
|
+
*/
|
|
383
551
|
function paypalProfileUrlToWebsiteLink(input) {
|
|
384
552
|
return {
|
|
385
553
|
t: PAYPAL_WEBSITE_LINK_TYPE,
|
|
386
554
|
d: usernameFromUsernameOrWebsiteWithBaseUrlUsername(input)
|
|
387
555
|
};
|
|
388
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* Constructs a full PayPal.me profile URL from a profile ID.
|
|
559
|
+
*
|
|
560
|
+
* @param profileId - the PayPal username
|
|
561
|
+
* @returns the full PayPal.me URL
|
|
562
|
+
*/
|
|
389
563
|
function paypalProfileUrl(profileId) {
|
|
390
564
|
return `${PAYPAL_BASE_URL}/${profileId}`;
|
|
391
565
|
}
|
|
392
566
|
// MARK: Cashapp
|
|
567
|
+
/** Base URL for Cash App profiles. */
|
|
393
568
|
const CASHAPP_BASE_URL = `https://cash.app`;
|
|
569
|
+
/** Cash App usernames are prefixed with "$" (cashtag). */
|
|
394
570
|
const CASHAPP_USERNAME_PREFIX = '$';
|
|
571
|
+
/** {@link WebsiteLinkType} code for Cash App. */
|
|
395
572
|
const CASHAPP_WEBSITE_LINK_TYPE = 'ca';
|
|
573
|
+
/**
|
|
574
|
+
* Converts a Cash App profile ID or URL into a {@link WebsiteLink}.
|
|
575
|
+
*
|
|
576
|
+
* Automatically prepends the "$" prefix if not already present.
|
|
577
|
+
*
|
|
578
|
+
* @param input - a Cash App username (with or without "$") or full profile URL
|
|
579
|
+
* @returns a WebsiteLink with the "$"-prefixed username as data
|
|
580
|
+
*/
|
|
396
581
|
function cashappProfileUrlToWebsiteLink(input) {
|
|
397
582
|
return {
|
|
398
583
|
t: CASHAPP_WEBSITE_LINK_TYPE,
|
|
399
584
|
d: usernameFromUsernameOrWebsiteWithBaseUrlUsername(input, CASHAPP_USERNAME_PREFIX)
|
|
400
585
|
};
|
|
401
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* Constructs a full Cash App profile URL from a profile ID.
|
|
589
|
+
*
|
|
590
|
+
* @param profileId - the Cash App username (without "$" prefix)
|
|
591
|
+
* @returns the full Cash App URL with "$" prefix
|
|
592
|
+
*/
|
|
402
593
|
function cashappProfileUrl(profileId) {
|
|
403
594
|
return `${CASHAPP_BASE_URL}/$${profileId}`;
|
|
404
595
|
}
|
|
405
596
|
// MARK: Venmo
|
|
597
|
+
/** Base URL for Venmo profiles. */
|
|
406
598
|
const VENMO_BASE_URL = `https://account.venmo.com`;
|
|
599
|
+
/** {@link WebsiteLinkType} code for Venmo. */
|
|
407
600
|
const VENMO_WEBSITE_LINK_TYPE = 'vn';
|
|
601
|
+
/**
|
|
602
|
+
* Isolates a Venmo username from a URL, ignoring the `/u/` base path segment.
|
|
603
|
+
*/
|
|
408
604
|
const VENMO_WEBSITE_LINK_ISOLATE_PROFILE_ID = isolateWebsitePathFunction({
|
|
409
605
|
ignoredBasePath: 'u',
|
|
410
606
|
isolatePathComponents: 0,
|
|
411
607
|
removeTrailingSlash: true,
|
|
412
608
|
removeQueryParameters: true
|
|
413
609
|
});
|
|
610
|
+
/**
|
|
611
|
+
* Converts a Venmo profile ID or URL into a {@link WebsiteLink}.
|
|
612
|
+
*
|
|
613
|
+
* Handles Venmo's `/u/{username}` URL pattern.
|
|
614
|
+
*
|
|
615
|
+
* @param input - a Venmo username or full profile URL
|
|
616
|
+
* @returns a WebsiteLink with the isolated username as data
|
|
617
|
+
*/
|
|
414
618
|
function venmoProfileUrlToWebsiteLink(input) {
|
|
415
619
|
return {
|
|
416
620
|
t: VENMO_WEBSITE_LINK_TYPE,
|
|
417
621
|
d: usernameFromUsernameOrWebsiteWithOneOffBaseUrlUsername(input, VENMO_WEBSITE_LINK_ISOLATE_PROFILE_ID)
|
|
418
622
|
};
|
|
419
623
|
}
|
|
624
|
+
/**
|
|
625
|
+
* Constructs a full Venmo profile URL from a profile ID.
|
|
626
|
+
*
|
|
627
|
+
* @param profileId - the Venmo username
|
|
628
|
+
* @returns the full profile URL with `/u/` path
|
|
629
|
+
*/
|
|
420
630
|
function venmoProfileUrl(profileId) {
|
|
421
631
|
return `${VENMO_BASE_URL}/u/${profileId}`;
|
|
422
632
|
}
|
|
423
633
|
// MARK: Spotify
|
|
634
|
+
/** Base URL for Spotify profiles. */
|
|
424
635
|
const SPOTIFY_BASE_URL = `https://open.spotify.com/`;
|
|
636
|
+
/** {@link WebsiteLinkType} code for Spotify. */
|
|
425
637
|
const SPOTIFY_WEBSITE_LINK_TYPE = 'sp';
|
|
638
|
+
/**
|
|
639
|
+
* Isolates a Spotify username from a URL, ignoring the `/user/` base path segment.
|
|
640
|
+
*/
|
|
426
641
|
const SPOTIFY_WEBSITE_LINK_ISOLATE_PROFILE_ID = isolateWebsitePathFunction({
|
|
427
642
|
ignoredBasePath: 'user',
|
|
428
643
|
isolatePathComponents: 0,
|
|
429
644
|
removeTrailingSlash: true,
|
|
430
645
|
removeQueryParameters: true
|
|
431
646
|
});
|
|
647
|
+
/**
|
|
648
|
+
* Converts a Spotify profile ID or URL into a {@link WebsiteLink}.
|
|
649
|
+
*
|
|
650
|
+
* Handles Spotify's `/user/{username}` URL pattern.
|
|
651
|
+
*
|
|
652
|
+
* @param input - a Spotify username or full profile URL
|
|
653
|
+
* @returns a WebsiteLink with the isolated username as data
|
|
654
|
+
*/
|
|
432
655
|
function spotifyProfileUrlToWebsiteLink(input) {
|
|
433
656
|
return {
|
|
434
657
|
t: SPOTIFY_WEBSITE_LINK_TYPE,
|
|
435
658
|
d: usernameFromUsernameOrWebsiteWithOneOffBaseUrlUsername(input, SPOTIFY_WEBSITE_LINK_ISOLATE_PROFILE_ID)
|
|
436
659
|
};
|
|
437
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Constructs a full Spotify profile URL from a profile ID.
|
|
663
|
+
*
|
|
664
|
+
* @param profileId - the Spotify username
|
|
665
|
+
* @returns the full profile URL with `/user/` path
|
|
666
|
+
*/
|
|
438
667
|
function spotifyProfileUrl(profileId) {
|
|
439
668
|
return `${SPOTIFY_BASE_URL}/user/${profileId}`;
|
|
440
669
|
}
|
|
@@ -443,10 +672,17 @@ const GRANTED_SYS_ADMIN_ROLE_KEY = 'sysadmin';
|
|
|
443
672
|
const GRANTED_OWNER_ROLE_KEY = 'owner';
|
|
444
673
|
const GRANTED_ADMIN_ROLE_KEY = 'admin';
|
|
445
674
|
/**
|
|
446
|
-
*
|
|
675
|
+
* Checks whether the given role represents an admin-level permission (either "admin" or "owner").
|
|
447
676
|
*
|
|
448
|
-
* @param role
|
|
449
|
-
* @returns
|
|
677
|
+
* @param role - the role to check
|
|
678
|
+
* @returns true if the role is an admin or owner role
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* ```typescript
|
|
682
|
+
* isGrantedAdminLevelRole('admin'); // true
|
|
683
|
+
* isGrantedAdminLevelRole('owner'); // true
|
|
684
|
+
* isGrantedAdminLevelRole('read'); // false
|
|
685
|
+
* ```
|
|
450
686
|
*/
|
|
451
687
|
function isGrantedAdminLevelRole(role) {
|
|
452
688
|
return role === GRANTED_ADMIN_ROLE_KEY || role === GRANTED_OWNER_ROLE_KEY;
|
|
@@ -456,27 +692,75 @@ const GRANTED_UPDATE_ROLE_KEY = 'update';
|
|
|
456
692
|
const GRANTED_DELETE_ROLE_KEY = 'delete';
|
|
457
693
|
const FULL_ACCESS_ROLE_KEY = '__FULL__';
|
|
458
694
|
const NO_ACCESS_ROLE_KEY = '__EMPTY__';
|
|
695
|
+
/**
|
|
696
|
+
* Creates a {@link GrantedRoleMap} that explicitly denies all access.
|
|
697
|
+
*
|
|
698
|
+
* @returns a role map with only the no-access marker set
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```typescript
|
|
702
|
+
* const map = noAccessRoleMap();
|
|
703
|
+
* isNoAccessRoleMap(map); // true
|
|
704
|
+
* ```
|
|
705
|
+
*/
|
|
459
706
|
function noAccessRoleMap() {
|
|
460
707
|
return {
|
|
461
708
|
[NO_ACCESS_ROLE_KEY]: true
|
|
462
709
|
};
|
|
463
710
|
}
|
|
711
|
+
/**
|
|
712
|
+
* Type guard that checks whether a role map is a no-access map.
|
|
713
|
+
*
|
|
714
|
+
* @param input - the role map to check
|
|
715
|
+
* @returns true if the map has the no-access marker
|
|
716
|
+
*/
|
|
464
717
|
function isNoAccessRoleMap(input) {
|
|
465
718
|
return input[NO_ACCESS_ROLE_KEY] === true;
|
|
466
719
|
}
|
|
720
|
+
/**
|
|
721
|
+
* Creates a {@link GrantedRoleMap} that grants full access to all roles.
|
|
722
|
+
*
|
|
723
|
+
* @returns a role map with the full-access marker set
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
726
|
+
* ```typescript
|
|
727
|
+
* const map = fullAccessRoleMap();
|
|
728
|
+
* isFullAccessRoleMap(map); // true
|
|
729
|
+
* ```
|
|
730
|
+
*/
|
|
467
731
|
function fullAccessRoleMap() {
|
|
468
732
|
return {
|
|
469
733
|
[FULL_ACCESS_ROLE_KEY]: true
|
|
470
734
|
};
|
|
471
735
|
}
|
|
736
|
+
/**
|
|
737
|
+
* Type guard that checks whether a role map is a full-access map.
|
|
738
|
+
*
|
|
739
|
+
* @param input - the role map to check
|
|
740
|
+
* @returns true if the map has the full-access marker
|
|
741
|
+
*/
|
|
472
742
|
function isFullAccessRoleMap(input) {
|
|
473
743
|
return input[FULL_ACCESS_ROLE_KEY] === true;
|
|
474
744
|
}
|
|
475
745
|
/**
|
|
476
|
-
* Creates a GrantedRoleMapReader.
|
|
746
|
+
* Creates a {@link GrantedRoleMapReader} for querying granted roles from a role map.
|
|
477
747
|
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
748
|
+
* The reader handles full-access and no-access maps as special cases, and provides methods
|
|
749
|
+
* for checking individual roles or sets of roles.
|
|
750
|
+
*
|
|
751
|
+
* @param map - the granted role map to read from
|
|
752
|
+
* @returns a reader instance for querying the map
|
|
753
|
+
*
|
|
754
|
+
* @example
|
|
755
|
+
* ```typescript
|
|
756
|
+
* const roleMap: GrantedRoleMap = { read: true, first: true };
|
|
757
|
+
* const reader = grantedRoleMapReader(roleMap);
|
|
758
|
+
*
|
|
759
|
+
* reader.hasRole('read'); // true
|
|
760
|
+
* reader.hasRole('delete'); // false
|
|
761
|
+
* reader.containsRoles('any', ['read']); // true
|
|
762
|
+
* reader.containsRoles('all', ['read', 'delete']); // false
|
|
763
|
+
* ```
|
|
480
764
|
*/
|
|
481
765
|
function grantedRoleMapReader(map) {
|
|
482
766
|
return new GrantedRoleMapReaderInstance(map);
|
|
@@ -538,21 +822,62 @@ class GrantedRoleMapReaderInstance {
|
|
|
538
822
|
}
|
|
539
823
|
}
|
|
540
824
|
/**
|
|
541
|
-
* Converts
|
|
825
|
+
* Converts an array of role strings into a {@link GrantedRoleKeysMap} where each role is mapped to the given boolean value.
|
|
542
826
|
*
|
|
543
|
-
* @param roles
|
|
544
|
-
* @
|
|
827
|
+
* @param roles - the role strings to include
|
|
828
|
+
* @param value - the boolean value to assign to each role (defaults to true)
|
|
829
|
+
* @returns a map of roles to boolean values
|
|
830
|
+
*
|
|
831
|
+
* @example
|
|
832
|
+
* ```typescript
|
|
833
|
+
* const map = grantedRoleKeysMapFromArray(['read', 'update']);
|
|
834
|
+
* // { read: true, update: true }
|
|
835
|
+
* ```
|
|
545
836
|
*/
|
|
546
837
|
function grantedRoleKeysMapFromArray(roles, value = true) {
|
|
547
838
|
return arrayToObject(roles, (x) => x, () => value);
|
|
548
839
|
}
|
|
549
840
|
|
|
841
|
+
/**
|
|
842
|
+
* Creates a {@link ContextGrantedModelRoles} with a no-access role map, indicating the context has no permissions.
|
|
843
|
+
*
|
|
844
|
+
* @param context - the context that was evaluated
|
|
845
|
+
* @param data - optional model data, if it was loaded
|
|
846
|
+
* @returns a ContextGrantedModelRoles with no access
|
|
847
|
+
*
|
|
848
|
+
* @example
|
|
849
|
+
* ```typescript
|
|
850
|
+
* const result = noAccessContextGrantedModelRoles(userContext);
|
|
851
|
+
* // result.roleMap contains the no-access marker
|
|
852
|
+
* ```
|
|
853
|
+
*/
|
|
550
854
|
function noAccessContextGrantedModelRoles(context, data) {
|
|
551
855
|
return contextGrantedModelRoles(context, data, noAccessRoleMap());
|
|
552
856
|
}
|
|
857
|
+
/**
|
|
858
|
+
* Creates a {@link ContextGrantedModelRoles} with a full-access role map, granting all permissions.
|
|
859
|
+
*
|
|
860
|
+
* @param context - the context that was evaluated
|
|
861
|
+
* @param data - optional model data
|
|
862
|
+
* @returns a ContextGrantedModelRoles with full access
|
|
863
|
+
*
|
|
864
|
+
* @example
|
|
865
|
+
* ```typescript
|
|
866
|
+
* const result = fullAccessGrantedModelRoles(adminContext, modelData);
|
|
867
|
+
* // result.roleMap contains the full-access marker
|
|
868
|
+
* ```
|
|
869
|
+
*/
|
|
553
870
|
function fullAccessGrantedModelRoles(context, data) {
|
|
554
871
|
return contextGrantedModelRoles(context, data, fullAccessRoleMap());
|
|
555
872
|
}
|
|
873
|
+
/**
|
|
874
|
+
* Creates a {@link ContextGrantedModelRoles} with the given role map, data, and context.
|
|
875
|
+
*
|
|
876
|
+
* @param context - the context that was evaluated
|
|
877
|
+
* @param data - the model data, if loaded
|
|
878
|
+
* @param roles - the granted role map
|
|
879
|
+
* @returns a ContextGrantedModelRoles combining all inputs
|
|
880
|
+
*/
|
|
556
881
|
function contextGrantedModelRoles(context, data, roles) {
|
|
557
882
|
return {
|
|
558
883
|
data,
|
|
@@ -603,6 +928,22 @@ class AbstractModelPermissionService {
|
|
|
603
928
|
}
|
|
604
929
|
}
|
|
605
930
|
|
|
931
|
+
/**
|
|
932
|
+
* Creates a factory that normalizes a {@link SyncEntityCommonTypeIdPairFactoryInput} into a full {@link SyncEntityCommonTypeIdPair}.
|
|
933
|
+
*
|
|
934
|
+
* If the input is a string, it is treated as a commonId and paired with the given commonType.
|
|
935
|
+
* If the input is already a pair, it is returned as-is.
|
|
936
|
+
*
|
|
937
|
+
* @param commonType - the default common type to use when input is a plain string
|
|
938
|
+
* @returns a factory function that produces SyncEntityCommonTypeIdPair instances
|
|
939
|
+
*
|
|
940
|
+
* @example
|
|
941
|
+
* ```typescript
|
|
942
|
+
* const factory = syncEntityCommonTypeIdPairFactory('user');
|
|
943
|
+
* factory('abc123'); // { commonType: 'user', commonId: 'abc123' }
|
|
944
|
+
* factory({ commonType: 'user', commonId: 'abc123' }); // passed through as-is
|
|
945
|
+
* ```
|
|
946
|
+
*/
|
|
606
947
|
function syncEntityCommonTypeIdPairFactory(commonType) {
|
|
607
948
|
return (input) => {
|
|
608
949
|
if (typeof input === 'string') {
|
|
@@ -617,10 +958,23 @@ function syncEntityCommonTypeIdPairFactory(commonType) {
|
|
|
617
958
|
};
|
|
618
959
|
}
|
|
619
960
|
/**
|
|
620
|
-
* Creates a SyncEntityFactory.
|
|
961
|
+
* Creates a {@link SyncEntityFactory} that produces {@link SyncEntity} instances from a common type/id pair.
|
|
621
962
|
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
963
|
+
* The factory attaches the configured source info and optionally transforms the commonId into an entity id
|
|
964
|
+
* using the provided idFactory (defaults to identity).
|
|
965
|
+
*
|
|
966
|
+
* @param config - source info and optional id factory
|
|
967
|
+
* @returns a factory that creates SyncEntity instances
|
|
968
|
+
*
|
|
969
|
+
* @example
|
|
970
|
+
* ```typescript
|
|
971
|
+
* const factory = syncEntityFactory({
|
|
972
|
+
* sourceInfo: { id: 'api', name: 'External API' }
|
|
973
|
+
* });
|
|
974
|
+
*
|
|
975
|
+
* const entity = factory({ commonType: 'user', commonId: 'abc123' });
|
|
976
|
+
* // entity.id === 'abc123', entity.sourceInfo.id === 'api'
|
|
977
|
+
* ```
|
|
624
978
|
*/
|
|
625
979
|
function syncEntityFactory(config) {
|
|
626
980
|
const { idFactory: inputIdFactory, sourceInfo } = config;
|
|
@@ -680,6 +1034,26 @@ class SynchronizationFailedError extends BaseError {
|
|
|
680
1034
|
}
|
|
681
1035
|
}
|
|
682
1036
|
|
|
1037
|
+
/**
|
|
1038
|
+
* Creates a {@link SyncEntitySynchronizer} from the given configuration.
|
|
1039
|
+
*
|
|
1040
|
+
* Registers common type synchronizers and provides lookup by common type. Throws
|
|
1041
|
+
* {@link UnregisteredSyncEntityCommonTypeError} if an unregistered common type is requested.
|
|
1042
|
+
*
|
|
1043
|
+
* @param config - contains the list of common type synchronizers to register
|
|
1044
|
+
* @returns a synchronizer that delegates to the appropriate common type synchronizer
|
|
1045
|
+
* @throws {UnregisteredSyncEntityCommonTypeError} when requesting an unregistered common type
|
|
1046
|
+
*
|
|
1047
|
+
* @example
|
|
1048
|
+
* ```typescript
|
|
1049
|
+
* const synchronizer = syncEntitySynchronizer({
|
|
1050
|
+
* commonTypeSynchronizers: [userSynchronizer, orderSynchronizer]
|
|
1051
|
+
* });
|
|
1052
|
+
*
|
|
1053
|
+
* const instance = await synchronizer.synchronizeInstance({ commonType: 'user', commonId: '123' });
|
|
1054
|
+
* const result = await instance.synchronize();
|
|
1055
|
+
* ```
|
|
1056
|
+
*/
|
|
683
1057
|
function syncEntitySynchronizer(config) {
|
|
684
1058
|
const map = new Map(config.commonTypeSynchronizers.map((x) => [x.commonType, x]));
|
|
685
1059
|
const commonTypes = Array.from(map.keys());
|
|
@@ -700,6 +1074,21 @@ function syncEntitySynchronizer(config) {
|
|
|
700
1074
|
};
|
|
701
1075
|
}
|
|
702
1076
|
|
|
1077
|
+
/**
|
|
1078
|
+
* Creates a {@link BasicSyncEntityCommonTypeSynchronizer} that orchestrates synchronization across multiple sources
|
|
1079
|
+
* for a specific entity common type.
|
|
1080
|
+
*
|
|
1081
|
+
* The synchronizer follows a primary/secondary/replica flow:
|
|
1082
|
+
* 1. The primary source is synchronized first (exactly one required).
|
|
1083
|
+
* 2. Secondary sources are synchronized sequentially; if a secondary reports deletion while primary did not, the sync restarts once.
|
|
1084
|
+
* 3. Replica sources are synchronized concurrently (up to 3 in parallel).
|
|
1085
|
+
*
|
|
1086
|
+
* @param config - common type, sources, and context loader
|
|
1087
|
+
* @returns a synchronizer for the configured common type
|
|
1088
|
+
* @throws {NoPrimarySyncSourceError} when no primary source is found
|
|
1089
|
+
* @throws {MultiplePrimarySyncSourceError} when more than one primary source is found
|
|
1090
|
+
* @throws {SynchronizationFailedError} when primary or secondary sync returns failed/error
|
|
1091
|
+
*/
|
|
703
1092
|
function basicSyncEntityCommonTypeSynchronizerInstanceFactory(config) {
|
|
704
1093
|
const { commonType, sources, entitySourceContextLoader, dynamicSources = false } = config;
|
|
705
1094
|
const syncEntityCommonTypeIdPairForType = syncEntityCommonTypeIdPairFactory(commonType);
|
|
@@ -893,111 +1282,114 @@ function basicSyncEntityCommonTypeSynchronizerInstanceFactory(config) {
|
|
|
893
1282
|
return result;
|
|
894
1283
|
}
|
|
895
1284
|
|
|
896
|
-
|
|
897
|
-
function
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
function
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
const transformCommaSeparatedNumberValueToArray = transformCommaSeparatedValueToArray((x) => Number(x));
|
|
916
|
-
const transformCommaSeparatedStringValueToArray = transformCommaSeparatedValueToArray((x) => x);
|
|
917
|
-
|
|
918
|
-
// MARK: Transform Annotations
|
|
919
|
-
function TransformCommaSeparatedValueToArray(mapFn) {
|
|
920
|
-
return Transform(transformCommaSeparatedValueToArray(mapFn));
|
|
921
|
-
}
|
|
922
|
-
const TransformCommaSeparatedStringValueToArray = () => Transform(transformCommaSeparatedStringValueToArray);
|
|
923
|
-
const TransformCommaSeparatedNumberValueToArray = () => Transform(transformCommaSeparatedNumberValueToArray);
|
|
924
|
-
const TransformStringValueToBoolean = () => Transform(transformStringToBoolean());
|
|
925
|
-
|
|
1285
|
+
/**
|
|
1286
|
+
* Creates a function that validates input against an ArkType schema and then processes it.
|
|
1287
|
+
*
|
|
1288
|
+
* On validation success, calls the configured handler function. On validation failure, delegates to the error handler.
|
|
1289
|
+
*
|
|
1290
|
+
* @param config - schema, handler function, and validation error handler
|
|
1291
|
+
* @returns a function that validates and processes input objects
|
|
1292
|
+
*
|
|
1293
|
+
* @example
|
|
1294
|
+
* ```typescript
|
|
1295
|
+
* const processUser = transformAndValidateObject({
|
|
1296
|
+
* schema: userType,
|
|
1297
|
+
* fn: async (user) => ({ id: user.id }),
|
|
1298
|
+
* handleValidationError: async (errors) => { throw new Error(errors.summary); }
|
|
1299
|
+
* });
|
|
1300
|
+
*
|
|
1301
|
+
* const result = await processUser({ id: '123', name: 'John' });
|
|
1302
|
+
* ```
|
|
1303
|
+
*/
|
|
926
1304
|
function transformAndValidateObject(config) {
|
|
927
1305
|
const transformToResult = transformAndValidateObjectResult(config);
|
|
928
1306
|
const { handleValidationError } = config;
|
|
929
|
-
return
|
|
930
|
-
const
|
|
931
|
-
let result;
|
|
1307
|
+
return async (input, context) => {
|
|
1308
|
+
const x = await transformToResult(input, context);
|
|
932
1309
|
if (x.success) {
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
else {
|
|
936
|
-
result = await handleValidationError(x.validationErrors);
|
|
1310
|
+
return { object: x.object, result: x.result };
|
|
937
1311
|
}
|
|
938
|
-
return
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
});
|
|
1312
|
+
// Error handler is expected to throw. If it doesn't, there is no validated object to return.
|
|
1313
|
+
const result = await handleValidationError(x.validationErrors);
|
|
1314
|
+
return { object: undefined, result };
|
|
1315
|
+
};
|
|
943
1316
|
}
|
|
944
1317
|
/**
|
|
945
|
-
* Creates a
|
|
1318
|
+
* Creates a reusable factory for generating transform-and-validate functions with shared defaults.
|
|
1319
|
+
*
|
|
1320
|
+
* The factory pre-configures error handling so individual function calls
|
|
1321
|
+
* only need to specify the schema and handler.
|
|
946
1322
|
*
|
|
947
|
-
* @param defaults
|
|
948
|
-
* @returns
|
|
1323
|
+
* @param defaults - default error handler
|
|
1324
|
+
* @returns a factory function that creates TransformAndValidateObjectFunction instances
|
|
949
1325
|
*/
|
|
950
1326
|
function transformAndValidateObjectFactory(defaults) {
|
|
951
|
-
const { handleValidationError: defaultHandleValidationError
|
|
952
|
-
return (
|
|
1327
|
+
const { handleValidationError: defaultHandleValidationError } = defaults;
|
|
1328
|
+
return (schema, fn, handleValidationError) => {
|
|
953
1329
|
const config = {
|
|
954
|
-
|
|
1330
|
+
schema,
|
|
955
1331
|
fn,
|
|
956
|
-
handleValidationError: handleValidationError ?? defaultHandleValidationError
|
|
957
|
-
optionsForContext,
|
|
958
|
-
defaultValidationOptions
|
|
1332
|
+
handleValidationError: handleValidationError ?? defaultHandleValidationError
|
|
959
1333
|
};
|
|
960
1334
|
return transformAndValidateObject(config);
|
|
961
1335
|
};
|
|
962
1336
|
}
|
|
963
1337
|
/**
|
|
964
|
-
*
|
|
1338
|
+
* Creates a function that validates input against an ArkType schema and returns a discriminated result.
|
|
965
1339
|
*
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
1340
|
+
* Returns `{ success: true, object, result }` on valid input, or `{ success: false, validationErrors }` on failure.
|
|
1341
|
+
* The caller is responsible for handling the error case.
|
|
1342
|
+
*
|
|
1343
|
+
* @param config - schema and handler function
|
|
1344
|
+
* @returns a function that returns a success/error discriminated result
|
|
1345
|
+
*
|
|
1346
|
+
* @example
|
|
1347
|
+
* ```typescript
|
|
1348
|
+
* const validateUser = transformAndValidateObjectResult({
|
|
1349
|
+
* schema: userType,
|
|
1350
|
+
* fn: async (user) => ({ saved: true })
|
|
1351
|
+
* });
|
|
1352
|
+
*
|
|
1353
|
+
* const result = await validateUser({ name: 'John' });
|
|
1354
|
+
* if (result.success) {
|
|
1355
|
+
* console.log(result.result);
|
|
1356
|
+
* } else {
|
|
1357
|
+
* console.log(result.validationErrors.summary);
|
|
1358
|
+
* }
|
|
1359
|
+
* ```
|
|
969
1360
|
*/
|
|
970
1361
|
function transformAndValidateObjectResult(config) {
|
|
971
|
-
const {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
...transformOptions,
|
|
977
|
-
// Note: Each variable on the target class must be marked with the @Expose() annotation.
|
|
978
|
-
excludeExtraneousValues: true
|
|
979
|
-
});
|
|
980
|
-
const validationErrors = await validate(object, {
|
|
981
|
-
forbidUnknownValues: false, // allow classes without annotations by default
|
|
982
|
-
...defaultValidationOptions,
|
|
983
|
-
...validateOptions
|
|
984
|
-
});
|
|
985
|
-
if (validationErrors.length) {
|
|
986
|
-
return { object, validationErrors, success: false };
|
|
987
|
-
}
|
|
988
|
-
else {
|
|
989
|
-
const result = await fn(object);
|
|
990
|
-
return { object, result, success: true };
|
|
1362
|
+
const { schema, fn } = config;
|
|
1363
|
+
return async (input) => {
|
|
1364
|
+
const out = schema(input);
|
|
1365
|
+
if (out instanceof type.errors) {
|
|
1366
|
+
return { validationErrors: out, success: false };
|
|
991
1367
|
}
|
|
1368
|
+
const object = out;
|
|
1369
|
+
const result = await fn(object);
|
|
1370
|
+
return { object, result, success: true };
|
|
992
1371
|
};
|
|
993
1372
|
}
|
|
994
1373
|
|
|
1374
|
+
/**
|
|
1375
|
+
* Creates a factory for transform-and-validate functions that return the result with the parsed object attached as `params`.
|
|
1376
|
+
*
|
|
1377
|
+
* @param defaults - shared error handler defaults
|
|
1378
|
+
* @returns a factory that produces functions returning {@link TransformAndValidateFunctionResult}
|
|
1379
|
+
*/
|
|
995
1380
|
function transformAndValidateFunctionResultFactory(defaults) {
|
|
996
1381
|
return toTransformAndValidateFunctionResultFactory(transformAndValidateObjectFactory(defaults));
|
|
997
1382
|
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Wraps an existing {@link TransformAndValidateObjectFactory} to produce functions that attach the parsed object
|
|
1385
|
+
* as `params` on the result.
|
|
1386
|
+
*
|
|
1387
|
+
* @param transformAndValidateObjectFactory - the base factory to wrap
|
|
1388
|
+
* @returns a factory that produces functions returning results with `params` attached
|
|
1389
|
+
*/
|
|
998
1390
|
function toTransformAndValidateFunctionResultFactory(transformAndValidateObjectFactory) {
|
|
999
|
-
return (
|
|
1000
|
-
const transformAndValidateObjectFn = transformAndValidateObjectFactory(
|
|
1391
|
+
return (schema, fn, handleValidationError) => {
|
|
1392
|
+
const transformAndValidateObjectFn = transformAndValidateObjectFactory(schema, fn, handleValidationError);
|
|
1001
1393
|
return (input, context) => {
|
|
1002
1394
|
return toTransformAndValidateFunctionResult(transformAndValidateObjectFn(input, context));
|
|
1003
1395
|
};
|
|
@@ -1012,10 +1404,18 @@ function toTransformAndValidateFunctionResult(objectOutput) {
|
|
|
1012
1404
|
});
|
|
1013
1405
|
}
|
|
1014
1406
|
|
|
1407
|
+
/**
|
|
1408
|
+
* Creates a factory for transform-and-validate functions that return only the result (discarding the parsed object).
|
|
1409
|
+
*
|
|
1410
|
+
* Useful when you only need the processed output and don't need access to the validated input.
|
|
1411
|
+
*
|
|
1412
|
+
* @param defaults - shared error handler defaults
|
|
1413
|
+
* @returns a factory that produces functions returning only the handler's result
|
|
1414
|
+
*/
|
|
1015
1415
|
function transformAndValidateResultFactory(defaults) {
|
|
1016
1416
|
const factory = transformAndValidateObjectFactory(defaults);
|
|
1017
|
-
return (
|
|
1018
|
-
const transformAndValidateObjectFn = factory(
|
|
1417
|
+
return (schema, fn, handleValidationError) => {
|
|
1418
|
+
const transformAndValidateObjectFn = factory(schema, fn, handleValidationError);
|
|
1019
1419
|
return async (input, context) => {
|
|
1020
1420
|
const { result } = await transformAndValidateObjectFn(input, context);
|
|
1021
1421
|
return result;
|
|
@@ -1024,152 +1424,80 @@ function transformAndValidateResultFactory(defaults) {
|
|
|
1024
1424
|
}
|
|
1025
1425
|
|
|
1026
1426
|
/**
|
|
1027
|
-
*
|
|
1028
|
-
*/
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1427
|
+
* ArkType schema for a model key (non-empty string).
|
|
1428
|
+
*/
|
|
1429
|
+
const modelKeyType = type('string > 0');
|
|
1430
|
+
/**
|
|
1431
|
+
* ArkType schema for a model id (non-empty string).
|
|
1432
|
+
*/
|
|
1433
|
+
const modelIdType = type('string > 0');
|
|
1434
|
+
/**
|
|
1435
|
+
* ArkType schema for target model params with a required `key` field.
|
|
1436
|
+
*/
|
|
1437
|
+
const targetModelParamsType = type({
|
|
1438
|
+
key: modelKeyType
|
|
1439
|
+
});
|
|
1440
|
+
/**
|
|
1441
|
+
* ArkType schema for target model id params with a required `id` field.
|
|
1442
|
+
*/
|
|
1443
|
+
const targetModelIdParamsType = type({
|
|
1444
|
+
id: modelIdType
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
function clearable(definition) {
|
|
1448
|
+
if (typeof definition === 'string') {
|
|
1449
|
+
return `${definition} | null | undefined`;
|
|
1450
|
+
}
|
|
1451
|
+
return definition.or('null').or('undefined');
|
|
1042
1452
|
}
|
|
1043
1453
|
|
|
1044
1454
|
/**
|
|
1045
|
-
*
|
|
1046
|
-
*/
|
|
1047
|
-
|
|
1048
|
-
return function (object, propertyName) {
|
|
1049
|
-
registerDecorator({
|
|
1050
|
-
name: 'isMinuteOfDay',
|
|
1051
|
-
target: object.constructor,
|
|
1052
|
-
propertyName: propertyName,
|
|
1053
|
-
options: validationOptions,
|
|
1054
|
-
validator: {
|
|
1055
|
-
validate: isMinuteOfDay,
|
|
1056
|
-
defaultMessage: buildMessage((eachPrefix, args) => eachPrefix + `$property value of "${args?.value}" is not a valid minute of the day.`, validationOptions)
|
|
1057
|
-
}
|
|
1058
|
-
});
|
|
1059
|
-
};
|
|
1060
|
-
}
|
|
1455
|
+
* ArkType schema for a valid ISO 8601 day string (e.g., "2024-01-15").
|
|
1456
|
+
*/
|
|
1457
|
+
const iso8601DayStringType = type('string > 0').narrow((val, ctx) => (val != null && isISO8601DayString(val)) || ctx.mustBe('a valid ISO 8601 day string'));
|
|
1061
1458
|
|
|
1062
1459
|
/**
|
|
1063
|
-
*
|
|
1064
|
-
*/
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
registerDecorator({
|
|
1068
|
-
name: 'isE164PhoneNumber',
|
|
1069
|
-
target: object.constructor,
|
|
1070
|
-
propertyName: propertyName,
|
|
1071
|
-
options: validationOptions,
|
|
1072
|
-
validator: {
|
|
1073
|
-
validate: (x) => isE164PhoneNumber(x, false),
|
|
1074
|
-
defaultMessage: buildMessage((eachPrefix, args) => eachPrefix + `$property value of "${args?.value}" is not a E164PhoneNumber with no extension.`, validationOptions)
|
|
1075
|
-
}
|
|
1076
|
-
});
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1460
|
+
* ArkType schema for a valid minute of the day (0-1439).
|
|
1461
|
+
*/
|
|
1462
|
+
const minuteOfDayType = type('number').narrow((val, ctx) => (val != null && isMinuteOfDay(val)) || ctx.mustBe('a valid minute of the day (0-1439)'));
|
|
1463
|
+
|
|
1079
1464
|
/**
|
|
1080
|
-
*
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
* @returns
|
|
1084
|
-
*/
|
|
1085
|
-
function IsE164PhoneNumberWithOptionalExtension(validationOptions) {
|
|
1086
|
-
return function (object, propertyName) {
|
|
1087
|
-
registerDecorator({
|
|
1088
|
-
name: 'isE164PhoneNumber',
|
|
1089
|
-
target: object.constructor,
|
|
1090
|
-
propertyName: propertyName,
|
|
1091
|
-
options: validationOptions,
|
|
1092
|
-
validator: {
|
|
1093
|
-
validate: (x) => isE164PhoneNumber(x, true),
|
|
1094
|
-
defaultMessage: buildMessage((eachPrefix, args) => eachPrefix + `$property value of "${args?.value}" is not an E164PhoneNumber or has an invalid extension.`, validationOptions)
|
|
1095
|
-
}
|
|
1096
|
-
});
|
|
1097
|
-
};
|
|
1098
|
-
}
|
|
1465
|
+
* ArkType schema for a valid E.164 phone number without an extension.
|
|
1466
|
+
*/
|
|
1467
|
+
const e164PhoneNumberType = type('string > 0').narrow((val, ctx) => (val != null && isE164PhoneNumber(val, false)) || ctx.mustBe('a valid E.164 phone number without an extension'));
|
|
1099
1468
|
/**
|
|
1100
|
-
*
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
registerDecorator({
|
|
1108
|
-
name: 'isE164PhoneNumberWithExtension',
|
|
1109
|
-
target: object.constructor,
|
|
1110
|
-
propertyName: propertyName,
|
|
1111
|
-
options: validationOptions,
|
|
1112
|
-
validator: {
|
|
1113
|
-
validate: isE164PhoneNumberWithExtension,
|
|
1114
|
-
defaultMessage: buildMessage((eachPrefix, args) => eachPrefix + `$property value of "${args?.value}" is not a E164PhoneNumberWithExtension.`, validationOptions)
|
|
1115
|
-
}
|
|
1116
|
-
});
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1469
|
+
* ArkType schema for a valid E.164 phone number, optionally with an extension.
|
|
1470
|
+
*/
|
|
1471
|
+
const e164PhoneNumberWithOptionalExtensionType = type('string > 0').narrow((val, ctx) => (val != null && isE164PhoneNumber(val, true)) || ctx.mustBe('a valid E.164 phone number'));
|
|
1472
|
+
/**
|
|
1473
|
+
* ArkType schema for a valid E.164 phone number that includes an extension.
|
|
1474
|
+
*/
|
|
1475
|
+
const e164PhoneNumberWithExtensionType = type('string > 0').narrow((val, ctx) => (val != null && isE164PhoneNumberWithExtension(val)) || ctx.mustBe('a valid E.164 phone number with an extension'));
|
|
1119
1476
|
|
|
1120
1477
|
/**
|
|
1121
|
-
*
|
|
1478
|
+
* Creates an ArkType schema that validates an array has no duplicate keys.
|
|
1479
|
+
*
|
|
1480
|
+
* @param readKey - function that extracts the key from each array element
|
|
1481
|
+
* @returns an ArkType schema that narrows `T[]` to ensure uniqueness
|
|
1482
|
+
*
|
|
1483
|
+
* @example
|
|
1484
|
+
* ```typescript
|
|
1485
|
+
* const uniqueItemsType = uniqueKeyedType((item: Item) => item.id);
|
|
1486
|
+
* ```
|
|
1122
1487
|
*/
|
|
1123
|
-
function
|
|
1488
|
+
function uniqueKeyedType(readKey) {
|
|
1124
1489
|
const isUniqueKeyed = isUniqueKeyedFunction(readKey);
|
|
1125
|
-
return
|
|
1126
|
-
registerDecorator({
|
|
1127
|
-
name: 'isUniqueKeyed',
|
|
1128
|
-
target: object.constructor,
|
|
1129
|
-
propertyName: propertyName,
|
|
1130
|
-
options: validationOptions,
|
|
1131
|
-
validator: {
|
|
1132
|
-
validate: isUniqueKeyed,
|
|
1133
|
-
defaultMessage: buildMessage((eachPrefix, args) => eachPrefix + `$property value has one or more values with the same key. Keys must be unique.`, validationOptions)
|
|
1134
|
-
}
|
|
1135
|
-
});
|
|
1136
|
-
};
|
|
1490
|
+
return type('unknown[]').narrow((val, ctx) => (val != null && isUniqueKeyed(val)) || ctx.mustBe('an array with unique keys'));
|
|
1137
1491
|
}
|
|
1138
1492
|
|
|
1139
1493
|
/**
|
|
1140
|
-
*
|
|
1141
|
-
*/
|
|
1142
|
-
|
|
1143
|
-
return function (object, propertyName) {
|
|
1144
|
-
registerDecorator({
|
|
1145
|
-
name: 'isWebsiteUrl',
|
|
1146
|
-
target: object.constructor,
|
|
1147
|
-
propertyName: propertyName,
|
|
1148
|
-
options: validationOptions,
|
|
1149
|
-
validator: {
|
|
1150
|
-
validate: isWebsiteUrl,
|
|
1151
|
-
defaultMessage: buildMessage((eachPrefix, args) => eachPrefix + `$property value of "${args?.value}" is not a valid website url.`, validationOptions)
|
|
1152
|
-
}
|
|
1153
|
-
});
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1494
|
+
* ArkType schema for a valid website URL (with or without protocol prefix).
|
|
1495
|
+
*/
|
|
1496
|
+
const websiteUrlType = type('string > 0').narrow((val, ctx) => (val != null && isWebsiteUrl(val)) || ctx.mustBe('a valid website URL'));
|
|
1156
1497
|
/**
|
|
1157
|
-
*
|
|
1158
|
-
*/
|
|
1159
|
-
|
|
1160
|
-
return function (object, propertyName) {
|
|
1161
|
-
registerDecorator({
|
|
1162
|
-
name: 'isWebsiteUrlWithPrefix',
|
|
1163
|
-
target: object.constructor,
|
|
1164
|
-
propertyName: propertyName,
|
|
1165
|
-
options: validationOptions,
|
|
1166
|
-
validator: {
|
|
1167
|
-
validate: isWebsiteUrlWithPrefix,
|
|
1168
|
-
defaultMessage: buildMessage((eachPrefix, args) => eachPrefix + `$property value of "${args?.value}" is not a valid website url that starts with a http/https prefix.`, validationOptions)
|
|
1169
|
-
}
|
|
1170
|
-
});
|
|
1171
|
-
};
|
|
1172
|
-
}
|
|
1498
|
+
* ArkType schema for a valid website URL that starts with `http://` or `https://`.
|
|
1499
|
+
*/
|
|
1500
|
+
const websiteUrlWithPrefixType = type('string > 0').narrow((val, ctx) => (val != null && isWebsiteUrlWithPrefix(val)) || ctx.mustBe('a valid website URL starting with http:// or https://'));
|
|
1173
1501
|
|
|
1174
|
-
export { ADDRESS_CITY_MAX_LENGTH, ADDRESS_COUNTRY_MAX_LENGTH, ADDRESS_LINE_MAX_LENGTH, ADDRESS_STATE_CODE_MAX_LENGTH, ADDRESS_STATE_MAX_LENGTH, ADDRESS_ZIP_MAX_LENGTH, AbstractModelPermissionService,
|
|
1502
|
+
export { ADDRESS_CITY_MAX_LENGTH, ADDRESS_COUNTRY_MAX_LENGTH, ADDRESS_LINE_MAX_LENGTH, ADDRESS_STATE_CODE_MAX_LENGTH, ADDRESS_STATE_MAX_LENGTH, ADDRESS_ZIP_MAX_LENGTH, AbstractModelPermissionService, CASHAPP_BASE_URL, CASHAPP_USERNAME_PREFIX, CASHAPP_WEBSITE_LINK_TYPE, EMAIL_URL_WEBSITE_LINK_TYPE, FACEBOOK_BASE_URL, FACEBOOK_WEBSITE_LINK_TYPE, FULL_ACCESS_ROLE_KEY, GRANTED_ADMIN_ROLE_KEY, GRANTED_DELETE_ROLE_KEY, GRANTED_OWNER_ROLE_KEY, GRANTED_READ_ROLE_KEY, GRANTED_SYS_ADMIN_ROLE_KEY, GRANTED_UPDATE_ROLE_KEY, GrantedRoleMapReaderInstance, INSTAGRAM_BASE_URL, INSTAGRAM_WEBSITE_LINK_TYPE, NO_ACCESS_ROLE_KEY, PAYPAL_BASE_URL, PAYPAL_WEBSITE_LINK_TYPE, PHONE_URL_WEBSITE_LINK_TYPE, SNAPCHAT_BASE_URL, SNAPCHAT_WEBSITE_LINK_ISOLATE_PROFILE_ID, SNAPCHAT_WEBSITE_LINK_TYPE, SPOTIFY_BASE_URL, SPOTIFY_WEBSITE_LINK_ISOLATE_PROFILE_ID, SPOTIFY_WEBSITE_LINK_TYPE, TIKTOK_BASE_URL, TIKTOK_USERNAME_PREFIX, TIKTOK_WEBSITE_LINK_TYPE, TWITTER_BASE_URL, TWITTER_WEBSITE_LINK_TYPE, UNKNOWN_WEBSITE_LINK_TYPE, VENMO_BASE_URL, VENMO_WEBSITE_LINK_ISOLATE_PROFILE_ID, VENMO_WEBSITE_LINK_TYPE, WEBSITE_FILE_LINK_DATA_MAX_LENGTH, WEBSITE_FILE_LINK_DATA_REGEX, WEBSITE_FILE_LINK_ENCODE_SEPARATOR, WEBSITE_FILE_LINK_MIME_TYPE_MAX_LENGTH, WEBSITE_FILE_LINK_MIME_TYPE_REGEX, WEBSITE_FILE_LINK_NAME_MAX_LENGTH, WEBSITE_FILE_LINK_TYPE_MAX_LENGTH, WEBSITE_FILE_LINK_TYPE_REGEX, WEBSITE_FILE_LINK_WEBSITE_LINK_TYPE, WEBSITE_LINK_ENCODED_DATA_MAX_LENGTH, WEBSITE_LINK_ISOLATE_BASE_URL_PROFILE_ID, WEBSITE_LINK_TYPE_MAX_LENGTH, WEBSITE_LINK_TYPE_REGEX, WEBSITE_URL_WEBSITE_LINK_TYPE, YOUTUBE_BASE_URL, YOUTUBE_WEBSITE_LINK_ISOLATE_PROFILE_ID, YOUTUBE_WEBSITE_LINK_TYPE, basicSyncEntityCommonTypeSynchronizerInstanceFactory, cashappProfileUrl, cashappProfileUrlToWebsiteLink, clearable, contextGrantedModelRoles, decodeWebsiteLinkEncodedDataToWebsiteFileLink, e164PhoneNumberType, e164PhoneNumberWithExtensionType, e164PhoneNumberWithOptionalExtensionType, emailAddressToWebsiteLink, encodeWebsiteFileLinkToWebsiteLinkEncodedData, facebookProfileUrl, facebookProfileUrlToWebsiteLink, fullAccessGrantedModelRoles, fullAccessRoleMap, grantedRoleKeysMapFromArray, grantedRoleMapReader, instagramProfileUrl, instagramProfileUrlToWebsiteLink, isFullAccessRoleMap, isGrantedAdminLevelRole, isNoAccessRoleMap, isValidWebsiteLinkType, iso8601DayStringType, minuteOfDayType, modelIdType, modelKeyType, noAccessContextGrantedModelRoles, noAccessRoleMap, paypalProfileUrl, paypalProfileUrlToWebsiteLink, phoneNumberToWebsiteLink, snapchatProfileUrl, snapchatProfileUrlToWebsiteLink, spotifyProfileUrl, spotifyProfileUrlToWebsiteLink, syncEntityCommonTypeIdPairFactory, syncEntityFactory, syncEntitySynchronizer, targetModelIdParamsType, targetModelParamsType, tiktokProfileUrl, tiktokProfileUrlToWebsiteLink, toTransformAndValidateFunctionResult, toTransformAndValidateFunctionResultFactory, transformAndValidateFunctionResultFactory, transformAndValidateObject, transformAndValidateObjectFactory, transformAndValidateObjectResult, transformAndValidateResultFactory, twitterProfileUrl, twitterProfileUrlToWebsiteLink, uniqueKeyedType, unitedStatesAddressWithStateCodeType, unitedStatesAddressWithStateStringType, usernameFromUsernameOrWebsiteWithBaseUrlUsername, usernameFromUsernameOrWebsiteWithOneOffBaseUrlUsername, usernameOrWebsiteUrlToWebsiteUrl, venmoProfileUrl, venmoProfileUrlToWebsiteLink, websiteFileLinkToWebsiteLink, websiteFileLinkType, websiteLinkToWebsiteLinkFile, websiteLinkType, websiteUrlToWebsiteLink, websiteUrlType, websiteUrlWithPrefixType, youtubeProfileUrl, youtubeProfileUrlToWebsiteLink };
|
|
1175
1503
|
//# sourceMappingURL=index.esm.js.map
|