@atproto/syntax 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/at-identifier.d.ts +2 -2
  3. package/dist/at-identifier.d.ts.map +1 -1
  4. package/dist/at-identifier.js.map +1 -1
  5. package/dist/aturi.d.ts +5 -5
  6. package/dist/aturi.d.ts.map +1 -1
  7. package/dist/aturi.js +1 -1
  8. package/dist/aturi.js.map +1 -1
  9. package/dist/aturi_validation.d.ts +3 -3
  10. package/dist/aturi_validation.d.ts.map +1 -1
  11. package/dist/aturi_validation.js.map +1 -1
  12. package/dist/language.js +1 -1
  13. package/dist/language.js.map +1 -1
  14. package/dist/nsid.d.ts +1 -1
  15. package/dist/nsid.d.ts.map +1 -1
  16. package/dist/nsid.js.map +1 -1
  17. package/package.json +15 -14
  18. package/benchmark.cjs +0 -208
  19. package/src/at-identifier.ts +0 -104
  20. package/src/aturi.ts +0 -197
  21. package/src/aturi_validation.ts +0 -321
  22. package/src/datetime.ts +0 -369
  23. package/src/did.ts +0 -71
  24. package/src/handle.ts +0 -128
  25. package/src/index.ts +0 -10
  26. package/src/language.ts +0 -39
  27. package/src/lib/result.ts +0 -11
  28. package/src/nsid.ts +0 -182
  29. package/src/recordkey.ts +0 -51
  30. package/src/tid.ts +0 -22
  31. package/src/uri.ts +0 -5
  32. package/tests/aturi-string.test.ts +0 -223
  33. package/tests/aturi.test.ts +0 -428
  34. package/tests/datetime.test.ts +0 -280
  35. package/tests/did.test.ts +0 -104
  36. package/tests/handle.test.ts +0 -239
  37. package/tests/language.test.ts +0 -88
  38. package/tests/nsid.test.ts +0 -174
  39. package/tests/recordkey.test.ts +0 -43
  40. package/tests/tid.test.ts +0 -43
  41. package/tsconfig.build.json +0 -12
  42. package/tsconfig.build.tsbuildinfo +0 -1
  43. package/tsconfig.json +0 -7
  44. package/tsconfig.tests.json +0 -8
  45. package/vitest.config.ts +0 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atproto/syntax
2
2
 
3
+ ## 0.6.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#5169](https://github.com/bluesky-social/atproto/pull/5169) [`28a0b58`](https://github.com/bluesky-social/atproto/commit/28a0b588147863eaef948cd2bb8fc0f19d08cda9) Thanks [@ryanda9910](https://github.com/ryanda9910)! - `isValidLanguage` and `parseLanguageString` now accept BCP 47 tags whose
8
+ private-use subtag uses an uppercase `X` (for example `X-fr-CH` or `de-X-foo`).
9
+ Per RFC 5646 §2.1.1 (and RFC 5234 §2.3, which makes ABNF quoted-string literals
10
+ case-insensitive), private-use subtags are case-insensitive, so these tags are
11
+ well-formed. Previously only the lowercase `x` form was matched. Lowercase tags
12
+ behave exactly as before.
13
+
14
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update TypeScript build to rely on references to composite internal projects
15
+
16
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Build using `verbatimModuleSyntax`, reducing runtime import graph
17
+
18
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Bundle only necessary files in the NPM tarball, including the `CHANGELOG.md` and `README.md` files (if present).
19
+
3
20
  ## 0.6.3
4
21
 
5
22
  ### Patch Changes
