@atproto/syntax 0.4.3 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @atproto/syntax
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#4689](https://github.com/bluesky-social/atproto/pull/4689) [`f7c2610`](https://github.com/bluesky-social/atproto/commit/f7c26103a6d4e24e5bedbb6fd908be140420e0dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove global `Date.toISOString()` overload and replace with more accurate, less permissive, `AtprotoDate` interface. This change prevent using any `Date` instance to generate a `DatetimeString`, since some JS dates can actually not be safely stringified to a `DatetimeString`.
8
+
9
+ ### Patch Changes
10
+
11
+ - [#4689](https://github.com/bluesky-social/atproto/pull/4689) [`f7c2610`](https://github.com/bluesky-social/atproto/commit/f7c26103a6d4e24e5bedbb6fd908be140420e0dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add type safe accessors to read and write `AtUri`'s `did`, `collection` and `rkey` properties
12
+
13
+ - [#4689](https://github.com/bluesky-social/atproto/pull/4689) [`f7c2610`](https://github.com/bluesky-social/atproto/commit/f7c26103a6d4e24e5bedbb6fd908be140420e0dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Test `ensureValidDatetime`, `isValidDatetime` and `normalizeDatetime` individually, for expected failures.
14
+
15
+ - [#4689](https://github.com/bluesky-social/atproto/pull/4689) [`f7c2610`](https://github.com/bluesky-social/atproto/commit/f7c26103a6d4e24e5bedbb6fd908be140420e0dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Allow calling `DatetimeString` validation utilities with `unknown` values (instead of only `string`)
16
+
17
+ - [#4689](https://github.com/bluesky-social/atproto/pull/4689) [`f7c2610`](https://github.com/bluesky-social/atproto/commit/f7c26103a6d4e24e5bedbb6fd908be140420e0dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Preserve input type when normalizing a valid `HandleString`
18
+
19
+ - [#4688](https://github.com/bluesky-social/atproto/pull/4688) [`52834ab`](https://github.com/bluesky-social/atproto/commit/52834aba182da8df3611fd9dff924e6c6a3973a7) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Minor optimization in regex based NSID string validation
20
+
21
+ - [#4689](https://github.com/bluesky-social/atproto/pull/4689) [`f7c2610`](https://github.com/bluesky-social/atproto/commit/f7c26103a6d4e24e5bedbb6fd908be140420e0dd) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Improve type strictness of `NSID`'s `authority` and `toString()` properties
22
+
3
23
  ## 0.4.3
4
24
 
5
25
  ### Patch Changes
package/dist/aturi.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { AtIdentifierString } from './at-identifier.js';
2
2
  import { AtUriString } from './aturi_validation.js';
3
+ import { DidString } from './did.js';
4
+ import { NsidString } from './nsid.js';
5
+ import { RecordKeyString } from './recordkey.js';
3
6
  export * from './aturi_validation.js';
4
7
  export declare const ATP_URI_REGEX: RegExp;
5
8
  export declare class AtUri {
@@ -11,14 +14,19 @@ export declare class AtUri {
11
14
  static make(handleOrDid: string, collection?: string, rkey?: string): AtUri;
12
15
  get protocol(): string;
13
16
  get origin(): `at://did:${string}:${string}` | `at://${string}.${string}`;
17
+ get did(): DidString;
14
18
  get hostname(): string;
15
19
  set hostname(v: string);
16
20
  get search(): string;
17
21
  set search(v: string);
18
22
  get collection(): string;
23
+ get collectionSafe(): NsidString;
19
24
  set collection(v: string);
25
+ unsafelySetCollection(v: string): void;
20
26
  get rkey(): string;
27
+ get rkeySafe(): RecordKeyString;
21
28
  set rkey(v: string);
29
+ unsafelySetRkey(v: string): void;
22
30
  get href(): AtUriString;
23
31
  toString(): AtUriString;
24
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"aturi.d.ts","sourceRoot":"","sources":["../src/aturi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAA2B,MAAM,oBAAoB,CAAA;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAGnD,cAAc,uBAAuB,CAAA;AAErC,eAAO,MAAM,aAAa,QAEyE,CAAA;AAInG,qBAAa,KAAK;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,kBAAkB,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,eAAe,CAAA;gBAEjB,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK;IAgB9C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAOnE,IAAI,QAAQ,WAEX;IAED,IAAI,MAAM,gEAET;IAED,IAAI,QAAQ,IAII,MAAM,CAFrB;IAED,IAAI,QAAQ,CAAC,CAAC,EAAE,MAAM,EAGrB;IAED,IAAI,MAAM,IAII,MAAM,CAFnB;IAED,IAAI,MAAM,CAAC,CAAC,EAAE,MAAM,EAEnB;IAED,IAAI,UAAU,IAII,MAAM,CAFvB;IAED,IAAI,UAAU,CAAC,CAAC,EAAE,MAAM,EAKvB;IAED,IAAI,IAAI,IAII,MAAM,CAFjB;IAED,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,EAKjB;IAED,IAAI,IAAI,gBAEP;IAED,QAAQ,IAAI,WAAW;CAexB"}
1
+ {"version":3,"file":"aturi.d.ts","sourceRoot":"","sources":["../src/aturi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAA2B,MAAM,oBAAoB,CAAA;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAkB,MAAM,UAAU,CAAA;AACpD,OAAO,EAAE,UAAU,EAAmB,MAAM,WAAW,CAAA;AACvD,OAAO,EAAE,eAAe,EAAwB,MAAM,gBAAgB,CAAA;AAEtE,cAAc,uBAAuB,CAAA;AAErC,eAAO,MAAM,aAAa,QAEyE,CAAA;AAInG,qBAAa,KAAK;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,kBAAkB,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,eAAe,CAAA;gBAEjB,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK;IAgB9C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAOnE,IAAI,QAAQ,WAEX;IAED,IAAI,MAAM,gEAET;IAED,IAAI,GAAG,IAAI,SAAS,CAInB;IAED,IAAI,QAAQ,IAII,MAAM,CAFrB;IAED,IAAI,QAAQ,CAAC,CAAC,EAAE,MAAM,EAGrB;IAED,IAAI,MAAM,IAII,MAAM,CAFnB;IAED,IAAI,MAAM,CAAC,CAAC,EAAE,MAAM,EAEnB;IAED,IAAI,UAAU,IAUI,MAAM,CARvB;IAED,IAAI,cAAc,IAAI,UAAU,CAI/B;IAED,IAAI,UAAU,CAAC,CAAC,EAAE,MAAM,EAGvB;IAED,qBAAqB,CAAC,CAAC,EAAE,MAAM;IAM/B,IAAI,IAAI,IAUI,MAAM,CARjB;IAED,IAAI,QAAQ,IAAI,eAAe,CAI9B;IAED,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,EAGjB;IAED,eAAe,CAAC,CAAC,EAAE,MAAM;IAOzB,IAAI,IAAI,gBAEP;IAED,QAAQ,IAAI,WAAW;CAexB"}
package/dist/aturi.js CHANGED
@@ -3,7 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AtUri = exports.ATP_URI_REGEX = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const at_identifier_js_1 = require("./at-identifier.js");
6
+ const did_js_1 = require("./did.js");
6
7
  const nsid_js_1 = require("./nsid.js");
8
+ const recordkey_js_1 = require("./recordkey.js");
7
9
  tslib_1.__exportStar(require("./aturi_validation.js"), exports);
8
10
  exports.ATP_URI_REGEX =
9
11
  // proto- --did-------------- --name---------------- --path---- --query-- --hash--
@@ -41,6 +43,11 @@ class AtUri {
41
43
  get origin() {
42
44
  return `at://${this.host}`;
43
45
  }
46
+ get did() {
47
+ const { hostname } = this;
48
+ (0, did_js_1.ensureValidDid)(hostname);
49
+ return hostname;
50
+ }
44
51
  get hostname() {
45
52
  return this.host;
46
53
  }
@@ -57,8 +64,16 @@ class AtUri {
57
64
  get collection() {
58
65
  return this.pathname.split('/').filter(Boolean)[0] || '';
59
66
  }
67
+ get collectionSafe() {
68
+ const { collection } = this;
69
+ (0, nsid_js_1.ensureValidNsid)(collection);
70
+ return collection;
71
+ }
60
72
  set collection(v) {
61
73
  (0, nsid_js_1.ensureValidNsid)(v);
74
+ this.unsafelySetCollection(v);
75
+ }
76
+ unsafelySetCollection(v) {
62
77
  const parts = this.pathname.split('/').filter(Boolean);
63
78
  parts[0] = v;
64
79
  this.pathname = parts.join('/');
@@ -66,7 +81,16 @@ class AtUri {
66
81
  get rkey() {
67
82
  return this.pathname.split('/').filter(Boolean)[1] || '';
68
83
  }
84
+ get rkeySafe() {
85
+ const { rkey } = this;
86
+ (0, recordkey_js_1.ensureValidRecordKey)(rkey);
87
+ return rkey;
88
+ }
69
89
  set rkey(v) {
90
+ (0, recordkey_js_1.ensureValidRecordKey)(v);
91
+ this.unsafelySetRkey(v);
92
+ }
93
+ unsafelySetRkey(v) {
70
94
  const parts = this.pathname.split('/').filter(Boolean);
71
95
  parts[0] ||= 'undefined';
72
96
  parts[1] = v;
package/dist/aturi.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"aturi.js","sourceRoot":"","sources":["../src/aturi.ts"],"names":[],"mappings":"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","sourcesContent":["import { AtIdentifierString, ensureValidAtIdentifier } from './at-identifier.js'\nimport { AtUriString } from './aturi_validation.js'\nimport { ensureValidNsid } from './nsid.js'\n\nexport * from './aturi_validation.js'\n\nexport const ATP_URI_REGEX =\n // proto- --did-------------- --name---------------- --path---- --query-- --hash--\n /^(at:\\/\\/)?((?:did:[a-z0-9:%-]+)|(?:[a-z0-9][a-z0-9.:-]*))(\\/[^?#\\s]*)?(\\?[^#\\s]+)?(#[^\\s]+)?$/i\n// --path----- --query-- --hash--\nconst RELATIVE_REGEX = /^(\\/[^?#\\s]*)?(\\?[^#\\s]+)?(#[^\\s]+)?$/i\n\nexport class AtUri {\n hash: string\n host: AtIdentifierString\n pathname: string\n searchParams: URLSearchParams\n\n constructor(uri: string, base?: string | AtUri) {\n const parsed =\n base !== undefined\n ? typeof base === 'string'\n ? Object.assign(parse(base), parseRelative(uri))\n : Object.assign({ host: base.host }, parseRelative(uri))\n : parse(uri)\n\n ensureValidAtIdentifier(parsed.host)\n\n this.hash = parsed.hash ?? ''\n this.host = parsed.host\n this.pathname = parsed.pathname ?? ''\n this.searchParams = parsed.searchParams\n }\n\n static make(handleOrDid: string, collection?: string, rkey?: string) {\n let str = handleOrDid\n if (collection) str += '/' + collection\n if (rkey) str += '/' + rkey\n return new AtUri(str)\n }\n\n get protocol() {\n return 'at:'\n }\n\n get origin() {\n return `at://${this.host}` as const\n }\n\n get hostname() {\n return this.host\n }\n\n set hostname(v: string) {\n ensureValidAtIdentifier(v)\n this.host = v\n }\n\n get search() {\n return this.searchParams.toString()\n }\n\n set search(v: string) {\n this.searchParams = new URLSearchParams(v)\n }\n\n get collection() {\n return this.pathname.split('/').filter(Boolean)[0] || ''\n }\n\n set collection(v: string) {\n ensureValidNsid(v)\n const parts = this.pathname.split('/').filter(Boolean)\n parts[0] = v\n this.pathname = parts.join('/')\n }\n\n get rkey() {\n return this.pathname.split('/').filter(Boolean)[1] || ''\n }\n\n set rkey(v: string) {\n const parts = this.pathname.split('/').filter(Boolean)\n parts[0] ||= 'undefined'\n parts[1] = v\n this.pathname = parts.join('/')\n }\n\n get href() {\n return this.toString()\n }\n\n toString(): AtUriString {\n let path = this.pathname || '/'\n if (!path.startsWith('/')) {\n path = `/${path}`\n }\n let qs = ''\n if (this.searchParams.size) {\n qs = `?${this.searchParams.toString()}`\n }\n let hash = this.hash\n if (hash && !hash.startsWith('#')) {\n hash = `#${hash}`\n }\n return `at://${this.host}${path}${qs}${hash}` as AtUriString\n }\n}\n\nfunction parse(str: string) {\n const match = str.match(ATP_URI_REGEX) as null | {\n 0: string\n 1: string | undefined // proto\n 2: string // host\n 3: string | undefined // path\n 4: string | undefined // query\n 5: string | undefined // hash\n }\n\n if (!match) {\n throw new Error(`Invalid AT uri: ${str}`)\n }\n\n return {\n host: match[2],\n hash: match[5],\n pathname: match[3],\n searchParams: new URLSearchParams(match[4]),\n }\n}\n\nfunction parseRelative(str: string) {\n const match = str.match(RELATIVE_REGEX) as null | {\n 0: string\n 1: string | undefined // path\n 2: string | undefined // query\n 3: string | undefined // hash\n }\n\n if (!match) {\n throw new Error(`Invalid path: ${str}`)\n }\n\n return {\n hash: match[3],\n pathname: match[1],\n searchParams: new URLSearchParams(match[2]),\n }\n}\n"]}
1
+ {"version":3,"file":"aturi.js","sourceRoot":"","sources":["../src/aturi.ts"],"names":[],"mappings":"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","sourcesContent":["import { AtIdentifierString, ensureValidAtIdentifier } from './at-identifier.js'\nimport { AtUriString } from './aturi_validation.js'\nimport { DidString, ensureValidDid } from './did.js'\nimport { NsidString, ensureValidNsid } from './nsid.js'\nimport { RecordKeyString, ensureValidRecordKey } from './recordkey.js'\n\nexport * from './aturi_validation.js'\n\nexport const ATP_URI_REGEX =\n // proto- --did-------------- --name---------------- --path---- --query-- --hash--\n /^(at:\\/\\/)?((?:did:[a-z0-9:%-]+)|(?:[a-z0-9][a-z0-9.:-]*))(\\/[^?#\\s]*)?(\\?[^#\\s]+)?(#[^\\s]+)?$/i\n// --path----- --query-- --hash--\nconst RELATIVE_REGEX = /^(\\/[^?#\\s]*)?(\\?[^#\\s]+)?(#[^\\s]+)?$/i\n\nexport class AtUri {\n hash: string\n host: AtIdentifierString\n pathname: string\n searchParams: URLSearchParams\n\n constructor(uri: string, base?: string | AtUri) {\n const parsed =\n base !== undefined\n ? typeof base === 'string'\n ? Object.assign(parse(base), parseRelative(uri))\n : Object.assign({ host: base.host }, parseRelative(uri))\n : parse(uri)\n\n ensureValidAtIdentifier(parsed.host)\n\n this.hash = parsed.hash ?? ''\n this.host = parsed.host\n this.pathname = parsed.pathname ?? ''\n this.searchParams = parsed.searchParams\n }\n\n static make(handleOrDid: string, collection?: string, rkey?: string) {\n let str = handleOrDid\n if (collection) str += '/' + collection\n if (rkey) str += '/' + rkey\n return new AtUri(str)\n }\n\n get protocol() {\n return 'at:'\n }\n\n get origin() {\n return `at://${this.host}` as const\n }\n\n get did(): DidString {\n const { hostname } = this\n ensureValidDid(hostname)\n return hostname\n }\n\n get hostname() {\n return this.host\n }\n\n set hostname(v: string) {\n ensureValidAtIdentifier(v)\n this.host = v\n }\n\n get search() {\n return this.searchParams.toString()\n }\n\n set search(v: string) {\n this.searchParams = new URLSearchParams(v)\n }\n\n get collection() {\n return this.pathname.split('/').filter(Boolean)[0] || ''\n }\n\n get collectionSafe(): NsidString {\n const { collection } = this\n ensureValidNsid(collection)\n return collection\n }\n\n set collection(v: string) {\n ensureValidNsid(v)\n this.unsafelySetCollection(v)\n }\n\n unsafelySetCollection(v: string) {\n const parts = this.pathname.split('/').filter(Boolean)\n parts[0] = v\n this.pathname = parts.join('/')\n }\n\n get rkey() {\n return this.pathname.split('/').filter(Boolean)[1] || ''\n }\n\n get rkeySafe(): RecordKeyString {\n const { rkey } = this\n ensureValidRecordKey(rkey)\n return rkey\n }\n\n set rkey(v: string) {\n ensureValidRecordKey(v)\n this.unsafelySetRkey(v)\n }\n\n unsafelySetRkey(v: string) {\n const parts = this.pathname.split('/').filter(Boolean)\n parts[0] ||= 'undefined'\n parts[1] = v\n this.pathname = parts.join('/')\n }\n\n get href() {\n return this.toString()\n }\n\n toString(): AtUriString {\n let path = this.pathname || '/'\n if (!path.startsWith('/')) {\n path = `/${path}`\n }\n let qs = ''\n if (this.searchParams.size) {\n qs = `?${this.searchParams.toString()}`\n }\n let hash = this.hash\n if (hash && !hash.startsWith('#')) {\n hash = `#${hash}`\n }\n return `at://${this.host}${path}${qs}${hash}` as AtUriString\n }\n}\n\nfunction parse(str: string) {\n const match = str.match(ATP_URI_REGEX) as null | {\n 0: string\n 1: string | undefined // proto\n 2: string // host\n 3: string | undefined // path\n 4: string | undefined // query\n 5: string | undefined // hash\n }\n\n if (!match) {\n throw new Error(`Invalid AT uri: ${str}`)\n }\n\n return {\n host: match[2],\n hash: match[5],\n pathname: match[3],\n searchParams: new URLSearchParams(match[4]),\n }\n}\n\nfunction parseRelative(str: string) {\n const match = str.match(RELATIVE_REGEX) as null | {\n 0: string\n 1: string | undefined // path\n 2: string | undefined // query\n 3: string | undefined // hash\n }\n\n if (!match) {\n throw new Error(`Invalid path: ${str}`)\n }\n\n return {\n hash: match[3],\n pathname: match[1],\n searchParams: new URLSearchParams(match[2]),\n }\n}\n"]}
@@ -1,14 +1,131 @@
1
- /** An ISO 8601 formatted datetime string (YYYY-MM-DDTHH:mm:ss.sssZ) */
2
- export type DatetimeString = `${string}-${string}-${string}T${string}:${string}:${string}${'Z' | `+${string}` | `-${string}`}`;
3
- declare global {
4
- interface Date {
5
- toISOString(): `${string}-${string}-${string}T${string}:${string}:${string}Z`;
6
- }
7
- }
8
- export declare function ensureValidDatetime<I extends string>(input: I): asserts input is I & DatetimeString;
9
- export declare function isValidDatetime<I extends string>(input: I): input is I & DatetimeString;
10
- export declare function normalizeDatetime(dtStr: string): DatetimeString;
11
- export declare const normalizeDatetimeAlways: (dtStr: string) => DatetimeString;
1
+ /**
2
+ * Indicates a date or string is not a valid representation of a datetime
3
+ * according to the atproto
4
+ * {@link https://atproto.com/specs/lexicon#datetime specification}.
5
+ */
12
6
  export declare class InvalidDatetimeError extends Error {
13
7
  }
8
+ /**
9
+ * A subset of {@link DatetimeString} that represent valid datetime strings with
10
+ * the format: `YYYY-MM-DDTHH:mm:ss.sssZ`, as returned by `Date.toISOString()
11
+ * for dates between the years 0000 and 9999.
12
+ *
13
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString}
14
+ */
15
+ export type ISODatetimeString = `${string}-${string}-${string}T${string}:${string}:${string}.${string}Z`;
16
+ /**
17
+ * Represents a {@link Date} that can be safely stringified into a valid atproto
18
+ * {@link DatetimeString} using the {@link Date.toISOString toISOString()}
19
+ * method.
20
+ */
21
+ export interface AtprotoDate extends Date {
22
+ toISOString(): ISODatetimeString;
23
+ }
24
+ /**
25
+ * @see {@link AtprotoDate}
26
+ */
27
+ export declare function assertAtprotoDate(date: Date): asserts date is AtprotoDate;
28
+ /**
29
+ * @see {@link AtprotoDate}
30
+ */
31
+ export declare function asAtprotoDate(date: Date): AtprotoDate;
32
+ /**
33
+ * @see {@link AtprotoDate}
34
+ */
35
+ export declare function isAtprotoDate(date: Date): date is AtprotoDate;
36
+ /**
37
+ * @see {@link AtprotoDate}
38
+ */
39
+ export declare function ifAtprotoDate(date: Date): AtprotoDate | undefined;
40
+ /**
41
+ * Datetime strings in atproto data structures and API calls should meet the
42
+ * {@link https://ijmacd.github.io/rfc3339-iso8601/ intersecting} requirements
43
+ * of the RFC 3339, ISO 8601, and WHATWG HTML datetime standards.
44
+ *
45
+ * @note This literal template type is not accurate enough to ensure that a
46
+ * string is a valid atproto datetime. The {@link DatetimeString} validation
47
+ * functions ({@link assertDatetimeString}, {@link isDatetimeString}, etc)
48
+ * should be used to validate that a string meets the atproto datetime
49
+ * requirements, and the {@link toDatetimeString} function should be used to
50
+ * convert a {@link Date} object into a valid {@link DatetimeString} string.
51
+ *
52
+ * @example "2024-01-15T12:30:00Z"
53
+ * @example "2024-01-15T12:30:00.000Z"
54
+ * @example "2024-01-15T12:30:00+00:00"
55
+ * @example "2024-01-15T11:30:00-01:00"
56
+ * @see {@link https://atproto.com/specs/lexicon#datetime atproto Lexicon datetime format}
57
+ * @see {@link https://www.rfc-editor.org/rfc/rfc3339 RFC 3339}
58
+ * @see {@link https://www.iso.org/iso-8601-date-and-time-format.html ISO 8601}
59
+ */
60
+ export type DatetimeString = `${string}-${string}-${string}T${string}:${string}:${string}Z` | `${string}-${string}-${string}T${string}:${string}:${string}${'+' | '-'}${string}:${string}`;
61
+ /**
62
+ * Validates that a string is a valid {@link DatetimeString} format string,
63
+ * throwing an error if it is not.
64
+ *
65
+ * @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.
66
+ * @see {@link DatetimeString}
67
+ */
68
+ export declare function assertDatetimeString<I>(input: I): asserts input is I & DatetimeString;
69
+ /**
70
+ * Casts a string to a {@link DatetimeString} if it is a valid datetime format
71
+ * string, throwing an error if it is not.
72
+ *
73
+ * @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.
74
+ * @see {@link DatetimeString}
75
+ */
76
+ export declare function asDatetimeString<I>(input: I): I & DatetimeString;
77
+ /**
78
+ * Checks if a string is a valid {@link DatetimeString} format string.
79
+ *
80
+ * @see {@link DatetimeString}
81
+ */
82
+ export declare function isDatetimeString<I>(input: I): input is I & DatetimeString;
83
+ /**
84
+ * Returns the input if it is a valid {@link DatetimeString} format string, or
85
+ * `undefined` if it is not.
86
+ *
87
+ * @see {@link DatetimeString}
88
+ */
89
+ export declare function ifDatetimeString<I>(input: I): undefined | (I & DatetimeString);
90
+ /**
91
+ * Returns the current date and time as a {@link DatetimeString}.
92
+ *
93
+ * @see {@link DatetimeString}
94
+ */
95
+ export declare function currentDatetimeString(): DatetimeString;
96
+ /**
97
+ * Converts any {@link Date} into a {@link DatetimeString} if possible, throwing
98
+ * an error if the date is not a valid atproto datetime.
99
+ *
100
+ * This is short-hand for `asAtprotoDate(date).toISOString()`.
101
+ *
102
+ * @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).
103
+ * @see {@link DatetimeString}
104
+ */
105
+ export declare function toDatetimeString(date: Date): DatetimeString;
106
+ /**
107
+ * Takes a flexible datetime string and normalizes its representation.
108
+ *
109
+ * This function will work with any valid value that can be parsed as a date. It
110
+ * *additionally* is more flexible about accepting datetimes that are missing
111
+ * timezone information, and normalizing them to a valid atproto datetime.
112
+ *
113
+ * One use-case is a consistent, sortable string. Another is to work with older
114
+ * invalid createdAt datetimes.
115
+ *
116
+ * @returns ISODatetimeString - a valid atproto datetime with millisecond precision (3 sub-second digits) and UTC timezone with trailing 'Z' syntax.
117
+ * @throws InvalidDatetimeError - if the input string could not be parsed as a datetime, even with permissive parsing.
118
+ */
119
+ export declare function normalizeDatetime(dtStr: string): ISODatetimeString;
120
+ /**
121
+ * Variant of {@link normalizeDatetime} which always returns a valid datetime
122
+ * string.
123
+ *
124
+ * If a {@link InvalidDatetimeError} is encountered, returns the UNIX epoch time
125
+ * as a UTC datetime (`1970-01-01T00:00:00.000Z`).
126
+ *
127
+ * @see {@link normalizeDatetime}
128
+ */
129
+ export declare function normalizeDatetimeAlways(dtStr: string): ISODatetimeString;
130
+ export { assertDatetimeString as ensureValidDatetime, isDatetimeString as isValidDatetime, };
14
131
  //# sourceMappingURL=datetime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"datetime.d.ts","sourceRoot":"","sources":["../src/datetime.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,MAAM,MAAM,cAAc,GACxB,GAAG,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,GAAG,GAAG,GAAG,IAAI,MAAM,EAAE,GAAG,IAAI,MAAM,EAAE,EAAE,CAAA;AAGnG,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,IAAI;QACZ,WAAW,IAAI,GAAG,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,GAAG,CAAA;KAC9E;CACF;AAKD,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,EAClD,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,cAAc,CA4BrC;AAID,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAC9C,KAAK,EAAE,CAAC,GACP,KAAK,IAAI,CAAC,GAAG,cAAc,CAQ7B;AAYD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAkC/D;AAMD,eAAO,MAAM,uBAAuB,GAAI,OAAO,MAAM,KAAG,cASvD,CAAA;AAID,qBAAa,oBAAqB,SAAQ,KAAK;CAAG"}
1
+ {"version":3,"file":"datetime.d.ts","sourceRoot":"","sources":["../src/datetime.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;CAAG;AAElD;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAE3B,GAAG,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,GAAG,CAAA;AAE1E;;;;GAIG;AACH,MAAM,WAAW,WAAY,SAAQ,IAAI;IACvC,WAAW,IAAI,iBAAiB,CAAA;CACjC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,WAAW,CAKzE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,CAGrD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,IAAI,WAAW,CAE7D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,SAAS,CAEjE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,cAAc,GAEtB,GAAG,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,GAAG,GAC9D,GAAG,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAA;AAEhG;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,cAAc,CAKrC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,cAAc,CAGhE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,cAAc,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,KAAK,EAAE,CAAC,GACP,SAAS,GAAG,CAAC,CAAC,GAAG,cAAc,CAAC,CAElC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,cAAc,CAEtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,CAE3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAkBlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAMxE;AAGD,OAAO,EACL,oBAAoB,IAAI,mBAAmB,EAC3C,gBAAgB,IAAI,eAAe,GACpC,CAAA"}
package/dist/datetime.js CHANGED
@@ -1,105 +1,234 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InvalidDatetimeError = exports.normalizeDatetimeAlways = void 0;
4
- exports.ensureValidDatetime = ensureValidDatetime;
5
- exports.isValidDatetime = isValidDatetime;
3
+ exports.InvalidDatetimeError = void 0;
4
+ exports.assertAtprotoDate = assertAtprotoDate;
5
+ exports.asAtprotoDate = asAtprotoDate;
6
+ exports.isAtprotoDate = isAtprotoDate;
7
+ exports.ifAtprotoDate = ifAtprotoDate;
8
+ exports.assertDatetimeString = assertDatetimeString;
9
+ exports.ensureValidDatetime = assertDatetimeString;
10
+ exports.asDatetimeString = asDatetimeString;
11
+ exports.isDatetimeString = isDatetimeString;
12
+ exports.isValidDatetime = isDatetimeString;
13
+ exports.ifDatetimeString = ifDatetimeString;
14
+ exports.currentDatetimeString = currentDatetimeString;
15
+ exports.toDatetimeString = toDatetimeString;
6
16
  exports.normalizeDatetime = normalizeDatetime;
7
- /* Validates datetime string against atproto Lexicon 'datetime' format.
8
- * Syntax is described at: https://atproto.com/specs/lexicon#datetime
17
+ exports.normalizeDatetimeAlways = normalizeDatetimeAlways;
18
+ /**
19
+ * Indicates a date or string is not a valid representation of a datetime
20
+ * according to the atproto
21
+ * {@link https://atproto.com/specs/lexicon#datetime specification}.
9
22
  */
10
- function ensureValidDatetime(input) {
11
- const date = new Date(input);
12
- // must parse as ISO 8601; this also verifies semantics like month is not 13 or 00
13
- if (isNaN(date.getTime())) {
14
- throw new InvalidDatetimeError('datetime did not parse as ISO 8601');
15
- }
16
- if (date.toISOString().startsWith('-')) {
17
- throw new InvalidDatetimeError('datetime normalized to a negative time');
18
- }
19
- // regex and other checks for RFC-3339
20
- if (!/^[0-9]{4}-[01][0-9]-[0-3][0-9]T[0-2][0-9]:[0-6][0-9]:[0-6][0-9](.[0-9]{1,20})?(Z|([+-][0-2][0-9]:[0-5][0-9]))$/.test(input)) {
21
- throw new InvalidDatetimeError("datetime didn't validate via regex");
22
- }
23
- if (input.length > 64) {
24
- throw new InvalidDatetimeError('datetime is too long (64 chars max)');
25
- }
26
- if (input.endsWith('-00:00')) {
27
- throw new InvalidDatetimeError('datetime can not use "-00:00" for UTC timezone');
28
- }
29
- if (input.startsWith('000')) {
30
- throw new InvalidDatetimeError('datetime so close to year zero not allowed');
31
- }
23
+ class InvalidDatetimeError extends Error {
32
24
  }
33
- /* Same logic as ensureValidDatetime(), but returns a boolean instead of throwing an exception.
25
+ exports.InvalidDatetimeError = InvalidDatetimeError;
26
+ /**
27
+ * @see {@link AtprotoDate}
34
28
  */
35
- function isValidDatetime(input) {
36
- try {
37
- ensureValidDatetime(input);
29
+ function assertAtprotoDate(date) {
30
+ const res = parseDate(date);
31
+ if (!res.success) {
32
+ throw new InvalidDatetimeError(res.message);
38
33
  }
39
- catch (err) {
40
- return false;
34
+ }
35
+ /**
36
+ * @see {@link AtprotoDate}
37
+ */
38
+ function asAtprotoDate(date) {
39
+ assertAtprotoDate(date);
40
+ return date;
41
+ }
42
+ /**
43
+ * @see {@link AtprotoDate}
44
+ */
45
+ function isAtprotoDate(date) {
46
+ return parseDate(date).success;
47
+ }
48
+ /**
49
+ * @see {@link AtprotoDate}
50
+ */
51
+ function ifAtprotoDate(date) {
52
+ return isAtprotoDate(date) ? date : undefined;
53
+ }
54
+ /**
55
+ * Validates that a string is a valid {@link DatetimeString} format string,
56
+ * throwing an error if it is not.
57
+ *
58
+ * @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.
59
+ * @see {@link DatetimeString}
60
+ */
61
+ function assertDatetimeString(input) {
62
+ const result = parseString(input);
63
+ if (!result.success) {
64
+ throw new InvalidDatetimeError(result.message);
41
65
  }
42
- return true;
43
66
  }
44
- /* Takes a flexible datetime string and normalizes representation.
67
+ /**
68
+ * Casts a string to a {@link DatetimeString} if it is a valid datetime format
69
+ * string, throwing an error if it is not.
70
+ *
71
+ * @throws InvalidDatetimeError if the input string does not meet the atproto 'datetime' format requirements.
72
+ * @see {@link DatetimeString}
73
+ */
74
+ function asDatetimeString(input) {
75
+ assertDatetimeString(input);
76
+ return input;
77
+ }
78
+ /**
79
+ * Checks if a string is a valid {@link DatetimeString} format string.
80
+ *
81
+ * @see {@link DatetimeString}
82
+ */
83
+ function isDatetimeString(input) {
84
+ return parseString(input).success;
85
+ }
86
+ /**
87
+ * Returns the input if it is a valid {@link DatetimeString} format string, or
88
+ * `undefined` if it is not.
45
89
  *
46
- * This function will work with any valid atproto datetime (eg, anything which isValidDatetime() is true for). It *additionally* is more flexible about accepting datetimes that don't comply to RFC 3339, or are missing timezone information, and normalizing them to a valid datetime.
90
+ * @see {@link DatetimeString}
91
+ */
92
+ function ifDatetimeString(input) {
93
+ return isDatetimeString(input) ? input : undefined;
94
+ }
95
+ /**
96
+ * Returns the current date and time as a {@link DatetimeString}.
47
97
  *
48
- * One use-case is a consistent, sortable string. Another is to work with older invalid createdAt datetimes.
98
+ * @see {@link DatetimeString}
99
+ */
100
+ function currentDatetimeString() {
101
+ return toDatetimeString(new Date());
102
+ }
103
+ /**
104
+ * Converts any {@link Date} into a {@link DatetimeString} if possible, throwing
105
+ * an error if the date is not a valid atproto datetime.
49
106
  *
50
- * Successful output will be a valid atproto datetime with millisecond precision (3 sub-second digits) and UTC timezone with trailing 'Z' syntax. Throws `InvalidDatetimeError` if the input string could not be parsed as a datetime, even with permissive parsing.
107
+ * This is short-hand for `asAtprotoDate(date).toISOString()`.
51
108
  *
52
- * Expected output format: YYYY-MM-DDTHH:mm:ss.sssZ
109
+ * @throws InvalidDatetimeError if the input date is not a valid atproto datetime (eg, it is too far in the future or past, or it normalizes to a negative year).
110
+ * @see {@link DatetimeString}
111
+ */
112
+ function toDatetimeString(date) {
113
+ return asAtprotoDate(date).toISOString();
114
+ }
115
+ /**
116
+ * Takes a flexible datetime string and normalizes its representation.
117
+ *
118
+ * This function will work with any valid value that can be parsed as a date. It
119
+ * *additionally* is more flexible about accepting datetimes that are missing
120
+ * timezone information, and normalizing them to a valid atproto datetime.
121
+ *
122
+ * One use-case is a consistent, sortable string. Another is to work with older
123
+ * invalid createdAt datetimes.
124
+ *
125
+ * @returns ISODatetimeString - a valid atproto datetime with millisecond precision (3 sub-second digits) and UTC timezone with trailing 'Z' syntax.
126
+ * @throws InvalidDatetimeError - if the input string could not be parsed as a datetime, even with permissive parsing.
53
127
  */
54
128
  function normalizeDatetime(dtStr) {
55
- if (isValidDatetime(dtStr)) {
56
- const outStr = new Date(dtStr).toISOString();
57
- if (isValidDatetime(outStr)) {
58
- return outStr;
59
- }
60
- }
61
- // check if this permissive datetime is missing a timezone
62
- if (!/.*(([+-]\d\d:?\d\d)|[a-zA-Z])$/.test(dtStr)) {
63
- const date = new Date(dtStr + 'Z');
64
- if (!isNaN(date.getTime())) {
65
- const tzStr = date.toISOString();
66
- if (isValidDatetime(tzStr)) {
67
- return tzStr;
68
- }
69
- }
70
- }
71
- // finally try parsing as simple datetime
129
+ // Parse the string as is
72
130
  const date = new Date(dtStr);
73
- if (isNaN(date.getTime())) {
74
- throw new InvalidDatetimeError('datetime did not parse as any timestamp format');
75
- }
76
- const isoStr = date.toISOString();
77
- if (isValidDatetime(isoStr)) {
78
- return isoStr;
131
+ if (isAtprotoDate(date)) {
132
+ return date.toISOString();
79
133
  }
80
- else {
81
- throw new InvalidDatetimeError('datetime normalized to invalid timestamp string');
134
+ // if dtStr is not a valid date, try parsing again with a timezone
135
+ if (isNaN(date.getTime()) && !/.*(([+-]\d\d:?\d\d)|[a-zA-Z])$/.test(dtStr)) {
136
+ const date = new Date(`${dtStr}Z`);
137
+ if (isAtprotoDate(date)) {
138
+ return date.toISOString();
139
+ }
82
140
  }
141
+ throw new InvalidDatetimeError('datetime did not parse as any timestamp format');
83
142
  }
84
- /* Variant of normalizeDatetime() which always returns a valid datetime strings.
143
+ /**
144
+ * Variant of {@link normalizeDatetime} which always returns a valid datetime
145
+ * string.
146
+ *
147
+ * If a {@link InvalidDatetimeError} is encountered, returns the UNIX epoch time
148
+ * as a UTC datetime (`1970-01-01T00:00:00.000Z`).
85
149
  *
86
- * If a InvalidDatetimeError is encountered, returns the UNIX epoch time as a UTC datetime (1970-01-01T00:00:00.000Z).
150
+ * @see {@link normalizeDatetime}
87
151
  */
88
- const normalizeDatetimeAlways = (dtStr) => {
152
+ function normalizeDatetimeAlways(dtStr) {
89
153
  try {
90
154
  return normalizeDatetime(dtStr);
91
155
  }
92
156
  catch (err) {
93
- if (err instanceof InvalidDatetimeError) {
94
- return new Date(0).toISOString();
95
- }
96
- throw err;
157
+ return '1970-01-01T00:00:00.000Z';
97
158
  }
98
- };
99
- exports.normalizeDatetimeAlways = normalizeDatetimeAlways;
100
- /* Indicates a datetime string did not pass full atproto Lexicon datetime string format checks.
159
+ }
160
+ const failure = (m) => ({ success: false, message: m });
161
+ const success = (v) => ({ success: true, value: v });
162
+ /**
163
+ * @see {@link https://www.rfc-editor.org/rfc/rfc3339#section-5.6 Internet Date/Time Format}
164
+ *
165
+ * @example
166
+ * ```abnf
167
+ * date-fullyear = 4DIGIT
168
+ * date-month = 2DIGIT ; 01-12
169
+ * date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
170
+ * ; month/year
171
+ * time-hour = 2DIGIT ; 00-23
172
+ * time-minute = 2DIGIT ; 00-59
173
+ * time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
174
+ * ; rules
175
+ * time-secfrac = "." 1*DIGIT
176
+ * time-numoffset = ("+" / "-") time-hour ":" time-minute
177
+ * time-offset = "Z" / time-numoffset
178
+ * partial-time = time-hour ":" time-minute ":" time-second
179
+ * [time-secfrac]
180
+ * full-date = date-fullyear "-" date-month "-" date-mday
181
+ * full-time = partial-time time-offset
182
+ * date-time = full-date "T" full-time
183
+ * ```
101
184
  */
102
- class InvalidDatetimeError extends Error {
185
+ const DATETIME_REGEX = /^(?<full_year>[0-9]{4})-(?<date_month>0[1-9]|1[012])-(?<date_mday>[0-2][0-9]|3[01])T(?<time_hour>[0-1][0-9]|2[0-3]):(?<time_minute>[0-5][0-9]):(?<time_second>[0-5][0-9]|60)(?<time_secfrac>\.[0-9]+)?(?<time_offset>Z|(?<time_numoffset>[+-](?:[0-1][0-9]|2[0-3]):[0-5][0-9]))$/;
186
+ /**
187
+ * Validates that the input is a datetime string according to atproto Lexicon
188
+ * rules, and parses it into a Date object.
189
+ */
190
+ function parseString(input) {
191
+ // @NOTE Performing cheap tests first
192
+ if (typeof input !== 'string') {
193
+ return failure('datetime must be a string');
194
+ }
195
+ if (input.length > 64) {
196
+ return failure('datetime is too long (64 chars max)');
197
+ }
198
+ if (input.endsWith('-00:00')) {
199
+ return failure('datetime can not use "-00:00" for UTC timezone');
200
+ }
201
+ if (!DATETIME_REGEX.test(input)) {
202
+ return failure("datetime is not in a valid format (must match RFC 3339 & ISO 8601 with 'Z' or ±hh:mm timezone)");
203
+ }
204
+ // must parse as ISO 8601; this also verifies semantics like leap seconds and
205
+ // correct number of days in month, which the regex does not check for
206
+ const date = new Date(input);
207
+ return parseDate(date);
208
+ }
209
+ /**
210
+ * Ensures that a Date object represents a valid datetime according to atproto
211
+ * Lexicon rules. This ensures that `date.toISOString()` will produce a valid
212
+ * datetime string that can be used where {@link DatetimeString} is expected.
213
+ */
214
+ function parseDate(date) {
215
+ const fullYear = date.getUTCFullYear();
216
+ // Ensures that the date is valid. We could check isNaN(date.getTime()) here
217
+ // but since we'll check the year anyway, we just use that for the validity
218
+ // check since an invalid date will have NaN year.
219
+ if (Number.isNaN(fullYear)) {
220
+ return failure('datetime did not parse as ISO 8601');
221
+ }
222
+ // Ensure that the ISO string representation does not start with ±YYYYYY
223
+ if (fullYear < 0) {
224
+ return failure('datetime normalized to a negative time');
225
+ }
226
+ if (fullYear > 9999) {
227
+ return failure('datetime year is too far in the future');
228
+ }
229
+ if (fullYear < 10) {
230
+ return failure('datetime so close to year zero not allowed');
231
+ }
232
+ return success(date);
103
233
  }
104
- exports.InvalidDatetimeError = InvalidDatetimeError;
105
234
  //# sourceMappingURL=datetime.js.map