@atproto/syntax 0.5.4 → 0.6.0-next.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/dist/datetime.js CHANGED
@@ -1,32 +1,14 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
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;
16
- exports.normalizeDatetime = normalizeDatetime;
17
- exports.normalizeDatetimeAlways = normalizeDatetimeAlways;
18
1
  /**
19
2
  * Indicates a date or string is not a valid representation of a datetime
20
3
  * according to the atproto
21
4
  * {@link https://atproto.com/specs/lexicon#datetime specification}.
22
5
  */
23
- class InvalidDatetimeError extends Error {
6
+ export class InvalidDatetimeError extends Error {
24
7
  }
25
- exports.InvalidDatetimeError = InvalidDatetimeError;
26
8
  /**
27
9
  * @see {@link AtprotoDate}
28
10
  */
29
- function assertAtprotoDate(date) {
11
+ export function assertAtprotoDate(date) {
30
12
  const res = parseDate(date);
31
13
  if (!res.success) {
32
14
  throw new InvalidDatetimeError(res.message);
@@ -35,20 +17,20 @@ function assertAtprotoDate(date) {
35
17
  /**
36
18
  * @see {@link AtprotoDate}
37
19
  */
38
- function asAtprotoDate(date) {
20
+ export function asAtprotoDate(date) {
39
21
  assertAtprotoDate(date);
40
22
  return date;
41
23
  }
42
24
  /**
43
25
  * @see {@link AtprotoDate}
44
26
  */
45
- function isAtprotoDate(date) {
27
+ export function isAtprotoDate(date) {
46
28
  return parseDate(date).success;
47
29
  }
48
30
  /**
49
31
  * @see {@link AtprotoDate}
50
32
  */
51
- function ifAtprotoDate(date) {
33
+ export function ifAtprotoDate(date) {
52
34
  return isAtprotoDate(date) ? date : undefined;
53
35
  }
54
36
  /**
@@ -58,7 +40,7 @@ function ifAtprotoDate(date) {
58
40
  * @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.
59
41
  * @see {@link DatetimeString}
60
42
  */
61
- function assertDatetimeString(input) {
43
+ export function assertDatetimeString(input) {
62
44
  const result = parseString(input);
63
45
  if (!result.success) {
64
46
  throw new InvalidDatetimeError(result.message);
@@ -71,7 +53,7 @@ function assertDatetimeString(input) {
71
53
  * @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.
72
54
  * @see {@link DatetimeString}
73
55
  */
74
- function asDatetimeString(input) {
56
+ export function asDatetimeString(input) {
75
57
  assertDatetimeString(input);
76
58
  return input;
77
59
  }
@@ -80,7 +62,7 @@ function asDatetimeString(input) {
80
62
  *
81
63
  * @see {@link DatetimeString}
82
64
  */
83
- function isDatetimeString(input) {
65
+ export function isDatetimeString(input) {
84
66
  return parseString(input).success;
85
67
  }
86
68
  /**
@@ -89,7 +71,7 @@ function isDatetimeString(input) {
89
71
  *
90
72
  * @see {@link DatetimeString}
91
73
  */
92
- function ifDatetimeString(input) {
74
+ export function ifDatetimeString(input) {
93
75
  return isDatetimeString(input) ? input : undefined;
94
76
  }
95
77
  /**
@@ -97,7 +79,7 @@ function ifDatetimeString(input) {
97
79
  *
98
80
  * @see {@link DatetimeString}
99
81
  */
100
- function currentDatetimeString() {
82
+ export function currentDatetimeString() {
101
83
  return toDatetimeString(new Date());
102
84
  }
103
85
  /**
@@ -109,7 +91,7 @@ function currentDatetimeString() {
109
91
  * @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
92
  * @see {@link DatetimeString}
111
93
  */
112
- function toDatetimeString(date) {
94
+ export function toDatetimeString(date) {
113
95
  return asAtprotoDate(date).toISOString();
114
96
  }
115
97
  /**
@@ -130,7 +112,7 @@ function toDatetimeString(date) {
130
112
  * @returns ISODatetimeString - a valid atproto datetime with millisecond precision (3 sub-second digits) and UTC timezone with trailing 'Z' syntax.
131
113
  * @throws InvalidDatetimeError - if the input string could not be parsed as a datetime, even with permissive parsing.
132
114
  */
133
- function normalizeDatetime(dtStr) {
115
+ export function normalizeDatetime(dtStr) {
134
116
  if (
135
117
  // Explicit timezone offset
136
118
  /[+-]\d\d:?\d\d/.test(dtStr) ||
@@ -179,7 +161,7 @@ function normalizeDatetime(dtStr) {
179
161
  *
180
162
  * @see {@link normalizeDatetime}
181
163
  */
182
- function normalizeDatetimeAlways(dtStr) {
164
+ export function normalizeDatetimeAlways(dtStr) {
183
165
  try {
184
166
  return normalizeDatetime(dtStr);
185
167
  }
@@ -187,6 +169,8 @@ function normalizeDatetimeAlways(dtStr) {
187
169
  return '1970-01-01T00:00:00.000Z';
188
170
  }
189
171
  }
172
+ // Legacy exports (should we deprecate these ?)
173
+ export { assertDatetimeString as ensureValidDatetime, isDatetimeString as isValidDatetime, };
190
174
  const failure = (m) => ({ success: false, message: m });
191
175
  const success = (v) => ({ success: true, value: v });
192
176
  /**
@@ -1 +1 @@
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 * @note This function might return different normalized strings for the same\n * input depending on the timezone of the machine it is run on, since it will\n * attempt to parse the input \"as is\" if it fails to parse with an explicit\n * timezone.\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 if (\n // Explicit timezone offset\n /[+-]\\d\\d:?\\d\\d/.test(dtStr) ||\n // 'Z' timezone designator\n /\\dZ\\b/.test(dtStr) ||\n // Timezone abbreviation (eg. \"EST\", \"PST\", \"UTC\", \"GMT\", etc), as in:\n // > Tue Mar 17 2026 16:38:44 PST (Pacific Standard Time)\n /\\b[A-Z]{3,4}\\b/.test(dtStr)\n ) {\n // Since we do have a timezone designator, we can try parsing \"as is\" and\n // should get consistent results regardless of local timezone.\n\n // @NOTE NodeJS will reject dates with an un-recognized timezone designator\n // (like \"AFT\"), even if we add a well-known timezone abbreviation like\n // \"UTC\" or \"Z\".\n const date = new Date(dtStr)\n if (isAtprotoDate(date)) {\n return date.toISOString()\n }\n } else {\n // If there is no timezone information, try parsing as UTC using two\n // different syntaxes, falling back to parsing \"as is\".\n\n const dateZ = new Date(`${dtStr}Z`)\n if (isAtprotoDate(dateZ)) {\n return dateZ.toISOString()\n }\n\n const dateUTC = new Date(`${dtStr} UTC`)\n if (isAtprotoDate(dateUTC)) {\n return dateUTC.toISOString()\n }\n\n // Despite our best efforts to parse as a consistent value, appending \"Z\" or\n // \" UTC\" did not work, so we will try parsing \"as is\", which may yield\n // different results depending on the local timezone of the machine.\n const date = new Date(dtStr)\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 return success(date as AtprotoDate)\n}\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 * @note This function might return different normalized strings for the same\n * input depending on the timezone of the machine it is run on, since it will\n * attempt to parse the input \"as is\" if it fails to parse with an explicit\n * timezone.\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 if (\n // Explicit timezone offset\n /[+-]\\d\\d:?\\d\\d/.test(dtStr) ||\n // 'Z' timezone designator\n /\\dZ\\b/.test(dtStr) ||\n // Timezone abbreviation (eg. \"EST\", \"PST\", \"UTC\", \"GMT\", etc), as in:\n // > Tue Mar 17 2026 16:38:44 PST (Pacific Standard Time)\n /\\b[A-Z]{3,4}\\b/.test(dtStr)\n ) {\n // Since we do have a timezone designator, we can try parsing \"as is\" and\n // should get consistent results regardless of local timezone.\n\n // @NOTE NodeJS will reject dates with an un-recognized timezone designator\n // (like \"AFT\"), even if we add a well-known timezone abbreviation like\n // \"UTC\" or \"Z\".\n const date = new Date(dtStr)\n if (isAtprotoDate(date)) {\n return date.toISOString()\n }\n } else {\n // If there is no timezone information, try parsing as UTC using two\n // different syntaxes, falling back to parsing \"as is\".\n\n const dateZ = new Date(`${dtStr}Z`)\n if (isAtprotoDate(dateZ)) {\n return dateZ.toISOString()\n }\n\n const dateUTC = new Date(`${dtStr} UTC`)\n if (isAtprotoDate(dateUTC)) {\n return dateUTC.toISOString()\n }\n\n // Despite our best efforts to parse as a consistent value, appending \"Z\" or\n // \" UTC\" did not work, so we will try parsing \"as is\", which may yield\n // different results depending on the local timezone of the machine.\n const date = new Date(dtStr)\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 return success(date as AtprotoDate)\n}\n"]}
package/dist/did.js CHANGED
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  // Human-readable constraints:
3
2
  // - valid W3C DID (https://www.w3.org/TR/did-core/#did-syntax)
4
3
  // - entire URI is ASCII: [a-zA-Z0-9._:%-]
@@ -12,12 +11,7 @@
12
11
  // - in current atproto, only allowing did:plc and did:web. But not *forcing* this at lexicon layer
13
12
  // - hard length limit of 8KBytes
14
13
  // - not going to validate "percent encoding" here
15
- Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.InvalidDidError = void 0;
17
- exports.ensureValidDid = ensureValidDid;
18
- exports.ensureValidDidRegex = ensureValidDidRegex;
19
- exports.isValidDid = isValidDid;
20
- function ensureValidDid(input) {
14
+ export function ensureValidDid(input) {
21
15
  if (!input.startsWith('did:')) {
22
16
  throw new InvalidDidError('DID requires "did:" prefix');
23
17
  }
@@ -40,7 +34,7 @@ function ensureValidDid(input) {
40
34
  }
41
35
  }
42
36
  const DID_REGEX = /^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/;
43
- function ensureValidDidRegex(input) {
37
+ export function ensureValidDidRegex(input) {
44
38
  // simple regex to enforce most constraints via just regex and length.
45
39
  // hand wrote this regex based on above constraints
46
40
  if (!DID_REGEX.test(input)) {
@@ -50,10 +44,9 @@ function ensureValidDidRegex(input) {
50
44
  throw new InvalidDidError('DID is too long (2048 chars max)');
51
45
  }
52
46
  }
53
- function isValidDid(input) {
47
+ export function isValidDid(input) {
54
48
  return input.length <= 2048 && DID_REGEX.test(input);
55
49
  }
56
- class InvalidDidError extends Error {
50
+ export class InvalidDidError extends Error {
57
51
  }
58
- exports.InvalidDidError = InvalidDidError;
59
52
  //# sourceMappingURL=did.js.map
package/dist/did.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"did.js","sourceRoot":"","sources":["../src/did.ts"],"names":[],"mappings":";AAAA,8BAA8B;AAC9B,iEAAiE;AACjE,+CAA+C;AAC/C,2CAA2C;AAC3C,wEAAwE;AACxE,sFAAsF;AACtF,qFAAqF;AACrF,wHAAwH;AACxH,8GAA8G;AAC9G,6FAA6F;AAC7F,qGAAqG;AACrG,mCAAmC;AACnC,oDAAoD;;;AAIpD,wCAgCC;AAID,kDAYC;AAED,gCAEC;AApDD,SAAgB,cAAc,CAC5B,KAAQ;IAER,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,eAAe,CAAC,4BAA4B,CAAC,CAAA;IACzD,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QACxB,MAAM,IAAI,eAAe,CAAC,kCAAkC,CAAC,CAAA;IAC/D,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,eAAe,CAAC,iCAAiC,CAAC,CAAA;IAC9D,CAAC;IAED,wCAAwC;IACxC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,eAAe,CACvB,0FAA0F,CAC3F,CAAA;IACH,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC9C,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QACf,MAAM,IAAI,eAAe,CACvB,0DAA0D,CAC3D,CAAA;IACH,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,eAAe,CAAC,uCAAuC,CAAC,CAAA;IACpE,CAAC;AACH,CAAC;AAED,MAAM,SAAS,GAAG,8CAA8C,CAAA;AAEhE,SAAgB,mBAAmB,CACjC,KAAQ;IAER,sEAAsE;IACtE,mDAAmD;IACnD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,eAAe,CAAC,+BAA+B,CAAC,CAAA;IAC5D,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QACxB,MAAM,IAAI,eAAe,CAAC,kCAAkC,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC;AAED,SAAgB,UAAU,CAAmB,KAAQ;IACnD,OAAO,KAAK,CAAC,MAAM,IAAI,IAAI,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACtD,CAAC;AAED,MAAa,eAAgB,SAAQ,KAAK;CAAG;AAA7C,0CAA6C","sourcesContent":["// Human-readable constraints:\n// - valid W3C DID (https://www.w3.org/TR/did-core/#did-syntax)\n// - entire URI is ASCII: [a-zA-Z0-9._:%-]\n// - always starts \"did:\" (lower-case)\n// - method name is one or more lower-case letters, followed by \":\"\n// - remaining identifier can have any of the above chars, but can not end in \":\"\n// - it seems that a bunch of \":\" can be included, and don't need spaces between\n// - \"%\" is used only for \"percent encoding\" and must be followed by two hex characters (and thus can't end in \"%\")\n// - query (\"?\") and fragment (\"#\") stuff is defined for \"DID URIs\", but not as part of identifier itself\n// - \"The current specification does not take a position on the maximum length of a DID\"\n// - in current atproto, only allowing did:plc and did:web. But not *forcing* this at lexicon layer\n// - hard length limit of 8KBytes\n// - not going to validate \"percent encoding\" here\n\nexport type DidString<M extends string = string> = `did:${M}:${string}`\n\nexport function ensureValidDid<I extends string>(\n input: I,\n): asserts input is I & DidString {\n if (!input.startsWith('did:')) {\n throw new InvalidDidError('DID requires \"did:\" prefix')\n }\n\n if (input.length > 2048) {\n throw new InvalidDidError('DID is too long (2048 chars max)')\n }\n\n if (input.endsWith(':') || input.endsWith('%')) {\n throw new InvalidDidError('DID can not end with \":\" or \"%\"')\n }\n\n // check that all chars are boring ASCII\n if (!/^[a-zA-Z0-9._:%-]*$/.test(input)) {\n throw new InvalidDidError(\n 'Disallowed characters in DID (ASCII letters, digits, and a couple other characters only)',\n )\n }\n\n const { length, 1: method } = input.split(':')\n if (length < 3) {\n throw new InvalidDidError(\n 'DID requires prefix, method, and method-specific content',\n )\n }\n\n if (!/^[a-z]+$/.test(method)) {\n throw new InvalidDidError('DID method must be lower-case letters')\n }\n}\n\nconst DID_REGEX = /^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/\n\nexport function ensureValidDidRegex<I extends string>(\n input: I,\n): asserts input is I & DidString {\n // simple regex to enforce most constraints via just regex and length.\n // hand wrote this regex based on above constraints\n if (!DID_REGEX.test(input)) {\n throw new InvalidDidError(\"DID didn't validate via regex\")\n }\n\n if (input.length > 2048) {\n throw new InvalidDidError('DID is too long (2048 chars max)')\n }\n}\n\nexport function isValidDid<I extends string>(input: I): input is I & DidString {\n return input.length <= 2048 && DID_REGEX.test(input)\n}\n\nexport class InvalidDidError extends Error {}\n"]}
1
+ {"version":3,"file":"did.js","sourceRoot":"","sources":["../src/did.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,iEAAiE;AACjE,+CAA+C;AAC/C,2CAA2C;AAC3C,wEAAwE;AACxE,sFAAsF;AACtF,qFAAqF;AACrF,wHAAwH;AACxH,8GAA8G;AAC9G,6FAA6F;AAC7F,qGAAqG;AACrG,mCAAmC;AACnC,oDAAoD;AAIpD,MAAM,UAAU,cAAc,CAC5B,KAAQ;IAER,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,eAAe,CAAC,4BAA4B,CAAC,CAAA;IACzD,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QACxB,MAAM,IAAI,eAAe,CAAC,kCAAkC,CAAC,CAAA;IAC/D,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,eAAe,CAAC,iCAAiC,CAAC,CAAA;IAC9D,CAAC;IAED,wCAAwC;IACxC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,eAAe,CACvB,0FAA0F,CAC3F,CAAA;IACH,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC9C,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QACf,MAAM,IAAI,eAAe,CACvB,0DAA0D,CAC3D,CAAA;IACH,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,eAAe,CAAC,uCAAuC,CAAC,CAAA;IACpE,CAAC;AACH,CAAC;AAED,MAAM,SAAS,GAAG,8CAA8C,CAAA;AAEhE,MAAM,UAAU,mBAAmB,CACjC,KAAQ;IAER,sEAAsE;IACtE,mDAAmD;IACnD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,eAAe,CAAC,+BAA+B,CAAC,CAAA;IAC5D,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QACxB,MAAM,IAAI,eAAe,CAAC,kCAAkC,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAmB,KAAQ;IACnD,OAAO,KAAK,CAAC,MAAM,IAAI,IAAI,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACtD,CAAC;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;CAAG","sourcesContent":["// Human-readable constraints:\n// - valid W3C DID (https://www.w3.org/TR/did-core/#did-syntax)\n// - entire URI is ASCII: [a-zA-Z0-9._:%-]\n// - always starts \"did:\" (lower-case)\n// - method name is one or more lower-case letters, followed by \":\"\n// - remaining identifier can have any of the above chars, but can not end in \":\"\n// - it seems that a bunch of \":\" can be included, and don't need spaces between\n// - \"%\" is used only for \"percent encoding\" and must be followed by two hex characters (and thus can't end in \"%\")\n// - query (\"?\") and fragment (\"#\") stuff is defined for \"DID URIs\", but not as part of identifier itself\n// - \"The current specification does not take a position on the maximum length of a DID\"\n// - in current atproto, only allowing did:plc and did:web. But not *forcing* this at lexicon layer\n// - hard length limit of 8KBytes\n// - not going to validate \"percent encoding\" here\n\nexport type DidString<M extends string = string> = `did:${M}:${string}`\n\nexport function ensureValidDid<I extends string>(\n input: I,\n): asserts input is I & DidString {\n if (!input.startsWith('did:')) {\n throw new InvalidDidError('DID requires \"did:\" prefix')\n }\n\n if (input.length > 2048) {\n throw new InvalidDidError('DID is too long (2048 chars max)')\n }\n\n if (input.endsWith(':') || input.endsWith('%')) {\n throw new InvalidDidError('DID can not end with \":\" or \"%\"')\n }\n\n // check that all chars are boring ASCII\n if (!/^[a-zA-Z0-9._:%-]*$/.test(input)) {\n throw new InvalidDidError(\n 'Disallowed characters in DID (ASCII letters, digits, and a couple other characters only)',\n )\n }\n\n const { length, 1: method } = input.split(':')\n if (length < 3) {\n throw new InvalidDidError(\n 'DID requires prefix, method, and method-specific content',\n )\n }\n\n if (!/^[a-z]+$/.test(method)) {\n throw new InvalidDidError('DID method must be lower-case letters')\n }\n}\n\nconst DID_REGEX = /^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/\n\nexport function ensureValidDidRegex<I extends string>(\n input: I,\n): asserts input is I & DidString {\n // simple regex to enforce most constraints via just regex and length.\n // hand wrote this regex based on above constraints\n if (!DID_REGEX.test(input)) {\n throw new InvalidDidError(\"DID didn't validate via regex\")\n }\n\n if (input.length > 2048) {\n throw new InvalidDidError('DID is too long (2048 chars max)')\n }\n}\n\nexport function isValidDid<I extends string>(input: I): input is I & DidString {\n return input.length <= 2048 && DID_REGEX.test(input)\n}\n\nexport class InvalidDidError extends Error {}\n"]}
package/dist/handle.js CHANGED
@@ -1,18 +1,9 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DisallowedDomainError = exports.UnsupportedDomainError = exports.ReservedHandleError = exports.InvalidHandleError = exports.DISALLOWED_TLDS = exports.INVALID_HANDLE = void 0;
4
- exports.ensureValidHandle = ensureValidHandle;
5
- exports.ensureValidHandleRegex = ensureValidHandleRegex;
6
- exports.normalizeHandle = normalizeHandle;
7
- exports.normalizeAndEnsureValidHandle = normalizeAndEnsureValidHandle;
8
- exports.isValidHandle = isValidHandle;
9
- exports.isValidTld = isValidTld;
10
- exports.INVALID_HANDLE = 'handle.invalid';
1
+ export const INVALID_HANDLE = 'handle.invalid';
11
2
  // Currently these are registration-time restrictions, not protocol-level
12
3
  // restrictions. We have a couple accounts in the wild that we need to clean up
13
4
  // before hard-disallow.
14
5
  // See also: https://en.wikipedia.org/wiki/Top-level_domain#Reserved_domains
15
- exports.DISALLOWED_TLDS = [
6
+ export const DISALLOWED_TLDS = [
16
7
  '.local',
17
8
  '.arpa',
18
9
  '.invalid',
@@ -44,7 +35,7 @@ exports.DISALLOWED_TLDS = [
44
35
  // - does not validate whether domain or TLD exists, or is a reserved or
45
36
  // special TLD (eg, .onion or .local)
46
37
  // - does not validate punycode
47
- function ensureValidHandle(input) {
38
+ export function ensureValidHandle(input) {
48
39
  // check that all chars are boring ASCII
49
40
  if (!/^[a-zA-Z0-9.-]*$/.test(input)) {
50
41
  throw new InvalidHandleError('Disallowed characters in handle (ASCII letters, digits, dashes, periods only)');
@@ -74,7 +65,7 @@ function ensureValidHandle(input) {
74
65
  }
75
66
  // simple regex translation of above constraints
76
67
  const HANDLE_REGEX = /^([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])?$/;
77
- function ensureValidHandleRegex(input) {
68
+ export function ensureValidHandleRegex(input) {
78
69
  if (input.length > 253) {
79
70
  throw new InvalidHandleError('Handle is too long (253 chars max)');
80
71
  }
@@ -82,38 +73,34 @@ function ensureValidHandleRegex(input) {
82
73
  throw new InvalidHandleError("Handle didn't validate via regex");
83
74
  }
84
75
  }
85
- function normalizeHandle(handle) {
76
+ export function normalizeHandle(handle) {
86
77
  return handle.toLowerCase();
87
78
  }
88
- function normalizeAndEnsureValidHandle(handle) {
79
+ export function normalizeAndEnsureValidHandle(handle) {
89
80
  const normalized = normalizeHandle(handle);
90
81
  ensureValidHandle(normalized);
91
82
  return normalized;
92
83
  }
93
- function isValidHandle(input) {
84
+ export function isValidHandle(input) {
94
85
  return input.length <= 253 && HANDLE_REGEX.test(input);
95
86
  }
96
- function isValidTld(handle) {
97
- for (const tld of exports.DISALLOWED_TLDS) {
87
+ export function isValidTld(handle) {
88
+ for (const tld of DISALLOWED_TLDS) {
98
89
  if (handle.endsWith(tld)) {
99
90
  return false;
100
91
  }
101
92
  }
102
93
  return true;
103
94
  }
104
- class InvalidHandleError extends Error {
95
+ export class InvalidHandleError extends Error {
105
96
  }
106
- exports.InvalidHandleError = InvalidHandleError;
107
97
  /** @deprecated Never used */
108
- class ReservedHandleError extends Error {
98
+ export class ReservedHandleError extends Error {
109
99
  }
110
- exports.ReservedHandleError = ReservedHandleError;
111
100
  /** @deprecated Never used */
112
- class UnsupportedDomainError extends Error {
101
+ export class UnsupportedDomainError extends Error {
113
102
  }
114
- exports.UnsupportedDomainError = UnsupportedDomainError;
115
103
  /** @deprecated Never used */
116
- class DisallowedDomainError extends Error {
104
+ export class DisallowedDomainError extends Error {
117
105
  }
118
- exports.DisallowedDomainError = DisallowedDomainError;
119
106
  //# sourceMappingURL=handle.js.map
@@ -1 +1 @@
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"]}
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/index.js CHANGED
@@ -1,14 +1,11 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const tslib_1 = require("tslib");
4
- tslib_1.__exportStar(require("./at-identifier.js"), exports);
5
- tslib_1.__exportStar(require("./aturi.js"), exports);
6
- tslib_1.__exportStar(require("./datetime.js"), exports);
7
- tslib_1.__exportStar(require("./did.js"), exports);
8
- tslib_1.__exportStar(require("./handle.js"), exports);
9
- tslib_1.__exportStar(require("./nsid.js"), exports);
10
- tslib_1.__exportStar(require("./language.js"), exports);
11
- tslib_1.__exportStar(require("./recordkey.js"), exports);
12
- tslib_1.__exportStar(require("./tid.js"), exports);
13
- tslib_1.__exportStar(require("./uri.js"), exports);
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';
14
11
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6DAAkC;AAClC,qDAA0B;AAC1B,wDAA6B;AAC7B,mDAAwB;AACxB,sDAA2B;AAC3B,oDAAyB;AACzB,wDAA6B;AAC7B,yDAA8B;AAC9B,mDAAwB;AACxB,mDAAwB","sourcesContent":["export * from './at-identifier.js'\nexport * from './aturi.js'\nexport * from './datetime.js'\nexport * from './did.js'\nexport * from './handle.js'\nexport * from './nsid.js'\nexport * from './language.js'\nexport * from './recordkey.js'\nexport * from './tid.js'\nexport * from './uri.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA","sourcesContent":["export * from './at-identifier.js'\nexport * from './aturi.js'\nexport * from './datetime.js'\nexport * from './did.js'\nexport * from './handle.js'\nexport * from './nsid.js'\nexport * from './language.js'\nexport * from './recordkey.js'\nexport * from './tid.js'\nexport * from './uri.js'\n"]}
package/dist/language.js CHANGED
@@ -1,9 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseLanguageString = parseLanguageString;
4
- exports.isValidLanguage = isValidLanguage;
5
1
  const BCP47_REGEXP = /^((?<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})+))$/;
6
- function parseLanguageString(input) {
2
+ export function parseLanguageString(input) {
7
3
  const parsed = input.match(BCP47_REGEXP);
8
4
  if (!parsed?.groups)
9
5
  return null;
@@ -24,7 +20,7 @@ function parseLanguageString(input) {
24
20
  *
25
21
  * @see {@link https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1}
26
22
  */
27
- function isValidLanguage(input) {
23
+ export function isValidLanguage(input) {
28
24
  return BCP47_REGEXP.test(input);
29
25
  }
30
26
  //# sourceMappingURL=language.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"language.js","sourceRoot":"","sources":["../src/language.ts"],"names":[],"mappings":";;AAcA,kDAeC;AAOD,0CAEC;AAtCD,MAAM,YAAY,GAChB,mlBAAmlB,CAAA;AAarlB,SAAgB,mBAAmB,CAAC,KAAa;IAC/C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,EAAE,MAAM;QAAE,OAAO,IAAI,CAAA;IAEhC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAA;IACzB,OAAO;QACL,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,UAAU,EAAE,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW;KACrD,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,eAAe,CAAC,KAAa;IAC3C,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACjC,CAAC","sourcesContent":["const BCP47_REGEXP =\n /^((?<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})+))$/\n\nexport type LanguageTag = {\n grandfathered?: string\n language?: string\n extlang?: string\n script?: string\n region?: string\n variant?: string\n extension?: string\n privateUse?: string\n}\n\nexport function parseLanguageString(input: string): LanguageTag | null {\n const parsed = input.match(BCP47_REGEXP)\n if (!parsed?.groups) return null\n\n const { groups } = parsed\n return {\n grandfathered: groups.grandfathered,\n language: groups.language,\n extlang: groups.extlang,\n script: groups.script,\n region: groups.region,\n variant: groups.variant,\n extension: groups.extension,\n privateUse: groups.privateUseA || groups.privateUseB,\n }\n}\n\n/**\n * Validates well-formed BCP 47 syntax\n *\n * @see {@link https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1}\n */\nexport function isValidLanguage(input: string): boolean {\n return BCP47_REGEXP.test(input)\n}\n"]}
1
+ {"version":3,"file":"language.js","sourceRoot":"","sources":["../src/language.ts"],"names":[],"mappings":"AAAA,MAAM,YAAY,GAChB,mlBAAmlB,CAAA;AAarlB,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,EAAE,MAAM;QAAE,OAAO,IAAI,CAAA;IAEhC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAA;IACzB,OAAO;QACL,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,UAAU,EAAE,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW;KACrD,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACjC,CAAC","sourcesContent":["const BCP47_REGEXP =\n /^((?<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})+))$/\n\nexport type LanguageTag = {\n grandfathered?: string\n language?: string\n extlang?: string\n script?: string\n region?: string\n variant?: string\n extension?: string\n privateUse?: string\n}\n\nexport function parseLanguageString(input: string): LanguageTag | null {\n const parsed = input.match(BCP47_REGEXP)\n if (!parsed?.groups) return null\n\n const { groups } = parsed\n return {\n grandfathered: groups.grandfathered,\n language: groups.language,\n extlang: groups.extlang,\n script: groups.script,\n region: groups.region,\n variant: groups.variant,\n extension: groups.extension,\n privateUse: groups.privateUseA || groups.privateUseB,\n }\n}\n\n/**\n * Validates well-formed BCP 47 syntax\n *\n * @see {@link https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1}\n */\nexport function isValidLanguage(input: string): boolean {\n return BCP47_REGEXP.test(input)\n}\n"]}
@@ -1,11 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.success = success;
4
- exports.failure = failure;
5
- function success(value) {
1
+ export function success(value) {
6
2
  return { success: true, value };
7
3
  }
8
- function failure(message) {
4
+ export function failure(message) {
9
5
  return { success: false, message };
10
6
  }
11
7
  //# sourceMappingURL=result.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/lib/result.ts"],"names":[],"mappings":";;AAGA,0BAEC;AAGD,0BAEC;AAPD,SAAgB,OAAO,CAAI,KAAQ;IACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AACjC,CAAC;AAGD,SAAgB,OAAO,CAAC,OAAe;IACrC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AACpC,CAAC","sourcesContent":["export type Result<T> = Success<T> | Failure\n\nexport type Success<T> = { success: true; value: T }\nexport function success<T>(value: T): Success<T> {\n return { success: true, value }\n}\n\nexport type Failure = { success: false; message: string }\nexport function failure(message: string): Failure {\n return { success: false, message }\n}\n"]}
1
+ {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/lib/result.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,OAAO,CAAI,KAAQ;IACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AACjC,CAAC;AAGD,MAAM,UAAU,OAAO,CAAC,OAAe;IACrC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AACpC,CAAC","sourcesContent":["export type Result<T> = Success<T> | Failure\n\nexport type Success<T> = { success: true; value: T }\nexport function success<T>(value: T): Success<T> {\n return { success: true, value }\n}\n\nexport type Failure = { success: false; message: string }\nexport function failure(message: string): Failure {\n return { success: false, message }\n}\n"]}
package/dist/nsid.js CHANGED
@@ -1,15 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InvalidNsidError = exports.NSID = void 0;
4
- exports.ensureValidNsid = ensureValidNsid;
5
- exports.parseNsid = parseNsid;
6
- exports.isValidNsid = isValidNsid;
7
- exports.validateNsid = validateNsid;
8
- exports.ensureValidNsidRegex = ensureValidNsidRegex;
9
- exports.validateNsidRegex = validateNsidRegex;
10
- const result_js_1 = require("./lib/result.js");
11
- class NSID {
12
- segments;
1
+ import { failure, success } from './lib/result.js';
2
+ export class NSID {
13
3
  static parse(input) {
14
4
  return new NSID(input);
15
5
  }
@@ -46,19 +36,18 @@ class NSID {
46
36
  return this.segments.join('.');
47
37
  }
48
38
  }
49
- exports.NSID = NSID;
50
- function ensureValidNsid(input) {
39
+ export function ensureValidNsid(input) {
51
40
  const result = validateNsid(input);
52
41
  if (!result.success)
53
42
  throw new InvalidNsidError(result.message);
54
43
  }
55
- function parseNsid(nsid) {
44
+ export function parseNsid(nsid) {
56
45
  const result = validateNsid(nsid);
57
46
  if (!result.success)
58
47
  throw new InvalidNsidError(result.message);
59
48
  return result.value;
60
49
  }
61
- function isValidNsid(input) {
50
+ export function isValidNsid(input) {
62
51
  // Since the regex version is more performant for valid NSIDs, we use it when
63
52
  // we don't care about error details.
64
53
  return validateNsidRegex(input).success;
@@ -66,35 +55,35 @@ function isValidNsid(input) {
66
55
  // Human readable constraints on NSID:
67
56
  // - a valid domain in reversed notation
68
57
  // - followed by an additional period-separated name, which is camel-case letters
69
- function validateNsid(input) {
58
+ export function validateNsid(input) {
70
59
  if (input.length > 253 + 1 + 63) {
71
- return (0, result_js_1.failure)('NSID is too long (317 chars max)');
60
+ return failure('NSID is too long (317 chars max)');
72
61
  }
73
62
  if (hasDisallowedCharacters(input)) {
74
- return (0, result_js_1.failure)('Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)');
63
+ return failure('Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)');
75
64
  }
76
65
  const segments = input.split('.');
77
66
  if (segments.length < 3) {
78
- return (0, result_js_1.failure)('NSID needs at least three parts');
67
+ return failure('NSID needs at least three parts');
79
68
  }
80
69
  for (const l of segments) {
81
70
  if (l.length < 1) {
82
- return (0, result_js_1.failure)('NSID parts can not be empty');
71
+ return failure('NSID parts can not be empty');
83
72
  }
84
73
  if (l.length > 63) {
85
- return (0, result_js_1.failure)('NSID part too long (max 63 chars)');
74
+ return failure('NSID part too long (max 63 chars)');
86
75
  }
87
76
  if (startsWithHyphen(l) || endsWithHyphen(l)) {
88
- return (0, result_js_1.failure)('NSID parts can not start or end with hyphen');
77
+ return failure('NSID parts can not start or end with hyphen');
89
78
  }
90
79
  }
91
80
  if (startsWithNumber(segments[0])) {
92
- return (0, result_js_1.failure)('NSID first part may not start with a digit');
81
+ return failure('NSID first part may not start with a digit');
93
82
  }
94
83
  if (!isValidIdentifier(segments[segments.length - 1])) {
95
- return (0, result_js_1.failure)('NSID name part must be only letters and digits (and no leading digit)');
84
+ return failure('NSID name part must be only letters and digits (and no leading digit)');
96
85
  }
97
- return (0, result_js_1.success)(segments);
86
+ return success(segments);
98
87
  }
99
88
  function hasDisallowedCharacters(v) {
100
89
  return !/^[a-zA-Z0-9.-]*$/.test(v);
@@ -121,7 +110,7 @@ function isValidIdentifier(v) {
121
110
  * {@link parseNsid}/{@link NSID.parse} if you need the parsed segments, or
122
111
  * {@link isValidNsid} if you just want a boolean.
123
112
  */
124
- function ensureValidNsidRegex(nsid) {
113
+ export function ensureValidNsidRegex(nsid) {
125
114
  const result = validateNsidRegex(nsid);
126
115
  if (!result.success)
127
116
  throw new InvalidNsidError(result.message);
@@ -130,19 +119,18 @@ function ensureValidNsidRegex(nsid) {
130
119
  * Regexp based validation that behaves identically to the previous code but
131
120
  * provides less detailed error messages (while being 20% to 50% faster).
132
121
  */
133
- function validateNsidRegex(value) {
122
+ export function validateNsidRegex(value) {
134
123
  if (value.length > 253 + 1 + 63) {
135
- return (0, result_js_1.failure)('NSID is too long (317 chars max)');
124
+ return failure('NSID is too long (317 chars max)');
136
125
  }
137
126
  if (
138
127
  // Fast check for small values
139
128
  value.length < 5 ||
140
129
  !/^[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)) {
141
- return (0, result_js_1.failure)("NSID didn't validate via regex");
130
+ return failure("NSID didn't validate via regex");
142
131
  }
143
- return (0, result_js_1.success)(value);
132
+ return success(value);
144
133
  }
145
- class InvalidNsidError extends Error {
134
+ export class InvalidNsidError extends Error {
146
135
  }
147
- exports.InvalidNsidError = InvalidNsidError;
148
136
  //# sourceMappingURL=nsid.js.map