@@ -1,5 +1,5 @@
1
- import { DidString } from './did.js';
2
- import { HandleString } from './handle.js';
1
+ import type { DidString } from './did.js';
2
+ import type { HandleString } from './handle.js';
3
3
  /**
4
4
  * An "at-identifier" string - either a {@link DidString} or a {@link HandleString}
5
5
  *
@@ -1 +1 @@
1
- {"version":3,"file":"at-identifier.d.ts","sourceRoot":"","sources":["../src/at-identifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAmC,MAAM,UAAU,CAAA;AACrE,OAAO,EACL,YAAY,EAIb,MAAM,aAAa,CAAA;AAEpB;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,YAAY,CAAA;AAEzD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,kBAAkB,GAAG,EAAE,IAAI,YAAY,CAE7E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,kBAAkB,GAAG,EAAE,IAAI,SAAS,CAEvE;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,kBAAkB,CAYzC;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAGxE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,KAAK,EAAE,CAAC,GACP,KAAK,IAAI,CAAC,GAAG,kBAAkB,CAQjC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,KAAK,EAAE,CAAC,GACP,SAAS,GAAG,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAEtC;AAGD,OAAO,EACL,wBAAwB,IAAI,uBAAuB,EACnD,oBAAoB,IAAI,mBAAmB,GAC5C,CAAA"}
1
+ {"version":3,"file":"at-identifier.d.ts","sourceRoot":"","sources":["../src/at-identifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAO/C;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,YAAY,CAAA;AAEzD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,kBAAkB,GAAG,EAAE,IAAI,YAAY,CAE7E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,kBAAkB,GAAG,EAAE,IAAI,SAAS,CAEvE;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,kBAAkB,CAYzC;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAGxE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,KAAK,EAAE,CAAC,GACP,KAAK,IAAI,CAAC,GAAG,kBAAkB,CAQjC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,KAAK,EAAE,CAAC,GACP,SAAS,GAAG,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAEtC;AAGD,OAAO,EACL,wBAAwB,IAAI,uBAAuB,EACnD,oBAAoB,IAAI,mBAAmB,GAC5C,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"at-identifier.js","sourceRoot":"","sources":["../src/at-identifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,mBAAmB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACrE,OAAO,EAEL,kBAAkB,EAClB,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAA;AASpB;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAsB;IACvD,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC,CAAA;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAAsB;IACpD,OAAO,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;AAC9B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,KAAQ;IAER,IAAI,CAAC;QACH,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxC,MAAM,IAAI,SAAS,CAAC,uCAAuC,CAAC,CAAA;QAC9D,CAAC;aAAM,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,mBAAmB,CAAC,KAAK,CAAC,CAAA;QAC5B,CAAC;aAAM,CAAC;YACN,sBAAsB,CAAC,KAAK,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IAClE,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAI,KAAQ;IAC9C,wBAAwB,CAAC,KAAK,CAAC,CAAA;IAC/B,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAQ;IAER,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,KAAK,CAAA;IACd,CAAC;SAAM,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAA;IAC1B,CAAC;SAAM,CAAC;QACN,OAAO,aAAa,CAAC,KAAK,CAAC,CAAA;IAC7B,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAQ;IAER,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACxD,CAAC;AAED,+CAA+C;AAC/C,OAAO,EACL,wBAAwB,IAAI,uBAAuB,EACnD,oBAAoB,IAAI,mBAAmB,GAC5C,CAAA","sourcesContent":["import { DidString, ensureValidDidRegex, isValidDid } from './did.js'\nimport {\n HandleString,\n InvalidHandleError,\n ensureValidHandleRegex,\n isValidHandle,\n} from './handle.js'\n\n/**\n * An \"at-identifier\" string - either a {@link DidString} or a {@link HandleString}\n *\n * @example `\"did:plc:1234...\"`, `\"did:web:example.com\"` or `\"alice.bsky.social\"`\n */\nexport type AtIdentifierString = DidString | HandleString\n\n/**\n * Discriminates {@link HandleString} from a valid {@link AtIdentifierString}.\n *\n * @return `true` if the identifier is a handle, `false` otherwise\n */\nexport function isHandleIdentifier(id: AtIdentifierString): id is HandleString {\n return !isDidIdentifier(id)\n}\n\n/**\n * Discriminates {@link DidString} from a valid {@link AtIdentifierString}.\n *\n * @return `true` if the identifier is a DID, `false` otherwise\n */\nexport function isDidIdentifier(id: AtIdentifierString): id is DidString {\n return id.startsWith('did:')\n}\n\n/**\n * Validates that a string is a valid {@link AtIdentifierString} format string,\n * throwing an error if it is not.\n *\n * @throws InvalidHandleError if the input string does not meet the atproto 'datetime' format requirements.\n * @see {@link AtIdentifierString}\n */\nexport function assertAtIdentifierString<I>(\n input: I,\n): asserts input is I & AtIdentifierString {\n try {\n if (!input || typeof input !== 'string') {\n throw new TypeError('Identifier must be a non-empty string')\n } else if (input.startsWith('did:')) {\n ensureValidDidRegex(input)\n } else {\n ensureValidHandleRegex(input)\n }\n } catch (cause) {\n throw new InvalidHandleError('Invalid DID or handle', { cause })\n }\n}\n\n/**\n * Casts a string to a {@link AtIdentifierString} if it is a valid at-identifier\n * string, throwing an error if it is not.\n *\n * @throws InvalidHandleError if the input string does not meet the atproto 'at-identifier' format requirements.\n * @see {@link AtIdentifierString}\n */\nexport function asAtIdentifierString<I>(input: I): I & AtIdentifierString {\n assertAtIdentifierString(input)\n return input\n}\n\n/**\n * Type guard that checks if a value is a valid AT identifier (DID or handle).\n *\n * @param value - The value to check\n * @returns `true` if the value is a valid AT identifier\n * @see {@link AtIdentifierString}\n */\nexport function isAtIdentifierString<I>(\n input: I,\n): input is I & AtIdentifierString {\n if (!input || typeof input !== 'string') {\n return false\n } else if (input.startsWith('did:')) {\n return isValidDid(input)\n } else {\n return isValidHandle(input)\n }\n}\n\n/**\n * Returns the input if it is a valid {@link AtIdentifierString} format string, or\n * `undefined` if it is not.\n *\n * @see {@link AtIdentifierString}\n */\nexport function ifAtIdentifierString<I>(\n input: I,\n): undefined | (I & AtIdentifierString) {\n return isAtIdentifierString(input) ? input : undefined\n}\n\n// Legacy exports (should we deprecate these ?)\nexport {\n assertAtIdentifierString as ensureValidAtIdentifier,\n isAtIdentifierString as isValidAtIdentifier,\n}\n"]}
1
+ {"version":3,"file":"at-identifier.js","sourceRoot":"","sources":["../src/at-identifier.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1D,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,aAAa,GACd,MAAM,aAAa,CAAA;AASpB;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAsB;IACvD,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC,CAAA;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAAsB;IACpD,OAAO,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;AAC9B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,KAAQ;IAER,IAAI,CAAC;QACH,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxC,MAAM,IAAI,SAAS,CAAC,uCAAuC,CAAC,CAAA;QAC9D,CAAC;aAAM,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,mBAAmB,CAAC,KAAK,CAAC,CAAA;QAC5B,CAAC;aAAM,CAAC;YACN,sBAAsB,CAAC,KAAK,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IAClE,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAI,KAAQ;IAC9C,wBAAwB,CAAC,KAAK,CAAC,CAAA;IAC/B,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAQ;IAER,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,KAAK,CAAA;IACd,CAAC;SAAM,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAA;IAC1B,CAAC;SAAM,CAAC;QACN,OAAO,aAAa,CAAC,KAAK,CAAC,CAAA;IAC7B,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAQ;IAER,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACxD,CAAC;AAED,+CAA+C;AAC/C,OAAO,EACL,wBAAwB,IAAI,uBAAuB,EACnD,oBAAoB,IAAI,mBAAmB,GAC5C,CAAA","sourcesContent":["import type { DidString } from './did.js'\nimport { ensureValidDidRegex, isValidDid } from './did.js'\nimport type { HandleString } from './handle.js'\nimport {\n InvalidHandleError,\n ensureValidHandleRegex,\n isValidHandle,\n} from './handle.js'\n\n/**\n * An \"at-identifier\" string - either a {@link DidString} or a {@link HandleString}\n *\n * @example `\"did:plc:1234...\"`, `\"did:web:example.com\"` or `\"alice.bsky.social\"`\n */\nexport type AtIdentifierString = DidString | HandleString\n\n/**\n * Discriminates {@link HandleString} from a valid {@link AtIdentifierString}.\n *\n * @return `true` if the identifier is a handle, `false` otherwise\n */\nexport function isHandleIdentifier(id: AtIdentifierString): id is HandleString {\n return !isDidIdentifier(id)\n}\n\n/**\n * Discriminates {@link DidString} from a valid {@link AtIdentifierString}.\n *\n * @return `true` if the identifier is a DID, `false` otherwise\n */\nexport function isDidIdentifier(id: AtIdentifierString): id is DidString {\n return id.startsWith('did:')\n}\n\n/**\n * Validates that a string is a valid {@link AtIdentifierString} format string,\n * throwing an error if it is not.\n *\n * @throws InvalidHandleError if the input string does not meet the atproto 'datetime' format requirements.\n * @see {@link AtIdentifierString}\n */\nexport function assertAtIdentifierString<I>(\n input: I,\n): asserts input is I & AtIdentifierString {\n try {\n if (!input || typeof input !== 'string') {\n throw new TypeError('Identifier must be a non-empty string')\n } else if (input.startsWith('did:')) {\n ensureValidDidRegex(input)\n } else {\n ensureValidHandleRegex(input)\n }\n } catch (cause) {\n throw new InvalidHandleError('Invalid DID or handle', { cause })\n }\n}\n\n/**\n * Casts a string to a {@link AtIdentifierString} if it is a valid at-identifier\n * string, throwing an error if it is not.\n *\n * @throws InvalidHandleError if the input string does not meet the atproto 'at-identifier' format requirements.\n * @see {@link AtIdentifierString}\n */\nexport function asAtIdentifierString<I>(input: I): I & AtIdentifierString {\n assertAtIdentifierString(input)\n return input\n}\n\n/**\n * Type guard that checks if a value is a valid AT identifier (DID or handle).\n *\n * @param value - The value to check\n * @returns `true` if the value is a valid AT identifier\n * @see {@link AtIdentifierString}\n */\nexport function isAtIdentifierString<I>(\n input: I,\n): input is I & AtIdentifierString {\n if (!input || typeof input !== 'string') {\n return false\n } else if (input.startsWith('did:')) {\n return isValidDid(input)\n } else {\n return isValidHandle(input)\n }\n}\n\n/**\n * Returns the input if it is a valid {@link AtIdentifierString} format string, or\n * `undefined` if it is not.\n *\n * @see {@link AtIdentifierString}\n */\nexport function ifAtIdentifierString<I>(\n input: I,\n): undefined | (I & AtIdentifierString) {\n return isAtIdentifierString(input) ? input : undefined\n}\n\n// Legacy exports (should we deprecate these ?)\nexport {\n assertAtIdentifierString as ensureValidAtIdentifier,\n isAtIdentifierString as isValidAtIdentifier,\n}\n"]}
package/dist/aturi.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { AtIdentifierString } from './at-identifier.js';
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';
1
+ import type { AtIdentifierString } from './at-identifier.js';
2
+ import type { AtUriString } from './aturi_validation.js';
3
+ import type { DidString } from './did.js';
4
+ import type { NsidString } from './nsid.js';
5
+ import type { RecordKeyString } from './recordkey.js';
6
6
  export * from './aturi_validation.js';
7
7
  export type { AtIdentifierString, AtUriString, DidString, NsidString, RecordKeyString, };
8
8
  export declare const ATP_URI_REGEX: RegExp;
@@ -1 +1 @@
1
- {"version":3,"file":"aturi.d.ts","sourceRoot":"","sources":["../src/aturi.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAGnB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,SAAS,EAAmB,MAAM,UAAU,CAAA;AACrD,OAAO,EAAE,UAAU,EAAmB,MAAM,WAAW,CAAA;AACvD,OAAO,EAAE,eAAe,EAAwB,MAAM,gBAAgB,CAAA;AAEtE,cAAc,uBAAuB,CAAA;AAGrC,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,SAAS,EACT,UAAU,EACV,eAAe,GAChB,CAAA;AAED,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;IAE7B,YAAY,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,EAc7C;IAED,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,SAKlE;IAED,IAAI,QAAQ,WAEX;IAED,IAAI,MAAM,gEAET;IAED,IAAI,GAAG,IAAI,SAAS,CAInB;IAED,IAAI,QAAQ,IAAI,kBAAkB,CAEjC;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,QAI9B;IAED,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,QAKxB;IAED,IAAI,IAAI,gBAEP;IAED,QAAQ,IAAI,WAAW,CAoBtB;CACF"}
1
+ {"version":3,"file":"aturi.d.ts","sourceRoot":"","sources":["../src/aturi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAGrD,cAAc,uBAAuB,CAAA;AAGrC,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,SAAS,EACT,UAAU,EACV,eAAe,GAChB,CAAA;AAED,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;IAE7B,YAAY,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,EAc7C;IAED,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,SAKlE;IAED,IAAI,QAAQ,WAEX;IAED,IAAI,MAAM,gEAET;IAED,IAAI,GAAG,IAAI,SAAS,CAInB;IAED,IAAI,QAAQ,IAAI,kBAAkB,CAEjC;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,QAI9B;IAED,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,QAKxB;IAED,IAAI,IAAI,gBAEP;IAED,QAAQ,IAAI,WAAW,CAoBtB;CACF"}
package/dist/aturi.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ensureValidAtIdentifier, isDidIdentifier, } from './at-identifier.js';
1
+ import { ensureValidAtIdentifier, isDidIdentifier } from './at-identifier.js';
2
2
  import { InvalidDidError } from './did.js';
3
3
  import { ensureValidNsid } from './nsid.js';
4
4
  import { ensureValidRecordKey } from './recordkey.js';
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 {\n AtIdentifierString,\n ensureValidAtIdentifier,\n isDidIdentifier,\n} from './at-identifier.js'\nimport { AtUriString } from './aturi_validation.js'\nimport { DidString, InvalidDidError } from './did.js'\nimport { NsidString, ensureValidNsid } from './nsid.js'\nimport { RecordKeyString, ensureValidRecordKey } from './recordkey.js'\n\nexport * from './aturi_validation.js'\n\n// Re-export types used in public interface\nexport type {\n AtIdentifierString,\n AtUriString,\n DidString,\n NsidString,\n RecordKeyString,\n}\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 { host } = this\n if (isDidIdentifier(host)) return host\n throw new InvalidDidError(`AtUri \"${this}\" does not have a DID hostname`)\n }\n\n get hostname(): AtIdentifierString {\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 pathname = this.pathname\n if (pathname && !pathname.startsWith('/')) {\n pathname = `/${pathname}`\n }\n while (pathname.endsWith('/')) {\n pathname = pathname.slice(0, -1)\n }\n let qs = ''\n if (this.searchParams.size) {\n qs = `?${this.searchParams.toString()}`\n }\n // @NOTE We keep the hash as-is, even if it doesn't start with a '/'.\n let fragment = this.hash\n if (fragment === '#') {\n fragment = ''\n } else if (fragment && !fragment.startsWith('#')) {\n fragment = `#${fragment}`\n }\n return `at://${this.host}${pathname}${qs}${fragment}` 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 { ensureValidAtIdentifier, isDidIdentifier } from './at-identifier.js'\nimport type { AtIdentifierString } from './at-identifier.js'\nimport type { AtUriString } from './aturi_validation.js'\nimport type { DidString } from './did.js'\nimport { InvalidDidError } from './did.js'\nimport type { NsidString } from './nsid.js'\nimport { ensureValidNsid } from './nsid.js'\nimport type { RecordKeyString } from './recordkey.js'\nimport { ensureValidRecordKey } from './recordkey.js'\n\nexport * from './aturi_validation.js'\n\n// Re-export types used in public interface\nexport type {\n AtIdentifierString,\n AtUriString,\n DidString,\n NsidString,\n RecordKeyString,\n}\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 { host } = this\n if (isDidIdentifier(host)) return host\n throw new InvalidDidError(`AtUri \"${this}\" does not have a DID hostname`)\n }\n\n get hostname(): AtIdentifierString {\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 pathname = this.pathname\n if (pathname && !pathname.startsWith('/')) {\n pathname = `/${pathname}`\n }\n while (pathname.endsWith('/')) {\n pathname = pathname.slice(0, -1)\n }\n let qs = ''\n if (this.searchParams.size) {\n qs = `?${this.searchParams.toString()}`\n }\n // @NOTE We keep the hash as-is, even if it doesn't start with a '/'.\n let fragment = this.hash\n if (fragment === '#') {\n fragment = ''\n } else if (fragment && !fragment.startsWith('#')) {\n fragment = `#${fragment}`\n }\n return `at://${this.host}${pathname}${qs}${fragment}` 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,6 +1,6 @@
1
- import { AtIdentifierString } from './at-identifier.js';
2
- import { Result } from './lib/result.js';
3
- import { NsidString } from './nsid.js';
1
+ import type { AtIdentifierString } from './at-identifier.js';
2
+ import type { Result } from './lib/result.js';
3
+ import type { NsidString } from './nsid.js';
4
4
  export type AtUriStringBase = `at://${AtIdentifierString}` | `at://${AtIdentifierString}/${NsidString}` | `at://${AtIdentifierString}/${NsidString}/${string}`;
5
5
  export type AtUriStringFragment = `#/${string}`;
6
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"aturi_validation.d.ts","sourceRoot":"","sources":["../src/aturi_validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAwB,MAAM,oBAAoB,CAAA;AAC7E,OAAO,EAAE,MAAM,EAAoB,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,UAAU,EAAe,MAAM,WAAW,CAAA;AAGnD,MAAM,MAAM,eAAe,GACvB,QAAQ,kBAAkB,EAAE,GAC5B,QAAQ,kBAAkB,IAAI,UAAU,EAAE,GAC1C,QAAQ,kBAAkB,IAAI,UAAU,IAAI,MAAM,EAAE,CAAA;AAExD,MAAM,MAAM,mBAAmB,GAAG,KAAK,MAAM,EAAE,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,MAAM,WAAW,GACnB,eAAe,GACf,GAAG,eAAe,GAAG,mBAAmB,EAAE,CAAA;AAE9C;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,GAClD,KAAK,IAAI,CAAC,GAAG,WAAW,CAE1B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,GAClD,SAAS,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC,CAE/B;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,uBAAuB,GAChC,CAAC,GAAG,WAAW,CAGjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,WAAW,CASlC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,WAAW,CAElC;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,WAAW,CAElC;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,WAAW,CAElE;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,kBAAkB,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,CACA;IAAE,UAAU,CAAC,EAAE,UAAU,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,GAC7C;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAC5C,CAAA;AAMD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,uBAAuB,GAChC,MAAM,CAAC,UAAU,CAAC,CAsFpB"}
1
+ {"version":3,"file":"aturi_validation.d.ts","sourceRoot":"","sources":["../src/aturi_validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAE7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAI3C,MAAM,MAAM,eAAe,GACvB,QAAQ,kBAAkB,EAAE,GAC5B,QAAQ,kBAAkB,IAAI,UAAU,EAAE,GAC1C,QAAQ,kBAAkB,IAAI,UAAU,IAAI,MAAM,EAAE,CAAA;AAExD,MAAM,MAAM,mBAAmB,GAAG,KAAK,MAAM,EAAE,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,MAAM,WAAW,GACnB,eAAe,GACf,GAAG,eAAe,GAAG,mBAAmB,EAAE,CAAA;AAE9C;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,GAClD,KAAK,IAAI,CAAC,GAAG,WAAW,CAE1B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,GAClD,SAAS,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC,CAE/B;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,uBAAuB,GAChC,CAAC,GAAG,WAAW,CAGjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,WAAW,CASlC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,WAAW,CAElC;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,WAAW,CAElC;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,WAAW,CAElE;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,kBAAkB,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,CACA;IAAE,UAAU,CAAC,EAAE,UAAU,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,GAC7C;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAC5C,CAAA;AAMD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,uBAAuB,GAChC,MAAM,CAAC,UAAU,CAAC,CAsFpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"aturi_validation.js","sourceRoot":"","sources":["../src/aturi_validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { AtIdentifierString, isAtIdentifierString } from './at-identifier.js'\nimport { Result, failure, success } from './lib/result.js'\nimport { NsidString, isValidNsid } from './nsid.js'\nimport { isValidRecordKey } from './recordkey.js'\n\nexport type AtUriStringBase =\n | `at://${AtIdentifierString}`\n | `at://${AtIdentifierString}/${NsidString}`\n | `at://${AtIdentifierString}/${NsidString}/${string}`\n\nexport type AtUriStringFragment = `#/${string}`\n\n/**\n * A URI string as used to point at resources in the AT Protocol\n *\n * The full, general structure of an AT URI is:\n *\n * ```bnf\n * AT-URI = \"at://\" AUTHORITY [ PATH ] [ \"?\" QUERY ] [ \"#\" FRAGMENT ]\n * ```\n *\n * The authority part of the URI can be either a handle or a DID, indicating the\n * identity associated with the repository. In current atproto Lexicon use, the\n * query and fragment parts are not yet supported, and only a fixed pattern of\n * paths are allowed:\n *\n * ```bnf\n * AT-URI = \"at://\" AUTHORITY [ \"/\" COLLECTION [ \"/\" RKEY ] ]\n *\n * AUTHORITY = HANDLE | DID\n * COLLECTION = NSID\n * RKEY = RECORD-KEY\n * ```\n *\n * The authority section is required, and should be normalized.\n *\n * AT URI strings must respect the following syntax (as prescribed by the AT\n * protocol specification):\n *\n * - The overall URI is restricted to a subset of ASCII characters\n * - For reference below, the set of unreserved characters, as defined in [RFC-3986](https://www.rfc-editor.org/rfc/rfc3986), includes alphanumeric (`A-Za-z0-9`), period, hyphen, underscore, and tilde (`.-_~`)\n * - Maximum overall length is 8 kilobytes (which may be shortened in the future)\n * - Hex-encoding of characters is permitted (but in practice not necessary and should be avoided to keep the URI normalized and human-readable)\n * - The URI scheme is `at`, and an authority part preceded with double slashes is always required. AT URIs always start with `at://`.\n * - An authority section is required and must be non-empty. the authority can be either an atproto Handle, or a DID meeting the restrictions for use with atproto. The authority part can *not* be interpreted as a host:port pair, because of the use of colon characters (`:`) in DIDs. Colons and unreserved characters should not be escaped in DIDs, but other reserved characters (including `#`, `/`, `$`, `&`, `@`) must be escaped.\n * - Note that none of the current \"blessed\" DID methods for atproto allow these characters in DID identifiers\n * - An optional path section may follow the authority. The path may contain multiple segments separated by a single slash (`/`). Generic URI path normalization rules may be used.\n * - An optional query part is allowed, following generic URI syntax restrictions\n * - An optional fragment part is allowed, using JSON Path syntax\n *\n * @example \"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.actor.profile/self\"\n *\n * @see {@link https://atproto.com/specs/at-uri-scheme AT protocol - AT URI Scheme}\n */\nexport type AtUriString =\n | AtUriStringBase\n | `${AtUriStringBase}${AtUriStringFragment}`\n\n/**\n * Type guard that checks if a value is a valid {@link AtUriString}\n *\n * @see {@link AtUriString}\n */\nexport function isAtUriString<I>(\n input: I,\n options?: Omit<ParseAtUriStringOptions, 'detailed'>,\n): input is I & AtUriString {\n return parseAtUriString(input, options).success\n}\n\n/**\n * Returns the input if it is a valid {@link AtUriString} format string, or\n * `undefined` if it is not.\n *\n * @see {@link AtUriString}\n */\nexport function ifAtUriString<I>(\n input: I,\n options?: Omit<ParseAtUriStringOptions, 'detailed'>,\n): undefined | (I & AtUriString) {\n return isAtUriString(input, options) ? input : undefined\n}\n\n/**\n * Casts a string to an {@link AtUriString} if it is a valid AT URI format\n * string, throwing an error if it is not.\n *\n * @throws InvalidAtUriError if the input string does not meet the atproto AT URI format requirements.\n * @see {@link AtUriString}\n */\nexport function asAtUriString<I>(\n input: I,\n options?: ParseAtUriStringOptions,\n): I & AtUriString {\n assertAtUriString(input, options)\n return input\n}\n\n/**\n * Assert the validity of an {@link AtUriString}, throwing an error if the\n * {@link input} is not a valid AT URI.\n *\n * @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}\n */\nexport function assertAtUriString<I>(\n input: I,\n options?: ParseAtUriStringOptions,\n): asserts input is I & AtUriString {\n // Optimistically use faster isAtUriString(), throwing a detailed error only\n // in case of failure. This check, and the fact that the code after it always\n // throws, also ensures that isAtUriString() and assertAtUriString()'s\n // behavior are always consistent.\n const result = parseAtUriString(input, options)\n if (!result.success) {\n throw new InvalidAtUriError(result.message)\n }\n}\n\n/**\n * Assert the **non-strict** validity of an {@link AtUriString}, throwing a\n * detailed error if the {@link input} is not a valid AT URI.\n *\n * @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}\n * @deprecated use {@link assertAtUriString} with `{ strict: false }` option instead\n */\nexport function ensureValidAtUri<I>(\n input: I,\n): asserts input is I & AtUriString {\n assertAtUriString(input, { strict: false, detailed: true })\n}\n\n/**\n * Assert the (non-strict!) validity of an {@link AtUriString}, throwing an\n * error if the {@link input} is not a valid AT URI.\n *\n * @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}\n * @deprecated use {@link assertAtUriString} with `{ strict: false }` option instead\n */\nexport function ensureValidAtUriRegex<I>(\n input: I,\n): asserts input is I & AtUriString {\n assertAtUriString(input, { strict: false, detailed: false })\n}\n\n/**\n * Type guard that checks if a value is a valid {@link AtUriString} format\n * string, without enforcing strict record key validation. This is useful for\n * cases where you want to allow a wider range of valid ATURIs, such as when\n * validating user input or when the record key is not relevant.\n *\n * @deprecated use {@link isAtUriString} with `{ strict: false }` option instead\n */\nexport function isValidAtUri<I>(input: I): input is I & AtUriString {\n return isAtUriString(input, { strict: false })\n}\n\nexport class InvalidAtUriError extends Error {}\n\nexport type ParseAtUriStringOptions = {\n /**\n * If true, the parser will enforce that the record key (rkey) part of the URI\n * is a valid record key (validated by {@link isValidRecordKey}). If false,\n * any non-empty string of allowed chars will be accepted as a record key.\n *\n * @default true\n */\n strict?: boolean\n\n /**\n * If true, the parser will return detailed error messages for why a string is\n * not a valid AT URI. This option has no effect on the behavior of\n * {@link isAtUriString}, which will always return false for invalid strings\n * regardless of this option.\n *\n * @default false\n */\n detailed?: boolean\n}\n\nexport type AtUriParts = {\n authority: AtIdentifierString\n query?: string\n hash?: string\n} & (\n | { collection?: NsidString; rkey?: undefined }\n | { collection: NsidString; rkey?: string }\n)\n\nconst INVALID_CHAR_REGEXP = /[^a-zA-Z0-9._~:@!$&'()*+,;=%/\\\\[\\]#?-]/\nconst AT_URI_REGEXP =\n /^(?<uri>at:\\/\\/(?<authority>[^/?#\\s]+)(?:\\/(?<collection>[^/?#\\s]+)(?:\\/(?<rkey>[^/?#\\s]+))?)?(?<trailingSlash>\\/)?)(?:\\?(?<query>[^#\\s]*))?(?:#(?<hash>[^\\s]*))?$/\n\n/**\n * Parses a valid {@link AtUriString} into a {@link AtUriParts} object, or\n * returns a failure with a detailed error message if the string is not a valid\n * {@link AtUriString}.\n */\nexport function parseAtUriString(\n input: unknown,\n options?: ParseAtUriStringOptions,\n): Result<AtUriParts> {\n if (typeof input !== 'string') {\n return failure('ATURI must be a string')\n }\n\n if (input.length > 8192) {\n return failure('ATURI exceeds maximum length')\n }\n\n const invalidChar = input.match(INVALID_CHAR_REGEXP)\n if (invalidChar) {\n return failure('Disallowed characters in ATURI (ASCII)')\n }\n\n const match = input.match(AT_URI_REGEXP)\n const groups = match?.groups\n if (!groups) {\n // Regex validation failed, but we don't know exactly why. Provide more\n // detailed error messages if the \"detailed\" option is set, falling back to\n // a generic error.\n if (options?.detailed) {\n if (!input.startsWith('at://')) {\n return failure('ATURI must start with \"at://\"')\n }\n\n if (input.includes(' ')) {\n return failure('ATURI can not contain spaces')\n }\n\n if (input.includes('//', 5)) {\n return failure('ATURI can not have empty path segments')\n }\n\n const pathStart = input.indexOf('/', 5) // after \"at://\"\n if (pathStart !== -1) {\n const fragmentIndex = input.indexOf('#')\n const pathEnd = fragmentIndex !== -1 ? fragmentIndex : input.length\n const secondSlash = input.indexOf('/', pathStart + 1)\n if (secondSlash !== -1 && secondSlash !== pathEnd - 1) {\n return failure('ATURI can not have more than two path segments')\n }\n }\n }\n\n return failure('ATURI does not match expected format')\n }\n\n // @NOTE Percent-encoding is allowed by the AT URI specification, but any\n // percent-encoded characters appearing in the collection NSID or record key\n // will effectively be rejected by the isValidNsid and isValidRecordKey\n // validators. Since these values are defined to be plain ASCII identifiers,\n // this legacy behavior is beneficial: it ensures that normalized\n // (non-percent-encoded) values are always used, as prescribed by the spec.\n\n if (!isAtIdentifierString(groups.authority)) {\n return failure('ATURI has invalid authority')\n }\n\n if (groups.collection != null && !isValidNsid(groups.collection)) {\n return failure('ATURI has invalid collection')\n }\n\n if (groups.hash != null) {\n const result = parseJsonPointer(groups.hash, options)\n if (result.success) {\n groups.hash = result.value\n } else {\n return failure(`ATURI has invalid fragment (${result.message})`)\n }\n }\n\n if (options?.strict !== false) {\n if (groups.trailingSlash != null) {\n return failure('ATURI can not have a trailing slash')\n }\n\n if (groups.query != null) {\n return failure('ATURI query part is not allowed')\n }\n\n if (groups.rkey != null && !isValidRecordKey(groups.rkey)) {\n return failure('ATURI has invalid record key')\n }\n }\n\n return success(groups as AtUriParts)\n}\n\nconst BASIC_JSON_POINTER_REGEXP = /^\\/[a-zA-Z0-9._~:@!$&')(*+,;=%[\\]/-]*$/\n\n/**\n * Checks if a string is a valid JSON pointer (RFC-6901) with the allowed chars\n * for ATURI fragments. This is a very loose validation that only checks the\n * basic syntax and charset.\n */\nfunction parseJsonPointer(\n value: string,\n options?: { strict?: boolean },\n): Result<string> {\n if (!BASIC_JSON_POINTER_REGEXP.test(value)) {\n return failure('Invalid JSON pointer')\n }\n\n const result = parsePercentEncoding(value)\n\n // In non-strict mode, we allow invalid percent-encoding in the fragment\n if (!result.success && options?.strict === false) {\n return success(value)\n }\n\n return result\n}\n\nfunction parsePercentEncoding(value: string): Result<string> {\n try {\n return success(decodeURIComponent(value))\n } catch {\n // decodeURIComponent throws if the percent-encoding is invalid (e.g. \"%FF\")\n return failure('Invalid percent-encoding')\n }\n}\n"]}
1
+ {"version":3,"file":"aturi_validation.js","sourceRoot":"","sources":["../src/aturi_validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAGzD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAuDjD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAQ,EACR,OAAmD;IAEnD,OAAO,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,OAAO,CAAA;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAQ,EACR,OAAmD;IAEnD,OAAO,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAQ,EACR,OAAiC;IAEjC,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACjC,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAQ,EACR,OAAiC;IAEjC,4EAA4E;IAC5E,6EAA6E;IAC7E,sEAAsE;IACtE,kCAAkC;IAClC,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC/C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAC7C,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAQ;IAER,iBAAiB,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;AAC7D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAQ;IAER,iBAAiB,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;AAC9D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAI,KAAQ;IACtC,OAAO,aAAa,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;AAChD,CAAC;AAED,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG;AAgC/C,MAAM,mBAAmB,GAAG,wCAAwC,CAAA;AACpE,MAAM,aAAa,GACjB,oKAAoK,CAAA;AAEtK;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAc,EACd,OAAiC;IAEjC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,OAAO,CAAC,wBAAwB,CAAC,CAAA;IAC1C,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;QACxB,OAAO,OAAO,CAAC,8BAA8B,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAA;IACpD,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,OAAO,CAAC,wCAAwC,CAAC,CAAA;IAC1D,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;IACxC,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,CAAA;IAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,uEAAuE;QACvE,2EAA2E;QAC3E,mBAAmB;QACnB,IAAI,OAAO,EAAE,QAAQ,EAAE,CAAC;YACtB,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/B,OAAO,OAAO,CAAC,+BAA+B,CAAC,CAAA;YACjD,CAAC;YAED,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,OAAO,OAAO,CAAC,8BAA8B,CAAC,CAAA;YAChD,CAAC;YAED,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;gBAC5B,OAAO,OAAO,CAAC,wCAAwC,CAAC,CAAA;YAC1D,CAAC;YAED,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA,CAAC,gBAAgB;YACxD,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;gBACrB,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;gBACxC,MAAM,OAAO,GAAG,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAA;gBACnE,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,SAAS,GAAG,CAAC,CAAC,CAAA;gBACrD,IAAI,WAAW,KAAK,CAAC,CAAC,IAAI,WAAW,KAAK,OAAO,GAAG,CAAC,EAAE,CAAC;oBACtD,OAAO,OAAO,CAAC,gDAAgD,CAAC,CAAA;gBAClE,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC,sCAAsC,CAAC,CAAA;IACxD,CAAC;IAED,yEAAyE;IACzE,4EAA4E;IAC5E,uEAAuE;IACvE,4EAA4E;IAC5E,iEAAiE;IACjE,2EAA2E;IAE3E,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5C,OAAO,OAAO,CAAC,6BAA6B,CAAC,CAAA;IAC/C,CAAC;IAED,IAAI,MAAM,CAAC,UAAU,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QACjE,OAAO,OAAO,CAAC,8BAA8B,CAAC,CAAA;IAChD,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QACrD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,CAAA;QAC5B,CAAC;aAAM,CAAC;YACN,OAAO,OAAO,CAAC,+BAA+B,MAAM,CAAC,OAAO,GAAG,CAAC,CAAA;QAClE,CAAC;IACH,CAAC;IAED,IAAI,OAAO,EAAE,MAAM,KAAK,KAAK,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,aAAa,IAAI,IAAI,EAAE,CAAC;YACjC,OAAO,OAAO,CAAC,qCAAqC,CAAC,CAAA;QACvD,CAAC;QAED,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;YACzB,OAAO,OAAO,CAAC,iCAAiC,CAAC,CAAA;QACnD,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,IAAI,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1D,OAAO,OAAO,CAAC,8BAA8B,CAAC,CAAA;QAChD,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,MAAoB,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,yBAAyB,GAAG,wCAAwC,CAAA;AAE1E;;;;GAIG;AACH,SAAS,gBAAgB,CACvB,KAAa,EACb,OAA8B;IAE9B,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,OAAO,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACxC,CAAC;IAED,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAA;IAE1C,wEAAwE;IACxE,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,OAAO,EAAE,MAAM,KAAK,KAAK,EAAE,CAAC;QACjD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAA;IACvB,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAa;IACzC,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAA;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;QAC5E,OAAO,OAAO,CAAC,0BAA0B,CAAC,CAAA;IAC5C,CAAC;AACH,CAAC","sourcesContent":["import { isAtIdentifierString } from './at-identifier.js'\nimport type { AtIdentifierString } from './at-identifier.js'\nimport type { Result } from './lib/result.js'\nimport { failure, success } from './lib/result.js'\nimport type { NsidString } from './nsid.js'\nimport { isValidNsid } from './nsid.js'\nimport { isValidRecordKey } from './recordkey.js'\n\nexport type AtUriStringBase =\n | `at://${AtIdentifierString}`\n | `at://${AtIdentifierString}/${NsidString}`\n | `at://${AtIdentifierString}/${NsidString}/${string}`\n\nexport type AtUriStringFragment = `#/${string}`\n\n/**\n * A URI string as used to point at resources in the AT Protocol\n *\n * The full, general structure of an AT URI is:\n *\n * ```bnf\n * AT-URI = \"at://\" AUTHORITY [ PATH ] [ \"?\" QUERY ] [ \"#\" FRAGMENT ]\n * ```\n *\n * The authority part of the URI can be either a handle or a DID, indicating the\n * identity associated with the repository. In current atproto Lexicon use, the\n * query and fragment parts are not yet supported, and only a fixed pattern of\n * paths are allowed:\n *\n * ```bnf\n * AT-URI = \"at://\" AUTHORITY [ \"/\" COLLECTION [ \"/\" RKEY ] ]\n *\n * AUTHORITY = HANDLE | DID\n * COLLECTION = NSID\n * RKEY = RECORD-KEY\n * ```\n *\n * The authority section is required, and should be normalized.\n *\n * AT URI strings must respect the following syntax (as prescribed by the AT\n * protocol specification):\n *\n * - The overall URI is restricted to a subset of ASCII characters\n * - For reference below, the set of unreserved characters, as defined in [RFC-3986](https://www.rfc-editor.org/rfc/rfc3986), includes alphanumeric (`A-Za-z0-9`), period, hyphen, underscore, and tilde (`.-_~`)\n * - Maximum overall length is 8 kilobytes (which may be shortened in the future)\n * - Hex-encoding of characters is permitted (but in practice not necessary and should be avoided to keep the URI normalized and human-readable)\n * - The URI scheme is `at`, and an authority part preceded with double slashes is always required. AT URIs always start with `at://`.\n * - An authority section is required and must be non-empty. the authority can be either an atproto Handle, or a DID meeting the restrictions for use with atproto. The authority part can *not* be interpreted as a host:port pair, because of the use of colon characters (`:`) in DIDs. Colons and unreserved characters should not be escaped in DIDs, but other reserved characters (including `#`, `/`, `$`, `&`, `@`) must be escaped.\n * - Note that none of the current \"blessed\" DID methods for atproto allow these characters in DID identifiers\n * - An optional path section may follow the authority. The path may contain multiple segments separated by a single slash (`/`). Generic URI path normalization rules may be used.\n * - An optional query part is allowed, following generic URI syntax restrictions\n * - An optional fragment part is allowed, using JSON Path syntax\n *\n * @example \"at://did:plc:ewvi7nxzyoun6zhxrhs64oiz/app.bsky.actor.profile/self\"\n *\n * @see {@link https://atproto.com/specs/at-uri-scheme AT protocol - AT URI Scheme}\n */\nexport type AtUriString =\n | AtUriStringBase\n | `${AtUriStringBase}${AtUriStringFragment}`\n\n/**\n * Type guard that checks if a value is a valid {@link AtUriString}\n *\n * @see {@link AtUriString}\n */\nexport function isAtUriString<I>(\n input: I,\n options?: Omit<ParseAtUriStringOptions, 'detailed'>,\n): input is I & AtUriString {\n return parseAtUriString(input, options).success\n}\n\n/**\n * Returns the input if it is a valid {@link AtUriString} format string, or\n * `undefined` if it is not.\n *\n * @see {@link AtUriString}\n */\nexport function ifAtUriString<I>(\n input: I,\n options?: Omit<ParseAtUriStringOptions, 'detailed'>,\n): undefined | (I & AtUriString) {\n return isAtUriString(input, options) ? input : undefined\n}\n\n/**\n * Casts a string to an {@link AtUriString} if it is a valid AT URI format\n * string, throwing an error if it is not.\n *\n * @throws InvalidAtUriError if the input string does not meet the atproto AT URI format requirements.\n * @see {@link AtUriString}\n */\nexport function asAtUriString<I>(\n input: I,\n options?: ParseAtUriStringOptions,\n): I & AtUriString {\n assertAtUriString(input, options)\n return input\n}\n\n/**\n * Assert the validity of an {@link AtUriString}, throwing an error if the\n * {@link input} is not a valid AT URI.\n *\n * @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}\n */\nexport function assertAtUriString<I>(\n input: I,\n options?: ParseAtUriStringOptions,\n): asserts input is I & AtUriString {\n // Optimistically use faster isAtUriString(), throwing a detailed error only\n // in case of failure. This check, and the fact that the code after it always\n // throws, also ensures that isAtUriString() and assertAtUriString()'s\n // behavior are always consistent.\n const result = parseAtUriString(input, options)\n if (!result.success) {\n throw new InvalidAtUriError(result.message)\n }\n}\n\n/**\n * Assert the **non-strict** validity of an {@link AtUriString}, throwing a\n * detailed error if the {@link input} is not a valid AT URI.\n *\n * @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}\n * @deprecated use {@link assertAtUriString} with `{ strict: false }` option instead\n */\nexport function ensureValidAtUri<I>(\n input: I,\n): asserts input is I & AtUriString {\n assertAtUriString(input, { strict: false, detailed: true })\n}\n\n/**\n * Assert the (non-strict!) validity of an {@link AtUriString}, throwing an\n * error if the {@link input} is not a valid AT URI.\n *\n * @throws InvalidAtUriError if the {@link input} is not a valid {@link AtUriString}\n * @deprecated use {@link assertAtUriString} with `{ strict: false }` option instead\n */\nexport function ensureValidAtUriRegex<I>(\n input: I,\n): asserts input is I & AtUriString {\n assertAtUriString(input, { strict: false, detailed: false })\n}\n\n/**\n * Type guard that checks if a value is a valid {@link AtUriString} format\n * string, without enforcing strict record key validation. This is useful for\n * cases where you want to allow a wider range of valid ATURIs, such as when\n * validating user input or when the record key is not relevant.\n *\n * @deprecated use {@link isAtUriString} with `{ strict: false }` option instead\n */\nexport function isValidAtUri<I>(input: I): input is I & AtUriString {\n return isAtUriString(input, { strict: false })\n}\n\nexport class InvalidAtUriError extends Error {}\n\nexport type ParseAtUriStringOptions = {\n /**\n * If true, the parser will enforce that the record key (rkey) part of the URI\n * is a valid record key (validated by {@link isValidRecordKey}). If false,\n * any non-empty string of allowed chars will be accepted as a record key.\n *\n * @default true\n */\n strict?: boolean\n\n /**\n * If true, the parser will return detailed error messages for why a string is\n * not a valid AT URI. This option has no effect on the behavior of\n * {@link isAtUriString}, which will always return false for invalid strings\n * regardless of this option.\n *\n * @default false\n */\n detailed?: boolean\n}\n\nexport type AtUriParts = {\n authority: AtIdentifierString\n query?: string\n hash?: string\n} & (\n | { collection?: NsidString; rkey?: undefined }\n | { collection: NsidString; rkey?: string }\n)\n\nconst INVALID_CHAR_REGEXP = /[^a-zA-Z0-9._~:@!$&'()*+,;=%/\\\\[\\]#?-]/\nconst AT_URI_REGEXP =\n /^(?<uri>at:\\/\\/(?<authority>[^/?#\\s]+)(?:\\/(?<collection>[^/?#\\s]+)(?:\\/(?<rkey>[^/?#\\s]+))?)?(?<trailingSlash>\\/)?)(?:\\?(?<query>[^#\\s]*))?(?:#(?<hash>[^\\s]*))?$/\n\n/**\n * Parses a valid {@link AtUriString} into a {@link AtUriParts} object, or\n * returns a failure with a detailed error message if the string is not a valid\n * {@link AtUriString}.\n */\nexport function parseAtUriString(\n input: unknown,\n options?: ParseAtUriStringOptions,\n): Result<AtUriParts> {\n if (typeof input !== 'string') {\n return failure('ATURI must be a string')\n }\n\n if (input.length > 8192) {\n return failure('ATURI exceeds maximum length')\n }\n\n const invalidChar = input.match(INVALID_CHAR_REGEXP)\n if (invalidChar) {\n return failure('Disallowed characters in ATURI (ASCII)')\n }\n\n const match = input.match(AT_URI_REGEXP)\n const groups = match?.groups\n if (!groups) {\n // Regex validation failed, but we don't know exactly why. Provide more\n // detailed error messages if the \"detailed\" option is set, falling back to\n // a generic error.\n if (options?.detailed) {\n if (!input.startsWith('at://')) {\n return failure('ATURI must start with \"at://\"')\n }\n\n if (input.includes(' ')) {\n return failure('ATURI can not contain spaces')\n }\n\n if (input.includes('//', 5)) {\n return failure('ATURI can not have empty path segments')\n }\n\n const pathStart = input.indexOf('/', 5) // after \"at://\"\n if (pathStart !== -1) {\n const fragmentIndex = input.indexOf('#')\n const pathEnd = fragmentIndex !== -1 ? fragmentIndex : input.length\n const secondSlash = input.indexOf('/', pathStart + 1)\n if (secondSlash !== -1 && secondSlash !== pathEnd - 1) {\n return failure('ATURI can not have more than two path segments')\n }\n }\n }\n\n return failure('ATURI does not match expected format')\n }\n\n // @NOTE Percent-encoding is allowed by the AT URI specification, but any\n // percent-encoded characters appearing in the collection NSID or record key\n // will effectively be rejected by the isValidNsid and isValidRecordKey\n // validators. Since these values are defined to be plain ASCII identifiers,\n // this legacy behavior is beneficial: it ensures that normalized\n // (non-percent-encoded) values are always used, as prescribed by the spec.\n\n if (!isAtIdentifierString(groups.authority)) {\n return failure('ATURI has invalid authority')\n }\n\n if (groups.collection != null && !isValidNsid(groups.collection)) {\n return failure('ATURI has invalid collection')\n }\n\n if (groups.hash != null) {\n const result = parseJsonPointer(groups.hash, options)\n if (result.success) {\n groups.hash = result.value\n } else {\n return failure(`ATURI has invalid fragment (${result.message})`)\n }\n }\n\n if (options?.strict !== false) {\n if (groups.trailingSlash != null) {\n return failure('ATURI can not have a trailing slash')\n }\n\n if (groups.query != null) {\n return failure('ATURI query part is not allowed')\n }\n\n if (groups.rkey != null && !isValidRecordKey(groups.rkey)) {\n return failure('ATURI has invalid record key')\n }\n }\n\n return success(groups as AtUriParts)\n}\n\nconst BASIC_JSON_POINTER_REGEXP = /^\\/[a-zA-Z0-9._~:@!$&')(*+,;=%[\\]/-]*$/\n\n/**\n * Checks if a string is a valid JSON pointer (RFC-6901) with the allowed chars\n * for ATURI fragments. This is a very loose validation that only checks the\n * basic syntax and charset.\n */\nfunction parseJsonPointer(\n value: string,\n options?: { strict?: boolean },\n): Result<string> {\n if (!BASIC_JSON_POINTER_REGEXP.test(value)) {\n return failure('Invalid JSON pointer')\n }\n\n const result = parsePercentEncoding(value)\n\n // In non-strict mode, we allow invalid percent-encoding in the fragment\n if (!result.success && options?.strict === false) {\n return success(value)\n }\n\n return result\n}\n\nfunction parsePercentEncoding(value: string): Result<string> {\n try {\n return success(decodeURIComponent(value))\n } catch {\n // decodeURIComponent throws if the percent-encoding is invalid (e.g. \"%FF\")\n return failure('Invalid percent-encoding')\n }\n}\n"]}
package/dist/language.js CHANGED
@@ -1,4 +1,4 @@
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})+))$/;
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>[xX](-[A-Za-z0-9]{1,8})+))?)|(?<privateUseB>[xX](-[A-Za-z0-9]{1,8})+))$/;
2
2
  export function parseLanguageString(input) {
3
3
  const parsed = input.match(BCP47_REGEXP);
4
4
  if (!parsed?.groups)
@@ -1 +1 @@
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
+ {"version":3,"file":"language.js","sourceRoot":"","sources":["../src/language.ts"],"names":[],"mappings":"AAAA,MAAM,YAAY,GAChB,ylBAAylB,CAAA;AAa3lB,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>[xX](-[A-Za-z0-9]{1,8})+))?)|(?<privateUseB>[xX](-[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"]}
package/dist/nsid.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Result } from './lib/result.js';
1
+ import { type Result } from './lib/result.js';
2
2
  export type NsidString = `${string}.${string}.${string}`;
