@atproto/syntax 0.6.3 → 0.6.4

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/at-identifier.d.ts +2 -2
  3. package/dist/at-identifier.d.ts.map +1 -1
  4. package/dist/at-identifier.js.map +1 -1
  5. package/dist/aturi.d.ts +5 -5
  6. package/dist/aturi.d.ts.map +1 -1
  7. package/dist/aturi.js +1 -1
  8. package/dist/aturi.js.map +1 -1
  9. package/dist/aturi_validation.d.ts +3 -3
  10. package/dist/aturi_validation.d.ts.map +1 -1
  11. package/dist/aturi_validation.js.map +1 -1
  12. package/dist/language.js +1 -1
  13. package/dist/language.js.map +1 -1
  14. package/dist/nsid.d.ts +1 -1
  15. package/dist/nsid.d.ts.map +1 -1
  16. package/dist/nsid.js.map +1 -1
  17. package/package.json +15 -14
  18. package/benchmark.cjs +0 -208
  19. package/src/at-identifier.ts +0 -104
  20. package/src/aturi.ts +0 -197
  21. package/src/aturi_validation.ts +0 -321
  22. package/src/datetime.ts +0 -369
  23. package/src/did.ts +0 -71
  24. package/src/handle.ts +0 -128
  25. package/src/index.ts +0 -10
  26. package/src/language.ts +0 -39
  27. package/src/lib/result.ts +0 -11
  28. package/src/nsid.ts +0 -182
  29. package/src/recordkey.ts +0 -51
  30. package/src/tid.ts +0 -22
  31. package/src/uri.ts +0 -5
  32. package/tests/aturi-string.test.ts +0 -223
  33. package/tests/aturi.test.ts +0 -428
  34. package/tests/datetime.test.ts +0 -280
  35. package/tests/did.test.ts +0 -104
  36. package/tests/handle.test.ts +0 -239
  37. package/tests/language.test.ts +0 -88
  38. package/tests/nsid.test.ts +0 -174
  39. package/tests/recordkey.test.ts +0 -43
  40. package/tests/tid.test.ts +0 -43
  41. package/tsconfig.build.json +0 -12
  42. package/tsconfig.build.tsbuildinfo +0 -1
  43. package/tsconfig.json +0 -7
  44. package/tsconfig.tests.json +0 -8
  45. package/vitest.config.ts +0 -5
