@atproto/syntax 0.4.3 → 0.5.1
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/CHANGELOG.md +26 -0
- package/dist/at-identifier.d.ts +49 -2
- package/dist/at-identifier.d.ts.map +1 -1
- package/dist/at-identifier.js +68 -6
- package/dist/at-identifier.js.map +1 -1
- package/dist/aturi.d.ts +10 -1
- package/dist/aturi.d.ts.map +1 -1
- package/dist/aturi.js +25 -0
- package/dist/aturi.js.map +1 -1
- package/dist/datetime.d.ts +128 -11
- package/dist/datetime.d.ts.map +1 -1
- package/dist/datetime.js +205 -76
- package/dist/datetime.js.map +1 -1
- package/dist/handle.d.ts +1 -0
- package/dist/handle.d.ts.map +1 -1
- package/dist/handle.js.map +1 -1
- package/dist/nsid.d.ts +2 -2
- package/dist/nsid.d.ts.map +1 -1
- package/dist/nsid.js +4 -1
- package/dist/nsid.js.map +1 -1
- package/package.json +1 -1
- package/src/at-identifier.ts +77 -6
- package/src/aturi.ts +45 -3
- package/src/datetime.ts +268 -88
- package/src/handle.ts +2 -0
- package/src/nsid.ts +5 -3
- package/tests/aturi.test.ts +47 -0
- package/tests/datetime.test.ts +148 -62
package/dist/datetime.js
CHANGED
|
@@ -1,105 +1,234 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InvalidDatetimeError =
|
|
4
|
-
exports.
|
|
5
|
-
exports.
|
|
3
|
+
exports.InvalidDatetimeError = void 0;
|
|
4
|
+
exports.assertAtprotoDate = assertAtprotoDate;
|
|
5
|
+
exports.asAtprotoDate = asAtprotoDate;
|
|
6
|
+
exports.isAtprotoDate = isAtprotoDate;
|
|
7
|
+
exports.ifAtprotoDate = ifAtprotoDate;
|
|
8
|
+
exports.assertDatetimeString = assertDatetimeString;
|
|
9
|
+
exports.ensureValidDatetime = assertDatetimeString;
|
|
10
|
+
exports.asDatetimeString = asDatetimeString;
|
|
11
|
+
exports.isDatetimeString = isDatetimeString;
|
|
12
|
+
exports.isValidDatetime = isDatetimeString;
|
|
13
|
+
exports.ifDatetimeString = ifDatetimeString;
|
|
14
|
+
exports.currentDatetimeString = currentDatetimeString;
|
|
15
|
+
exports.toDatetimeString = toDatetimeString;
|
|
6
16
|
exports.normalizeDatetime = normalizeDatetime;
|
|
7
|
-
|
|
8
|
-
|
|
17
|
+
exports.normalizeDatetimeAlways = normalizeDatetimeAlways;
|
|
18
|
+
/**
|
|
19
|
+
* Indicates a date or string is not a valid representation of a datetime
|
|
20
|
+
* according to the atproto
|
|
21
|
+
* {@link https://atproto.com/specs/lexicon#datetime specification}.
|
|
9
22
|
*/
|
|
10
|
-
|
|
11
|
-
const date = new Date(input);
|
|
12
|
-
// must parse as ISO 8601; this also verifies semantics like month is not 13 or 00
|
|
13
|
-
if (isNaN(date.getTime())) {
|
|
14
|
-
throw new InvalidDatetimeError('datetime did not parse as ISO 8601');
|
|
15
|
-
}
|
|
16
|
-
if (date.toISOString().startsWith('-')) {
|
|
17
|
-
throw new InvalidDatetimeError('datetime normalized to a negative time');
|
|
18
|
-
}
|
|
19
|
-
// regex and other checks for RFC-3339
|
|
20
|
-
if (!/^[0-9]{4}-[01][0-9]-[0-3][0-9]T[0-2][0-9]:[0-6][0-9]:[0-6][0-9](.[0-9]{1,20})?(Z|([+-][0-2][0-9]:[0-5][0-9]))$/.test(input)) {
|
|
21
|
-
throw new InvalidDatetimeError("datetime didn't validate via regex");
|
|
22
|
-
}
|
|
23
|
-
if (input.length > 64) {
|
|
24
|
-
throw new InvalidDatetimeError('datetime is too long (64 chars max)');
|
|
25
|
-
}
|
|
26
|
-
if (input.endsWith('-00:00')) {
|
|
27
|
-
throw new InvalidDatetimeError('datetime can not use "-00:00" for UTC timezone');
|
|
28
|
-
}
|
|
29
|
-
if (input.startsWith('000')) {
|
|
30
|
-
throw new InvalidDatetimeError('datetime so close to year zero not allowed');
|
|
31
|
-
}
|
|
23
|
+
class InvalidDatetimeError extends Error {
|
|
32
24
|
}
|
|
33
|
-
|
|
25
|
+
exports.InvalidDatetimeError = InvalidDatetimeError;
|
|
26
|
+
/**
|
|
27
|
+
* @see {@link AtprotoDate}
|
|
34
28
|
*/
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
function assertAtprotoDate(date) {
|
|
30
|
+
const res = parseDate(date);
|
|
31
|
+
if (!res.success) {
|
|
32
|
+
throw new InvalidDatetimeError(res.message);
|
|
38
33
|
}
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @see {@link AtprotoDate}
|
|
37
|
+
*/
|
|
38
|
+
function asAtprotoDate(date) {
|
|
39
|
+
assertAtprotoDate(date);
|
|
40
|
+
return date;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* @see {@link AtprotoDate}
|
|
44
|
+
*/
|
|
45
|
+
function isAtprotoDate(date) {
|
|
46
|
+
return parseDate(date).success;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* @see {@link AtprotoDate}
|
|
50
|
+
*/
|
|
51
|
+
function ifAtprotoDate(date) {
|
|
52
|
+
return isAtprotoDate(date) ? date : undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validates that a string is a valid {@link DatetimeString} format string,
|
|
56
|
+
* throwing an error if it is not.
|
|
57
|
+
*
|
|
58
|
+
* @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.
|
|
59
|
+
* @see {@link DatetimeString}
|
|
60
|
+
*/
|
|
61
|
+
function assertDatetimeString(input) {
|
|
62
|
+
const result = parseString(input);
|
|
63
|
+
if (!result.success) {
|
|
64
|
+
throw new InvalidDatetimeError(result.message);
|
|
41
65
|
}
|
|
42
|
-
return true;
|
|
43
66
|
}
|
|
44
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Casts a string to a {@link DatetimeString} if it is a valid datetime format
|
|
69
|
+
* string, throwing an error if it is not.
|
|
70
|
+
*
|
|
71
|
+
* @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.
|
|
72
|
+
* @see {@link DatetimeString}
|
|
73
|
+
*/
|
|
74
|
+
function asDatetimeString(input) {
|
|
75
|
+
assertDatetimeString(input);
|
|
76
|
+
return input;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Checks if a string is a valid {@link DatetimeString} format string.
|
|
80
|
+
*
|
|
81
|
+
* @see {@link DatetimeString}
|
|
82
|
+
*/
|
|
83
|
+
function isDatetimeString(input) {
|
|
84
|
+
return parseString(input).success;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Returns the input if it is a valid {@link DatetimeString} format string, or
|
|
88
|
+
* `undefined` if it is not.
|
|
45
89
|
*
|
|
46
|
-
*
|
|
90
|
+
* @see {@link DatetimeString}
|
|
91
|
+
*/
|
|
92
|
+
function ifDatetimeString(input) {
|
|
93
|
+
return isDatetimeString(input) ? input : undefined;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns the current date and time as a {@link DatetimeString}.
|
|
47
97
|
*
|
|
48
|
-
*
|
|
98
|
+
* @see {@link DatetimeString}
|
|
99
|
+
*/
|
|
100
|
+
function currentDatetimeString() {
|
|
101
|
+
return toDatetimeString(new Date());
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Converts any {@link Date} into a {@link DatetimeString} if possible, throwing
|
|
105
|
+
* an error if the date is not a valid atproto datetime.
|
|
49
106
|
*
|
|
50
|
-
*
|
|
107
|
+
* This is short-hand for `asAtprotoDate(date).toISOString()`.
|
|
51
108
|
*
|
|
52
|
-
*
|
|
109
|
+
* @throws InvalidDatetimeError if the input date is not a valid atproto datetime (eg, it is too far in the future or past, or it normalizes to a negative year).
|
|
110
|
+
* @see {@link DatetimeString}
|
|
111
|
+
*/
|
|
112
|
+
function toDatetimeString(date) {
|
|
113
|
+
return asAtprotoDate(date).toISOString();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Takes a flexible datetime string and normalizes its representation.
|
|
117
|
+
*
|
|
118
|
+
* This function will work with any valid value that can be parsed as a date. It
|
|
119
|
+
* *additionally* is more flexible about accepting datetimes that are missing
|
|
120
|
+
* timezone information, and normalizing them to a valid atproto datetime.
|
|
121
|
+
*
|
|
122
|
+
* One use-case is a consistent, sortable string. Another is to work with older
|
|
123
|
+
* invalid createdAt datetimes.
|
|
124
|
+
*
|
|
125
|
+
* @returns ISODatetimeString - a valid atproto datetime with millisecond precision (3 sub-second digits) and UTC timezone with trailing 'Z' syntax.
|
|
126
|
+
* @throws InvalidDatetimeError - if the input string could not be parsed as a datetime, even with permissive parsing.
|
|
53
127
|
*/
|
|
54
128
|
function normalizeDatetime(dtStr) {
|
|
55
|
-
|
|
56
|
-
const outStr = new Date(dtStr).toISOString();
|
|
57
|
-
if (isValidDatetime(outStr)) {
|
|
58
|
-
return outStr;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// check if this permissive datetime is missing a timezone
|
|
62
|
-
if (!/.*(([+-]\d\d:?\d\d)|[a-zA-Z])$/.test(dtStr)) {
|
|
63
|
-
const date = new Date(dtStr + 'Z');
|
|
64
|
-
if (!isNaN(date.getTime())) {
|
|
65
|
-
const tzStr = date.toISOString();
|
|
66
|
-
if (isValidDatetime(tzStr)) {
|
|
67
|
-
return tzStr;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// finally try parsing as simple datetime
|
|
129
|
+
// Parse the string as is
|
|
72
130
|
const date = new Date(dtStr);
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
const isoStr = date.toISOString();
|
|
77
|
-
if (isValidDatetime(isoStr)) {
|
|
78
|
-
return isoStr;
|
|
131
|
+
if (isAtprotoDate(date)) {
|
|
132
|
+
return date.toISOString();
|
|
79
133
|
}
|
|
80
|
-
|
|
81
|
-
|
|
134
|
+
// if dtStr is not a valid date, try parsing again with a timezone
|
|
135
|
+
if (isNaN(date.getTime()) && !/.*(([+-]\d\d:?\d\d)|[a-zA-Z])$/.test(dtStr)) {
|
|
136
|
+
const date = new Date(`${dtStr}Z`);
|
|
137
|
+
if (isAtprotoDate(date)) {
|
|
138
|
+
return date.toISOString();
|
|
139
|
+
}
|
|
82
140
|
}
|
|
141
|
+
throw new InvalidDatetimeError('datetime did not parse as any timestamp format');
|
|
83
142
|
}
|
|
84
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Variant of {@link normalizeDatetime} which always returns a valid datetime
|
|
145
|
+
* string.
|
|
146
|
+
*
|
|
147
|
+
* If a {@link InvalidDatetimeError} is encountered, returns the UNIX epoch time
|
|
148
|
+
* as a UTC datetime (`1970-01-01T00:00:00.000Z`).
|
|
85
149
|
*
|
|
86
|
-
*
|
|
150
|
+
* @see {@link normalizeDatetime}
|
|
87
151
|
*/
|
|
88
|
-
|
|
152
|
+
function normalizeDatetimeAlways(dtStr) {
|
|
89
153
|
try {
|
|
90
154
|
return normalizeDatetime(dtStr);
|
|
91
155
|
}
|
|
92
156
|
catch (err) {
|
|
93
|
-
|
|
94
|
-
return new Date(0).toISOString();
|
|
95
|
-
}
|
|
96
|
-
throw err;
|
|
157
|
+
return '1970-01-01T00:00:00.000Z';
|
|
97
158
|
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
159
|
+
}
|
|
160
|
+
const failure = (m) => ({ success: false, message: m });
|
|
161
|
+
const success = (v) => ({ success: true, value: v });
|
|
162
|
+
/**
|
|
163
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc3339#section-5.6 Internet Date/Time Format}
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```abnf
|
|
167
|
+
* date-fullyear = 4DIGIT
|
|
168
|
+
* date-month = 2DIGIT ; 01-12
|
|
169
|
+
* date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
|
|
170
|
+
* ; month/year
|
|
171
|
+
* time-hour = 2DIGIT ; 00-23
|
|
172
|
+
* time-minute = 2DIGIT ; 00-59
|
|
173
|
+
* time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
|
|
174
|
+
* ; rules
|
|
175
|
+
* time-secfrac = "." 1*DIGIT
|
|
176
|
+
* time-numoffset = ("+" / "-") time-hour ":" time-minute
|
|
177
|
+
* time-offset = "Z" / time-numoffset
|
|
178
|
+
* partial-time = time-hour ":" time-minute ":" time-second
|
|
179
|
+
* [time-secfrac]
|
|
180
|
+
* full-date = date-fullyear "-" date-month "-" date-mday
|
|
181
|
+
* full-time = partial-time time-offset
|
|
182
|
+
* date-time = full-date "T" full-time
|
|
183
|
+
* ```
|
|
101
184
|
*/
|
|
102
|
-
|
|
185
|
+
const DATETIME_REGEX = /^(?<full_year>[0-9]{4})-(?<date_month>0[1-9]|1[012])-(?<date_mday>[0-2][0-9]|3[01])T(?<time_hour>[0-1][0-9]|2[0-3]):(?<time_minute>[0-5][0-9]):(?<time_second>[0-5][0-9]|60)(?<time_secfrac>\.[0-9]+)?(?<time_offset>Z|(?<time_numoffset>[+-](?:[0-1][0-9]|2[0-3]):[0-5][0-9]))$/;
|
|
186
|
+
/**
|
|
187
|
+
* Validates that the input is a datetime string according to atproto Lexicon
|
|
188
|
+
* rules, and parses it into a Date object.
|
|
189
|
+
*/
|
|
190
|
+
function parseString(input) {
|
|
191
|
+
// @NOTE Performing cheap tests first
|
|
192
|
+
if (typeof input !== 'string') {
|
|
193
|
+
return failure('datetime must be a string');
|
|
194
|
+
}
|
|
195
|
+
if (input.length > 64) {
|
|
196
|
+
return failure('datetime is too long (64 chars max)');
|
|
197
|
+
}
|
|
198
|
+
if (input.endsWith('-00:00')) {
|
|
199
|
+
return failure('datetime can not use "-00:00" for UTC timezone');
|
|
200
|
+
}
|
|
201
|
+
if (!DATETIME_REGEX.test(input)) {
|
|
202
|
+
return failure("datetime is not in a valid format (must match RFC 3339 & ISO 8601 with 'Z' or ±hh:mm timezone)");
|
|
203
|
+
}
|
|
204
|
+
// must parse as ISO 8601; this also verifies semantics like leap seconds and
|
|
205
|
+
// correct number of days in month, which the regex does not check for
|
|
206
|
+
const date = new Date(input);
|
|
207
|
+
return parseDate(date);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Ensures that a Date object represents a valid datetime according to atproto
|
|
211
|
+
* Lexicon rules. This ensures that `date.toISOString()` will produce a valid
|
|
212
|
+
* datetime string that can be used where {@link DatetimeString} is expected.
|
|
213
|
+
*/
|
|
214
|
+
function parseDate(date) {
|
|
215
|
+
const fullYear = date.getUTCFullYear();
|
|
216
|
+
// Ensures that the date is valid. We could check isNaN(date.getTime()) here
|
|
217
|
+
// but since we'll check the year anyway, we just use that for the validity
|
|
218
|
+
// check since an invalid date will have NaN year.
|
|
219
|
+
if (Number.isNaN(fullYear)) {
|
|
220
|
+
return failure('datetime did not parse as ISO 8601');
|
|
221
|
+
}
|
|
222
|
+
// Ensure that the ISO string representation does not start with ±YYYYYY
|
|
223
|
+
if (fullYear < 0) {
|
|
224
|
+
return failure('datetime normalized to a negative time');
|
|
225
|
+
}
|
|
226
|
+
if (fullYear > 9999) {
|
|
227
|
+
return failure('datetime year is too far in the future');
|
|
228
|
+
}
|
|
229
|
+
if (fullYear < 10) {
|
|
230
|
+
return failure('datetime so close to year zero not allowed');
|
|
231
|
+
}
|
|
232
|
+
return success(date);
|
|
103
233
|
}
|
|
104
|
-
exports.InvalidDatetimeError = InvalidDatetimeError;
|
|
105
234
|
//# sourceMappingURL=datetime.js.map
|
package/dist/datetime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"datetime.js","sourceRoot":"","sources":["../src/datetime.ts"],"names":[],"mappings":"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","sourcesContent":["/** An ISO 8601 formatted datetime string (YYYY-MM-DDTHH:mm:ss.sssZ) */\nexport type DatetimeString =\n `${string}-${string}-${string}T${string}:${string}:${string}${'Z' | `+${string}` | `-${string}`}`\n\n// Allow date.toISOString() to be used where datetime format is expected\ndeclare global {\n interface Date {\n toISOString(): `${string}-${string}-${string}T${string}:${string}:${string}Z`\n }\n}\n\n/* Validates datetime string against atproto Lexicon 'datetime' format.\n * Syntax is described at: https://atproto.com/specs/lexicon#datetime\n */\nexport function ensureValidDatetime<I extends string>(\n input: I,\n): asserts input is I & DatetimeString {\n const date = new Date(input)\n // must parse as ISO 8601; this also verifies semantics like month is not 13 or 00\n if (isNaN(date.getTime())) {\n throw new InvalidDatetimeError('datetime did not parse as ISO 8601')\n }\n if (date.toISOString().startsWith('-')) {\n throw new InvalidDatetimeError('datetime normalized to a negative time')\n }\n // regex and other checks for RFC-3339\n if (\n !/^[0-9]{4}-[01][0-9]-[0-3][0-9]T[0-2][0-9]:[0-6][0-9]:[0-6][0-9](.[0-9]{1,20})?(Z|([+-][0-2][0-9]:[0-5][0-9]))$/.test(\n input,\n )\n ) {\n throw new InvalidDatetimeError(\"datetime didn't validate via regex\")\n }\n if (input.length > 64) {\n throw new InvalidDatetimeError('datetime is too long (64 chars max)')\n }\n if (input.endsWith('-00:00')) {\n throw new InvalidDatetimeError(\n 'datetime can not use \"-00:00\" for UTC timezone',\n )\n }\n if (input.startsWith('000')) {\n throw new InvalidDatetimeError('datetime so close to year zero not allowed')\n }\n}\n\n/* Same logic as ensureValidDatetime(), but returns a boolean instead of throwing an exception.\n */\nexport function isValidDatetime<I extends string>(\n input: I,\n): input is I & DatetimeString {\n try {\n ensureValidDatetime(input)\n } catch (err) {\n return false\n }\n\n return true\n}\n\n/* Takes a flexible datetime string and normalizes representation.\n *\n * This function will work with any valid atproto datetime (eg, anything which isValidDatetime() is true for). It *additionally* is more flexible about accepting datetimes that don't comply to RFC 3339, or are missing timezone information, and normalizing them to a valid datetime.\n *\n * One use-case is a consistent, sortable string. Another is to work with older invalid createdAt datetimes.\n *\n * Successful output will be a valid atproto datetime with millisecond precision (3 sub-second digits) and UTC timezone with trailing 'Z' syntax. Throws `InvalidDatetimeError` if the input string could not be parsed as a datetime, even with permissive parsing.\n *\n * Expected output format: YYYY-MM-DDTHH:mm:ss.sssZ\n */\nexport function normalizeDatetime(dtStr: string): DatetimeString {\n if (isValidDatetime(dtStr)) {\n const outStr = new Date(dtStr).toISOString()\n if (isValidDatetime(outStr)) {\n return outStr\n }\n }\n\n // check if this permissive datetime is missing a timezone\n if (!/.*(([+-]\\d\\d:?\\d\\d)|[a-zA-Z])$/.test(dtStr)) {\n const date = new Date(dtStr + 'Z')\n if (!isNaN(date.getTime())) {\n const tzStr = date.toISOString()\n if (isValidDatetime(tzStr)) {\n return tzStr\n }\n }\n }\n\n // finally try parsing as simple datetime\n const date = new Date(dtStr)\n if (isNaN(date.getTime())) {\n throw new InvalidDatetimeError(\n 'datetime did not parse as any timestamp format',\n )\n }\n const isoStr = date.toISOString()\n if (isValidDatetime(isoStr)) {\n return isoStr\n } else {\n throw new InvalidDatetimeError(\n 'datetime normalized to invalid timestamp string',\n )\n }\n}\n\n/* Variant of normalizeDatetime() which always returns a valid datetime strings.\n *\n * If a InvalidDatetimeError is encountered, returns the UNIX epoch time as a UTC datetime (1970-01-01T00:00:00.000Z).\n */\nexport const normalizeDatetimeAlways = (dtStr: string): DatetimeString => {\n try {\n return normalizeDatetime(dtStr)\n } catch (err) {\n if (err instanceof InvalidDatetimeError) {\n return new Date(0).toISOString()\n }\n throw err\n }\n}\n\n/* Indicates a datetime string did not pass full atproto Lexicon datetime string format checks.\n */\nexport class InvalidDatetimeError extends Error {}\n"]}
|
|
1
|
+
{"version":3,"file":"datetime.js","sourceRoot":"","sources":["../src/datetime.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Indicates a date or string is not a valid representation of a datetime\n * according to the atproto\n * {@link https://atproto.com/specs/lexicon#datetime specification}.\n */\nexport class InvalidDatetimeError extends Error {}\n\n/**\n * A subset of {@link DatetimeString} that represent valid datetime strings with\n * the format: `YYYY-MM-DDTHH:mm:ss.sssZ`, as returned by `Date.toISOString()\n * for dates between the years 0000 and 9999.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString}\n */\nexport type ISODatetimeString =\n // @TODO Switch to branded types for more accurate type safety.\n `${string}-${string}-${string}T${string}:${string}:${string}.${string}Z`\n\n/**\n * Represents a {@link Date} that can be safely stringified into a valid atproto\n * {@link DatetimeString} using the {@link Date.toISOString toISOString()}\n * method.\n */\nexport interface AtprotoDate extends Date {\n toISOString(): ISODatetimeString\n}\n\n/**\n * @see {@link AtprotoDate}\n */\nexport function assertAtprotoDate(date: Date): asserts date is AtprotoDate {\n const res = parseDate(date)\n if (!res.success) {\n throw new InvalidDatetimeError(res.message)\n }\n}\n\n/**\n * @see {@link AtprotoDate}\n */\nexport function asAtprotoDate(date: Date): AtprotoDate {\n assertAtprotoDate(date)\n return date\n}\n\n/**\n * @see {@link AtprotoDate}\n */\nexport function isAtprotoDate(date: Date): date is AtprotoDate {\n return parseDate(date).success\n}\n\n/**\n * @see {@link AtprotoDate}\n */\nexport function ifAtprotoDate(date: Date): AtprotoDate | undefined {\n return isAtprotoDate(date) ? date : undefined\n}\n\n/**\n * Datetime strings in atproto data structures and API calls should meet the\n * {@link https://ijmacd.github.io/rfc3339-iso8601/ intersecting} requirements\n * of the RFC 3339, ISO 8601, and WHATWG HTML datetime standards.\n *\n * @note This literal template type is not accurate enough to ensure that a\n * string is a valid atproto datetime. The {@link DatetimeString} validation\n * functions ({@link assertDatetimeString}, {@link isDatetimeString}, etc)\n * should be used to validate that a string meets the atproto datetime\n * requirements, and the {@link toDatetimeString} function should be used to\n * convert a {@link Date} object into a valid {@link DatetimeString} string.\n *\n * @example \"2024-01-15T12:30:00Z\"\n * @example \"2024-01-15T12:30:00.000Z\"\n * @example \"2024-01-15T12:30:00+00:00\"\n * @example \"2024-01-15T11:30:00-01:00\"\n * @see {@link https://atproto.com/specs/lexicon#datetime atproto Lexicon datetime format}\n * @see {@link https://www.rfc-editor.org/rfc/rfc3339 RFC 3339}\n * @see {@link https://www.iso.org/iso-8601-date-and-time-format.html ISO 8601}\n */\nexport type DatetimeString =\n // @TODO Switch to branded types for more accurate type safety?\n | `${string}-${string}-${string}T${string}:${string}:${string}Z`\n | `${string}-${string}-${string}T${string}:${string}:${string}${'+' | '-'}${string}:${string}`\n\n/**\n * Validates that a string is a valid {@link DatetimeString} format string,\n * throwing an error if it is not.\n *\n * @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.\n * @see {@link DatetimeString}\n */\nexport function assertDatetimeString<I>(\n input: I,\n): asserts input is I & DatetimeString {\n const result = parseString(input)\n if (!result.success) {\n throw new InvalidDatetimeError(result.message)\n }\n}\n\n/**\n * Casts a string to a {@link DatetimeString} if it is a valid datetime format\n * string, throwing an error if it is not.\n *\n * @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.\n * @see {@link DatetimeString}\n */\nexport function asDatetimeString<I>(input: I): I & DatetimeString {\n assertDatetimeString(input)\n return input\n}\n\n/**\n * Checks if a string is a valid {@link DatetimeString} format string.\n *\n * @see {@link DatetimeString}\n */\nexport function isDatetimeString<I>(input: I): input is I & DatetimeString {\n return parseString(input).success\n}\n\n/**\n * Returns the input if it is a valid {@link DatetimeString} format string, or\n * `undefined` if it is not.\n *\n * @see {@link DatetimeString}\n */\nexport function ifDatetimeString<I>(\n input: I,\n): undefined | (I & DatetimeString) {\n return isDatetimeString(input) ? input : undefined\n}\n\n/**\n * Returns the current date and time as a {@link DatetimeString}.\n *\n * @see {@link DatetimeString}\n */\nexport function currentDatetimeString(): DatetimeString {\n return toDatetimeString(new Date())\n}\n\n/**\n * Converts any {@link Date} into a {@link DatetimeString} if possible, throwing\n * an error if the date is not a valid atproto datetime.\n *\n * This is short-hand for `asAtprotoDate(date).toISOString()`.\n *\n * @throws InvalidDatetimeError if the input date is not a valid atproto datetime (eg, it is too far in the future or past, or it normalizes to a negative year).\n * @see {@link DatetimeString}\n */\nexport function toDatetimeString(date: Date): DatetimeString {\n return asAtprotoDate(date).toISOString()\n}\n\n/**\n * Takes a flexible datetime string and normalizes its representation.\n *\n * This function will work with any valid value that can be parsed as a date. It\n * *additionally* is more flexible about accepting datetimes that are missing\n * timezone information, and normalizing them to a valid atproto datetime.\n *\n * One use-case is a consistent, sortable string. Another is to work with older\n * invalid createdAt datetimes.\n *\n * @returns ISODatetimeString - a valid atproto datetime with millisecond precision (3 sub-second digits) and UTC timezone with trailing 'Z' syntax.\n * @throws InvalidDatetimeError - if the input string could not be parsed as a datetime, even with permissive parsing.\n */\nexport function normalizeDatetime(dtStr: string): ISODatetimeString {\n // Parse the string as is\n const date = new Date(dtStr)\n if (isAtprotoDate(date)) {\n return date.toISOString()\n }\n\n // if dtStr is not a valid date, try parsing again with a timezone\n if (isNaN(date.getTime()) && !/.*(([+-]\\d\\d:?\\d\\d)|[a-zA-Z])$/.test(dtStr)) {\n const date = new Date(`${dtStr}Z`)\n if (isAtprotoDate(date)) {\n return date.toISOString()\n }\n }\n\n throw new InvalidDatetimeError(\n 'datetime did not parse as any timestamp format',\n )\n}\n\n/**\n * Variant of {@link normalizeDatetime} which always returns a valid datetime\n * string.\n *\n * If a {@link InvalidDatetimeError} is encountered, returns the UNIX epoch time\n * as a UTC datetime (`1970-01-01T00:00:00.000Z`).\n *\n * @see {@link normalizeDatetime}\n */\nexport function normalizeDatetimeAlways(dtStr: string): ISODatetimeString {\n try {\n return normalizeDatetime(dtStr)\n } catch (err) {\n return '1970-01-01T00:00:00.000Z'\n }\n}\n\n// Legacy exports (should we deprecate these ?)\nexport {\n assertDatetimeString as ensureValidDatetime,\n isDatetimeString as isValidDatetime,\n}\n\n// -----------------------------------------------------------------------------\n// ------------------------- Internal validation logic -------------------------\n// -----------------------------------------------------------------------------\n\n// Validation utils that allow avoiding try/catch for control flow (performance\n// optimization). Other syntax formats should also use this pattern to avoid\n// try/catch in their validation logic, at which point these utils can be moved\n// to a common internal utils.\ntype FailureResult = { success: false; message: string }\nconst failure = (m: string): FailureResult => ({ success: false, message: m })\ntype SuccessResult<V> = { success: true; value: V }\nconst success = <V>(v: V): SuccessResult<V> => ({ success: true, value: v })\ntype Result<V> = FailureResult | SuccessResult<V>\n\n/**\n * @see {@link https://www.rfc-editor.org/rfc/rfc3339#section-5.6 Internet Date/Time Format}\n *\n * @example\n * ```abnf\n * date-fullyear = 4DIGIT\n * date-month = 2DIGIT ; 01-12\n * date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on\n * ; month/year\n * time-hour = 2DIGIT ; 00-23\n * time-minute = 2DIGIT ; 00-59\n * time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second\n * ; rules\n * time-secfrac = \".\" 1*DIGIT\n * time-numoffset = (\"+\" / \"-\") time-hour \":\" time-minute\n * time-offset = \"Z\" / time-numoffset\n * partial-time = time-hour \":\" time-minute \":\" time-second\n * [time-secfrac]\n * full-date = date-fullyear \"-\" date-month \"-\" date-mday\n * full-time = partial-time time-offset\n * date-time = full-date \"T\" full-time\n * ```\n */\nconst DATETIME_REGEX =\n /^(?<full_year>[0-9]{4})-(?<date_month>0[1-9]|1[012])-(?<date_mday>[0-2][0-9]|3[01])T(?<time_hour>[0-1][0-9]|2[0-3]):(?<time_minute>[0-5][0-9]):(?<time_second>[0-5][0-9]|60)(?<time_secfrac>\\.[0-9]+)?(?<time_offset>Z|(?<time_numoffset>[+-](?:[0-1][0-9]|2[0-3]):[0-5][0-9]))$/\n\n/**\n * Validates that the input is a datetime string according to atproto Lexicon\n * rules, and parses it into a Date object.\n */\nfunction parseString(input: unknown): Result<AtprotoDate> {\n // @NOTE Performing cheap tests first\n if (typeof input !== 'string') {\n return failure('datetime must be a string')\n }\n if (input.length > 64) {\n return failure('datetime is too long (64 chars max)')\n }\n if (input.endsWith('-00:00')) {\n return failure('datetime can not use \"-00:00\" for UTC timezone')\n }\n if (!DATETIME_REGEX.test(input)) {\n return failure(\n \"datetime is not in a valid format (must match RFC 3339 & ISO 8601 with 'Z' or ±hh:mm timezone)\",\n )\n }\n\n // must parse as ISO 8601; this also verifies semantics like leap seconds and\n // correct number of days in month, which the regex does not check for\n const date = new Date(input)\n\n return parseDate(date)\n}\n\n/**\n * Ensures that a Date object represents a valid datetime according to atproto\n * Lexicon rules. This ensures that `date.toISOString()` will produce a valid\n * datetime string that can be used where {@link DatetimeString} is expected.\n */\nfunction parseDate(date: Date): Result<AtprotoDate> {\n const fullYear = date.getUTCFullYear()\n // Ensures that the date is valid. We could check isNaN(date.getTime()) here\n // but since we'll check the year anyway, we just use that for the validity\n // check since an invalid date will have NaN year.\n if (Number.isNaN(fullYear)) {\n return failure('datetime did not parse as ISO 8601')\n }\n // Ensure that the ISO string representation does not start with ±YYYYYY\n if (fullYear < 0) {\n return failure('datetime normalized to a negative time')\n }\n if (fullYear > 9999) {\n return failure('datetime year is too far in the future')\n }\n if (fullYear < 10) {\n return failure('datetime so close to year zero not allowed')\n }\n return success(date as AtprotoDate)\n}\n"]}
|
package/dist/handle.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type HandleString = `${string}.${string}`;
|
|
|
3
3
|
export declare const DISALLOWED_TLDS: string[];
|
|
4
4
|
export declare function ensureValidHandle<I extends string>(input: I): asserts input is I & HandleString;
|
|
5
5
|
export declare function ensureValidHandleRegex<I extends string>(input: I): asserts input is I & HandleString;
|
|
6
|
+
export declare function normalizeHandle(handle: HandleString): HandleString;
|
|
6
7
|
export declare function normalizeHandle(handle: string): string;
|
|
7
8
|
export declare function normalizeAndEnsureValidHandle(handle: string): HandleString;
|
|
8
9
|
export declare function isValidHandle<I extends string>(input: I): input is I & HandleString;
|
package/dist/handle.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../src/handle.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,mBAAmB,CAAA;AAE9C,MAAM,MAAM,YAAY,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAA;AAMhD,eAAO,MAAM,eAAe,UAY3B,CAAA;AAqBD,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAChD,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,YAAY,CAkCnC;AAMD,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,EACrD,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,YAAY,CAOnC;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../src/handle.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,mBAAmB,CAAA;AAE9C,MAAM,MAAM,YAAY,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAA;AAMhD,eAAO,MAAM,eAAe,UAY3B,CAAA;AAqBD,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAChD,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,YAAY,CAkCnC;AAMD,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,EACrD,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,YAAY,CAOnC;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAAA;AACnE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;AAKvD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAI1E;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAC5C,KAAK,EAAE,CAAC,GACP,KAAK,IAAI,CAAC,GAAG,YAAY,CAE3B;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAOlD;AAED,qBAAa,kBAAmB,SAAQ,KAAK;CAAG;AAChD,6BAA6B;AAC7B,qBAAa,mBAAoB,SAAQ,KAAK;CAAG;AACjD,6BAA6B;AAC7B,qBAAa,sBAAuB,SAAQ,KAAK;CAAG;AACpD,6BAA6B;AAC7B,qBAAa,qBAAsB,SAAQ,KAAK;CAAG"}
|
package/dist/handle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle.js","sourceRoot":"","sources":["../src/handle.ts"],"names":[],"mappings":";;;AAyCA,8CAoCC;AAMD,wDASC;
|
|
1
|
+
{"version":3,"file":"handle.js","sourceRoot":"","sources":["../src/handle.ts"],"names":[],"mappings":"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","sourcesContent":["export const INVALID_HANDLE = 'handle.invalid'\n\nexport type HandleString = `${string}.${string}`\n\n// Currently these are registration-time restrictions, not protocol-level\n// restrictions. We have a couple accounts in the wild that we need to clean up\n// before hard-disallow.\n// See also: https://en.wikipedia.org/wiki/Top-level_domain#Reserved_domains\nexport const DISALLOWED_TLDS = [\n '.local',\n '.arpa',\n '.invalid',\n '.localhost',\n '.internal',\n '.example',\n '.alt',\n // policy could concievably change on \".onion\" some day\n '.onion',\n // NOTE: .test is allowed in testing and devopment. In practical terms\n // \"should\" \"never\" actually resolve and get registered in production\n]\n\n// Handle constraints, in English:\n// - must be a possible domain name\n// - RFC-1035 is commonly referenced, but has been updated. eg, RFC-3696,\n// section 2. and RFC-3986, section 3. can now have leading numbers (eg,\n// 4chan.org)\n// - \"labels\" (sub-names) are made of ASCII letters, digits, hyphens\n// - can not start or end with a hyphen\n// - TLD (last component) should not start with a digit\n// - can't end with a hyphen (can end with digit)\n// - each segment must be between 1 and 63 characters (not including any periods)\n// - overall length can't be more than 253 characters\n// - separated by (ASCII) periods; does not start or end with period\n// - case insensitive\n// - domains (handles) are equal if they are the same lower-case\n// - punycode allowed for internationalization\n// - no whitespace, null bytes, joining chars, etc\n// - does not validate whether domain or TLD exists, or is a reserved or\n// special TLD (eg, .onion or .local)\n// - does not validate punycode\nexport function ensureValidHandle<I extends string>(\n input: I,\n): asserts input is I & HandleString {\n // check that all chars are boring ASCII\n if (!/^[a-zA-Z0-9.-]*$/.test(input)) {\n throw new InvalidHandleError(\n 'Disallowed characters in handle (ASCII letters, digits, dashes, periods only)',\n )\n }\n\n if (input.length > 253) {\n throw new InvalidHandleError('Handle is too long (253 chars max)')\n }\n const labels = input.split('.')\n if (labels.length < 2) {\n throw new InvalidHandleError('Handle domain needs at least two parts')\n }\n for (let i = 0; i < labels.length; i++) {\n const l = labels[i]\n if (l.length < 1) {\n throw new InvalidHandleError('Handle parts can not be empty')\n }\n if (l.length > 63) {\n throw new InvalidHandleError('Handle part too long (max 63 chars)')\n }\n if (l.endsWith('-') || l.startsWith('-')) {\n throw new InvalidHandleError(\n 'Handle parts can not start or end with hyphens',\n )\n }\n if (i + 1 === labels.length && !/^[a-zA-Z]/.test(l)) {\n throw new InvalidHandleError(\n 'Handle final component (TLD) must start with ASCII letter',\n )\n }\n }\n}\n\n// simple regex translation of above constraints\nconst HANDLE_REGEX =\n /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/\n\nexport function ensureValidHandleRegex<I extends string>(\n input: I,\n): asserts input is I & HandleString {\n if (input.length > 253) {\n throw new InvalidHandleError('Handle is too long (253 chars max)')\n }\n if (!HANDLE_REGEX.test(input)) {\n throw new InvalidHandleError(\"Handle didn't validate via regex\")\n }\n}\n\nexport function normalizeHandle(handle: HandleString): HandleString\nexport function normalizeHandle(handle: string): string\nexport function normalizeHandle(handle: string): string {\n return handle.toLowerCase()\n}\n\nexport function normalizeAndEnsureValidHandle(handle: string): HandleString {\n const normalized = normalizeHandle(handle)\n ensureValidHandle(normalized)\n return normalized\n}\n\nexport function isValidHandle<I extends string>(\n input: I,\n): input is I & HandleString {\n return input.length <= 253 && HANDLE_REGEX.test(input)\n}\n\nexport function isValidTld(handle: string): boolean {\n for (const tld of DISALLOWED_TLDS) {\n if (handle.endsWith(tld)) {\n return false\n }\n }\n return true\n}\n\nexport class InvalidHandleError extends Error {}\n/** @deprecated Never used */\nexport class ReservedHandleError extends Error {}\n/** @deprecated Never used */\nexport class UnsupportedDomainError extends Error {}\n/** @deprecated Never used */\nexport class DisallowedDomainError extends Error {}\n"]}
|
package/dist/nsid.d.ts
CHANGED
|
@@ -8,9 +8,9 @@ export declare class NSID {
|
|
|
8
8
|
toString: () => string;
|
|
9
9
|
}): NSID;
|
|
10
10
|
constructor(nsid: string);
|
|
11
|
-
get authority(): string
|
|
11
|
+
get authority(): `${string}.${string}`;
|
|
12
12
|
get name(): string | undefined;
|
|
13
|
-
toString():
|
|
13
|
+
toString(): NsidString;
|
|
14
14
|
}
|
|
15
15
|
export declare function ensureValidNsid<I extends string>(input: I): asserts input is I & NsidString;
|
|
16
16
|
export declare function parseNsid(nsid: string): string[];
|
package/dist/nsid.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nsid.d.ts","sourceRoot":"","sources":["../src/nsid.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,UAAU,GAAG,GAAG,MAAM,IAAI,MAAM,IAAI,MAAM,EAAE,CAAA;AAExD,qBAAa,IAAI;IACf,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IAEpC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIjC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKpD,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM;IAI3B,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;KAAE,GAAG,IAAI;gBAWxC,IAAI,EAAE,MAAM;IAIxB,IAAI,SAAS,
|
|
1
|
+
{"version":3,"file":"nsid.d.ts","sourceRoot":"","sources":["../src/nsid.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,UAAU,GAAG,GAAG,MAAM,IAAI,MAAM,IAAI,MAAM,EAAE,CAAA;AAExD,qBAAa,IAAI;IACf,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IAEpC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIjC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAKpD,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM;IAI3B,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;KAAE,GAAG,IAAI;gBAWxC,IAAI,EAAE,MAAM;IAIxB,IAAI,SAAS,IAIK,GAAG,MAAM,IAAI,MAAM,EAAE,CACtC;IAED,IAAI,IAAI,uBAEP;IAED,QAAQ,IAAI,UAAU;CAGvB;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAC9C,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,UAAU,CAGjC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAIhD;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAC1C,KAAK,EAAE,CAAC,GACP,KAAK,IAAI,CAAC,GAAG,UAAU,CAIzB;AAED,KAAK,cAAc,CAAC,CAAC,IACjB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAC3B;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAKvC,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC,CA0DpE;AA4BD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,IAAI,UAAU,CAG7E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,CAyB3E;AAED,qBAAa,gBAAiB,SAAQ,KAAK;CAAG"}
|
package/dist/nsid.js
CHANGED
|
@@ -175,7 +175,10 @@ function validateNsidRegex(value) {
|
|
|
175
175
|
message: 'NSID is too long (317 chars max)',
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
|
-
if (
|
|
178
|
+
if (
|
|
179
|
+
// Fast check for small values
|
|
180
|
+
value.length < 5 ||
|
|
181
|
+
!/^[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:\.[a-zA-Z](?:[a-zA-Z0-9]{0,62})?)$/.test(value)) {
|
|
179
182
|
return {
|
|
180
183
|
success: false,
|
|
181
184
|
message: "NSID didn't validate via regex",
|
package/dist/nsid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nsid.js","sourceRoot":"","sources":["../src/nsid.ts"],"names":[],"mappings":"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","sourcesContent":["/*\nGrammar:\n\nalpha = \"a\" / \"b\" / \"c\" / \"d\" / \"e\" / \"f\" / \"g\" / \"h\" / \"i\" / \"j\" / \"k\" / \"l\" / \"m\" / \"n\" / \"o\" / \"p\" / \"q\" / \"r\" / \"s\" / \"t\" / \"u\" / \"v\" / \"w\" / \"x\" / \"y\" / \"z\" / \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\" / \"G\" / \"H\" / \"I\" / \"J\" / \"K\" / \"L\" / \"M\" / \"N\" / \"O\" / \"P\" / \"Q\" / \"R\" / \"S\" / \"T\" / \"U\" / \"V\" / \"W\" / \"X\" / \"Y\" / \"Z\"\nnumber = \"1\" / \"2\" / \"3\" / \"4\" / \"5\" / \"6\" / \"7\" / \"8\" / \"9\" / \"0\"\ndelim = \".\"\nsegment = alpha *( alpha / number / \"-\" )\nauthority = segment *( delim segment )\nname = alpha *( alpha / number )\nnsid = authority delim name\n\n*/\n\nexport type NsidString = `${string}.${string}.${string}`\n\nexport class NSID {\n readonly segments: readonly string[]\n\n static parse(input: string): NSID {\n return new NSID(input)\n }\n\n static create(authority: string, name: string): NSID {\n const input = [...authority.split('.').reverse(), name].join('.')\n return new NSID(input)\n }\n\n static isValid(nsid: string) {\n return isValidNsid(nsid)\n }\n\n static from(input: { toString: () => string }): NSID {\n if (input instanceof NSID) {\n // No need to clone, NSID is immutable\n return input\n }\n if (Array.isArray(input)) {\n return new NSID((input as string[]).join('.'))\n }\n return new NSID(String(input))\n }\n\n constructor(nsid: string) {\n this.segments = parseNsid(nsid)\n }\n\n get authority() {\n return this.segments\n .slice(0, this.segments.length - 1)\n .reverse()\n .join('.')\n }\n\n get name() {\n return this.segments.at(this.segments.length - 1)\n }\n\n toString() {\n return this.segments.join('.')\n }\n}\n\nexport function ensureValidNsid<I extends string>(\n input: I,\n): asserts input is I & NsidString {\n const result = validateNsid(input)\n if (!result.success) throw new InvalidNsidError(result.message)\n}\n\nexport function parseNsid(nsid: string): string[] {\n const result = validateNsid(nsid)\n if (!result.success) throw new InvalidNsidError(result.message)\n return result.value\n}\n\nexport function isValidNsid<I extends string>(\n input: I,\n): input is I & NsidString {\n // Since the regex version is more performant for valid NSIDs, we use it when\n // we don't care about error details.\n return validateNsidRegex(input).success\n}\n\ntype ValidateResult<T> =\n | { success: true; value: T }\n | { success: false; message: string }\n\n// Human readable constraints on NSID:\n// - a valid domain in reversed notation\n// - followed by an additional period-separated name, which is camel-case letters\nexport function validateNsid(input: string): ValidateResult<string[]> {\n if (input.length > 253 + 1 + 63) {\n return {\n success: false,\n message: 'NSID is too long (317 chars max)',\n }\n }\n if (hasDisallowedCharacters(input)) {\n return {\n success: false,\n message:\n 'Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)',\n }\n }\n const segments = input.split('.')\n if (segments.length < 3) {\n return {\n success: false,\n message: 'NSID needs at least three parts',\n }\n }\n for (const l of segments) {\n if (l.length < 1) {\n return {\n success: false,\n message: 'NSID parts can not be empty',\n }\n }\n if (l.length > 63) {\n return {\n success: false,\n message: 'NSID part too long (max 63 chars)',\n }\n }\n if (startsWithHyphen(l) || endsWithHyphen(l)) {\n return {\n success: false,\n message: 'NSID parts can not start or end with hyphen',\n }\n }\n }\n if (startsWithNumber(segments[0])) {\n return {\n success: false,\n message: 'NSID first part may not start with a digit',\n }\n }\n if (!isValidIdentifier(segments[segments.length - 1])) {\n return {\n success: false,\n message:\n 'NSID name part must be only letters and digits (and no leading digit)',\n }\n }\n return {\n success: true,\n value: segments,\n }\n}\n\nfunction hasDisallowedCharacters(v: string) {\n return !/^[a-zA-Z0-9.-]*$/.test(v)\n}\n\nfunction startsWithNumber(v: string) {\n const charCode = v.charCodeAt(0)\n return charCode >= 48 && charCode <= 57\n}\n\nfunction startsWithHyphen(v: string) {\n return v.charCodeAt(0) === 45 /* - */\n}\n\nfunction endsWithHyphen(v: string) {\n return v.charCodeAt(v.length - 1) === 45 /* - */\n}\n\nfunction isValidIdentifier(v: string) {\n // Note, since we already know that \"v\" only contains [a-zA-Z0-9-], we can\n // simplify the following regex by checking only the first char and presence\n // of \"-\".\n\n // return /^[a-zA-Z][a-zA-Z0-9]*$/.test(v)\n return !startsWithNumber(v) && !v.includes('-')\n}\n\n/**\n * @deprecated Use {@link ensureValidNsid} if you care about error details,\n * {@link parseNsid}/{@link NSID.parse} if you need the parsed segments, or\n * {@link isValidNsid} if you just want a boolean.\n */\nexport function ensureValidNsidRegex(nsid: string): asserts nsid is NsidString {\n const result = validateNsidRegex(nsid)\n if (!result.success) throw new InvalidNsidError(result.message)\n}\n\n/**\n * Regexp based validation that behaves identically to the previous code but\n * provides less detailed error messages (while being 20% to 50% faster).\n */\nexport function validateNsidRegex(value: string): ValidateResult<NsidString> {\n if (value.length > 253 + 1 + 63) {\n return {\n success: false,\n message: 'NSID is too long (317 chars max)',\n }\n }\n\n if (\n !/^[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:\\.[a-zA-Z](?:[a-zA-Z0-9]{0,62})?)$/.test(\n value,\n )\n ) {\n return {\n success: false,\n message: \"NSID didn't validate via regex\",\n }\n }\n\n return {\n success: true,\n value: value as NsidString,\n }\n}\n\nexport class InvalidNsidError extends Error {}\n"]}
|
|
1
|
+
{"version":3,"file":"nsid.js","sourceRoot":"","sources":["../src/nsid.ts"],"names":[],"mappings":"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","sourcesContent":["/*\nGrammar:\n\nalpha = \"a\" / \"b\" / \"c\" / \"d\" / \"e\" / \"f\" / \"g\" / \"h\" / \"i\" / \"j\" / \"k\" / \"l\" / \"m\" / \"n\" / \"o\" / \"p\" / \"q\" / \"r\" / \"s\" / \"t\" / \"u\" / \"v\" / \"w\" / \"x\" / \"y\" / \"z\" / \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\" / \"G\" / \"H\" / \"I\" / \"J\" / \"K\" / \"L\" / \"M\" / \"N\" / \"O\" / \"P\" / \"Q\" / \"R\" / \"S\" / \"T\" / \"U\" / \"V\" / \"W\" / \"X\" / \"Y\" / \"Z\"\nnumber = \"1\" / \"2\" / \"3\" / \"4\" / \"5\" / \"6\" / \"7\" / \"8\" / \"9\" / \"0\"\ndelim = \".\"\nsegment = alpha *( alpha / number / \"-\" )\nauthority = segment *( delim segment )\nname = alpha *( alpha / number )\nnsid = authority delim name\n\n*/\n\nexport type NsidString = `${string}.${string}.${string}`\n\nexport class NSID {\n readonly segments: readonly string[]\n\n static parse(input: string): NSID {\n return new NSID(input)\n }\n\n static create(authority: string, name: string): NSID {\n const input = [...authority.split('.').reverse(), name].join('.')\n return new NSID(input)\n }\n\n static isValid(nsid: string) {\n return isValidNsid(nsid)\n }\n\n static from(input: { toString: () => string }): NSID {\n if (input instanceof NSID) {\n // No need to clone, NSID is immutable\n return input\n }\n if (Array.isArray(input)) {\n return new NSID((input as string[]).join('.'))\n }\n return new NSID(String(input))\n }\n\n constructor(nsid: string) {\n this.segments = parseNsid(nsid)\n }\n\n get authority() {\n return this.segments\n .slice(0, this.segments.length - 1)\n .reverse()\n .join('.') as `${string}.${string}`\n }\n\n get name() {\n return this.segments.at(this.segments.length - 1)\n }\n\n toString(): NsidString {\n return this.segments.join('.') as NsidString\n }\n}\n\nexport function ensureValidNsid<I extends string>(\n input: I,\n): asserts input is I & NsidString {\n const result = validateNsid(input)\n if (!result.success) throw new InvalidNsidError(result.message)\n}\n\nexport function parseNsid(nsid: string): string[] {\n const result = validateNsid(nsid)\n if (!result.success) throw new InvalidNsidError(result.message)\n return result.value\n}\n\nexport function isValidNsid<I extends string>(\n input: I,\n): input is I & NsidString {\n // Since the regex version is more performant for valid NSIDs, we use it when\n // we don't care about error details.\n return validateNsidRegex(input).success\n}\n\ntype ValidateResult<T> =\n | { success: true; value: T }\n | { success: false; message: string }\n\n// Human readable constraints on NSID:\n// - a valid domain in reversed notation\n// - followed by an additional period-separated name, which is camel-case letters\nexport function validateNsid(input: string): ValidateResult<string[]> {\n if (input.length > 253 + 1 + 63) {\n return {\n success: false,\n message: 'NSID is too long (317 chars max)',\n }\n }\n if (hasDisallowedCharacters(input)) {\n return {\n success: false,\n message:\n 'Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)',\n }\n }\n const segments = input.split('.')\n if (segments.length < 3) {\n return {\n success: false,\n message: 'NSID needs at least three parts',\n }\n }\n for (const l of segments) {\n if (l.length < 1) {\n return {\n success: false,\n message: 'NSID parts can not be empty',\n }\n }\n if (l.length > 63) {\n return {\n success: false,\n message: 'NSID part too long (max 63 chars)',\n }\n }\n if (startsWithHyphen(l) || endsWithHyphen(l)) {\n return {\n success: false,\n message: 'NSID parts can not start or end with hyphen',\n }\n }\n }\n if (startsWithNumber(segments[0])) {\n return {\n success: false,\n message: 'NSID first part may not start with a digit',\n }\n }\n if (!isValidIdentifier(segments[segments.length - 1])) {\n return {\n success: false,\n message:\n 'NSID name part must be only letters and digits (and no leading digit)',\n }\n }\n return {\n success: true,\n value: segments,\n }\n}\n\nfunction hasDisallowedCharacters(v: string) {\n return !/^[a-zA-Z0-9.-]*$/.test(v)\n}\n\nfunction startsWithNumber(v: string) {\n const charCode = v.charCodeAt(0)\n return charCode >= 48 && charCode <= 57\n}\n\nfunction startsWithHyphen(v: string) {\n return v.charCodeAt(0) === 45 /* - */\n}\n\nfunction endsWithHyphen(v: string) {\n return v.charCodeAt(v.length - 1) === 45 /* - */\n}\n\nfunction isValidIdentifier(v: string) {\n // Note, since we already know that \"v\" only contains [a-zA-Z0-9-], we can\n // simplify the following regex by checking only the first char and presence\n // of \"-\".\n\n // return /^[a-zA-Z][a-zA-Z0-9]*$/.test(v)\n return !startsWithNumber(v) && !v.includes('-')\n}\n\n/**\n * @deprecated Use {@link ensureValidNsid} if you care about error details,\n * {@link parseNsid}/{@link NSID.parse} if you need the parsed segments, or\n * {@link isValidNsid} if you just want a boolean.\n */\nexport function ensureValidNsidRegex(nsid: string): asserts nsid is NsidString {\n const result = validateNsidRegex(nsid)\n if (!result.success) throw new InvalidNsidError(result.message)\n}\n\n/**\n * Regexp based validation that behaves identically to the previous code but\n * provides less detailed error messages (while being 20% to 50% faster).\n */\nexport function validateNsidRegex(value: string): ValidateResult<NsidString> {\n if (value.length > 253 + 1 + 63) {\n return {\n success: false,\n message: 'NSID is too long (317 chars max)',\n }\n }\n\n if (\n // Fast check for small values\n value.length < 5 ||\n !/^[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:\\.[a-zA-Z](?:[a-zA-Z0-9]{0,62})?)$/.test(\n value,\n )\n ) {\n return {\n success: false,\n message: \"NSID didn't validate via regex\",\n }\n }\n\n return {\n success: true,\n value: value as NsidString,\n }\n}\n\nexport class InvalidNsidError extends Error {}\n"]}
|
package/package.json
CHANGED
package/src/at-identifier.ts
CHANGED
|
@@ -6,13 +6,45 @@ import {
|
|
|
6
6
|
isValidHandle,
|
|
7
7
|
} from './handle.js'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* An "at-identifier" string - either a {@link DidString} or a {@link HandleString}
|
|
11
|
+
*
|
|
12
|
+
* @example `"did:plc:1234..."`, `"did:web:example.com"` or `"alice.bsky.social"`
|
|
13
|
+
*/
|
|
9
14
|
export type AtIdentifierString = DidString | HandleString
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Discriminates {@link HandleString} from a valid {@link AtIdentifierString}.
|
|
18
|
+
*
|
|
19
|
+
* @return `true` if the identifier is a handle, `false` otherwise
|
|
20
|
+
*/
|
|
21
|
+
export function isHandleIdentifier(id: AtIdentifierString): id is HandleString {
|
|
22
|
+
return !isDidIdentifier(id)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Discriminates {@link DidString} from a valid {@link AtIdentifierString}.
|
|
27
|
+
*
|
|
28
|
+
* @return `true` if the identifier is a DID, `false` otherwise
|
|
29
|
+
*/
|
|
30
|
+
export function isDidIdentifier(id: AtIdentifierString): id is DidString {
|
|
31
|
+
return id.startsWith('did:')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validates that a string is a valid {@link AtIdentifierString} format string,
|
|
36
|
+
* throwing an error if it is not.
|
|
37
|
+
*
|
|
38
|
+
* @throws InvalidHandleError if the input string does not meet the atproto 'datetime' format requirements.
|
|
39
|
+
* @see {@link AtIdentifierString}
|
|
40
|
+
*/
|
|
41
|
+
export function assertAtIdentifierString<I>(
|
|
42
|
+
input: I,
|
|
43
|
+
): asserts input is I & AtIdentifierString {
|
|
14
44
|
try {
|
|
15
|
-
if (input
|
|
45
|
+
if (!input || typeof input !== 'string') {
|
|
46
|
+
throw new TypeError('Identifier must be a non-empty string')
|
|
47
|
+
} else if (input.startsWith('did:')) {
|
|
16
48
|
ensureValidDidRegex(input)
|
|
17
49
|
} else {
|
|
18
50
|
ensureValidHandleRegex(input)
|
|
@@ -22,12 +54,51 @@ export function ensureValidAtIdentifier(
|
|
|
22
54
|
}
|
|
23
55
|
}
|
|
24
56
|
|
|
25
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Casts a string to a {@link AtIdentifierString} if it is a valid at-identifier
|
|
59
|
+
* string, throwing an error if it is not.
|
|
60
|
+
*
|
|
61
|
+
* @throws InvalidHandleError if the input string does not meet the atproto 'at-identifier' format requirements.
|
|
62
|
+
* @see {@link AtIdentifierString}
|
|
63
|
+
*/
|
|
64
|
+
export function asAtIdentifierString<I>(input: I): I & AtIdentifierString {
|
|
65
|
+
assertAtIdentifierString(input)
|
|
66
|
+
return input
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Type guard that checks if a value is a valid AT identifier (DID or handle).
|
|
71
|
+
*
|
|
72
|
+
* @param value - The value to check
|
|
73
|
+
* @returns `true` if the value is a valid AT identifier
|
|
74
|
+
* @see {@link AtIdentifierString}
|
|
75
|
+
*/
|
|
76
|
+
export function isAtIdentifierString<I>(
|
|
26
77
|
input: I,
|
|
27
78
|
): input is I & AtIdentifierString {
|
|
28
|
-
if (input
|
|
79
|
+
if (!input || typeof input !== 'string') {
|
|
80
|
+
return false
|
|
81
|
+
} else if (input.startsWith('did:')) {
|
|
29
82
|
return isValidDid(input)
|
|
30
83
|
} else {
|
|
31
84
|
return isValidHandle(input)
|
|
32
85
|
}
|
|
33
86
|
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Returns the input if it is a valid {@link AtIdentifierString} format string, or
|
|
90
|
+
* `undefined` if it is not.
|
|
91
|
+
*
|
|
92
|
+
* @see {@link AtIdentifierString}
|
|
93
|
+
*/
|
|
94
|
+
export function ifAtIdentifierString<I>(
|
|
95
|
+
input: I,
|
|
96
|
+
): undefined | (I & AtIdentifierString) {
|
|
97
|
+
return isAtIdentifierString(input) ? input : undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Legacy exports (should we deprecate these ?)
|
|
101
|
+
export {
|
|
102
|
+
assertAtIdentifierString as ensureValidAtIdentifier,
|
|
103
|
+
isAtIdentifierString as isValidAtIdentifier,
|
|
104
|
+
}
|