3
3
  export declare class NSID {
4
4
  readonly segments: readonly string[];
@@ -1 +1 @@
1
- {"version":3,"file":"nsid.d.ts","sourceRoot":"","sources":["../src/nsid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoB,MAAM,iBAAiB,CAAA;AAe1D,MAAM,MAAM,UAAU,GAAG,GAAG,MAAM,IAAI,MAAM,IAAI,MAAM,EAAE,CAAA;AAExD,qBAAa,IAAI;IACf,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IAEpC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEhC;IAED,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAGnD;IAED,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,2CAE1B;IAED,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;KAAE,GAAG,IAAI,CASnD;IAED,YAAY,IAAI,EAAE,MAAM,EAEvB;IAED,IAAI,SAAS,IAIK,GAAG,MAAM,IAAI,MAAM,EAAE,CACtC;IAED,IAAI,IAAI,uBAEP;IAED,QAAQ,IAAI,UAAU,CAErB;CACF;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAC9C,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,UAAU,CAGjC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAIhD;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAC1C,KAAK,EAAE,CAAC,GACP,KAAK,IAAI,CAAC,GAAG,UAAU,CAIzB;AAKD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAiC5D;AA4BD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,IAAI,UAAU,CAG7E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAgBnE;AAED,qBAAa,gBAAiB,SAAQ,KAAK;CAAG"}
1
+ {"version":3,"file":"nsid.d.ts","sourceRoot":"","sources":["../src/nsid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAoB,MAAM,iBAAiB,CAAA;AAe/D,MAAM,MAAM,UAAU,GAAG,GAAG,MAAM,IAAI,MAAM,IAAI,MAAM,EAAE,CAAA;AAExD,qBAAa,IAAI;IACf,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IAEpC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEhC;IAED,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAGnD;IAED,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,2CAE1B;IAED,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;KAAE,GAAG,IAAI,CASnD;IAED,YAAY,IAAI,EAAE,MAAM,EAEvB;IAED,IAAI,SAAS,IAIK,GAAG,MAAM,IAAI,MAAM,EAAE,CACtC;IAED,IAAI,IAAI,uBAEP;IAED,QAAQ,IAAI,UAAU,CAErB;CACF;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAC9C,KAAK,EAAE,CAAC,GACP,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,UAAU,CAGjC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAIhD;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAC1C,KAAK,EAAE,CAAC,GACP,KAAK,IAAI,CAAC,GAAG,UAAU,CAIzB;AAKD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAiC5D;AA4BD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,IAAI,UAAU,CAG7E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAgBnE;AAED,qBAAa,gBAAiB,SAAQ,KAAK;CAAG"}
package/dist/nsid.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"nsid.js","sourceRoot":"","sources":["../src/nsid.ts"],"names":[],"mappings":"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","sourcesContent":["import { Result, failure, success } from './lib/result.js'\n\n/*\nGrammar:\n\nalpha = \"a\" / \"b\" / \"c\" / \"d\" / \"e\" / \"f\" / \"g\" / \"h\" / \"i\" / \"j\" / \"k\" / \"l\" / \"m\" / \"n\" / \"o\" / \"p\" / \"q\" / \"r\" / \"s\" / \"t\" / \"u\" / \"v\" / \"w\" / \"x\" / \"y\" / \"z\" / \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\" / \"G\" / \"H\" / \"I\" / \"J\" / \"K\" / \"L\" / \"M\" / \"N\" / \"O\" / \"P\" / \"Q\" / \"R\" / \"S\" / \"T\" / \"U\" / \"V\" / \"W\" / \"X\" / \"Y\" / \"Z\"\nnumber = \"1\" / \"2\" / \"3\" / \"4\" / \"5\" / \"6\" / \"7\" / \"8\" / \"9\" / \"0\"\ndelim = \".\"\nsegment = alpha *( alpha / number / \"-\" )\nauthority = segment *( delim segment )\nname = alpha *( alpha / number )\nnsid = authority delim name\n\n*/\n\nexport type NsidString = `${string}.${string}.${string}`\n\nexport class NSID {\n readonly segments: readonly string[]\n\n static parse(input: string): NSID {\n return new NSID(input)\n }\n\n static create(authority: string, name: string): NSID {\n const input = [...authority.split('.').reverse(), name].join('.')\n return new NSID(input)\n }\n\n static isValid(nsid: string) {\n return isValidNsid(nsid)\n }\n\n static from(input: { toString: () => string }): NSID {\n if (input instanceof NSID) {\n // No need to clone, NSID is immutable\n return input\n }\n if (Array.isArray(input)) {\n return new NSID((input as string[]).join('.'))\n }\n return new NSID(String(input))\n }\n\n constructor(nsid: string) {\n this.segments = parseNsid(nsid)\n }\n\n get authority() {\n return this.segments\n .slice(0, this.segments.length - 1)\n .reverse()\n .join('.') as `${string}.${string}`\n }\n\n get name() {\n return this.segments.at(this.segments.length - 1)\n }\n\n toString(): NsidString {\n return this.segments.join('.') as NsidString\n }\n}\n\nexport function ensureValidNsid<I extends string>(\n input: I,\n): asserts input is I & NsidString {\n const result = validateNsid(input)\n if (!result.success) throw new InvalidNsidError(result.message)\n}\n\nexport function parseNsid(nsid: string): string[] {\n const result = validateNsid(nsid)\n if (!result.success) throw new InvalidNsidError(result.message)\n return result.value\n}\n\nexport function isValidNsid<I extends string>(\n input: I,\n): input is I & NsidString {\n // Since the regex version is more performant for valid NSIDs, we use it when\n // we don't care about error details.\n return validateNsidRegex(input).success\n}\n\n// Human readable constraints on NSID:\n// - a valid domain in reversed notation\n// - followed by an additional period-separated name, which is camel-case letters\nexport function validateNsid(input: string): Result<string[]> {\n if (input.length > 253 + 1 + 63) {\n return failure('NSID is too long (317 chars max)')\n }\n if (hasDisallowedCharacters(input)) {\n return failure(\n 'Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)',\n )\n }\n const segments = input.split('.')\n if (segments.length < 3) {\n return failure('NSID needs at least three parts')\n }\n for (const l of segments) {\n if (l.length < 1) {\n return failure('NSID parts can not be empty')\n }\n if (l.length > 63) {\n return failure('NSID part too long (max 63 chars)')\n }\n if (startsWithHyphen(l) || endsWithHyphen(l)) {\n return failure('NSID parts can not start or end with hyphen')\n }\n }\n if (startsWithNumber(segments[0])) {\n return failure('NSID first part may not start with a digit')\n }\n if (!isValidIdentifier(segments[segments.length - 1])) {\n return failure(\n 'NSID name part must be only letters and digits (and no leading digit)',\n )\n }\n return success(segments)\n}\n\nfunction hasDisallowedCharacters(v: string) {\n return !/^[a-zA-Z0-9.-]*$/.test(v)\n}\n\nfunction startsWithNumber(v: string) {\n const charCode = v.charCodeAt(0)\n return charCode >= 48 && charCode <= 57\n}\n\nfunction startsWithHyphen(v: string) {\n return v.charCodeAt(0) === 45 /* - */\n}\n\nfunction endsWithHyphen(v: string) {\n return v.charCodeAt(v.length - 1) === 45 /* - */\n}\n\nfunction isValidIdentifier(v: string) {\n // Note, since we already know that \"v\" only contains [a-zA-Z0-9-], we can\n // simplify the following regex by checking only the first char and presence\n // of \"-\".\n\n // return /^[a-zA-Z][a-zA-Z0-9]*$/.test(v)\n return !startsWithNumber(v) && !v.includes('-')\n}\n\n/**\n * @deprecated Use {@link ensureValidNsid} if you care about error details,\n * {@link parseNsid}/{@link NSID.parse} if you need the parsed segments, or\n * {@link isValidNsid} if you just want a boolean.\n */\nexport function ensureValidNsidRegex(nsid: string): asserts nsid is NsidString {\n const result = validateNsidRegex(nsid)\n if (!result.success) throw new InvalidNsidError(result.message)\n}\n\n/**\n * Regexp based validation that behaves identically to the previous code but\n * provides less detailed error messages (while being 20% to 50% faster).\n */\nexport function validateNsidRegex(value: string): Result<NsidString> {\n if (value.length > 253 + 1 + 63) {\n return failure('NSID is too long (317 chars max)')\n }\n\n if (\n // Fast check for small values\n value.length < 5 ||\n !/^[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:\\.[a-zA-Z](?:[a-zA-Z0-9]{0,62})?)$/.test(\n value,\n )\n ) {\n return failure(\"NSID didn't validate via regex\")\n }\n\n return success(value as NsidString)\n}\n\nexport class InvalidNsidError extends Error {}\n"]}
1
+ {"version":3,"file":"nsid.js","sourceRoot":"","sources":["../src/nsid.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Result, failure, success } from './lib/result.js'\n\n/*\nGrammar:\n\nalpha = \"a\" / \"b\" / \"c\" / \"d\" / \"e\" / \"f\" / \"g\" / \"h\" / \"i\" / \"j\" / \"k\" / \"l\" / \"m\" / \"n\" / \"o\" / \"p\" / \"q\" / \"r\" / \"s\" / \"t\" / \"u\" / \"v\" / \"w\" / \"x\" / \"y\" / \"z\" / \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\" / \"G\" / \"H\" / \"I\" / \"J\" / \"K\" / \"L\" / \"M\" / \"N\" / \"O\" / \"P\" / \"Q\" / \"R\" / \"S\" / \"T\" / \"U\" / \"V\" / \"W\" / \"X\" / \"Y\" / \"Z\"\nnumber = \"1\" / \"2\" / \"3\" / \"4\" / \"5\" / \"6\" / \"7\" / \"8\" / \"9\" / \"0\"\ndelim = \".\"\nsegment = alpha *( alpha / number / \"-\" )\nauthority = segment *( delim segment )\nname = alpha *( alpha / number )\nnsid = authority delim name\n\n*/\n\nexport type NsidString = `${string}.${string}.${string}`\n\nexport class NSID {\n readonly segments: readonly string[]\n\n static parse(input: string): NSID {\n return new NSID(input)\n }\n\n static create(authority: string, name: string): NSID {\n const input = [...authority.split('.').reverse(), name].join('.')\n return new NSID(input)\n }\n\n static isValid(nsid: string) {\n return isValidNsid(nsid)\n }\n\n static from(input: { toString: () => string }): NSID {\n if (input instanceof NSID) {\n // No need to clone, NSID is immutable\n return input\n }\n if (Array.isArray(input)) {\n return new NSID((input as string[]).join('.'))\n }\n return new NSID(String(input))\n }\n\n constructor(nsid: string) {\n this.segments = parseNsid(nsid)\n }\n\n get authority() {\n return this.segments\n .slice(0, this.segments.length - 1)\n .reverse()\n .join('.') as `${string}.${string}`\n }\n\n get name() {\n return this.segments.at(this.segments.length - 1)\n }\n\n toString(): NsidString {\n return this.segments.join('.') as NsidString\n }\n}\n\nexport function ensureValidNsid<I extends string>(\n input: I,\n): asserts input is I & NsidString {\n const result = validateNsid(input)\n if (!result.success) throw new InvalidNsidError(result.message)\n}\n\nexport function parseNsid(nsid: string): string[] {\n const result = validateNsid(nsid)\n if (!result.success) throw new InvalidNsidError(result.message)\n return result.value\n}\n\nexport function isValidNsid<I extends string>(\n input: I,\n): input is I & NsidString {\n // Since the regex version is more performant for valid NSIDs, we use it when\n // we don't care about error details.\n return validateNsidRegex(input).success\n}\n\n// Human readable constraints on NSID:\n// - a valid domain in reversed notation\n// - followed by an additional period-separated name, which is camel-case letters\nexport function validateNsid(input: string): Result<string[]> {\n if (input.length > 253 + 1 + 63) {\n return failure('NSID is too long (317 chars max)')\n }\n if (hasDisallowedCharacters(input)) {\n return failure(\n 'Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)',\n )\n }\n const segments = input.split('.')\n if (segments.length < 3) {\n return failure('NSID needs at least three parts')\n }\n for (const l of segments) {\n if (l.length < 1) {\n return failure('NSID parts can not be empty')\n }\n if (l.length > 63) {\n return failure('NSID part too long (max 63 chars)')\n }\n if (startsWithHyphen(l) || endsWithHyphen(l)) {\n return failure('NSID parts can not start or end with hyphen')\n }\n }\n if (startsWithNumber(segments[0])) {\n return failure('NSID first part may not start with a digit')\n }\n if (!isValidIdentifier(segments[segments.length - 1])) {\n return failure(\n 'NSID name part must be only letters and digits (and no leading digit)',\n )\n }\n return success(segments)\n}\n\nfunction hasDisallowedCharacters(v: string) {\n return !/^[a-zA-Z0-9.-]*$/.test(v)\n}\n\nfunction startsWithNumber(v: string) {\n const charCode = v.charCodeAt(0)\n return charCode >= 48 && charCode <= 57\n}\n\nfunction startsWithHyphen(v: string) {\n return v.charCodeAt(0) === 45 /* - */\n}\n\nfunction endsWithHyphen(v: string) {\n return v.charCodeAt(v.length - 1) === 45 /* - */\n}\n\nfunction isValidIdentifier(v: string) {\n // Note, since we already know that \"v\" only contains [a-zA-Z0-9-], we can\n // simplify the following regex by checking only the first char and presence\n // of \"-\".\n\n // return /^[a-zA-Z][a-zA-Z0-9]*$/.test(v)\n return !startsWithNumber(v) && !v.includes('-')\n}\n\n/**\n * @deprecated Use {@link ensureValidNsid} if you care about error details,\n * {@link parseNsid}/{@link NSID.parse} if you need the parsed segments, or\n * {@link isValidNsid} if you just want a boolean.\n */\nexport function ensureValidNsidRegex(nsid: string): asserts nsid is NsidString {\n const result = validateNsidRegex(nsid)\n if (!result.success) throw new InvalidNsidError(result.message)\n}\n\n/**\n * Regexp based validation that behaves identically to the previous code but\n * provides less detailed error messages (while being 20% to 50% faster).\n */\nexport function validateNsidRegex(value: string): Result<NsidString> {\n if (value.length > 253 + 1 + 63) {\n return failure('NSID is too long (317 chars max)')\n }\n\n if (\n // Fast check for small values\n value.length < 5 ||\n !/^[a-zA-Z](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?:\\.[a-zA-Z](?:[a-zA-Z0-9]{0,62})?)$/.test(\n value,\n )\n ) {\n return failure(\"NSID didn't validate via regex\")\n }\n\n return success(value as NsidString)\n}\n\nexport class InvalidNsidError extends Error {}\n"]}
package/package.json CHANGED
@@ -1,9 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/syntax",
3
- "version": "0.6.3",
4
- "engines": {
5
- "node": ">=22"
6
- },
3
+ "version": "0.6.4",
7
4
  "license": "MIT",
8
5
  "description": "Validation for atproto identifiers and formats: DID, handle, NSID, AT URI, etc",
9
6
  "keywords": [
@@ -18,16 +15,10 @@
18
15
  "url": "https://github.com/bluesky-social/atproto",
19
16
  "directory": "packages/syntax"
20
17
  },
21
- "dependencies": {
22
- "iso-datestring-validator": "^2.2.2",
23
- "tslib": "^2.8.1"
24
- },
25
- "devDependencies": {
26
- "vitest": "^4.0.16"
27
- },
28
- "browser": {
29
- "dns/promises": false
30
- },
18
+ "files": [
19
+ "./dist",
20
+ "./CHANGELOG.md"
21
+ ],
31
22
  "type": "module",
32
23
  "exports": {
33
24
  ".": {
@@ -35,6 +26,16 @@
35
26
  "default": "./dist/index.js"
36
27
  }
37
28
  },
29
+ "engines": {
30
+ "node": ">=22"
31
+ },
32
+ "dependencies": {
33
+ "iso-datestring-validator": "^2.2.2",
34
+ "tslib": "^2.8.1"
35
+ },
36
+ "devDependencies": {
37
+ "vitest": "^4.0.16"
38
+ },
38
39
  "scripts": {
39
40
  "test": "vitest run",
40
41
  "build": "tsgo --build tsconfig.build.json"
package/benchmark.cjs DELETED
@@ -1,208 +0,0 @@
1
- /* eslint-env node, commonjs */
2
-
3
- const { validateNsid, validateNsidRegex } = require('.')
4
-
5
- // $ node benchmark.js
6
- // valid NSIDs {
7
- // parsed: 181.56524884700775,
8
- // regexp: 77.61082607507706,
9
- // optimized: 60.183539509773254
10
- // }
11
- // invalid NSIDs {
12
- // parsed: 128.7685609459877,
13
- // regexp: 108.75775015354156,
14
- // optimized: 53.196488440036774
15
- // }
16
-
17
- bench('valid NSIDs', true, [
18
- 'com.example.foo',
19
- 'o'.repeat(63) + '.foo.bar',
20
- 'com.' + 'o'.repeat(63) + '.foo',
21
- 'com.example.' + 'o'.repeat(63),
22
- 'com.' + 'middle.'.repeat(40) + 'foo',
23
- 'com.example.fooBar',
24
- 'net.users.bob.ping',
25
- 'a.b.c',
26
- 'm.xn--masekowski-d0b.pl',
27
- 'one.two.three',
28
- 'one.two.three.four-and.FiVe',
29
- 'one.2.three',
30
- 'a-0.b-1.c',
31
- 'a0.b1.cc',
32
- 'cn.8.lex.stuff',
33
- 'test.12345.record',
34
- 'a01.thing.record',
35
- 'a.0.c',
36
- 'xn--fiqs8s.xn--fiqa61au8b7zsevnm8ak20mc4a87e.record.two',
37
- 'a0.b1.c3',
38
- 'com.example.f00',
39
- 'onion.expyuzz4wqqyqhjn.spec.getThing',
40
- 'onion.g2zyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.lex.deleteThing',
41
- 'org.4chan.lex.getThing',
42
- 'cn.8.lex.stuff',
43
- 'onion.2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.lex.deleteThing',
44
- 'a.'.repeat(158) + 'a',
45
- ])
46
-
47
- bench('invalid NSIDs', false, [
48
- 'a.'.repeat(158) + '9',
49
- 'a.'.repeat(154) + 'a😅.9',
50
- 'o'.repeat(64) + '.foo.bar',
51
- 'com.' + 'o'.repeat(64) + '.foo',
52
- 'com.example.' + 'o'.repeat(64),
53
- 'com.' + 'middle.'.repeat(50) + 'foo',
54
- 'com.example.foo.*',
55
- 'com.example.foo.blah*',
56
- 'com.example.foo.*blah',
57
- 'com.exa💩ple.thing',
58
- 'a-0.b-1.c-3',
59
- 'a-0.b-1.c-o',
60
- '1.0.0.127.record',
61
- '0two.example.foo',
62
- 'example.com',
63
- 'com.example',
64
- 'a.',
65
- '.one.two.three',
66
- 'one.two.three ',
67
- 'one.two..three',
68
- 'one .two.three',
69
- ' one.two.three',
70
- 'com.atproto.feed.p@st',
71
- 'com.atproto.feed.p_st',
72
- 'com.atproto.feed.p*st',
73
- 'com.atproto.feed.po#t',
74
- 'com.atproto.feed.p!ot',
75
- 'com.example-.foo',
76
- 'com.-example.foo',
77
- 'com.example.0foo',
78
- 'com.example.f-o',
79
- ])
80
-
81
- function bench(name, expectedResult, cases) {
82
- const validators = {
83
- parsed: (nsid) => validateNsid(nsid).success,
84
- regexp: (nsid) => validateNsidRegex(nsid).success,
85
- optimized: (nsid) => validateNsidOptimized(nsid).success,
86
- }
87
-
88
- const times = Object.fromEntries(Object.keys(validators).map((k) => [k, 0]))
89
-
90
- for (let i = 0; i < 1000; i++) {
91
- for (const [name, fn] of Object.entries(validators)) {
92
- const start = performance.now()
93
- for (let j = 0; j < 20; j++) {
94
- for (const value of cases) {
95
- if (fn(value) !== expectedResult) {
96
- throw new Error(`Validator ${name} gave wrong result`)
97
- }
98
- }
99
- }
100
- times[name] += performance.now() - start
101
- }
102
- }
103
-
104
- console.log(
105
- name,
106
- Object.fromEntries(
107
- Object.entries(times).map(([k, v]) => [k, `${v.toFixed(2)} ms`]),
108
- ),
109
- )
110
- }
111
-
112
- /** @param value {string} */
113
- function validateNsidOptimized(value) {
114
- const { length } = value
115
- if (length > 253 + 1 + 63) {
116
- return { success: false, message: 'NSID is too long (317 chars max)' }
117
- }
118
-
119
- let partCount = 1
120
- let partStart = 0
121
- let partHasLeadingDigit = false
122
- let partHasHyphen = false
123
-
124
- let charCode
125
- for (let i = 0; i < length; i++) {
126
- charCode = value.charCodeAt(i)
127
-
128
- // Hot path: check frequent chars first
129
- if (
130
- (charCode >= 97 && charCode <= 122) /* a-z */ ||
131
- (charCode >= 65 && charCode <= 90) /* A-Z */
132
- ) {
133
- // All good
134
- } else if (charCode >= 48 && charCode <= 57 /* 0-9 */) {
135
- if (i === 0) {
136
- return {
137
- success: false,
138
- message: 'NSID first part may not start with a digit',
139
- }
140
- }
141
-
142
- // All good
143
-
144
- if (i === partStart) {
145
- partHasLeadingDigit = true
146
- }
147
- } else if (charCode === 45 /* - */) {
148
- if (i === partStart) {
149
- return {
150
- success: false,
151
- message: 'NSID part can not start with hyphen',
152
- }
153
- }
154
- if (i === length - 1 || value.charCodeAt(i + 1) === 46 /* . */) {
155
- return { success: false, message: 'NSID part can not end with hyphen' }
156
- }
157
-
158
- // All good
159
-
160
- partHasHyphen = true
161
- } else if (charCode === 46 /* . */) {
162
- // Check prev part size
163
- if (i === partStart) {
164
- return { success: false, message: 'NSID parts can not be empty' }
165
- }
166
- if (i - partStart > 63) {
167
- return { success: false, message: 'NSID part too long (max 63 chars)' }
168
- }
169
-
170
- // All good
171
-
172
- partCount++
173
- partStart = i + 1
174
- partHasHyphen = false
175
- partHasLeadingDigit = false
176
- } else {
177
- return {
178
- success: false,
179
- message:
180
- 'Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)',
181
- }
182
- }
183
- }
184
-
185
- // Check last part size
186
- if (length === partStart) {
187
- return { success: false, message: 'NSID parts can not be empty' }
188
- }
189
- if (length - partStart > 63) {
190
- return { success: false, message: 'NSID part too long (max 63 chars)' }
191
- }
192
-
193
- // Check last part chars
194
- if (partHasHyphen || partHasLeadingDigit) {
195
- return {
196
- success: false,
197
- message:
198
- 'NSID name part must be only letters and digits (and no leading digit)',
199
- }
200
- }
201
-
202
- // Check part count
203
- if (partCount < 3) {
204
- return { success: false, message: 'NSID needs at least three parts' }
205
- }
206
-
207
- return { success: true, value }
208
- }