@atproto/lex-data 0.0.2 → 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 +20 -0
- package/dist/blob.d.ts +6 -0
- package/dist/blob.d.ts.map +1 -1
- package/dist/blob.js +4 -1
- package/dist/blob.js.map +1 -1
- package/dist/cid.d.ts +2 -3
- package/dist/cid.d.ts.map +1 -1
- package/dist/cid.js.map +1 -1
- package/dist/language.d.ts +2 -2
- package/dist/language.d.ts.map +1 -1
- package/dist/language.js +4 -4
- package/dist/language.js.map +1 -1
- package/dist/lex.d.ts.map +1 -1
- package/dist/lex.js +49 -48
- package/dist/lex.js.map +1 -1
- package/dist/object.d.ts +13 -1
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +15 -2
- package/dist/object.js.map +1 -1
- package/dist/uint8array-base64.d.ts +3 -0
- package/dist/uint8array-base64.d.ts.map +1 -0
- package/dist/uint8array-base64.js +3 -0
- package/dist/uint8array-base64.js.map +1 -0
- package/dist/uint8array-from-base64.d.ts +4 -3
- package/dist/uint8array-from-base64.d.ts.map +1 -1
- package/dist/uint8array-from-base64.js +9 -6
- package/dist/uint8array-from-base64.js.map +1 -1
- package/dist/uint8array-to-base64.d.ts +4 -3
- package/dist/uint8array-to-base64.d.ts.map +1 -1
- package/dist/uint8array-to-base64.js +7 -6
- package/dist/uint8array-to-base64.js.map +1 -1
- package/dist/uint8array.d.ts +6 -3
- package/dist/uint8array.d.ts.map +1 -1
- package/dist/uint8array.js +2 -1
- package/dist/uint8array.js.map +1 -1
- package/package.json +1 -1
- package/src/blob.ts +12 -1
- package/src/cid.ts +7 -7
- package/src/language.test.ts +33 -33
- package/src/language.ts +2 -2
- package/src/lex.test.ts +104 -1
- package/src/lex.ts +50 -43
- package/src/object.ts +16 -4
- package/src/uint8array-base64.ts +2 -0
- package/src/uint8array-from-base64.test.ts +33 -19
- package/src/uint8array-from-base64.ts +19 -6
- package/src/uint8array-to-base64.test.ts +170 -0
- package/src/uint8array-to-base64.ts +18 -8
- package/src/uint8array.ts +13 -5
- package/tsconfig.tests.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uint8array-to-base64.js","sourceRoot":"","sources":["../src/uint8array-to-base64.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"uint8array-to-base64.js","sourceRoot":"","sources":["../src/uint8array-to-base64.ts"],"names":[],"mappings":";;;AAiDA,4CAKC;AAtDD,qDAAgD;AAChD,6DAAqD;AAGrD,MAAM,MAAM,GAAG,+BAAY,CAAA;AAed,QAAA,cAAc,GACzB,OAAO,UAAU,CAAC,SAAS,CAAC,QAAQ,KAAK,UAAU;IACjD,CAAC,CAAC,SAAS,cAAc,CACrB,KAAiB,EACjB,WAA2B,QAAQ;QAEnC,OAAO,KAAK,CAAC,QAAS,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAA;IACzD,CAAC;IACH,CAAC,CAAC,IAAI,CAAA;AAEG,QAAA,YAAY,GAAG,MAAM;IAChC,CAAC,CAAC,SAAS,YAAY,CACnB,KAAiB,EACjB,WAA2B,QAAQ;QAEnC,MAAM,MAAM,GAAG,KAAK,YAAY,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACnE,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAErC,uDAAuD;QACvD,0EAA0E;QAC1E,sEAAsE;QACtE,6DAA6D;QAC7D,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,SAAS,CAAC,IAAI;YACtD,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,SAAS,CAAC,IAAI;gBACjD,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO;gBAC1B,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM;YAC3B,CAAC,CAAC,GAAG,CAAA;IACT,CAAC;IACH,CAAC,CAAC,IAAI,CAAA;AAER,SAAgB,gBAAgB,CAC9B,KAAiB,EACjB,WAA2B,QAAQ;IAEnC,OAAO,IAAA,oBAAQ,EAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;AAClC,CAAC","sourcesContent":["import { toString } from 'uint8arrays/to-string'\nimport { NodeJSBuffer } from './lib/nodejs-buffer.js'\nimport { Base64Alphabet } from './uint8array-base64.js'\n\nconst Buffer = NodeJSBuffer\n\ndeclare global {\n interface Uint8Array {\n /**\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64 Uint8Array.prototype.toBase64()}\n */\n toBase64?: (options?: {\n /** @default 'base64' */\n alphabet?: 'base64' | 'base64url'\n omitPadding?: boolean\n }) => string\n }\n}\n\nexport const toBase64Native =\n typeof Uint8Array.prototype.toBase64 === 'function'\n ? function toBase64Native(\n bytes: Uint8Array,\n alphabet: Base64Alphabet = 'base64',\n ): string {\n return bytes.toBase64!({ alphabet, omitPadding: true })\n }\n : null\n\nexport const toBase64Node = Buffer\n ? function toBase64Node(\n bytes: Uint8Array,\n alphabet: Base64Alphabet = 'base64',\n ): string {\n const buffer = bytes instanceof Buffer ? bytes : Buffer.from(bytes)\n const b64 = buffer.toString(alphabet)\n\n // @NOTE We strip padding for strict compatibility with\n // uint8arrays.toString behavior. Tests failing because of the presence of\n // padding are not really synonymous with an actual error and we might\n // (should?) actually want to keep the padding at some point.\n return b64.charCodeAt(b64.length - 1) === /* '=' */ 0x3d\n ? b64.charCodeAt(b64.length - 2) === /* '=' */ 0x3d\n ? b64.slice(0, -2) // '=='\n : b64.slice(0, -1) // '='\n : b64\n }\n : null\n\nexport function toBase64Ponyfill(\n bytes: Uint8Array,\n alphabet: Base64Alphabet = 'base64',\n): string {\n return toString(bytes, alphabet)\n}\n"]}
|
package/dist/uint8array.d.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import { Base64Alphabet } from './uint8array-base64.js';
|
|
2
|
+
export type { Base64Alphabet };
|
|
1
3
|
/**
|
|
2
4
|
* Encodes a Uint8Array into a base64 string.
|
|
3
5
|
*
|
|
4
6
|
* @returns The base64 encoded string
|
|
5
7
|
*/
|
|
6
|
-
export declare const toBase64: (bytes: Uint8Array) => string;
|
|
8
|
+
export declare const toBase64: (bytes: Uint8Array, alphabet?: Base64Alphabet) => string;
|
|
7
9
|
/**
|
|
8
|
-
* Decodes a base64 string into a Uint8Array.
|
|
10
|
+
* Decodes a base64 string into a Uint8Array. This function supports both padded
|
|
11
|
+
* and unpadded base64 strings.
|
|
9
12
|
*
|
|
10
13
|
* @returns The decoded {@link Uint8Array}
|
|
11
14
|
* @throws If the input is not a valid base64 string
|
|
12
15
|
*/
|
|
13
|
-
export declare const fromBase64: (b64: string) => Uint8Array;
|
|
16
|
+
export declare const fromBase64: (b64: string, alphabet?: Base64Alphabet) => Uint8Array;
|
|
14
17
|
/**
|
|
15
18
|
* Coerces various binary data representations into a Uint8Array.
|
|
16
19
|
*
|
package/dist/uint8array.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uint8array.d.ts","sourceRoot":"","sources":["../src/uint8array.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"uint8array.d.ts","sourceRoot":"","sources":["../src/uint8array.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAYvD,YAAY,EAAE,cAAc,EAAE,CAAA;AAO9B;;;;GAIG;AACH,eAAO,MAAM,QAAQ,EAAE,CACrB,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,cAAc,KACtB,MAA2D,CAAA;AAEhE;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,EAAE,CACvB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,cAAc,KACtB,UAAqE,CAAA;AAS1E;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAkBnE;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAY/D"}
|
package/dist/uint8array.js
CHANGED
|
@@ -16,7 +16,8 @@ const uint8array_to_base64_js_1 = require("./uint8array-to-base64.js");
|
|
|
16
16
|
*/
|
|
17
17
|
exports.toBase64 = uint8array_to_base64_js_1.toBase64Native ?? uint8array_to_base64_js_1.toBase64Node ?? uint8array_to_base64_js_1.toBase64Ponyfill;
|
|
18
18
|
/**
|
|
19
|
-
* Decodes a base64 string into a Uint8Array.
|
|
19
|
+
* Decodes a base64 string into a Uint8Array. This function supports both padded
|
|
20
|
+
* and unpadded base64 strings.
|
|
20
21
|
*
|
|
21
22
|
* @returns The decoded {@link Uint8Array}
|
|
22
23
|
* @throws If the input is not a valid base64 string
|
package/dist/uint8array.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uint8array.js","sourceRoot":"","sources":["../src/uint8array.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"uint8array.js","sourceRoot":"","sources":["../src/uint8array.ts"],"names":[],"mappings":";;;AAqDA,oCAkBC;AAED,8BAYC;AApFD,2EAIoC;AACpC,uEAIkC;AAIlC,4EAA4E;AAC5E,2EAA2E;AAC3E,2EAA2E;AAC3E,0CAA0C;AAE1C;;;;GAIG;AACU,QAAA,QAAQ,GAGP,wCAAc,IAAI,sCAAY,IAAI,0CAAgB,CAAA;AAEhE;;;;;;GAMG;AACU,QAAA,UAAU,GAGL,4CAAgB,IAAI,0CAAc,IAAI,8CAAkB,CAAA;AAE1E,IAAI,gBAAQ,KAAK,0CAAgB,IAAI,kBAAU,KAAK,8CAAkB,EAAE,CAAC;IACvE,aAAa;IACb,OAAO,CAAC,IAAI,CACV,wJAAwJ,CACzJ,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;QAChC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,UAAU,CACnB,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC,iBAAiB,CAChD,CAAA;IACH,CAAC;IAED,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;QACjC,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,CAAA;IAC9B,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAgB,SAAS,CAAC,CAAa,EAAE,CAAa;IACpD,IAAI,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,UAAU,EAAE,CAAC;QAClC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAClB,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { Base64Alphabet } from './uint8array-base64.js'\nimport {\n fromBase64Native,\n fromBase64Node,\n fromBase64Ponyfill,\n} from './uint8array-from-base64.js'\nimport {\n toBase64Native,\n toBase64Node,\n toBase64Ponyfill,\n} from './uint8array-to-base64.js'\n\nexport type { Base64Alphabet }\n\n// @TODO drop dependency on uint8arrays package once Uint8Array.fromBase64 /\n// Uint8Array.prototype.toBase64 is widely supported, and mark fromBase64 /\n// toBase64 as deprecated. We can also drop NodeJS specific implementations\n// once NodeJS <24 is no longer supported.\n\n/**\n * Encodes a Uint8Array into a base64 string.\n *\n * @returns The base64 encoded string\n */\nexport const toBase64: (\n bytes: Uint8Array,\n alphabet?: Base64Alphabet,\n) => string = toBase64Native ?? toBase64Node ?? toBase64Ponyfill\n\n/**\n * Decodes a base64 string into a Uint8Array. This function supports both padded\n * and unpadded base64 strings.\n *\n * @returns The decoded {@link Uint8Array}\n * @throws If the input is not a valid base64 string\n */\nexport const fromBase64: (\n b64: string,\n alphabet?: Base64Alphabet,\n) => Uint8Array = fromBase64Native ?? fromBase64Node ?? fromBase64Ponyfill\n\nif (toBase64 === toBase64Ponyfill || fromBase64 === fromBase64Ponyfill) {\n /*#__PURE__*/\n console.warn(\n '[@atproto/lex-data]: Uint8Array.fromBase64 / Uint8Array.prototype.toBase64 not available in this environment. Falling back to ponyfill implementation.',\n )\n}\n\n/**\n * Coerces various binary data representations into a Uint8Array.\n *\n * @return `undefined` if the input could not be coerced into a {@link Uint8Array}.\n */\nexport function asUint8Array(input: unknown): Uint8Array | undefined {\n if (input instanceof Uint8Array) {\n return input\n }\n\n if (ArrayBuffer.isView(input)) {\n return new Uint8Array(\n input.buffer,\n input.byteOffset,\n input.byteLength / Uint8Array.BYTES_PER_ELEMENT,\n )\n }\n\n if (input instanceof ArrayBuffer) {\n return new Uint8Array(input)\n }\n\n return undefined\n}\n\nexport function ui8Equals(a: Uint8Array, b: Uint8Array): boolean {\n if (a.byteLength !== b.byteLength) {\n return false\n }\n\n for (let i = 0; i < a.byteLength; i++) {\n if (a[i] !== b[i]) {\n return false\n }\n }\n\n return true\n}\n"]}
|
package/package.json
CHANGED
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 (
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
50
|
-
// control over the API
|
|
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
|
/**
|
package/src/language.test.ts
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import {
|
|
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(
|
|
8
|
-
expect(
|
|
9
|
-
expect(
|
|
10
|
-
expect(
|
|
11
|
-
expect(
|
|
12
|
-
expect(
|
|
13
|
-
expect(
|
|
14
|
-
expect(
|
|
15
|
-
|
|
16
|
-
)
|
|
17
|
-
expect(
|
|
18
|
-
expect(
|
|
19
|
-
expect(
|
|
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(
|
|
22
|
-
expect(
|
|
23
|
-
expect(
|
|
24
|
-
expect(
|
|
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(
|
|
29
|
+
expect(parseLanguageString('de')).toEqual({
|
|
30
30
|
language: 'de',
|
|
31
31
|
})
|
|
32
|
-
expect(
|
|
32
|
+
expect(parseLanguageString('de-CH')).toEqual({
|
|
33
33
|
language: 'de',
|
|
34
34
|
region: 'CH',
|
|
35
35
|
})
|
|
36
|
-
expect(
|
|
36
|
+
expect(parseLanguageString('de-DE-1901')).toEqual({
|
|
37
37
|
language: 'de',
|
|
38
38
|
region: 'DE',
|
|
39
39
|
variant: '1901',
|
|
40
40
|
})
|
|
41
|
-
expect(
|
|
41
|
+
expect(parseLanguageString('es-419')).toEqual({
|
|
42
42
|
language: 'es',
|
|
43
43
|
region: '419',
|
|
44
44
|
})
|
|
45
|
-
expect(
|
|
45
|
+
expect(parseLanguageString('sl-IT-nedis')).toEqual({
|
|
46
46
|
language: 'sl',
|
|
47
47
|
region: 'IT',
|
|
48
48
|
variant: 'nedis',
|
|
49
49
|
})
|
|
50
|
-
expect(
|
|
50
|
+
expect(parseLanguageString('mn-Cyrl-MN')).toEqual({
|
|
51
51
|
language: 'mn',
|
|
52
52
|
script: 'Cyrl',
|
|
53
53
|
region: 'MN',
|
|
54
54
|
})
|
|
55
|
-
expect(
|
|
55
|
+
expect(parseLanguageString('x-fr-CH')).toEqual({
|
|
56
56
|
privateUse: 'x-fr-CH',
|
|
57
57
|
})
|
|
58
58
|
expect(
|
|
59
|
-
|
|
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(
|
|
67
|
+
expect(parseLanguageString('sr-Cyrl')).toEqual({
|
|
68
68
|
language: 'sr',
|
|
69
69
|
script: 'Cyrl',
|
|
70
70
|
})
|
|
71
|
-
expect(
|
|
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(
|
|
77
|
+
expect(parseLanguageString('i-klingon')).toEqual({
|
|
78
78
|
grandfathered: 'i-klingon',
|
|
79
79
|
})
|
|
80
80
|
// invalid
|
|
81
|
-
expect(
|
|
82
|
-
expect(
|
|
83
|
-
expect(
|
|
84
|
-
expect(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
+
// fallthrough
|
|
38
27
|
default:
|
|
39
|
-
|
|
28
|
+
return false
|
|
40
29
|
}
|
|
41
30
|
}
|
|
42
31
|
|
|
43
32
|
export function isLexValue(value: unknown): value is LexValue {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 (
|
|
@@ -40,34 +40,48 @@ for (const fromBase64 of [
|
|
|
40
40
|
expect(ui8Equals(decoded, bytes)).toBe(true)
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
for (const
|
|
44
|
-
'',
|
|
45
|
-
'\0\0',
|
|
46
|
-
'\0\0\0',
|
|
47
|
-
'\0\0\0\0',
|
|
48
|
-
'__',
|
|
49
|
-
'é',
|
|
50
|
-
'àç',
|
|
51
|
-
'\0éàç',
|
|
52
|
-
'```',
|
|
53
|
-
'aaa',
|
|
54
|
-
'Hello, World!',
|
|
55
|
-
'😀😃😄😁😆😅😂🤣😊😇',
|
|
56
|
-
'👩💻👨💻👩🔬👨🔬👩🚀👨🚀',
|
|
57
|
-
'🌍🌎🌏🌐🪐🌟✨⚡🔥💧',
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
for (const buffer of [
|
|
44
|
+
Buffer.from(''),
|
|
45
|
+
Buffer.from('\0\0'),
|
|
46
|
+
Buffer.from('\0\0\0'),
|
|
47
|
+
Buffer.from('\0\0\0\0'),
|
|
48
|
+
Buffer.from('__'),
|
|
49
|
+
Buffer.from('é'),
|
|
50
|
+
Buffer.from('àç'),
|
|
51
|
+
Buffer.from('\0éàç'),
|
|
52
|
+
Buffer.from('```'),
|
|
53
|
+
Buffer.from('aaa'),
|
|
54
|
+
Buffer.from('Hello, World!'),
|
|
55
|
+
Buffer.from('😀😃😄😁😆😅😂🤣😊😇'),
|
|
56
|
+
Buffer.from('👩💻👨💻👩🔬👨🔬👩🚀👨🚀'),
|
|
57
|
+
Buffer.from('🌍🌎🌏🌐🪐🌟✨⚡🔥💧'),
|
|
58
|
+
Buffer.from(new Uint8Array([0xfb, 0xff, 0xbf])),
|
|
59
|
+
Buffer.from(new Uint8Array([0xfb, 0xff, 0xbf])),
|
|
60
|
+
Buffer.from(new Uint8Array([0x4d])),
|
|
61
|
+
Buffer.from(new Uint8Array([0x4d, 0x61])),
|
|
62
|
+
Buffer.from(new Uint8Array([0x4d, 0x61, 0x6e])),
|
|
63
|
+
Buffer.from(new Uint8Array([0x4d])),
|
|
64
|
+
Buffer.from(new Uint8Array([0x4d, 0x61])),
|
|
65
|
+
Buffer.from(new Uint8Array([0x00, 0x4d, 0x61, 0x6e, 0x00])),
|
|
66
|
+
]) {
|
|
60
67
|
const base64 = buffer.toString('base64')
|
|
61
68
|
const base64Unpadded = base64.replace(/=+$/, '')
|
|
69
|
+
const base64url = buffer.toString('base64url') // No padding in base64url
|
|
62
70
|
|
|
63
|
-
it(`decodes ${JSON.stringify(
|
|
71
|
+
it(`decodes ${JSON.stringify(base64)}`, () => {
|
|
64
72
|
const decoded = fromBase64(base64)
|
|
65
73
|
expect(decoded).toBeInstanceOf(Uint8Array)
|
|
66
74
|
expect(ui8Equals(decoded, buffer)).toBe(true)
|
|
67
75
|
})
|
|
68
76
|
|
|
77
|
+
it(`decodes ${JSON.stringify(base64url)} (base64url)`, () => {
|
|
78
|
+
const decoded = fromBase64(base64url, 'base64url')
|
|
79
|
+
expect(decoded).toBeInstanceOf(Uint8Array)
|
|
80
|
+
expect(ui8Equals(decoded, buffer)).toBe(true)
|
|
81
|
+
})
|
|
82
|
+
|
|
69
83
|
if (base64 !== base64Unpadded) {
|
|
70
|
-
it(`decodes ${JSON.stringify(
|
|
84
|
+
it(`decodes ${JSON.stringify(base64Unpadded)} (unpadded)`, () => {
|
|
71
85
|
const decoded = fromBase64(base64Unpadded)
|
|
72
86
|
expect(decoded).toBeInstanceOf(Uint8Array)
|
|
73
87
|
expect(ui8Equals(decoded, buffer)).toBe(true)
|