@atproto/lex-data 0.0.3 → 0.0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @atproto/lex-data
2
2
 
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#4457](https://github.com/bluesky-social/atproto/pull/4457) [`e6b6107`](https://github.com/bluesky-social/atproto/commit/e6b6107e028fee964972274b71f5da1329a7bece) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Use stack based approach in `isLexValue` allowing to be called with very deep structures;
8
+
9
+ - [#4457](https://github.com/bluesky-social/atproto/pull/4457) [`e6b6107`](https://github.com/bluesky-social/atproto/commit/e6b6107e028fee964972274b71f5da1329a7bece) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Do not throw an error from `isLexScalar` (return `false` instead)
10
+
11
+ - [#4457](https://github.com/bluesky-social/atproto/pull/4457) [`e6b6107`](https://github.com/bluesky-social/atproto/commit/e6b6107e028fee964972274b71f5da1329a7bece) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Forbid use of unsafe integers
12
+
13
+ - [#4457](https://github.com/bluesky-social/atproto/pull/4457) [`e6b6107`](https://github.com/bluesky-social/atproto/commit/e6b6107e028fee964972274b71f5da1329a7bece) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Expose `isPlainProto` utility to check if an object is a plain object.
14
+
15
+ - [#4457](https://github.com/bluesky-social/atproto/pull/4457) [`e6b6107`](https://github.com/bluesky-social/atproto/commit/e6b6107e028fee964972274b71f5da1329a7bece) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Rename `parseLanguage` and `isLanguage` exports to `parseLanguageString` and `isLanguageString` respectively
16
+
3
17
  ## 0.0.3
4
18
 
5
19
  ### Patch Changes
package/dist/blob.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import { Cid } from './cid.js';
2
+ /**
3
+ * @note {@link BlobRef} is just a {@link LexMap} with a specific shape.
4
+ */
2
5
  export type BlobRef = {
3
6
  $type: 'blob';
4
7
  mimeType: string;
@@ -8,6 +11,9 @@ export type BlobRef = {
8
11
  export declare function isBlobRef(input: unknown, options?: {
9
12
  strict?: boolean;
10
13
  }): input is BlobRef;
14
+ /**
15
+ * @note {@link LegacyBlobRef} is just a {@link LexMap} with a specific shape.
16
+ */
11
17
  export type LegacyBlobRef = {
12
18
  cid: string;
13
19
  mimeType: string;
@@ -1 +1 @@
1
- {"version":3,"file":"blob.d.ts","sourceRoot":"","sources":["../src/blob.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EAKJ,MAAM,UAAU,CAAA;AAGjB,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;IACR,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,wBAAgB,SAAS,CACvB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7B,KAAK,IAAI,OAAO,CAoDlB;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CA2BtE"}
1
+ {"version":3,"file":"blob.d.ts","sourceRoot":"","sources":["../src/blob.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EAKJ,MAAM,UAAU,CAAA;AAGjB;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,GAAG,CAAA;IACR,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,wBAAgB,SAAS,CACvB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7B,KAAK,IAAI,OAAO,CAyDlB;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CA2BtE"}
package/dist/blob.js CHANGED
@@ -16,7 +16,10 @@ function isBlobRef(input, options) {
16
16
  if (typeof mimeType !== 'string' || !mimeType.includes('/')) {
17
17
  return false;
18
18
  }
19
- if (typeof size !== 'number' || size < 0 || !Number.isInteger(size)) {
19
+ if (typeof size !== 'number' ||
20
+ size < 0 ||
21
+ !Number.isInteger(size) ||
22
+ !Number.isSafeInteger(size)) {
20
23
  return false;
21
24
  }
22
25
  if (typeof ref !== 'object' || ref === null) {
package/dist/blob.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"blob.js","sourceRoot":"","sources":["../src/blob.ts"],"names":[],"mappings":";;AAgBA,8BAuDC;AAOD,0CA2BC;AAzGD,qCAMiB;AACjB,2CAA2C;AAS3C,SAAgB,SAAS,CACvB,KAAc,EACd,OAA8B;IAE9B,IAAI,CAAC,IAAA,yBAAa,EAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,KAAK,EAAE,KAAK,KAAK,MAAM,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,KAAK,CAAA;IACrC,mCAAmC;IACnC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5D,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QACpE,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAA;IACd,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IACE,GAAG,KAAK,OAAO;YACf,GAAG,KAAK,UAAU;YAClB,GAAG,KAAK,KAAK;YACb,GAAG,KAAK,MAAM,EACd,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,IAAA,cAAK,EAAC,GAAG,CAAC,CAAA;IACtB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;QACpB,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,2BAAkB,EAAE,CAAC;YACpC,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK,gCAAuB,EAAE,CAAC;YACnD,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAOD,SAAgB,eAAe,CAAC,KAAc;IAC5C,IAAI,CAAC,IAAA,yBAAa,EAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAA;IAC/B,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACxC,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,IAAA,iBAAQ,EAAC,GAAG,CAAC,CAAA;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import {\n Cid,\n RAW_BIN_MULTICODEC,\n SHA2_256_MULTIHASH_CODE,\n asCid,\n parseCid,\n} from './cid.js'\nimport { isPlainObject } from './object.js'\n\nexport type BlobRef = {\n $type: 'blob'\n mimeType: string\n ref: Cid\n size: number\n}\n\nexport function isBlobRef(\n input: unknown,\n options?: { strict?: boolean },\n): input is BlobRef {\n if (!isPlainObject(input)) {\n return false\n }\n\n if (input?.$type !== 'blob') {\n return false\n }\n\n const { mimeType, size, ref } = input\n // @NOTE Very basic mime validation\n if (typeof mimeType !== 'string' || !mimeType.includes('/')) {\n return false\n }\n\n if (typeof size !== 'number' || size < 0 || !Number.isInteger(size)) {\n return false\n }\n\n if (typeof ref !== 'object' || ref === null) {\n return false\n }\n\n for (const key in input) {\n if (\n key !== '$type' &&\n key !== 'mimeType' &&\n key !== 'ref' &&\n key !== 'size'\n ) {\n return false\n }\n }\n\n const cid = asCid(ref)\n if (!cid) {\n return false\n }\n\n if (options?.strict) {\n if (cid.version !== 1) {\n return false\n }\n if (cid.code !== RAW_BIN_MULTICODEC) {\n return false\n }\n if (cid.multihash.code !== SHA2_256_MULTIHASH_CODE) {\n return false\n }\n }\n\n return true\n}\n\nexport type LegacyBlobRef = {\n cid: string\n mimeType: string\n}\n\nexport function isLegacyBlobRef(input: unknown): input is LegacyBlobRef {\n if (!isPlainObject(input)) {\n return false\n }\n\n const { cid, mimeType } = input\n if (typeof cid !== 'string') {\n return false\n }\n\n if (typeof mimeType !== 'string') {\n return false\n }\n\n for (const key in input) {\n if (key !== 'cid' && key !== 'mimeType') {\n return false\n }\n }\n\n try {\n parseCid(cid)\n } catch {\n return false\n }\n\n return true\n}\n"]}
1
+ {"version":3,"file":"blob.js","sourceRoot":"","sources":["../src/blob.ts"],"names":[],"mappings":";;AAmBA,8BA4DC;AAUD,0CA2BC;AApHD,qCAMiB;AACjB,2CAA2C;AAY3C,SAAgB,SAAS,CACvB,KAAc,EACd,OAA8B;IAE9B,IAAI,CAAC,IAAA,yBAAa,EAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,KAAK,EAAE,KAAK,KAAK,MAAM,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,KAAK,CAAA;IACrC,mCAAmC;IACnC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5D,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IACE,OAAO,IAAI,KAAK,QAAQ;QACxB,IAAI,GAAG,CAAC;QACR,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;QACvB,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAC3B,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAA;IACd,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IACE,GAAG,KAAK,OAAO;YACf,GAAG,KAAK,UAAU;YAClB,GAAG,KAAK,KAAK;YACb,GAAG,KAAK,MAAM,EACd,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,IAAA,cAAK,EAAC,GAAG,CAAC,CAAA;IACtB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;QACpB,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,2BAAkB,EAAE,CAAC;YACpC,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK,gCAAuB,EAAE,CAAC;YACnD,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAUD,SAAgB,eAAe,CAAC,KAAc;IAC5C,IAAI,CAAC,IAAA,yBAAa,EAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAA;IAC/B,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACxC,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,IAAI,CAAC;QACH,IAAA,iBAAQ,EAAC,GAAG,CAAC,CAAA;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import {\n Cid,\n RAW_BIN_MULTICODEC,\n SHA2_256_MULTIHASH_CODE,\n asCid,\n parseCid,\n} from './cid.js'\nimport { isPlainObject } from './object.js'\n\n/**\n * @note {@link BlobRef} is just a {@link LexMap} with a specific shape.\n */\nexport type BlobRef = {\n $type: 'blob'\n mimeType: string\n ref: Cid\n size: number\n}\n\nexport function isBlobRef(\n input: unknown,\n options?: { strict?: boolean },\n): input is BlobRef {\n if (!isPlainObject(input)) {\n return false\n }\n\n if (input?.$type !== 'blob') {\n return false\n }\n\n const { mimeType, size, ref } = input\n // @NOTE Very basic mime validation\n if (typeof mimeType !== 'string' || !mimeType.includes('/')) {\n return false\n }\n\n if (\n typeof size !== 'number' ||\n size < 0 ||\n !Number.isInteger(size) ||\n !Number.isSafeInteger(size)\n ) {\n return false\n }\n\n if (typeof ref !== 'object' || ref === null) {\n return false\n }\n\n for (const key in input) {\n if (\n key !== '$type' &&\n key !== 'mimeType' &&\n key !== 'ref' &&\n key !== 'size'\n ) {\n return false\n }\n }\n\n const cid = asCid(ref)\n if (!cid) {\n return false\n }\n\n if (options?.strict) {\n if (cid.version !== 1) {\n return false\n }\n if (cid.code !== RAW_BIN_MULTICODEC) {\n return false\n }\n if (cid.multihash.code !== SHA2_256_MULTIHASH_CODE) {\n return false\n }\n }\n\n return true\n}\n\n/**\n * @note {@link LegacyBlobRef} is just a {@link LexMap} with a specific shape.\n */\nexport type LegacyBlobRef = {\n cid: string\n mimeType: string\n}\n\nexport function isLegacyBlobRef(input: unknown): input is LegacyBlobRef {\n if (!isPlainObject(input)) {\n return false\n }\n\n const { cid, mimeType } = input\n if (typeof cid !== 'string') {\n return false\n }\n\n if (typeof mimeType !== 'string') {\n return false\n }\n\n for (const key in input) {\n if (key !== 'cid' && key !== 'mimeType') {\n return false\n }\n }\n\n try {\n parseCid(cid)\n } catch {\n return false\n }\n\n return true\n}\n"]}
package/dist/cid.d.ts CHANGED
@@ -18,15 +18,14 @@ declare module 'multiformats/cid' {
18
18
  * `multiformats/cid` in dependent packages, and instead have them rely on the
19
19
  * {@link Cid} interface from `@atproto/lex-data`. The {@link CID} class from
20
20
  * `multiformats` version <10 has compatibility issues with certain TypeScript
21
- * module resolution strategies, which can lead to type errors in dependent
22
- * packages.
21
+ * configuration, which can lead to type errors in dependent packages.
23
22
  *
24
23
  * We are stuck with version 9 because `@atproto` packages did not drop
25
24
  * CommonJS support yet, and multiformats version 10 only supports ES modules.
26
25
  *
27
26
  * In order to avoid compatibility issues, while preparing for future breaking
28
27
  * changes (CID in multiformats v10+ has a slightly different interface), as
29
- * we update or swap out `multiformats`, we provide our own stable `Cid`
28
+ * we update or swap out `multiformats`, we provide our own stable {@link Cid}
30
29
  * interface.
31
30
  */
32
31
  interface CID {
package/dist/cid.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cid.d.ts","sourceRoot":"","sources":["../src/cid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAEtC,eAAO,MAAM,mBAAmB,MAAO,CAAA;AACvC,eAAO,MAAM,kBAAkB,KAAO,CAAA;AAEtC,eAAO,MAAM,uBAAuB,KAAO,CAAA;AAE3C,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAAI;IAC1D,IAAI,EAAE,IAAI,CAAA;IACV,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAED,OAAO,QAAQ,kBAAkB,CAAC;IAChC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,UAAU,GAAG;KAAG;CACjB;AAoBD,OAAO,EAAE,GAAG,EAAE,CAAA;AAEd;;;GAGG;AACH,MAAM,WAAW,GAAG;IAClB,OAAO,EAAE,CAAC,GAAG,CAAC,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,eAAe,CAAA;IAC1B,KAAK,EAAE,UAAU,CAAA;IACjB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAA;IAC/B,QAAQ,IAAI,MAAM,CAAA;CACnB;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,GAAG,GAAG,IAAI,CAEhD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAE3C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAEhD;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,GAAG,CAEpE;AAED,wBAAgB,KAAK,CACnB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7B,KAAK,IAAI,GAAG,CAmBd;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAM7D;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAIxD"}
1
+ {"version":3,"file":"cid.d.ts","sourceRoot":"","sources":["../src/cid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAEtC,eAAO,MAAM,mBAAmB,MAAO,CAAA;AACvC,eAAO,MAAM,kBAAkB,KAAO,CAAA;AAEtC,eAAO,MAAM,uBAAuB,KAAO,CAAA;AAE3C,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAAI;IAC1D,IAAI,EAAE,IAAI,CAAA;IACV,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,UAAU,CAAA;CAClB,CAAA;AAED,OAAO,QAAQ,kBAAkB,CAAC;IAChC;;;;;;;;;;;;;;;;;;OAkBG;IACH,UAAU,GAAG;KAAG;CACjB;AAqBD,OAAO,EAAE,GAAG,EAAE,CAAA;AAEd;;;GAGG;AACH,MAAM,WAAW,GAAG;IAClB,OAAO,EAAE,CAAC,GAAG,CAAC,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,eAAe,CAAA;IAC1B,KAAK,EAAE,UAAU,CAAA;IACjB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAA;IAC/B,QAAQ,IAAI,MAAM,CAAA;CACnB;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,GAAG,GAAG,IAAI,CAEhD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAE3C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAEhD;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,GAAG,CAEpE;AAED,wBAAgB,KAAK,CACnB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7B,KAAK,IAAI,GAAG,CAmBd;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAM7D;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAIxD"}
package/dist/cid.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cid.js","sourceRoot":"","sources":["../src/cid.ts"],"names":[],"mappings":";;;AAuEA,sBAEC;AAED,4BAEC;AAED,8BAEC;AAED,8BAEC;AAED,sBAsBC;AAED,8CAEC;AAED,wCAMC;AAED,oDAIC;AA/HD,0CAAsC;AAwD7B,oFAxDA,SAAG,OAwDA;AAtDC,QAAA,mBAAmB,GAAG,IAAI,CAAA;AAC1B,QAAA,kBAAkB,GAAG,IAAI,CAAA;AAEzB,QAAA,uBAAuB,GAAG,IAAI,CAAA;AAkE3C,SAAgB,KAAK,CAAC,KAAc;IAClC,OAAO,SAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;AACzB,CAAC;AAED,SAAgB,QAAQ,CAAC,KAAa;IACpC,OAAO,SAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;AACzB,CAAC;AAED,SAAgB,SAAS,CAAC,KAAiB;IACzC,OAAO,SAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC1B,CAAC;AAED,SAAgB,SAAS,CAAC,IAAY,EAAE,MAAuB;IAC7D,OAAO,SAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AACnC,CAAC;AAED,SAAgB,KAAK,CACnB,KAAc,EACd,OAA8B;IAE9B,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAA;IACxB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;QACpB,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,0BAAkB,IAAI,GAAG,CAAC,IAAI,KAAK,2BAAmB,EAAE,CAAC;YACxE,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK,+BAAuB,EAAE,CAAC;YACnD,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAgB,iBAAiB,CAAC,KAAa;IAC7C,OAAO,cAAc,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,KAAK,CAAA;AACpD,CAAC;AAED,SAAgB,cAAc,CAAC,KAAa;IAC1C,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAA;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,SAAgB,oBAAoB,CAAC,KAAa;IAChD,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAA;IACvC,CAAC;AACH,CAAC","sourcesContent":["import { CID } from 'multiformats/cid'\n\nexport const DAG_CBOR_MULTICODEC = 0x71\nexport const RAW_BIN_MULTICODEC = 0x55\n\nexport const SHA2_256_MULTIHASH_CODE = 0x12\n\nexport type MultihashDigest<Code extends number = number> = {\n code: Code\n digest: Uint8Array\n size: number\n bytes: Uint8Array\n}\n\ndeclare module 'multiformats/cid' {\n /**\n * @deprecated use the {@link Cid} interface from `@atproto/lex-data`, and\n * related helpers ({@link asCid}, {@link parseCid}, {@link decodeCid},\n * {@link createCid}, {@link isCid}), instead.\n *\n * This is marked as deprecated because we want to discourage direct usage of\n * `multiformats/cid` in dependent packages, and instead have them rely on the\n * {@link Cid} interface from `@atproto/lex-data`. The {@link CID} class from\n * `multiformats` version <10 has compatibility issues with certain TypeScript\n * module resolution strategies, which can lead to type errors in dependent\n * packages.\n *\n * We are stuck with version 9 because `@atproto` packages did not drop\n * CommonJS support yet, and multiformats version 10 only supports ES modules.\n *\n * In order to avoid compatibility issues, while preparing for future breaking\n * changes (CID in multiformats v10+ has a slightly different interface), as\n * we update or swap out `multiformats`, we provide our own stable `Cid`\n * interface.\n */\n interface CID {}\n}\n\n// CID form \"multiformats\" is not very portable because:\n//\n// - In dependent packages that use \"moduleResolution\" set to \"node16\",\n// \"nodenext\" or \"bundler\", TypeScript fails to properly resolve the\n// multiformats package when importing CID from @atproto/lex-data. This causes\n// type errors in those packages. This is caused by the fact that the\n// multiformats version <10 (which is the last version that supports CommonJS)\n// uses \"exports\" field in package.json, which do not contain \"types\"\n// entrypoints.\n// https://www.npmjs.com/package/multiformats/v/9.9.0?activeTab=code\n// - By defining our own Cid interface and related functions, we can have more\n// control over the API we expose to dependent packages.\n// - It allow us to have a stable interface in case we need to swap out, or\n// eventually update multiformats (should we choose to drop CommonJS support)\n// in the future.\n\n// @NOTE Even though it is not portable, we still re-export CID here so that\n// dependent packages where it can be used, have access to it.\nexport { CID }\n\n/**\n * Interface for working with decoded CID string, compatible with\n * {@link CID} implementation.\n */\nexport interface Cid {\n version: 0 | 1\n code: number\n multihash: MultihashDigest\n bytes: Uint8Array\n equals(other: unknown): boolean\n toString(): string\n}\n\nexport function asCid(value: unknown): Cid | null {\n return CID.asCID(value)\n}\n\nexport function parseCid(input: string): Cid {\n return CID.parse(input)\n}\n\nexport function decodeCid(bytes: Uint8Array): Cid {\n return CID.decode(bytes)\n}\n\nexport function createCid(code: number, digest: MultihashDigest): Cid {\n return CID.createV1(code, digest)\n}\n\nexport function isCid(\n value: unknown,\n options?: { strict?: boolean },\n): value is Cid {\n const cid = asCid(value)\n if (!cid) {\n return false\n }\n\n if (options?.strict) {\n if (cid.version !== 1) {\n return false\n }\n if (cid.code !== RAW_BIN_MULTICODEC && cid.code !== DAG_CBOR_MULTICODEC) {\n return false\n }\n if (cid.multihash.code !== SHA2_256_MULTIHASH_CODE) {\n return false\n }\n }\n\n return true\n}\n\nexport function validateCidString(input: string): boolean {\n return parseCidString(input)?.toString() === input\n}\n\nexport function parseCidString(input: string): Cid | undefined {\n try {\n return parseCid(input)\n } catch {\n return undefined\n }\n}\n\nexport function ensureValidCidString(input: string): void {\n if (!validateCidString(input)) {\n throw new Error(`Invalid CID string`)\n }\n}\n"]}
1
+ {"version":3,"file":"cid.js","sourceRoot":"","sources":["../src/cid.ts"],"names":[],"mappings":";;;AAuEA,sBAEC;AAED,4BAEC;AAED,8BAEC;AAED,8BAEC;AAED,sBAsBC;AAED,8CAEC;AAED,wCAMC;AAED,oDAIC;AA/HD,0CAAsC;AAwD7B,oFAxDA,SAAG,OAwDA;AAtDC,QAAA,mBAAmB,GAAG,IAAI,CAAA;AAC1B,QAAA,kBAAkB,GAAG,IAAI,CAAA;AAEzB,QAAA,uBAAuB,GAAG,IAAI,CAAA;AAkE3C,SAAgB,KAAK,CAAC,KAAc;IAClC,OAAO,SAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;AACzB,CAAC;AAED,SAAgB,QAAQ,CAAC,KAAa;IACpC,OAAO,SAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;AACzB,CAAC;AAED,SAAgB,SAAS,CAAC,KAAiB;IACzC,OAAO,SAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC1B,CAAC;AAED,SAAgB,SAAS,CAAC,IAAY,EAAE,MAAuB;IAC7D,OAAO,SAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;AACnC,CAAC;AAED,SAAgB,KAAK,CACnB,KAAc,EACd,OAA8B;IAE9B,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAA;IACxB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC;QACpB,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,0BAAkB,IAAI,GAAG,CAAC,IAAI,KAAK,2BAAmB,EAAE,CAAC;YACxE,OAAO,KAAK,CAAA;QACd,CAAC;QACD,IAAI,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK,+BAAuB,EAAE,CAAC;YACnD,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAgB,iBAAiB,CAAC,KAAa;IAC7C,OAAO,cAAc,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,KAAK,CAAA;AACpD,CAAC;AAED,SAAgB,cAAc,CAAC,KAAa;IAC1C,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAA;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,SAAgB,oBAAoB,CAAC,KAAa;IAChD,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAA;IACvC,CAAC;AACH,CAAC","sourcesContent":["import { CID } from 'multiformats/cid'\n\nexport const DAG_CBOR_MULTICODEC = 0x71\nexport const RAW_BIN_MULTICODEC = 0x55\n\nexport const SHA2_256_MULTIHASH_CODE = 0x12\n\nexport type MultihashDigest<Code extends number = number> = {\n code: Code\n digest: Uint8Array\n size: number\n bytes: Uint8Array\n}\n\ndeclare module 'multiformats/cid' {\n /**\n * @deprecated use the {@link Cid} interface from `@atproto/lex-data`, and\n * related helpers ({@link asCid}, {@link parseCid}, {@link decodeCid},\n * {@link createCid}, {@link isCid}), instead.\n *\n * This is marked as deprecated because we want to discourage direct usage of\n * `multiformats/cid` in dependent packages, and instead have them rely on the\n * {@link Cid} interface from `@atproto/lex-data`. The {@link CID} class from\n * `multiformats` version <10 has compatibility issues with certain TypeScript\n * configuration, which can lead to type errors in dependent packages.\n *\n * We are stuck with version 9 because `@atproto` packages did not drop\n * CommonJS support yet, and multiformats version 10 only supports ES modules.\n *\n * In order to avoid compatibility issues, while preparing for future breaking\n * changes (CID in multiformats v10+ has a slightly different interface), as\n * we update or swap out `multiformats`, we provide our own stable {@link Cid}\n * interface.\n */\n interface CID {}\n}\n\n// multiformats' CID class is not very portable because:\n//\n// - In dependent packages that use \"moduleResolution\" set to \"node16\",\n// \"nodenext\" or \"bundler\", TypeScript fails to properly resolve the\n// multiformats package when importing CID from @atproto/lex-data. This causes\n// type errors in those packages. This is caused by the fact that the\n// multiformats version <10 (which is the last version that supports CommonJS)\n// uses \"exports\" field in package.json, which do not contain \"types\"\n// entrypoints.\n// https://www.npmjs.com/package/multiformats/v/9.9.0?activeTab=code\n// - By defining our own interface and helper functions, we can have more\n// control over the public API exposed by this package.\n// - It allow us to have a stable interface in case we need to swap out, or\n// eventually update multiformats (should we choose to drop CommonJS support)\n// in the future.\n\n// @NOTE Even though it is not portable, we still re-export CID here so that\n// dependent packages where it can be used, have access to it (instead of\n// importing directly from \"multiformats\" or\"multiformats/cid\").\nexport { CID }\n\n/**\n * Interface for working with decoded CID string, compatible with\n * {@link CID} implementation.\n */\nexport interface Cid {\n version: 0 | 1\n code: number\n multihash: MultihashDigest\n bytes: Uint8Array\n equals(other: unknown): boolean\n toString(): string\n}\n\nexport function asCid(value: unknown): Cid | null {\n return CID.asCID(value)\n}\n\nexport function parseCid(input: string): Cid {\n return CID.parse(input)\n}\n\nexport function decodeCid(bytes: Uint8Array): Cid {\n return CID.decode(bytes)\n}\n\nexport function createCid(code: number, digest: MultihashDigest): Cid {\n return CID.createV1(code, digest)\n}\n\nexport function isCid(\n value: unknown,\n options?: { strict?: boolean },\n): value is Cid {\n const cid = asCid(value)\n if (!cid) {\n return false\n }\n\n if (options?.strict) {\n if (cid.version !== 1) {\n return false\n }\n if (cid.code !== RAW_BIN_MULTICODEC && cid.code !== DAG_CBOR_MULTICODEC) {\n return false\n }\n if (cid.multihash.code !== SHA2_256_MULTIHASH_CODE) {\n return false\n }\n }\n\n return true\n}\n\nexport function validateCidString(input: string): boolean {\n return parseCidString(input)?.toString() === input\n}\n\nexport function parseCidString(input: string): Cid | undefined {\n try {\n return parseCid(input)\n } catch {\n return undefined\n }\n}\n\nexport function ensureValidCidString(input: string): void {\n if (!validateCidString(input)) {\n throw new Error(`Invalid CID string`)\n }\n}\n"]}
@@ -8,11 +8,11 @@ export type LanguageTag = {
8
8
  extension?: string;
9
9
  privateUse?: string;
10
10
  };
11
- export declare function parseLanguage(input: string): LanguageTag | null;
11
+ export declare function parseLanguageString(input: string): LanguageTag | null;
12
12
  /**
13
13
  * Validates well-formed BCP 47 syntax
14
14
  *
15
15
  * @see {@link https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1}
16
16
  */
17
- export declare function isLanguage(input: string): boolean;
17
+ export declare function isLanguageString(input: string): boolean;
18
18
  //# sourceMappingURL=language.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"language.d.ts","sourceRoot":"","sources":["../src/language.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAe/D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD"}
1
+ {"version":3,"file":"language.d.ts","sourceRoot":"","sources":["../src/language.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAerE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEvD"}
package/dist/language.js CHANGED
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseLanguage = parseLanguage;
4
- exports.isLanguage = isLanguage;
3
+ exports.parseLanguageString = parseLanguageString;
4
+ exports.isLanguageString = isLanguageString;
5
5
  const BCP47_REGEXP = /^((?<grandfathered>(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?<language>([A-Za-z]{2,3}(-(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?<script>[A-Za-z]{4}))?(-(?<region>[A-Za-z]{2}|[0-9]{3}))?(-(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?<extension>[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?<privateUseA>x(-[A-Za-z0-9]{1,8})+))?)|(?<privateUseB>x(-[A-Za-z0-9]{1,8})+))$/;
6
- function parseLanguage(input) {
6
+ function parseLanguageString(input) {
7
7
  const parsed = input.match(BCP47_REGEXP);
8
8
  if (!parsed?.groups)
9
9
  return null;
@@ -24,7 +24,7 @@ function parseLanguage(input) {
24
24
  *
25
25
  * @see {@link https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1}
26
26
  */
27
- function isLanguage(input) {
27
+ function isLanguageString(input) {
28
28
  return BCP47_REGEXP.test(input);
29
29
  }
30
30
  //# sourceMappingURL=language.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"language.js","sourceRoot":"","sources":["../src/language.ts"],"names":[],"mappings":";;AAcA,sCAeC;AAOD,gCAEC;AAtCD,MAAM,YAAY,GAChB,mlBAAmlB,CAAA;AAarlB,SAAgB,aAAa,CAAC,KAAa;IACzC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,EAAE,MAAM;QAAE,OAAO,IAAI,CAAA;IAEhC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAA;IACzB,OAAO;QACL,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,UAAU,EAAE,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW;KACrD,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,UAAU,CAAC,KAAa;IACtC,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 parseLanguage(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 isLanguage(input: string): boolean {\n return BCP47_REGEXP.test(input)\n}\n"]}
1
+ {"version":3,"file":"language.js","sourceRoot":"","sources":["../src/language.ts"],"names":[],"mappings":";;AAcA,kDAeC;AAOD,4CAEC;AAtCD,MAAM,YAAY,GAChB,mlBAAmlB,CAAA;AAarlB,SAAgB,mBAAmB,CAAC,KAAa;IAC/C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,EAAE,MAAM;QAAE,OAAO,IAAI,CAAA;IAEhC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAA;IACzB,OAAO;QACL,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,UAAU,EAAE,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW;KACrD,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,KAAa;IAC5C,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 isLanguageString(input: string): boolean {\n return BCP47_REGEXP.test(input)\n}\n"]}
package/dist/lex.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lex.d.ts","sourceRoot":"","sources":["../src/lex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAS,MAAM,UAAU,CAAA;AAKrC,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,GAAG,GAAG,UAAU,CAAA;AAC3E,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,QAAQ,EAAE,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE,QAAQ;CAAE,CAAA;AAC5E,MAAM,MAAM,MAAM,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE,QAAQ;CAAE,CAAA;AACjD,MAAM,MAAM,QAAQ,GAAG,QAAQ,EAAE,CAAA;AAEjC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAMxD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAM5D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAc9D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CA4B5D;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AACpD,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,GAAG,KAAK,IAAI,WAAW,CAInE"}
1
+ {"version":3,"file":"lex.d.ts","sourceRoot":"","sources":["../src/lex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAS,MAAM,UAAU,CAAA;AAGrC,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,GAAG,GAAG,UAAU,CAAA;AAC3E,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,QAAQ,EAAE,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE,QAAQ;CAAE,CAAA;AAC5E,MAAM,MAAM,MAAM,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE,QAAQ;CAAE,CAAA;AACjD,MAAM,MAAM,QAAQ,GAAG,QAAQ,EAAE,CAAA;AAEjC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAExD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAa9D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CA8C5D;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AACpD,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,GAAG,KAAK,IAAI,WAAW,CAInE"}
package/dist/lex.js CHANGED
@@ -8,74 +8,75 @@ exports.isTypedLexMap = isTypedLexMap;
8
8
  const cid_js_1 = require("./cid.js");
9
9
  const object_js_1 = require("./object.js");
10
10
  function isLexMap(value) {
11
- if (!(0, object_js_1.isPlainObject)(value))
12
- return false;
13
- for (const key in value) {
14
- if (!isLexValue(value[key]))
15
- return false;
16
- }
17
- return true;
11
+ return (0, object_js_1.isPlainObject)(value) && Object.values(value).every(isLexValue);
18
12
  }
19
13
  function isLexArray(value) {
20
- if (!Array.isArray(value))
21
- return false;
22
- for (let i = 0; i < value.length; i++) {
23
- if (!isLexValue(value[i]))
24
- return false;
25
- }
26
- return true;
14
+ return Array.isArray(value) && value.every(isLexValue);
27
15
  }
28
16
  function isLexScalar(value) {
29
17
  switch (typeof value) {
30
18
  case 'object':
31
- if (value === null)
32
- return true;
33
- return value instanceof Uint8Array || (0, cid_js_1.isCid)(value);
19
+ return value === null || value instanceof Uint8Array || (0, cid_js_1.isCid)(value);
34
20
  case 'string':
35
21
  case 'boolean':
36
22
  return true;
37
23
  case 'number':
38
24
  if (Number.isInteger(value))
39
25
  return true;
40
- throw new TypeError(`Invalid Lex value: ${value}`);
26
+ // fallthrough
41
27
  default:
42
- throw new TypeError(`Invalid Lex value: ${typeof value}`);
28
+ return false;
43
29
  }
44
30
  }
45
31
  function isLexValue(value) {
46
- switch (typeof value) {
47
- case 'number':
48
- if (!Number.isInteger(value))
49
- return false;
50
- // fallthrough
51
- case 'string':
52
- case 'boolean':
53
- return true;
54
- case 'object':
55
- if (value === null)
56
- return true;
57
- if (Array.isArray(value)) {
58
- for (let i = 0; i < value.length; i++) {
59
- if (!isLexValue(value[i]))
32
+ // Using a stack to avoid recursion depth issues.
33
+ const stack = [value];
34
+ // Cyclic structures are not valid LexValues as they cannot be serialized to
35
+ // JSON or CBOR. This also allows us to avoid infinite loops when traversing
36
+ // the structure.
37
+ const visited = new Set();
38
+ do {
39
+ const value = stack.pop();
40
+ // Optimization: we are not using `isLexScalar` here to avoid extra function
41
+ // calls, and to avoid computing `typeof value` multiple times.
42
+ switch (typeof value) {
43
+ case 'object':
44
+ if (value === null) {
45
+ // LexScalar
46
+ }
47
+ else if ((0, object_js_1.isPlainProto)(value)) {
48
+ if (visited.has(value))
60
49
  return false;
50
+ visited.add(value);
51
+ stack.push(...Object.values(value));
61
52
  }
62
- return true;
63
- }
64
- if ((0, object_js_1.isPlainObject)(value)) {
65
- for (const key in value) {
66
- if (!isLexValue(value[key]))
53
+ else if (Array.isArray(value)) {
54
+ if (visited.has(value))
67
55
  return false;
56
+ visited.add(value);
57
+ stack.push(...value);
68
58
  }
69
- return true;
70
- }
71
- if (value instanceof Uint8Array)
72
- return true;
73
- if ((0, cid_js_1.isCid)(value))
74
- return true;
75
- // fallthrough
76
- default:
77
- return false;
78
- }
59
+ else if (value instanceof Uint8Array || (0, cid_js_1.isCid)(value)) {
60
+ // LexScalar
61
+ }
62
+ else {
63
+ return false;
64
+ }
65
+ break;
66
+ case 'string':
67
+ case 'boolean':
68
+ break;
69
+ case 'number':
70
+ if (Number.isInteger(value))
71
+ break;
72
+ // fallthrough
73
+ default:
74
+ return false;
75
+ }
76
+ } while (stack.length > 0);
77
+ // Optimization: ease GC's work
78
+ visited.clear();
79
+ return true;
79
80
  }
80
81
  function isTypedLexMap(value) {
81
82
  return (isLexMap(value) && typeof value.$type === 'string' && value.$type.length > 0);
package/dist/lex.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lex.js","sourceRoot":"","sources":["../src/lex.ts"],"names":[],"mappings":"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","sourcesContent":["import { Cid, isCid } from './cid.js'\nimport { isPlainObject } from './object.js'\n\n// @NOTE BlobRef is just a special case of LexMap.\n\nexport type LexScalar = number | string | boolean | null | Cid | Uint8Array\nexport type LexValue = LexScalar | LexValue[] | { [_ in string]?: LexValue }\nexport type LexMap = { [_ in string]?: LexValue }\nexport type LexArray = LexValue[]\n\nexport function isLexMap(value: unknown): value is LexMap {\n if (!isPlainObject(value)) return false\n for (const key in value) {\n if (!isLexValue(value[key])) return false\n }\n return true\n}\n\nexport function isLexArray(value: unknown): value is LexArray {\n if (!Array.isArray(value)) return false\n for (let i = 0; i < value.length; i++) {\n if (!isLexValue(value[i])) return false\n }\n return true\n}\n\nexport function isLexScalar(value: unknown): value is LexScalar {\n switch (typeof value) {\n case 'object':\n if (value === null) return true\n return value instanceof Uint8Array || isCid(value)\n case 'string':\n case 'boolean':\n return true\n case 'number':\n if (Number.isInteger(value)) return true\n throw new TypeError(`Invalid Lex value: ${value}`)\n default:\n throw new TypeError(`Invalid Lex value: ${typeof value}`)\n }\n}\n\nexport function isLexValue(value: unknown): value is LexValue {\n switch (typeof value) {\n case 'number':\n if (!Number.isInteger(value)) return false\n // fallthrough\n case 'string':\n case 'boolean':\n return true\n case 'object':\n if (value === null) return true\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n if (!isLexValue(value[i])) return false\n }\n return true\n }\n if (isPlainObject(value)) {\n for (const key in value) {\n if (!isLexValue(value[key])) return false\n }\n return true\n }\n if (value instanceof Uint8Array) return true\n if (isCid(value)) return true\n // fallthrough\n default:\n return false\n }\n}\n\nexport type TypedLexMap = LexMap & { $type: string }\nexport function isTypedLexMap(value: LexValue): value is TypedLexMap {\n return (\n isLexMap(value) && typeof value.$type === 'string' && value.$type.length > 0\n )\n}\n"]}
1
+ {"version":3,"file":"lex.js","sourceRoot":"","sources":["../src/lex.ts"],"names":[],"mappings":"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","sourcesContent":["import { Cid, isCid } from './cid.js'\nimport { isPlainObject, isPlainProto } from './object.js'\n\nexport type LexScalar = number | string | boolean | null | Cid | Uint8Array\nexport type LexValue = LexScalar | LexValue[] | { [_ in string]?: LexValue }\nexport type LexMap = { [_ in string]?: LexValue }\nexport type LexArray = LexValue[]\n\nexport function isLexMap(value: unknown): value is LexMap {\n return isPlainObject(value) && Object.values(value).every(isLexValue)\n}\n\nexport function isLexArray(value: unknown): value is LexArray {\n return Array.isArray(value) && value.every(isLexValue)\n}\n\nexport function isLexScalar(value: unknown): value is LexScalar {\n switch (typeof value) {\n case 'object':\n return value === null || value instanceof Uint8Array || isCid(value)\n case 'string':\n case 'boolean':\n return true\n case 'number':\n if (Number.isInteger(value)) return true\n // fallthrough\n default:\n return false\n }\n}\n\nexport function isLexValue(value: unknown): value is LexValue {\n // Using a stack to avoid recursion depth issues.\n const stack: unknown[] = [value]\n // Cyclic structures are not valid LexValues as they cannot be serialized to\n // JSON or CBOR. This also allows us to avoid infinite loops when traversing\n // the structure.\n const visited = new Set<object>()\n\n do {\n const value = stack.pop()!\n\n // Optimization: we are not using `isLexScalar` here to avoid extra function\n // calls, and to avoid computing `typeof value` multiple times.\n switch (typeof value) {\n case 'object':\n if (value === null) {\n // LexScalar\n } else if (isPlainProto(value)) {\n if (visited.has(value)) return false\n visited.add(value)\n stack.push(...Object.values(value))\n } else if (Array.isArray(value)) {\n if (visited.has(value)) return false\n visited.add(value)\n stack.push(...value)\n } else if (value instanceof Uint8Array || isCid(value)) {\n // LexScalar\n } else {\n return false\n }\n break\n case 'string':\n case 'boolean':\n break\n case 'number':\n if (Number.isInteger(value)) break\n // fallthrough\n default:\n return false\n }\n } while (stack.length > 0)\n\n // Optimization: ease GC's work\n visited.clear()\n\n return true\n}\n\nexport type TypedLexMap = LexMap & { $type: string }\nexport function isTypedLexMap(value: LexValue): value is TypedLexMap {\n return (\n isLexMap(value) && typeof value.$type === 'string' && value.$type.length > 0\n )\n}\n"]}
package/dist/object.d.ts CHANGED
@@ -1,3 +1,15 @@
1
+ /**
2
+ * Checks whether the input is an object (not null).
3
+ */
1
4
  export declare function isObject(input: unknown): input is object;
2
- export declare function isPlainObject(input: unknown): input is object & Record<string, unknown>;
5
+ /**
6
+ * Checks whether the input is an object (not null) whose prototype is either
7
+ * null or `Object.prototype`.
8
+ */
9
+ export declare function isPlainObject(input: unknown): input is Record<string, unknown>;
10
+ /**
11
+ * Checks whether the prototype of the input object is either null or
12
+ * `Object.prototype`.
13
+ */
14
+ export declare function isPlainProto(input: object): input is Record<string, unknown>;
3
15
  //# sourceMappingURL=object.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../src/object.ts"],"names":[],"mappings":"AAAA,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAExD;AAKD,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAW3C"}
1
+ {"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../src/object.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAExD;AAKD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,oCAE3C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAU5E"}
package/dist/object.js CHANGED
@@ -2,14 +2,27 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isObject = isObject;
4
4
  exports.isPlainObject = isPlainObject;
5
+ exports.isPlainProto = isPlainProto;
6
+ /**
7
+ * Checks whether the input is an object (not null).
8
+ */
5
9
  function isObject(input) {
6
10
  return input != null && typeof input === 'object';
7
11
  }
8
12
  const ObjectProto = Object.prototype;
9
13
  const ObjectToString = Object.prototype.toString;
14
+ /**
15
+ * Checks whether the input is an object (not null) whose prototype is either
16
+ * null or `Object.prototype`.
17
+ */
10
18
  function isPlainObject(input) {
11
- if (!input || typeof input !== 'object')
12
- return false;
19
+ return isObject(input) && isPlainProto(input);
20
+ }
21
+ /**
22
+ * Checks whether the prototype of the input object is either null or
23
+ * `Object.prototype`.
24
+ */
25
+ function isPlainProto(input) {
13
26
  const proto = Object.getPrototypeOf(input);
14
27
  if (proto === null)
15
28
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"object.js","sourceRoot":"","sources":["../src/object.ts"],"names":[],"mappings":";;AAAA,4BAEC;AAKD,sCAaC;AApBD,SAAgB,QAAQ,CAAC,KAAc;IACrC,OAAO,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAA;AACnD,CAAC;AAED,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAA;AACpC,MAAM,cAAc,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAA;AAEhD,SAAgB,aAAa,CAC3B,KAAc;IAEd,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACrD,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;IAC1C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC/B,OAAO,CACL,CAAC,KAAK,KAAK,WAAW;QACpB,sEAAsE;QACtE,6BAA6B;QAC7B,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;QACxC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,iBAAiB,CACjD,CAAA;AACH,CAAC","sourcesContent":["export function isObject(input: unknown): input is object {\n return input != null && typeof input === 'object'\n}\n\nconst ObjectProto = Object.prototype\nconst ObjectToString = Object.prototype.toString\n\nexport function isPlainObject(\n input: unknown,\n): input is object & Record<string, unknown> {\n if (!input || typeof input !== 'object') return false\n const proto = Object.getPrototypeOf(input)\n if (proto === null) return true\n return (\n (proto === ObjectProto ||\n // Needed to support NodeJS's `runInNewContext` which produces objects\n // with a different prototype\n Object.getPrototypeOf(proto) === null) &&\n ObjectToString.call(input) === '[object Object]'\n )\n}\n"]}
1
+ {"version":3,"file":"object.js","sourceRoot":"","sources":["../src/object.ts"],"names":[],"mappings":";;AAGA,4BAEC;AASD,sCAEC;AAMD,oCAUC;AAhCD;;GAEG;AACH,SAAgB,QAAQ,CAAC,KAAc;IACrC,OAAO,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAA;AACnD,CAAC;AAED,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAA;AACpC,MAAM,cAAc,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAA;AAEhD;;;GAGG;AACH,SAAgB,aAAa,CAAC,KAAc;IAC1C,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC,CAAA;AAC/C,CAAC;AAED;;;GAGG;AACH,SAAgB,YAAY,CAAC,KAAa;IACxC,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;IAC1C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC/B,OAAO,CACL,CAAC,KAAK,KAAK,WAAW;QACpB,sEAAsE;QACtE,6BAA6B;QAC7B,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;QACxC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,iBAAiB,CACjD,CAAA;AACH,CAAC","sourcesContent":["/**\n * Checks whether the input is an object (not null).\n */\nexport function isObject(input: unknown): input is object {\n return input != null && typeof input === 'object'\n}\n\nconst ObjectProto = Object.prototype\nconst ObjectToString = Object.prototype.toString\n\n/**\n * Checks whether the input is an object (not null) whose prototype is either\n * null or `Object.prototype`.\n */\nexport function isPlainObject(input: unknown) {\n return isObject(input) && isPlainProto(input)\n}\n\n/**\n * Checks whether the prototype of the input object is either null or\n * `Object.prototype`.\n */\nexport function isPlainProto(input: object): input is Record<string, unknown> {\n const proto = Object.getPrototypeOf(input)\n if (proto === null) return true\n return (\n (proto === ObjectProto ||\n // Needed to support NodeJS's `runInNewContext` which produces objects\n // with a different prototype\n Object.getPrototypeOf(proto) === null) &&\n ObjectToString.call(input) === '[object Object]'\n )\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/lex-data",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "license": "MIT",
5
5
  "description": "Core utilities for AT Lexicons",
6
6
  "keywords": [
package/src/blob.ts CHANGED
@@ -7,6 +7,9 @@ import {
7
7
  } from './cid.js'
8
8
  import { isPlainObject } from './object.js'
9
9
 
10
+ /**
11
+ * @note {@link BlobRef} is just a {@link LexMap} with a specific shape.
12
+ */
10
13
  export type BlobRef = {
11
14
  $type: 'blob'
12
15
  mimeType: string
@@ -32,7 +35,12 @@ export function isBlobRef(
32
35
  return false
33
36
  }
34
37
 
35
- if (typeof size !== 'number' || size < 0 || !Number.isInteger(size)) {
38
+ if (
39
+ typeof size !== 'number' ||
40
+ size < 0 ||
41
+ !Number.isInteger(size) ||
42
+ !Number.isSafeInteger(size)
43
+ ) {
36
44
  return false
37
45
  }
38
46
 
@@ -71,6 +79,9 @@ export function isBlobRef(
71
79
  return true
72
80
  }
73
81
 
82
+ /**
83
+ * @note {@link LegacyBlobRef} is just a {@link LexMap} with a specific shape.
84
+ */
74
85
  export type LegacyBlobRef = {
75
86
  cid: string
76
87
  mimeType: string
package/src/cid.ts CHANGED
@@ -22,21 +22,20 @@ declare module 'multiformats/cid' {
22
22
  * `multiformats/cid` in dependent packages, and instead have them rely on the
23
23
  * {@link Cid} interface from `@atproto/lex-data`. The {@link CID} class from
24
24
  * `multiformats` version <10 has compatibility issues with certain TypeScript
25
- * module resolution strategies, which can lead to type errors in dependent
26
- * packages.
25
+ * configuration, which can lead to type errors in dependent packages.
27
26
  *
28
27
  * We are stuck with version 9 because `@atproto` packages did not drop
29
28
  * CommonJS support yet, and multiformats version 10 only supports ES modules.
30
29
  *
31
30
  * In order to avoid compatibility issues, while preparing for future breaking
32
31
  * changes (CID in multiformats v10+ has a slightly different interface), as
33
- * we update or swap out `multiformats`, we provide our own stable `Cid`
32
+ * we update or swap out `multiformats`, we provide our own stable {@link Cid}
34
33
  * interface.
35
34
  */
36
35
  interface CID {}
37
36
  }
38
37
 
39
- // CID form "multiformats" is not very portable because:
38
+ // multiformats' CID class is not very portable because:
40
39
  //
41
40
  // - In dependent packages that use "moduleResolution" set to "node16",
42
41
  // "nodenext" or "bundler", TypeScript fails to properly resolve the
@@ -46,14 +45,15 @@ declare module 'multiformats/cid' {
46
45
  // uses "exports" field in package.json, which do not contain "types"
47
46
  // entrypoints.
48
47
  // https://www.npmjs.com/package/multiformats/v/9.9.0?activeTab=code
49
- // - By defining our own Cid interface and related functions, we can have more
50
- // control over the API we expose to dependent packages.
48
+ // - By defining our own interface and helper functions, we can have more
49
+ // control over the public API exposed by this package.
51
50
  // - It allow us to have a stable interface in case we need to swap out, or
52
51
  // eventually update multiformats (should we choose to drop CommonJS support)
53
52
  // in the future.
54
53
 
55
54
  // @NOTE Even though it is not portable, we still re-export CID here so that
56
- // dependent packages where it can be used, have access to it.
55
+ // dependent packages where it can be used, have access to it (instead of
56
+ // importing directly from "multiformats" or"multiformats/cid").
57
57
  export { CID }
58
58
 
59
59
  /**
@@ -1,62 +1,62 @@
1
- import { isLanguage, parseLanguage } from './language'
1
+ import { isLanguageString, parseLanguageString } from './language'
2
2
 
3
3
  describe('string', () => {
4
4
  describe('languages', () => {
5
5
  it('validates BCP 47', () => {
6
6
  // valid
7
- expect(isLanguage('de')).toEqual(true)
8
- expect(isLanguage('de-CH')).toEqual(true)
9
- expect(isLanguage('de-DE-1901')).toEqual(true)
10
- expect(isLanguage('es-419')).toEqual(true)
11
- expect(isLanguage('sl-IT-nedis')).toEqual(true)
12
- expect(isLanguage('mn-Cyrl-MN')).toEqual(true)
13
- expect(isLanguage('x-fr-CH')).toEqual(true)
14
- expect(isLanguage('en-GB-boont-r-extended-sequence-x-private')).toEqual(
15
- true,
16
- )
17
- expect(isLanguage('sr-Cyrl')).toEqual(true)
18
- expect(isLanguage('hy-Latn-IT-arevela')).toEqual(true)
19
- expect(isLanguage('i-klingon')).toEqual(true)
7
+ expect(isLanguageString('de')).toEqual(true)
8
+ expect(isLanguageString('de-CH')).toEqual(true)
9
+ expect(isLanguageString('de-DE-1901')).toEqual(true)
10
+ expect(isLanguageString('es-419')).toEqual(true)
11
+ expect(isLanguageString('sl-IT-nedis')).toEqual(true)
12
+ expect(isLanguageString('mn-Cyrl-MN')).toEqual(true)
13
+ expect(isLanguageString('x-fr-CH')).toEqual(true)
14
+ expect(
15
+ isLanguageString('en-GB-boont-r-extended-sequence-x-private'),
16
+ ).toEqual(true)
17
+ expect(isLanguageString('sr-Cyrl')).toEqual(true)
18
+ expect(isLanguageString('hy-Latn-IT-arevela')).toEqual(true)
19
+ expect(isLanguageString('i-klingon')).toEqual(true)
20
20
  // invalid
21
- expect(isLanguage('')).toEqual(false)
22
- expect(isLanguage('x')).toEqual(false)
23
- expect(isLanguage('de-CH-')).toEqual(false)
24
- expect(isLanguage('i-bad-grandfathered')).toEqual(false)
21
+ expect(isLanguageString('')).toEqual(false)
22
+ expect(isLanguageString('x')).toEqual(false)
23
+ expect(isLanguageString('de-CH-')).toEqual(false)
24
+ expect(isLanguageString('i-bad-grandfathered')).toEqual(false)
25
25
  })
26
26
 
27
27
  it('parses BCP 47', () => {
28
28
  // valid
29
- expect(parseLanguage('de')).toEqual({
29
+ expect(parseLanguageString('de')).toEqual({
30
30
  language: 'de',
31
31
  })
32
- expect(parseLanguage('de-CH')).toEqual({
32
+ expect(parseLanguageString('de-CH')).toEqual({
33
33
  language: 'de',
34
34
  region: 'CH',
35
35
  })
36
- expect(parseLanguage('de-DE-1901')).toEqual({
36
+ expect(parseLanguageString('de-DE-1901')).toEqual({
37
37
  language: 'de',
38
38
  region: 'DE',
39
39
  variant: '1901',
40
40
  })
41
- expect(parseLanguage('es-419')).toEqual({
41
+ expect(parseLanguageString('es-419')).toEqual({
42
42
  language: 'es',
43
43
  region: '419',
44
44
  })
45
- expect(parseLanguage('sl-IT-nedis')).toEqual({
45
+ expect(parseLanguageString('sl-IT-nedis')).toEqual({
46
46
  language: 'sl',
47
47
  region: 'IT',
48
48
  variant: 'nedis',
49
49
  })
50
- expect(parseLanguage('mn-Cyrl-MN')).toEqual({
50
+ expect(parseLanguageString('mn-Cyrl-MN')).toEqual({
51
51
  language: 'mn',
52
52
  script: 'Cyrl',
53
53
  region: 'MN',
54
54
  })
55
- expect(parseLanguage('x-fr-CH')).toEqual({
55
+ expect(parseLanguageString('x-fr-CH')).toEqual({
56
56
  privateUse: 'x-fr-CH',
57
57
  })
58
58
  expect(
59
- parseLanguage('en-GB-boont-r-extended-sequence-x-private'),
59
+ parseLanguageString('en-GB-boont-r-extended-sequence-x-private'),
60
60
  ).toEqual({
61
61
  language: 'en',
62
62
  region: 'GB',
@@ -64,24 +64,24 @@ describe('string', () => {
64
64
  extension: 'r-extended-sequence',
65
65
  privateUse: 'x-private',
66
66
  })
67
- expect(parseLanguage('sr-Cyrl')).toEqual({
67
+ expect(parseLanguageString('sr-Cyrl')).toEqual({
68
68
  language: 'sr',
69
69
  script: 'Cyrl',
70
70
  })
71
- expect(parseLanguage('hy-Latn-IT-arevela')).toEqual({
71
+ expect(parseLanguageString('hy-Latn-IT-arevela')).toEqual({
72
72
  language: 'hy',
73
73
  script: 'Latn',
74
74
  region: 'IT',
75
75
  variant: 'arevela',
76
76
  })
77
- expect(parseLanguage('i-klingon')).toEqual({
77
+ expect(parseLanguageString('i-klingon')).toEqual({
78
78
  grandfathered: 'i-klingon',
79
79
  })
80
80
  // invalid
81
- expect(parseLanguage('')).toEqual(null)
82
- expect(parseLanguage('x')).toEqual(null)
83
- expect(parseLanguage('de-CH-')).toEqual(null)
84
- expect(parseLanguage('i-bad-grandfathered')).toEqual(null)
81
+ expect(parseLanguageString('')).toEqual(null)
82
+ expect(parseLanguageString('x')).toEqual(null)
83
+ expect(parseLanguageString('de-CH-')).toEqual(null)
84
+ expect(parseLanguageString('i-bad-grandfathered')).toEqual(null)
85
85
  })
86
86
  })
87
87
  })
package/src/language.ts CHANGED
@@ -12,7 +12,7 @@ export type LanguageTag = {
12
12
  privateUse?: string
13
13
  }
14
14
 
15
- export function parseLanguage(input: string): LanguageTag | null {
15
+ export function parseLanguageString(input: string): LanguageTag | null {
16
16
  const parsed = input.match(BCP47_REGEXP)
17
17
  if (!parsed?.groups) return null
18
18
 
@@ -34,6 +34,6 @@ export function parseLanguage(input: string): LanguageTag | null {
34
34
  *
35
35
  * @see {@link https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1}
36
36
  */
37
- export function isLanguage(input: string): boolean {
37
+ export function isLanguageString(input: string): boolean {
38
38
  return BCP47_REGEXP.test(input)
39
39
  }
package/src/lex.test.ts CHANGED
@@ -1,4 +1,107 @@
1
- import { isTypedLexMap } from './lex'
1
+ import { isLexValue, isTypedLexMap } from './lex'
2
+
3
+ describe('isLexValue', () => {
4
+ describe('valid values', () => {
5
+ for (const { note, value } of [
6
+ { note: 'string', value: 'hello' },
7
+ { note: 'boolean', value: true },
8
+ { note: 'null', value: null },
9
+ { note: 'Uint8Array', value: new Uint8Array([1, 2, 3]) },
10
+ {
11
+ note: 'Cid',
12
+ value: { code: 1, version: 1, multihash: new Uint8Array([18, 32]) },
13
+ },
14
+ {
15
+ note: 'record with Lex values',
16
+ value: {
17
+ a: 123,
18
+ b: 'blah',
19
+ c: true,
20
+ d: null,
21
+ e: new Uint8Array([1, 2, 3]),
22
+ f: {
23
+ nested: 'value',
24
+ },
25
+ g: [1, 2, 3],
26
+ },
27
+ },
28
+ {
29
+ note: 'list with Lex values',
30
+ value: [
31
+ 123,
32
+ 'blah',
33
+ true,
34
+ null,
35
+ new Uint8Array([1, 2, 3]),
36
+ {
37
+ nested: 'value',
38
+ },
39
+ [1, 2, 3],
40
+ ],
41
+ },
42
+ ]) {
43
+ it(note, () => {
44
+ expect(isLexValue(value)).toBe(true)
45
+ })
46
+ }
47
+ })
48
+
49
+ describe('invalid values', () => {
50
+ for (const { note, value } of [
51
+ { note: 'float', value: 123.456 },
52
+ { note: 'undefined', value: undefined },
53
+ { note: 'function', value: () => {} },
54
+ {
55
+ note: 'record with non-Lex value',
56
+ value: {
57
+ a: 123,
58
+ b: () => {},
59
+ },
60
+ },
61
+ {
62
+ note: 'list with non-Lex value',
63
+ value: [123, 'blah', () => {}],
64
+ },
65
+ ]) {
66
+ it(note, () => {
67
+ expect(isLexValue(value)).toBe(false)
68
+ })
69
+ }
70
+ })
71
+
72
+ it('handles cyclic structures', () => {
73
+ const record: any = {
74
+ a: 123,
75
+ b: 'blah',
76
+ }
77
+ record.c = record
78
+
79
+ expect(isLexValue(record)).toBe(false)
80
+
81
+ const list: any[] = [123, 'blah']
82
+ list.push(list)
83
+
84
+ expect(isLexValue(list)).toBe(false)
85
+
86
+ const complex: any = {
87
+ a: {
88
+ b: [1, 2, 3],
89
+ },
90
+ }
91
+ complex.a.b.push(complex)
92
+
93
+ expect(isLexValue(complex)).toBe(false)
94
+ })
95
+
96
+ it('handles deeply nested structures', () => {
97
+ type Value = null | { nested: Value }
98
+ let value: Value = null
99
+ for (let i = 0; i < 1_000_000; i++) {
100
+ value = { nested: value }
101
+ }
102
+ expect(isLexValue(value)).toBe(true)
103
+ })
104
+ })
2
105
 
3
106
  describe('isLexMap', () => {
4
107
  it('returns true for valid LexMap', () => {
package/src/lex.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import { Cid, isCid } from './cid.js'
2
- import { isPlainObject } from './object.js'
3
-
4
- // @NOTE BlobRef is just a special case of LexMap.
2
+ import { isPlainObject, isPlainProto } from './object.js'
5
3
 
6
4
  export type LexScalar = number | string | boolean | null | Cid | Uint8Array
7
5
  export type LexValue = LexScalar | LexValue[] | { [_ in string]?: LexValue }
@@ -9,65 +7,74 @@ export type LexMap = { [_ in string]?: LexValue }
9
7
  export type LexArray = LexValue[]
10
8
 
11
9
  export function isLexMap(value: unknown): value is LexMap {
12
- if (!isPlainObject(value)) return false
13
- for (const key in value) {
14
- if (!isLexValue(value[key])) return false
15
- }
16
- return true
10
+ return isPlainObject(value) && Object.values(value).every(isLexValue)
17
11
  }
18
12
 
19
13
  export function isLexArray(value: unknown): value is LexArray {
20
- if (!Array.isArray(value)) return false
21
- for (let i = 0; i < value.length; i++) {
22
- if (!isLexValue(value[i])) return false
23
- }
24
- return true
14
+ return Array.isArray(value) && value.every(isLexValue)
25
15
  }
26
16
 
27
17
  export function isLexScalar(value: unknown): value is LexScalar {
28
18
  switch (typeof value) {
29
19
  case 'object':
30
- if (value === null) return true
31
- return value instanceof Uint8Array || isCid(value)
20
+ return value === null || value instanceof Uint8Array || isCid(value)
32
21
  case 'string':
33
22
  case 'boolean':
34
23
  return true
35
24
  case 'number':
36
25
  if (Number.isInteger(value)) return true
37
- throw new TypeError(`Invalid Lex value: ${value}`)
26
+ // fallthrough
38
27
  default:
39
- throw new TypeError(`Invalid Lex value: ${typeof value}`)
28
+ return false
40
29
  }
41
30
  }
42
31
 
43
32
  export function isLexValue(value: unknown): value is LexValue {
44
- switch (typeof value) {
45
- case 'number':
46
- if (!Number.isInteger(value)) return false
47
- // fallthrough
48
- case 'string':
49
- case 'boolean':
50
- return true
51
- case 'object':
52
- if (value === null) return true
53
- if (Array.isArray(value)) {
54
- for (let i = 0; i < value.length; i++) {
55
- if (!isLexValue(value[i])) return false
56
- }
57
- return true
58
- }
59
- if (isPlainObject(value)) {
60
- for (const key in value) {
61
- if (!isLexValue(value[key])) return false
33
+ // Using a stack to avoid recursion depth issues.
34
+ const stack: unknown[] = [value]
35
+ // Cyclic structures are not valid LexValues as they cannot be serialized to
36
+ // JSON or CBOR. This also allows us to avoid infinite loops when traversing
37
+ // the structure.
38
+ const visited = new Set<object>()
39
+
40
+ do {
41
+ const value = stack.pop()!
42
+
43
+ // Optimization: we are not using `isLexScalar` here to avoid extra function
44
+ // calls, and to avoid computing `typeof value` multiple times.
45
+ switch (typeof value) {
46
+ case 'object':
47
+ if (value === null) {
48
+ // LexScalar
49
+ } else if (isPlainProto(value)) {
50
+ if (visited.has(value)) return false
51
+ visited.add(value)
52
+ stack.push(...Object.values(value))
53
+ } else if (Array.isArray(value)) {
54
+ if (visited.has(value)) return false
55
+ visited.add(value)
56
+ stack.push(...value)
57
+ } else if (value instanceof Uint8Array || isCid(value)) {
58
+ // LexScalar
59
+ } else {
60
+ return false
62
61
  }
63
- return true
64
- }
65
- if (value instanceof Uint8Array) return true
66
- if (isCid(value)) return true
67
- // fallthrough
68
- default:
69
- return false
70
- }
62
+ break
63
+ case 'string':
64
+ case 'boolean':
65
+ break
66
+ case 'number':
67
+ if (Number.isInteger(value)) break
68
+ // fallthrough
69
+ default:
70
+ return false
71
+ }
72
+ } while (stack.length > 0)
73
+
74
+ // Optimization: ease GC's work
75
+ visited.clear()
76
+
77
+ return true
71
78
  }
72
79
 
73
80
  export type TypedLexMap = LexMap & { $type: string }
package/src/object.ts CHANGED
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Checks whether the input is an object (not null).
3
+ */
1
4
  export function isObject(input: unknown): input is object {
2
5
  return input != null && typeof input === 'object'
3
6
  }
@@ -5,10 +8,19 @@ export function isObject(input: unknown): input is object {
5
8
  const ObjectProto = Object.prototype
6
9
  const ObjectToString = Object.prototype.toString
7
10
 
8
- export function isPlainObject(
9
- input: unknown,
10
- ): input is object & Record<string, unknown> {
11
- if (!input || typeof input !== 'object') return false
11
+ /**
12
+ * Checks whether the input is an object (not null) whose prototype is either
13
+ * null or `Object.prototype`.
14
+ */
15
+ export function isPlainObject(input: unknown) {
16
+ return isObject(input) && isPlainProto(input)
17
+ }
18
+
19
+ /**
20
+ * Checks whether the prototype of the input object is either null or
21
+ * `Object.prototype`.
22
+ */
23
+ export function isPlainProto(input: object): input is Record<string, unknown> {
12
24
  const proto = Object.getPrototypeOf(input)
13
25
  if (proto === null) return true
14
26
  return (
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "extends": "../../../tsconfig/tests.json",
3
- "include": ["./tests", "./src/**.test.ts"],
3
+ "include": ["./tests", "./src/**/*.test.ts"],
4
4
  "compilerOptions": {
5
5
  "noImplicitAny": true,
6
6
  "rootDir": "./",