package/src/datetime.ts DELETED
@@ -1,369 +0,0 @@
1
- import * as isoDatestringValidator from 'iso-datestring-validator'
2
-
3
- // Node ESM interop wraps "iso-datestring-validator" as { default: { ... } }
4
- // @TODO Remove "iso-datestring-validator" dependency
5
- const { isValidISODateString } = ((m) => m.default ?? m)(isoDatestringValidator)
6
-
7
- /**
8
- * Indicates a date or string is not a valid representation of a datetime
9
- * according to the atproto
10
- * {@link https://atproto.com/specs/lexicon#datetime specification}.
11
- */
12
- export class InvalidDatetimeError extends Error {}
13
-
14
- /**
15
- * A subset of {@link DatetimeString} that represent valid datetime strings with
16
- * the format: `YYYY-MM-DDTHH:mm:ss.sssZ`, as returned by `Date.toISOString()
17
- * for dates between the years 0000 and 9999.
18
- *
19
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString}
20
- */
21
- export type ISODatetimeString =
22
- // @TODO Switch to branded types for more accurate type safety.
23
- `${string}-${string}-${string}T${string}:${string}:${string}.${string}Z`
24
-
25
- /**
26
- * Represents a {@link Date} that can be safely stringified into a valid atproto
27
- * {@link DatetimeString} using the {@link Date.toISOString toISOString()}
28
- * method.
29
- */
30
- export interface AtprotoDate extends Date {
31
- toISOString(): ISODatetimeString
32
- }
33
-
34
- /**
35
- * @see {@link AtprotoDate}
36
- */
37
- export function assertAtprotoDate(date: Date): asserts date is AtprotoDate {
38
- const res = parseDate(date)
39
- if (!res.success) {
40
- throw new InvalidDatetimeError(res.message)
41
- }
42
- }
43
-
44
- /**
45
- * @see {@link AtprotoDate}
46
- */
47
- export function asAtprotoDate(date: Date): AtprotoDate {
48
- assertAtprotoDate(date)
49
- return date
50
- }
51
-
52
- /**
53
- * @see {@link AtprotoDate}
54
- */
55
- export function isAtprotoDate(date: Date): date is AtprotoDate {
56
- return parseDate(date).success
57
- }
58
-
59
- /**
60
- * @see {@link AtprotoDate}
61
- */
62
- export function ifAtprotoDate(date: Date): AtprotoDate | undefined {
63
- return isAtprotoDate(date) ? date : undefined
64
- }
65
-
66
- /**
67
- * Datetime strings in atproto data structures and API calls should meet the
68
- * {@link https://ijmacd.github.io/rfc3339-iso8601/ intersecting} requirements
69
- * of the RFC 3339, ISO 8601, and WHATWG HTML datetime standards.
70
- *
71
- * @note This literal template type is not accurate enough to ensure that a
72
- * string is a valid atproto datetime. The {@link DatetimeString} validation
73
- * functions ({@link assertDatetimeString}, {@link isDatetimeString}, etc)
74
- * should be used to validate that a string meets the atproto datetime
75
- * requirements, and the {@link toDatetimeString} function should be used to
76
- * convert a {@link Date} object into a valid {@link DatetimeString} string.
77
- *
78
- * @example "2024-01-15T12:30:00Z"
79
- * @example "2024-01-15T12:30:00.000Z"
80
- * @example "2024-01-15T12:30:00+00:00"
81
- * @example "2024-01-15T11:30:00-01:00"
82
- * @see {@link https://atproto.com/specs/lexicon#datetime atproto Lexicon datetime format}
83
- * @see {@link https://www.rfc-editor.org/rfc/rfc3339 RFC 3339}
84
- * @see {@link https://www.iso.org/iso-8601-date-and-time-format.html ISO 8601}
85
- */
86
- export type DatetimeString =
87
- // @TODO Switch to branded types for more accurate type safety?
88
- | `${string}-${string}-${string}T${string}:${string}:${string}Z`
89
- | `${string}-${string}-${string}T${string}:${string}:${string}${'+' | '-'}${string}:${string}`
90
-
91
- /**
92
- * Validates that a string is a valid {@link DatetimeString} format string,
93
- * throwing an error if it is not.
94
- *
95
- * @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.
96
- * @see {@link DatetimeString}
97
- */
98
- export function assertDatetimeString<I>(
99
- input: I,
100
- ): asserts input is I & DatetimeString {
101
- const result = parseString(input)
102
- if (!result.success) {
103
- throw new InvalidDatetimeError(result.message)
104
- }
105
- }
106
-
107
- /**
108
- * Casts a string to a {@link DatetimeString} if it is a valid datetime format
109
- * string, throwing an error if it is not.
110
- *
111
- * @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.
112
- * @see {@link DatetimeString}
113
- */
114
- export function asDatetimeString<I>(input: I): I & DatetimeString {
115
- assertDatetimeString(input)
116
- return input
117
- }
118
-
119
- /**
120
- * Checks if a string is a valid {@link DatetimeString} format string.
121
- *
122
- * @see {@link DatetimeString}
123
- */
124
- export function isDatetimeString<I>(input: I): input is I & DatetimeString {
125
- return parseString(input).success
126
- }
127
-
128
- /**
129
- * Matches any ISO-ish datetime string. This is a more lenient check than
130
- * the strict {@link isDatetimeString} guard, which only allows datetimes that
131
- * fully conform to the AT Protocol specification (e.g. must include timezone).
132
- */
133
- export function isDatetimeStringLenient<I>(
134
- input: I,
135
- ): input is I & DatetimeString {
136
- // @NOTE the returned type assertion is inaccurate wrt. the DatetimeString
137
- // type definition. A more accurate solution would be to use a branded type
138
- // instead of a template literal for the "datetime" format
139
-
140
- if (typeof input !== 'string') return false
141
-
142
- try {
143
- if (isValidISODateString(input)) return true
144
- } catch {
145
- // isValidISODateString can throw on some inputs.
146
- }
147
-
148
- // @NOTE The "iso-datestring-validator" implementation is *not* compliant with
149
- // the AT Protocol datetime specification. In particular, it rejects some
150
- // valid AT Protocol datetimes (eg: "1985-04-12T23:20:50.1235678912345Z",
151
- // "1985-04-12T23:20:50.123+01:45", "1985-04-12T23:20:50.1234567890Z"). For
152
- // this reason, we run "isDatetimeString" validation if "isValidISODateString"
153
- // does not return true.
154
- return isDatetimeString(input)
155
- }
156
-
157
- /**
158
- * Returns the input if it is a valid {@link DatetimeString} format string, or
159
- * `undefined` if it is not.
160
- *
161
- * @see {@link DatetimeString}
162
- */
163
- export function ifDatetimeString<I>(
164
- input: I,
165
- ): undefined | (I & DatetimeString) {
166
- return isDatetimeString(input) ? input : undefined
167
- }
168
-
169
- /**
170
- * Returns the current date and time as a {@link DatetimeString}.
171
- *
172
- * @see {@link DatetimeString}
173
- */
174
- export function currentDatetimeString(): DatetimeString {
175
- return toDatetimeString(new Date())
176
- }
177
-
178
- /**
179
- * Converts any {@link Date} into a {@link DatetimeString} if possible, throwing
180
- * an error if the date is not a valid atproto datetime.
181
- *
182
- * This is short-hand for `asAtprotoDate(date).toISOString()`.
183
- *
184
- * @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).
185
- * @see {@link DatetimeString}
186
- */
187
- export function toDatetimeString(date: Date): DatetimeString {
188
- return asAtprotoDate(date).toISOString()
189
- }
190
-
191
- /**
192
- * Takes a flexible datetime string and normalizes its representation.
193
- *
194
- * This function will work with any valid value that can be parsed as a date. It
195
- * *additionally* is more flexible about accepting datetimes that are missing
196
- * timezone information, and normalizing them to a valid atproto datetime.
197
- *
198
- * One use-case is a consistent, sortable string. Another is to work with older
199
- * invalid createdAt datetimes.
200
- *
201
- * @note This function might return different normalized strings for the same
202
- * input depending on the timezone of the machine it is run on, since it will
203
- * attempt to parse the input "as is" if it fails to parse with an explicit
204
- * timezone.
205
- *
206
- * @returns ISODatetimeString - a valid atproto datetime with millisecond precision (3 sub-second digits) and UTC timezone with trailing 'Z' syntax.
207
- * @throws InvalidDatetimeError - if the input string could not be parsed as a datetime, even with permissive parsing.
208
- */
209
- export function normalizeDatetime(dtStr: string): ISODatetimeString {
210
- if (
211
- // Explicit timezone offset
212
- /[+-]\d\d:?\d\d/.test(dtStr) ||
213
- // 'Z' timezone designator
214
- /\dZ\b/.test(dtStr) ||
215
- // Timezone abbreviation (eg. "EST", "PST", "UTC", "GMT", etc), as in:
216
- // > Tue Mar 17 2026 16:38:44 PST (Pacific Standard Time)
217
- /\b[A-Z]{3,4}\b/.test(dtStr)
218
- ) {
219
- // Since we do have a timezone designator, we can try parsing "as is" and
220
- // should get consistent results regardless of local timezone.
221
-
222
- // @NOTE NodeJS will reject dates with an un-recognized timezone designator
223
- // (like "AFT"), even if we add a well-known timezone abbreviation like
224
- // "UTC" or "Z".
225
- const date = new Date(dtStr)
226
- if (isAtprotoDate(date)) {
227
- return date.toISOString()
228
- }
229
- } else {
230
- // If there is no timezone information, try parsing as UTC using two
231
- // different syntaxes, falling back to parsing "as is".
232
-
233
- const dateZ = new Date(`${dtStr}Z`)
234
- if (isAtprotoDate(dateZ)) {
235
- return dateZ.toISOString()
236
- }
237
-
238
- const dateUTC = new Date(`${dtStr} UTC`)
239
- if (isAtprotoDate(dateUTC)) {
240
- return dateUTC.toISOString()
241
- }
242
-
243
- // Despite our best efforts to parse as a consistent value, appending "Z" or
244
- // " UTC" did not work, so we will try parsing "as is", which may yield
245
- // different results depending on the local timezone of the machine.
246
- const date = new Date(dtStr)
247
- if (isAtprotoDate(date)) {
248
- return date.toISOString()
249
- }
250
- }
251
-
252
- throw new InvalidDatetimeError(
253
- 'datetime did not parse as any timestamp format',
254
- )
255
- }
256
-
257
- /**
258
- * Variant of {@link normalizeDatetime} which always returns a valid datetime
259
- * string.
260
- *
261
- * If a {@link InvalidDatetimeError} is encountered, returns the UNIX epoch time
262
- * as a UTC datetime (`1970-01-01T00:00:00.000Z`).
263
- *
264
- * @see {@link normalizeDatetime}
265
- */
266
- export function normalizeDatetimeAlways(dtStr: string): ISODatetimeString {
267
- try {
268
- return normalizeDatetime(dtStr)
269
- } catch (err) {
270
- return '1970-01-01T00:00:00.000Z'
271
- }
272
- }
273
-
274
- // Legacy exports (should we deprecate these ?)
275
- export {
276
- assertDatetimeString as ensureValidDatetime,
277
- isDatetimeString as isValidDatetime,
278
- }
279
-
280
- // -----------------------------------------------------------------------------
281
- // ------------------------- Internal validation logic -------------------------
282
- // -----------------------------------------------------------------------------
283
-
284
- // Validation utils that allow avoiding try/catch for control flow (performance
285
- // optimization). Other syntax formats should also use this pattern to avoid
286
- // try/catch in their validation logic, at which point these utils can be moved
287
- // to a common internal utils.
288
- type FailureResult = { success: false; message: string }
289
- const failure = (m: string): FailureResult => ({ success: false, message: m })
290
- type SuccessResult<V> = { success: true; value: V }
291
- const success = <V>(v: V): SuccessResult<V> => ({ success: true, value: v })
292
- type Result<V> = FailureResult | SuccessResult<V>
293
-
294
- /**
295
- * @see {@link https://www.rfc-editor.org/rfc/rfc3339#section-5.6 Internet Date/Time Format}
296
- *
297
- * @example
298
- * ```abnf
299
- * date-fullyear = 4DIGIT
300
- * date-month = 2DIGIT ; 01-12
301
- * date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
302
- * ; month/year
303
- * time-hour = 2DIGIT ; 00-23
304
- * time-minute = 2DIGIT ; 00-59
305
- * time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
306
- * ; rules
307
- * time-secfrac = "." 1*DIGIT
308
- * time-numoffset = ("+" / "-") time-hour ":" time-minute
309
- * time-offset = "Z" / time-numoffset
310
- * partial-time = time-hour ":" time-minute ":" time-second
311
- * [time-secfrac]
312
- * full-date = date-fullyear "-" date-month "-" date-mday
313
- * full-time = partial-time time-offset
314
- * date-time = full-date "T" full-time
315
- * ```
316
- */
317
- const DATETIME_REGEX =
318
- /^(?<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]))$/
319
-
320
- /**
321
- * Validates that the input is a datetime string according to atproto Lexicon
322
- * rules, and parses it into a Date object.
323
- */
324
- function parseString(input: unknown): Result<AtprotoDate> {
325
- // @NOTE Performing cheap tests first
326
- if (typeof input !== 'string') {
327
- return failure('datetime must be a string')
328
- }
329
- if (input.length > 64) {
330
- return failure('datetime is too long (64 chars max)')
331
- }
332
- if (input.endsWith('-00:00')) {
333
- return failure('datetime can not use "-00:00" for UTC timezone')
334
- }
335
- if (!DATETIME_REGEX.test(input)) {
336
- return failure(
337
- "datetime is not in a valid format (must match RFC 3339 & ISO 8601 with 'Z' or ±hh:mm timezone)",
338
- )
339
- }
340
-
341
- // must parse as ISO 8601; this also verifies semantics like leap seconds and
342
- // correct number of days in month, which the regex does not check for
343
- const date = new Date(input)
344
-
345
- return parseDate(date)
346
- }
347
-
348
- /**
349
- * Ensures that a Date object represents a valid datetime according to atproto
350
- * Lexicon rules. This ensures that `date.toISOString()` will produce a valid
351
- * datetime string that can be used where {@link DatetimeString} is expected.
352
- */
353
- function parseDate(date: Date): Result<AtprotoDate> {
354
- const fullYear = date.getUTCFullYear()
355
- // Ensures that the date is valid. We could check isNaN(date.getTime()) here
356
- // but since we'll check the year anyway, we just use that for the validity
357
- // check since an invalid date will have NaN year.
358
- if (Number.isNaN(fullYear)) {
359
- return failure('datetime did not parse as ISO 8601')
360
- }
361
- // Ensure that the ISO string representation does not start with ±YYYYYY
362
- if (fullYear < 0) {
363
- return failure('datetime normalized to a negative time')
364
- }
365
- if (fullYear > 9999) {
366
- return failure('datetime year is too far in the future')
367
- }
368
- return success(date as AtprotoDate)
369
- }
package/src/did.ts DELETED
@@ -1,71 +0,0 @@
1
- // Human-readable constraints:
2
- // - valid W3C DID (https://www.w3.org/TR/did-core/#did-syntax)
3
- // - entire URI is ASCII: [a-zA-Z0-9._:%-]
4
- // - always starts "did:" (lower-case)
5
- // - method name is one or more lower-case letters, followed by ":"
6
- // - remaining identifier can have any of the above chars, but can not end in ":"
7
- // - it seems that a bunch of ":" can be included, and don't need spaces between
8
- // - "%" is used only for "percent encoding" and must be followed by two hex characters (and thus can't end in "%")
9
- // - query ("?") and fragment ("#") stuff is defined for "DID URIs", but not as part of identifier itself
10
- // - "The current specification does not take a position on the maximum length of a DID"
11
- // - in current atproto, only allowing did:plc and did:web. But not *forcing* this at lexicon layer
12
- // - hard length limit of 8KBytes
13
- // - not going to validate "percent encoding" here
14
-
15
- export type DidString<M extends string = string> = `did:${M}:${string}`
16
-
17
- export function ensureValidDid<I extends string>(
18
- input: I,
19
- ): asserts input is I & DidString {
20
- if (!input.startsWith('did:')) {
21
- throw new InvalidDidError('DID requires "did:" prefix')
22
- }
23
-
24
- if (input.length > 2048) {
25
- throw new InvalidDidError('DID is too long (2048 chars max)')
26
- }
27
-
28
- if (input.endsWith(':') || input.endsWith('%')) {
29
- throw new InvalidDidError('DID can not end with ":" or "%"')
30
- }
31
-
32
- // check that all chars are boring ASCII
33
- if (!/^[a-zA-Z0-9._:%-]*$/.test(input)) {
34
- throw new InvalidDidError(
35
- 'Disallowed characters in DID (ASCII letters, digits, and a couple other characters only)',
36
- )
37
- }
38
-
39
- const { length, 1: method } = input.split(':')
40
- if (length < 3) {
41
- throw new InvalidDidError(
42
- 'DID requires prefix, method, and method-specific content',
43
- )
44
- }
45
-
46
- if (!/^[a-z]+$/.test(method)) {
47
- throw new InvalidDidError('DID method must be lower-case letters')
48
- }
49
- }
50
-
51
- const DID_REGEX = /^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/
52
-
53
- export function ensureValidDidRegex<I extends string>(
54
- input: I,
55
- ): asserts input is I & DidString {
56
- // simple regex to enforce most constraints via just regex and length.
57
- // hand wrote this regex based on above constraints
58
- if (!DID_REGEX.test(input)) {
59
- throw new InvalidDidError("DID didn't validate via regex")
60
- }
61
-
62
- if (input.length > 2048) {
63
- throw new InvalidDidError('DID is too long (2048 chars max)')
64
- }
65
- }
66
-
67
- export function isValidDid<I extends string>(input: I): input is I & DidString {
68
- return input.length <= 2048 && DID_REGEX.test(input)
69
- }
70
-
71
- export class InvalidDidError extends Error {}
package/src/handle.ts DELETED
@@ -1,128 +0,0 @@
1
- export const INVALID_HANDLE = 'handle.invalid'
2
-
3
- export type HandleString = `${string}.${string}`
4
-
5
- // Currently these are registration-time restrictions, not protocol-level
6
- // restrictions. We have a couple accounts in the wild that we need to clean up
7
- // before hard-disallow.
8
- // See also: https://en.wikipedia.org/wiki/Top-level_domain#Reserved_domains
9
- export const DISALLOWED_TLDS = [
10
- '.local',
11
- '.arpa',
12
- '.invalid',
13
- '.localhost',
14
- '.internal',
15
- '.example',
16
- '.alt',
17
- // policy could concievably change on ".onion" some day
18
- '.onion',
19
- // NOTE: .test is allowed in testing and devopment. In practical terms
20
- // "should" "never" actually resolve and get registered in production
21
- ]
22
-
23
- // Handle constraints, in English:
24
- // - must be a possible domain name
25
- // - RFC-1035 is commonly referenced, but has been updated. eg, RFC-3696,
26
- // section 2. and RFC-3986, section 3. can now have leading numbers (eg,
27
- // 4chan.org)
28
- // - "labels" (sub-names) are made of ASCII letters, digits, hyphens
29
- // - can not start or end with a hyphen
30
- // - TLD (last component) should not start with a digit
31
- // - can't end with a hyphen (can end with digit)
32
- // - each segment must be between 1 and 63 characters (not including any periods)
33
- // - overall length can't be more than 253 characters
34
- // - separated by (ASCII) periods; does not start or end with period
35
- // - case insensitive
36
- // - domains (handles) are equal if they are the same lower-case
37
- // - punycode allowed for internationalization
38
- // - no whitespace, null bytes, joining chars, etc
39
- // - does not validate whether domain or TLD exists, or is a reserved or
40
- // special TLD (eg, .onion or .local)
41
- // - does not validate punycode
42
- export function ensureValidHandle<I extends string>(
43
- input: I,
44
- ): asserts input is I & HandleString {
45
- // check that all chars are boring ASCII
46
- if (!/^[a-zA-Z0-9.-]*$/.test(input)) {
47
- throw new InvalidHandleError(
48
- 'Disallowed characters in handle (ASCII letters, digits, dashes, periods only)',
49
- )
50
- }
51
-
52
- if (input.length > 253) {
53
- throw new InvalidHandleError('Handle is too long (253 chars max)')
54
- }
55
- const labels = input.split('.')
56
- if (labels.length < 2) {
57
- throw new InvalidHandleError('Handle domain needs at least two parts')
58
- }
59
- for (let i = 0; i < labels.length; i++) {
60
- const l = labels[i]
61
- if (l.length < 1) {
62
- throw new InvalidHandleError('Handle parts can not be empty')
63
- }
64
- if (l.length > 63) {
65
- throw new InvalidHandleError('Handle part too long (max 63 chars)')
66
- }
67
- if (l.endsWith('-') || l.startsWith('-')) {
68
- throw new InvalidHandleError(
69
- 'Handle parts can not start or end with hyphens',
70
- )
71
- }
72
- if (i + 1 === labels.length && !/^[a-zA-Z]/.test(l)) {
73
- throw new InvalidHandleError(
74
- 'Handle final component (TLD) must start with ASCII letter',
75
- )
76
- }
77
- }
78
- }
79
-
80
- // simple regex translation of above constraints
81
- const HANDLE_REGEX =
82
- /^([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])?$/
83
-
84
- export function ensureValidHandleRegex<I extends string>(
85
- input: I,
86
- ): asserts input is I & HandleString {
87
- if (input.length > 253) {
88
- throw new InvalidHandleError('Handle is too long (253 chars max)')
89
- }
90
- if (!HANDLE_REGEX.test(input)) {
91
- throw new InvalidHandleError("Handle didn't validate via regex")
92
- }
93
- }
94
-
95
- export function normalizeHandle(handle: HandleString): HandleString
96
- export function normalizeHandle(handle: string): string
97
- export function normalizeHandle(handle: string): string {
98
- return handle.toLowerCase()
99
- }
100
-
101
- export function normalizeAndEnsureValidHandle(handle: string): HandleString {
102
- const normalized = normalizeHandle(handle)
103
- ensureValidHandle(normalized)
104
- return normalized
105
- }
106
-
107
- export function isValidHandle<I extends string>(
108
- input: I,
109
- ): input is I & HandleString {
110
- return input.length <= 253 && HANDLE_REGEX.test(input)
111
- }
112
-
113
- export function isValidTld(handle: string): boolean {
114
- for (const tld of DISALLOWED_TLDS) {
115
- if (handle.endsWith(tld)) {
116
- return false
117
- }
118
- }
119
- return true
120
- }
121
-
122
- export class InvalidHandleError extends Error {}
123
- /** @deprecated Never used */
124
- export class ReservedHandleError extends Error {}
125
- /** @deprecated Never used */
126
- export class UnsupportedDomainError extends Error {}
127
- /** @deprecated Never used */
128
- export class DisallowedDomainError extends Error {}
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- export * from './at-identifier.js'
2
- export * from './aturi.js'
3
- export * from './datetime.js'
4
- export * from './did.js'
5
- export * from './handle.js'
6
- export * from './nsid.js'
7
- export * from './language.js'
8
- export * from './recordkey.js'
9
- export * from './tid.js'
10
- export * from './uri.js'
package/src/language.ts DELETED
@@ -1,39 +0,0 @@
1
- const BCP47_REGEXP =
2
- /^((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?<extension>[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?<privateUseA>x(-[A-Za-z0-9]{1,8})+))?)|(?<privateUseB>x(-[A-Za-z0-9]{1,8})+))$/
3
-
4
- export type LanguageTag = {
5
- grandfathered?: string
6
- language?: string
7
- extlang?: string
8
- script?: string
9
- region?: string
10
- variant?: string
11
- extension?: string
12
- privateUse?: string
13
- }
14
-
15
- export function parseLanguageString(input: string): LanguageTag | null {
16
- const parsed = input.match(BCP47_REGEXP)
17
- if (!parsed?.groups) return null
18
-
19
- const { groups } = parsed
20
- return {
21
- grandfathered: groups.grandfathered,
22
- language: groups.language,
23
- extlang: groups.extlang,
24
- script: groups.script,
25
- region: groups.region,
26
- variant: groups.variant,
27
- extension: groups.extension,
28
- privateUse: groups.privateUseA || groups.privateUseB,
29
- }
30
- }
31
-
32
- /**
33
- * Validates well-formed BCP 47 syntax
34
- *
35
- * @see {@link https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1}
36
- */
37
- export function isValidLanguage(input: string): boolean {
38
- return BCP47_REGEXP.test(input)
39
- }
package/src/lib/result.ts DELETED
@@ -1,11 +0,0 @@
1
- export type Result<T> = Success<T> | Failure
2
-
3
- export type Success<T> = { success: true; value: T }
4
- export function success<T>(value: T): Success<T> {
5
- return { success: true, value }
6
- }
7
-
8
- export type Failure = { success: false; message: string }
9
- export function failure(message: string): Failure {
10
- return { success: false, message }
11
- }