@atproto/lex-json 0.0.14 → 0.0.15
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 +9 -0
- package/dist/blob.d.ts +4 -4
- package/dist/blob.d.ts.map +1 -1
- package/dist/blob.js +6 -6
- package/dist/blob.js.map +1 -1
- package/dist/lex-json.d.ts +4 -0
- package/dist/lex-json.d.ts.map +1 -1
- package/dist/lex-json.js +15 -1
- package/dist/lex-json.js.map +1 -1
- package/package.json +2 -2
- package/src/blob.ts +8 -8
- package/src/lex-json.test.ts +167 -69
- package/src/lex-json.ts +22 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @atproto/lex-json
|
|
2
2
|
|
|
3
|
+
## 0.0.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#4835](https://github.com/bluesky-social/atproto/pull/4835) [`f6f100c`](https://github.com/bluesky-social/atproto/commit/f6f100c33700a7ff58a1458109cc7420131feed0) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `lexParseJsonBytes` utility to parse a byte array containing a json string directly into lex data
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`c62651d`](https://github.com/bluesky-social/atproto/commit/c62651dd69f1e18bd854b66e499b91fee9eaa856), [`f6f100c`](https://github.com/bluesky-social/atproto/commit/f6f100c33700a7ff58a1458109cc7420131feed0), [`c62651d`](https://github.com/bluesky-social/atproto/commit/c62651dd69f1e18bd854b66e499b91fee9eaa856), [`c62651d`](https://github.com/bluesky-social/atproto/commit/c62651dd69f1e18bd854b66e499b91fee9eaa856)]:
|
|
10
|
+
- @atproto/lex-data@0.0.15
|
|
11
|
+
|
|
3
12
|
## 0.0.14
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/dist/blob.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BlobRefCheckOptions, LexMap, TypedBlobRef } from '@atproto/lex-data';
|
|
2
2
|
/**
|
|
3
3
|
* Parses a blob reference from a JSON object.
|
|
4
4
|
*
|
|
@@ -19,7 +19,7 @@ import { BlobRef, BlobRefCheckOptions, LexMap } from '@atproto/lex-data';
|
|
|
19
19
|
* @example
|
|
20
20
|
* ```typescript
|
|
21
21
|
* // Parse a blob reference from JSON
|
|
22
|
-
* const blobRef =
|
|
22
|
+
* const blobRef = parseTypedBlobRef({
|
|
23
23
|
* $type: 'blob',
|
|
24
24
|
* ref: { $link: 'bafyreib2rxk3rybloqtqwbo' },
|
|
25
25
|
* mimeType: 'image/png',
|
|
@@ -29,9 +29,9 @@ import { BlobRef, BlobRefCheckOptions, LexMap } from '@atproto/lex-data';
|
|
|
29
29
|
* // blobRef.ref is a Cid instance
|
|
30
30
|
*
|
|
31
31
|
* // Returns undefined for non-blob objects
|
|
32
|
-
* const result =
|
|
32
|
+
* const result = parseTypedBlobRef({ foo: 'bar' })
|
|
33
33
|
* // result is undefined
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
|
-
export declare function
|
|
36
|
+
export declare function parseTypedBlobRef(input: LexMap, options?: BlobRefCheckOptions): TypedBlobRef | undefined;
|
|
37
37
|
//# sourceMappingURL=blob.d.ts.map
|
package/dist/blob.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blob.d.ts","sourceRoot":"","sources":["../src/blob.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"blob.d.ts","sourceRoot":"","sources":["../src/blob.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,MAAM,EACN,YAAY,EAEb,MAAM,mBAAmB,CAAA;AAG1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,mBAAmB,GAC5B,YAAY,GAAG,SAAS,CAwB1B"}
|
package/dist/blob.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.parseTypedBlobRef = parseTypedBlobRef;
|
|
4
4
|
const lex_data_1 = require("@atproto/lex-data");
|
|
5
5
|
const link_js_1 = require("./link.js");
|
|
6
6
|
/**
|
|
@@ -23,7 +23,7 @@ const link_js_1 = require("./link.js");
|
|
|
23
23
|
* @example
|
|
24
24
|
* ```typescript
|
|
25
25
|
* // Parse a blob reference from JSON
|
|
26
|
-
* const blobRef =
|
|
26
|
+
* const blobRef = parseTypedBlobRef({
|
|
27
27
|
* $type: 'blob',
|
|
28
28
|
* ref: { $link: 'bafyreib2rxk3rybloqtqwbo' },
|
|
29
29
|
* mimeType: 'image/png',
|
|
@@ -33,11 +33,11 @@ const link_js_1 = require("./link.js");
|
|
|
33
33
|
* // blobRef.ref is a Cid instance
|
|
34
34
|
*
|
|
35
35
|
* // Returns undefined for non-blob objects
|
|
36
|
-
* const result =
|
|
36
|
+
* const result = parseTypedBlobRef({ foo: 'bar' })
|
|
37
37
|
* // result is undefined
|
|
38
38
|
* ```
|
|
39
39
|
*/
|
|
40
|
-
function
|
|
40
|
+
function parseTypedBlobRef(input, options) {
|
|
41
41
|
if (input.$type !== 'blob')
|
|
42
42
|
return undefined;
|
|
43
43
|
const ref = input?.ref;
|
|
@@ -52,10 +52,10 @@ function parseBlobRef(input, options) {
|
|
|
52
52
|
if (!cid)
|
|
53
53
|
return undefined;
|
|
54
54
|
const blob = { ...input, ref: cid };
|
|
55
|
-
if ((0, lex_data_1.
|
|
55
|
+
if ((0, lex_data_1.isTypedBlobRef)(blob, options))
|
|
56
56
|
return blob;
|
|
57
57
|
}
|
|
58
|
-
if ((0, lex_data_1.
|
|
58
|
+
if ((0, lex_data_1.isTypedBlobRef)(input)) {
|
|
59
59
|
return input;
|
|
60
60
|
}
|
|
61
61
|
return undefined;
|
package/dist/blob.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blob.js","sourceRoot":"","sources":["../src/blob.ts"],"names":[],"mappings":";;AA0CA,
|
|
1
|
+
{"version":3,"file":"blob.js","sourceRoot":"","sources":["../src/blob.ts"],"names":[],"mappings":";;AA0CA,8CA2BC;AArED,gDAK0B;AAC1B,uCAAwC;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,SAAgB,iBAAiB,CAC/B,KAAa,EACb,OAA6B;IAE7B,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM;QAAE,OAAO,SAAS,CAAA;IAE5C,MAAM,GAAG,GAAG,KAAK,EAAE,GAAG,CAAA;IACtB,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IAErD,8EAA8E;IAC9E,oEAAoE;IACpE,qEAAqE;IACrE,iDAAiD;IAEjD,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,IAAA,sBAAY,EAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAA;QAE1B,MAAM,IAAI,GAAG,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAA;QACnC,IAAI,IAAA,yBAAc,EAAC,IAAI,EAAE,OAAO,CAAC;YAAE,OAAO,IAAI,CAAA;IAChD,CAAC;IAED,IAAI,IAAA,yBAAc,EAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC","sourcesContent":["import {\n BlobRefCheckOptions,\n LexMap,\n TypedBlobRef,\n isTypedBlobRef,\n} from '@atproto/lex-data'\nimport { parseLexLink } from './link.js'\n\n/**\n * Parses a blob reference from a JSON object.\n *\n * In the AT Protocol, blobs are referenced using a specific object structure\n * with `$type: 'blob'`, a `ref` property containing a CID link, and metadata\n * like `mimeType` and `size`. This function validates and parses such objects\n * into `BlobRef` instances.\n *\n * The function handles both cases where the `ref` property is:\n * - A `{$link: string}` object (when parsing from JSON)\n * - Already a `Cid` instance (when the parent object has been partially converted)\n *\n * @param input - A Lex map potentially representing a blob reference\n * @param options - Optional blob reference validation options\n * @returns The parsed `BlobRef` if the input is a valid blob reference,\n * or `undefined` if the input is not a valid blob representation\n *\n * @example\n * ```typescript\n * // Parse a blob reference from JSON\n * const blobRef = parseTypedBlobRef({\n * $type: 'blob',\n * ref: { $link: 'bafyreib2rxk3rybloqtqwbo' },\n * mimeType: 'image/png',\n * size: 12345\n * })\n *\n * // blobRef.ref is a Cid instance\n *\n * // Returns undefined for non-blob objects\n * const result = parseTypedBlobRef({ foo: 'bar' })\n * // result is undefined\n * ```\n */\nexport function parseTypedBlobRef(\n input: LexMap,\n options?: BlobRefCheckOptions,\n): TypedBlobRef | undefined {\n if (input.$type !== 'blob') return undefined\n\n const ref = input?.ref\n if (!ref || typeof ref !== 'object') return undefined\n\n // @NOTE Because json to lex conversion can be performed both in a depth-first\n // manner (e.g. via lexParse) or in a breadth-first manner (e.g. via\n // jsonToLex), the `ref` property may either be a LexMap with a $link\n // property, or it may already be a CID instance.\n\n if ('$link' in ref) {\n const cid = parseLexLink(ref)\n if (!cid) return undefined\n\n const blob = { ...input, ref: cid }\n if (isTypedBlobRef(blob, options)) return blob\n }\n\n if (isTypedBlobRef(input)) {\n return input\n }\n\n return undefined\n}\n"]}
|
package/dist/lex-json.d.ts
CHANGED
|
@@ -78,6 +78,10 @@ export type LexParseOptions = {
|
|
|
78
78
|
* ```
|
|
79
79
|
*/
|
|
80
80
|
export declare function lexParse<T extends LexValue = LexValue>(input: string, options?: LexParseOptions): T;
|
|
81
|
+
/**
|
|
82
|
+
* Parses a `Uint8Array` containing JSON data into a Lex value.
|
|
83
|
+
*/
|
|
84
|
+
export declare function lexParseJsonBytes(jsonBytes: Uint8Array, options?: LexParseOptions): LexValue;
|
|
81
85
|
/**
|
|
82
86
|
* Converts a parsed JSON representation of Lexicon value to a {@link LexValue}.
|
|
83
87
|
*
|
package/dist/lex-json.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lex-json.d.ts","sourceRoot":"","sources":["../src/lex-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,QAAQ,
|
|
1
|
+
{"version":3,"file":"lex-json.d.ts","sourceRoot":"","sources":["../src/lex-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,QAAQ,EAGT,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAc,SAAS,EAAE,MAAM,WAAW,CAAA;AAGjD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAKpD;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,EACpD,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,eAAmC,GAC3C,CAAC,CAiBH;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,UAAU,EACrB,OAAO,CAAC,EAAE,eAAe,GACxB,QAAQ,CAWV;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE,eAAmC,GAC3C,QAAQ,CAsBV;AA+CD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,CAqBpD"}
|
package/dist/lex-json.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.lexStringify = lexStringify;
|
|
4
4
|
exports.lexParse = lexParse;
|
|
5
|
+
exports.lexParseJsonBytes = lexParseJsonBytes;
|
|
5
6
|
exports.jsonToLex = jsonToLex;
|
|
6
7
|
exports.lexToJson = lexToJson;
|
|
7
8
|
const lex_data_1 = require("@atproto/lex-data");
|
|
@@ -93,6 +94,19 @@ function lexParse(input, options = { strict: false }) {
|
|
|
93
94
|
}
|
|
94
95
|
});
|
|
95
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Parses a `Uint8Array` containing JSON data into a Lex value.
|
|
99
|
+
*/
|
|
100
|
+
function lexParseJsonBytes(jsonBytes, options) {
|
|
101
|
+
// @TODO optimize this to avoid intermediate string. This requires a custom
|
|
102
|
+
// JSON parser that can operate on binary data, which is non-trivial, but
|
|
103
|
+
// could be a future improvement if performance is a concern. See the link
|
|
104
|
+
// below for an example of a JSON parser that operates on binary data in
|
|
105
|
+
// @ipld/dag-json
|
|
106
|
+
// https://github.com/ipld/js-dag-json/blob/57912da6e9d64a179f7d2384c3b6d7b07fbfb143/src/index.js#L161
|
|
107
|
+
const jsonString = (0, lex_data_1.utf8FromBytes)(jsonBytes);
|
|
108
|
+
return lexParse(jsonString, options);
|
|
109
|
+
}
|
|
96
110
|
/**
|
|
97
111
|
* Converts a parsed JSON representation of Lexicon value to a {@link LexValue}.
|
|
98
112
|
*
|
|
@@ -292,7 +306,7 @@ function parseSpecialJsonObject(input, options) {
|
|
|
292
306
|
// the strict option is enabled.
|
|
293
307
|
if (options.strict) {
|
|
294
308
|
if (input.$type === 'blob') {
|
|
295
|
-
const blob = (0, blob_js_1.
|
|
309
|
+
const blob = (0, blob_js_1.parseTypedBlobRef)(input, options);
|
|
296
310
|
if (blob)
|
|
297
311
|
return blob;
|
|
298
312
|
throw new TypeError(`Invalid blob object`);
|
package/dist/lex-json.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lex-json.js","sourceRoot":"","sources":["../src/lex-json.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BlobRef,\n Cid,\n LexArray,\n LexMap,\n LexValue,\n isCid,\n} from '@atproto/lex-data'\nimport { parseBlobRef } from './blob.js'\nimport { encodeLexBytes, parseLexBytes } from './bytes.js'\nimport { JsonObject, JsonValue } from './json.js'\nimport { encodeLexLink, parseLexLink } from './link.js'\n\n/**\n * Serialize a Lex value to a JSON string.\n *\n * This function serializes AT Protocol data model values to JSON, automatically\n * encoding special types:\n * - `Cid` instances are encoded as `{$link: string}`\n * - `Uint8Array` instances are encoded as `{$bytes: string}` (base64)\n *\n * @param input - The Lex value to stringify\n * @returns A JSON string representation of the value\n *\n * @example\n * ```typescript\n * import { lexStringify } from '@atproto/lex'\n *\n * // Stringify with CID and bytes encoding\n * const json = lexStringify({\n * ref: someCid,\n * data: new Uint8Array([72, 101, 108, 108, 111])\n * })\n * // json is '{\"ref\":{\"$link\":\"bafyrei...\"},\"data\":{\"$bytes\":\"SGVsbG8=\"}}'\n * ```\n */\nexport function lexStringify(input: LexValue): string {\n // @NOTE Because of the way the \"replacer\" works in JSON.stringify, it's\n // simpler to convert Lex to JSON first rather than trying to do it\n // on-the-fly.\n return JSON.stringify(lexToJson(input))\n}\n\n/**\n * Options for parsing JSON to Lex values.\n */\nexport type LexParseOptions = {\n /**\n * When enabled, forbids the presence of invalid Lex values such as:\n * - Non-integer numbers (only safe integers are valid in the Lex data model)\n * - Malformed `$link` objects\n * - Malformed `$bytes` objects\n * - Objects with invalid or empty `$type` properties\n * - Invalid {@link BlobRef} (`$type: 'blob'`) objects\n *\n * When disabled (default), invalid special objects are left as plain objects.\n *\n * @default false\n */\n strict?: boolean\n}\n\n/**\n * Parses a JSON string into Lex values.\n *\n * This function parses JSON and automatically decodes AT Protocol special types:\n * - `{$link: string}` objects are decoded to `Cid` instances\n * - `{$bytes: string}` objects are decoded to `Uint8Array` instances\n * - `{$type: 'blob'}` objects are validated\n *\n * @typeParam T - Type cast for the resulting Lex value. Use when you want to specify the expected structure of the parsed data.\n * @param input - The JSON string to parse\n * @param options - Parsing options (e.g., strict mode)\n * @returns The parsed Lex value\n * @throws {SyntaxError} If the input is not valid JSON\n * @throws {TypeError} If strict mode is enabled and invalid Lex values are found\n *\n * @example\n * ```typescript\n * import { lexParse } from '@atproto/lex'\n *\n * // Parse JSON with $link and $bytes decoding\n * const parsed = lexParse<{\n * ref: Cid\n * data: Uint8Array\n * }>(`{\n * \"ref\": { \"$link\": \"bafyrei...\" },\n * \"data\": { \"$bytes\": \"SGVsbG8sIHdvcmxkIQ==\" }\n * }`)\n *\n * // Parse a single CID\n * const someCid = lexParse<Cid>('{\"$link\": \"bafyrei...\"}')\n *\n * // Parse binary data\n * const someBytes = lexParse<Uint8Array>('{\"$bytes\": \"SGVsbG8sIHdvcmxkIQ==\"}')\n * ```\n */\nexport function lexParse<T extends LexValue = LexValue>(\n input: string,\n options: LexParseOptions = { strict: false },\n): T {\n return JSON.parse(input, function (key: string, value: JsonValue): LexValue {\n switch (typeof value) {\n case 'object':\n if (value === null) return null\n if (Array.isArray(value)) return value\n return parseSpecialJsonObject(value, options) ?? value\n case 'number':\n if (Number.isSafeInteger(value)) return value\n if (options.strict) {\n throw new TypeError(`Invalid non-integer number: ${value}`)\n }\n // fallthrough\n default:\n return value\n }\n })\n}\n\n/**\n * Converts a parsed JSON representation of Lexicon value to a {@link LexValue}.\n *\n * This function transforms already-parsed JSON objects into Lex values by\n * decoding AT Protocol special types:\n * - `{$link: string}` objects are converted to `Cid` instances\n * - `{$bytes: string}` objects are converted to `Uint8Array` instances\n *\n * Use this when you have a JavaScript object (e.g., from `JSON.parse()`) and\n * need to convert it to the Lex data model. For parsing JSON strings directly,\n * use {@link lexParse} instead.\n *\n * @param value - The JSON value to convert\n * @param options - Parsing options (e.g., strict mode)\n * @returns The converted Lex value\n * @throws {TypeError} If strict mode is enabled and invalid Lex values are found\n * @throws {TypeError} If the value contains unsupported types (e.g., undefined at top level)\n *\n * @example\n * ```typescript\n * import { jsonToLex } from '@atproto/lex'\n *\n * // Convert parsed JSON to Lex values\n * const lex = jsonToLex({\n * ref: { $link: 'bafyrei...' }, // Converted to Cid\n * data: { $bytes: 'SGVsbG8sIHdvcmxkIQ==' } // Converted to Uint8Array\n * })\n * ```\n */\nexport function jsonToLex(\n value: JsonValue,\n options: LexParseOptions = { strict: false },\n): LexValue {\n switch (typeof value) {\n case 'object': {\n if (value === null) return null\n if (Array.isArray(value)) return jsonArrayToLex(value, options)\n return (\n parseSpecialJsonObject(value, options) ??\n jsonObjectToLexMap(value, options)\n )\n }\n case 'number':\n if (Number.isSafeInteger(value)) return value\n if (options.strict) {\n throw new TypeError(`Invalid non-integer number: ${value}`)\n }\n // fallthrough\n case 'boolean':\n case 'string':\n return value\n default:\n throw new TypeError(`Invalid JSON value: ${typeof value}`)\n }\n}\n\nfunction jsonArrayToLex(\n input: JsonValue[],\n options: LexParseOptions,\n): LexValue[] {\n // Lazily copy value\n let copy: LexValue[] | undefined\n for (let i = 0; i < input.length; i++) {\n const inputItem = input[i]\n const item = jsonToLex(inputItem, options)\n if (item !== inputItem) {\n copy ??= Array.from(input)\n copy[i] = item\n }\n }\n return copy ?? input\n}\n\nfunction jsonObjectToLexMap(\n input: JsonObject,\n options: LexParseOptions,\n): LexMap {\n // Lazily copy value\n let copy: LexMap | undefined = undefined\n for (const [key, jsonValue] of Object.entries(input)) {\n // Prevent prototype pollution\n if (key === '__proto__') {\n throw new TypeError('Invalid key: __proto__')\n }\n\n // Ignore (strip) undefined values\n if (jsonValue === undefined) {\n copy ??= { ...input }\n delete copy[key]\n continue\n }\n\n const value = jsonToLex(jsonValue!, options)\n if (value !== jsonValue) {\n copy ??= { ...input }\n copy[key] = value\n }\n }\n return copy ?? input\n}\n\n/**\n * Converts a Lex value to a JSON-compatible value.\n *\n * This function transforms Lex data model values into plain JavaScript objects\n * suitable for JSON serialization:\n * - `Cid` instances are converted to `{$link: string}` objects\n * - `Uint8Array` instances are converted to `{$bytes: string}` objects (base64)\n *\n * Use this when you need to convert Lex values to plain objects (e.g., for\n * custom serialization or inspection). For direct JSON string output, use\n * {@link lexStringify} instead.\n *\n * @param value - The Lex value to convert\n * @returns The JSON-compatible value\n * @throws {TypeError} If the value contains unsupported types\n *\n * @example\n * ```typescript\n * import { lexToJson } from '@atproto/lex'\n *\n * // Convert Lex values to JSON-compatible objects\n * const obj = lexToJson({\n * ref: someCid, // Converted to { $link: string }\n * data: someBytes // Converted to { $bytes: string }\n * })\n * ```\n */\nexport function lexToJson(value: LexValue): JsonValue {\n switch (typeof value) {\n case 'object':\n if (value === null) {\n return value\n } else if (Array.isArray(value)) {\n return lexArrayToJson(value)\n } else if (isCid(value)) {\n return encodeLexLink(value)\n } else if (ArrayBuffer.isView(value)) {\n return encodeLexBytes(value)\n } else {\n return encodeLexMap(value)\n }\n case 'boolean':\n case 'string':\n case 'number':\n return value\n default:\n throw new TypeError(`Invalid Lex value: ${typeof value}`)\n }\n}\n\nfunction lexArrayToJson(input: LexArray): JsonValue[] {\n // Lazily copy value\n let copy: JsonValue[] | undefined\n for (let i = 0; i < input.length; i++) {\n const inputItem = input[i]\n const item = lexToJson(inputItem)\n if (item !== inputItem) {\n copy ??= Array.from(input) as JsonValue[]\n copy[i] = item\n }\n }\n return copy ?? (input as JsonValue[])\n}\n\nfunction encodeLexMap(input: LexMap): JsonObject {\n // Lazily copy value\n let copy: JsonObject | undefined = undefined\n for (const [key, lexValue] of Object.entries(input)) {\n // Prevent prototype pollution\n if (key === '__proto__') {\n throw new TypeError('Invalid key: __proto__')\n }\n\n // Ignore (strip) undefined values\n if (lexValue === undefined) {\n copy ??= { ...input } as JsonObject\n delete copy[key]\n continue\n }\n\n const jsonValue = lexToJson(lexValue!)\n if (jsonValue !== lexValue) {\n copy ??= { ...input } as JsonObject\n copy[key] = jsonValue\n }\n }\n return copy ?? (input as JsonObject)\n}\n\nfunction parseSpecialJsonObject(\n input: LexMap,\n options: LexParseOptions,\n): Cid | Uint8Array | BlobRef | undefined {\n // Hot path: use hints to avoid parsing when possible\n\n if (input.$link !== undefined) {\n const cid = parseLexLink(input)\n if (cid) return cid\n if (options.strict) throw new TypeError(`Invalid $link object`)\n } else if (input.$bytes !== undefined) {\n const bytes = parseLexBytes(input)\n if (bytes) return bytes\n if (options.strict) throw new TypeError(`Invalid $bytes object`)\n } else if (input.$type !== undefined) {\n // @NOTE Since blobs are \"just\" regular lex objects with a special shape,\n // and because an object that does not conform to the blob shape would still\n // result in undefined being returned, we only attempt to parse blobs when\n // the strict option is enabled.\n if (options.strict) {\n if (input.$type === 'blob') {\n const blob = parseBlobRef(input, options)\n if (blob) return blob\n throw new TypeError(`Invalid blob object`)\n } else if (typeof input.$type !== 'string') {\n throw new TypeError(`Invalid $type property (${typeof input.$type})`)\n } else if (input.$type.length === 0) {\n throw new TypeError(`Empty $type property`)\n }\n }\n }\n\n // @NOTE We ignore legacy blob representation here. They can be handled at the\n // application level if needed.\n\n return undefined\n}\n"]}
|
|
1
|
+
{"version":3,"file":"lex-json.js","sourceRoot":"","sources":["../src/lex-json.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BlobRef,\n Cid,\n LexArray,\n LexMap,\n LexValue,\n isCid,\n utf8FromBytes,\n} from '@atproto/lex-data'\nimport { parseTypedBlobRef } from './blob.js'\nimport { encodeLexBytes, parseLexBytes } from './bytes.js'\nimport { JsonObject, JsonValue } from './json.js'\nimport { encodeLexLink, parseLexLink } from './link.js'\n\n/**\n * Serialize a Lex value to a JSON string.\n *\n * This function serializes AT Protocol data model values to JSON, automatically\n * encoding special types:\n * - `Cid` instances are encoded as `{$link: string}`\n * - `Uint8Array` instances are encoded as `{$bytes: string}` (base64)\n *\n * @param input - The Lex value to stringify\n * @returns A JSON string representation of the value\n *\n * @example\n * ```typescript\n * import { lexStringify } from '@atproto/lex'\n *\n * // Stringify with CID and bytes encoding\n * const json = lexStringify({\n * ref: someCid,\n * data: new Uint8Array([72, 101, 108, 108, 111])\n * })\n * // json is '{\"ref\":{\"$link\":\"bafyrei...\"},\"data\":{\"$bytes\":\"SGVsbG8=\"}}'\n * ```\n */\nexport function lexStringify(input: LexValue): string {\n // @NOTE Because of the way the \"replacer\" works in JSON.stringify, it's\n // simpler to convert Lex to JSON first rather than trying to do it\n // on-the-fly.\n return JSON.stringify(lexToJson(input))\n}\n\n/**\n * Options for parsing JSON to Lex values.\n */\nexport type LexParseOptions = {\n /**\n * When enabled, forbids the presence of invalid Lex values such as:\n * - Non-integer numbers (only safe integers are valid in the Lex data model)\n * - Malformed `$link` objects\n * - Malformed `$bytes` objects\n * - Objects with invalid or empty `$type` properties\n * - Invalid {@link BlobRef} (`$type: 'blob'`) objects\n *\n * When disabled (default), invalid special objects are left as plain objects.\n *\n * @default false\n */\n strict?: boolean\n}\n\n/**\n * Parses a JSON string into Lex values.\n *\n * This function parses JSON and automatically decodes AT Protocol special types:\n * - `{$link: string}` objects are decoded to `Cid` instances\n * - `{$bytes: string}` objects are decoded to `Uint8Array` instances\n * - `{$type: 'blob'}` objects are validated\n *\n * @typeParam T - Type cast for the resulting Lex value. Use when you want to specify the expected structure of the parsed data.\n * @param input - The JSON string to parse\n * @param options - Parsing options (e.g., strict mode)\n * @returns The parsed Lex value\n * @throws {SyntaxError} If the input is not valid JSON\n * @throws {TypeError} If strict mode is enabled and invalid Lex values are found\n *\n * @example\n * ```typescript\n * import { lexParse } from '@atproto/lex'\n *\n * // Parse JSON with $link and $bytes decoding\n * const parsed = lexParse<{\n * ref: Cid\n * data: Uint8Array\n * }>(`{\n * \"ref\": { \"$link\": \"bafyrei...\" },\n * \"data\": { \"$bytes\": \"SGVsbG8sIHdvcmxkIQ==\" }\n * }`)\n *\n * // Parse a single CID\n * const someCid = lexParse<Cid>('{\"$link\": \"bafyrei...\"}')\n *\n * // Parse binary data\n * const someBytes = lexParse<Uint8Array>('{\"$bytes\": \"SGVsbG8sIHdvcmxkIQ==\"}')\n * ```\n */\nexport function lexParse<T extends LexValue = LexValue>(\n input: string,\n options: LexParseOptions = { strict: false },\n): T {\n return JSON.parse(input, function (key: string, value: JsonValue): LexValue {\n switch (typeof value) {\n case 'object':\n if (value === null) return null\n if (Array.isArray(value)) return value\n return parseSpecialJsonObject(value, options) ?? value\n case 'number':\n if (Number.isSafeInteger(value)) return value\n if (options.strict) {\n throw new TypeError(`Invalid non-integer number: ${value}`)\n }\n // fallthrough\n default:\n return value\n }\n })\n}\n\n/**\n * Parses a `Uint8Array` containing JSON data into a Lex value.\n */\nexport function lexParseJsonBytes(\n jsonBytes: Uint8Array,\n options?: LexParseOptions,\n): LexValue {\n // @TODO optimize this to avoid intermediate string. This requires a custom\n // JSON parser that can operate on binary data, which is non-trivial, but\n // could be a future improvement if performance is a concern. See the link\n // below for an example of a JSON parser that operates on binary data in\n // @ipld/dag-json\n\n // https://github.com/ipld/js-dag-json/blob/57912da6e9d64a179f7d2384c3b6d7b07fbfb143/src/index.js#L161\n\n const jsonString = utf8FromBytes(jsonBytes)\n return lexParse(jsonString, options)\n}\n\n/**\n * Converts a parsed JSON representation of Lexicon value to a {@link LexValue}.\n *\n * This function transforms already-parsed JSON objects into Lex values by\n * decoding AT Protocol special types:\n * - `{$link: string}` objects are converted to `Cid` instances\n * - `{$bytes: string}` objects are converted to `Uint8Array` instances\n *\n * Use this when you have a JavaScript object (e.g., from `JSON.parse()`) and\n * need to convert it to the Lex data model. For parsing JSON strings directly,\n * use {@link lexParse} instead.\n *\n * @param value - The JSON value to convert\n * @param options - Parsing options (e.g., strict mode)\n * @returns The converted Lex value\n * @throws {TypeError} If strict mode is enabled and invalid Lex values are found\n * @throws {TypeError} If the value contains unsupported types (e.g., undefined at top level)\n *\n * @example\n * ```typescript\n * import { jsonToLex } from '@atproto/lex'\n *\n * // Convert parsed JSON to Lex values\n * const lex = jsonToLex({\n * ref: { $link: 'bafyrei...' }, // Converted to Cid\n * data: { $bytes: 'SGVsbG8sIHdvcmxkIQ==' } // Converted to Uint8Array\n * })\n * ```\n */\nexport function jsonToLex(\n value: JsonValue,\n options: LexParseOptions = { strict: false },\n): LexValue {\n switch (typeof value) {\n case 'object': {\n if (value === null) return null\n if (Array.isArray(value)) return jsonArrayToLex(value, options)\n return (\n parseSpecialJsonObject(value, options) ??\n jsonObjectToLexMap(value, options)\n )\n }\n case 'number':\n if (Number.isSafeInteger(value)) return value\n if (options.strict) {\n throw new TypeError(`Invalid non-integer number: ${value}`)\n }\n // fallthrough\n case 'boolean':\n case 'string':\n return value\n default:\n throw new TypeError(`Invalid JSON value: ${typeof value}`)\n }\n}\n\nfunction jsonArrayToLex(\n input: JsonValue[],\n options: LexParseOptions,\n): LexValue[] {\n // Lazily copy value\n let copy: LexValue[] | undefined\n for (let i = 0; i < input.length; i++) {\n const inputItem = input[i]\n const item = jsonToLex(inputItem, options)\n if (item !== inputItem) {\n copy ??= Array.from(input)\n copy[i] = item\n }\n }\n return copy ?? input\n}\n\nfunction jsonObjectToLexMap(\n input: JsonObject,\n options: LexParseOptions,\n): LexMap {\n // Lazily copy value\n let copy: LexMap | undefined = undefined\n for (const [key, jsonValue] of Object.entries(input)) {\n // Prevent prototype pollution\n if (key === '__proto__') {\n throw new TypeError('Invalid key: __proto__')\n }\n\n // Ignore (strip) undefined values\n if (jsonValue === undefined) {\n copy ??= { ...input }\n delete copy[key]\n continue\n }\n\n const value = jsonToLex(jsonValue!, options)\n if (value !== jsonValue) {\n copy ??= { ...input }\n copy[key] = value\n }\n }\n return copy ?? input\n}\n\n/**\n * Converts a Lex value to a JSON-compatible value.\n *\n * This function transforms Lex data model values into plain JavaScript objects\n * suitable for JSON serialization:\n * - `Cid` instances are converted to `{$link: string}` objects\n * - `Uint8Array` instances are converted to `{$bytes: string}` objects (base64)\n *\n * Use this when you need to convert Lex values to plain objects (e.g., for\n * custom serialization or inspection). For direct JSON string output, use\n * {@link lexStringify} instead.\n *\n * @param value - The Lex value to convert\n * @returns The JSON-compatible value\n * @throws {TypeError} If the value contains unsupported types\n *\n * @example\n * ```typescript\n * import { lexToJson } from '@atproto/lex'\n *\n * // Convert Lex values to JSON-compatible objects\n * const obj = lexToJson({\n * ref: someCid, // Converted to { $link: string }\n * data: someBytes // Converted to { $bytes: string }\n * })\n * ```\n */\nexport function lexToJson(value: LexValue): JsonValue {\n switch (typeof value) {\n case 'object':\n if (value === null) {\n return value\n } else if (Array.isArray(value)) {\n return lexArrayToJson(value)\n } else if (isCid(value)) {\n return encodeLexLink(value)\n } else if (ArrayBuffer.isView(value)) {\n return encodeLexBytes(value)\n } else {\n return encodeLexMap(value)\n }\n case 'boolean':\n case 'string':\n case 'number':\n return value\n default:\n throw new TypeError(`Invalid Lex value: ${typeof value}`)\n }\n}\n\nfunction lexArrayToJson(input: LexArray): JsonValue[] {\n // Lazily copy value\n let copy: JsonValue[] | undefined\n for (let i = 0; i < input.length; i++) {\n const inputItem = input[i]\n const item = lexToJson(inputItem)\n if (item !== inputItem) {\n copy ??= Array.from(input) as JsonValue[]\n copy[i] = item\n }\n }\n return copy ?? (input as JsonValue[])\n}\n\nfunction encodeLexMap(input: LexMap): JsonObject {\n // Lazily copy value\n let copy: JsonObject | undefined = undefined\n for (const [key, lexValue] of Object.entries(input)) {\n // Prevent prototype pollution\n if (key === '__proto__') {\n throw new TypeError('Invalid key: __proto__')\n }\n\n // Ignore (strip) undefined values\n if (lexValue === undefined) {\n copy ??= { ...input } as JsonObject\n delete copy[key]\n continue\n }\n\n const jsonValue = lexToJson(lexValue!)\n if (jsonValue !== lexValue) {\n copy ??= { ...input } as JsonObject\n copy[key] = jsonValue\n }\n }\n return copy ?? (input as JsonObject)\n}\n\nfunction parseSpecialJsonObject(\n input: LexMap,\n options: LexParseOptions,\n): Cid | Uint8Array | BlobRef | undefined {\n // Hot path: use hints to avoid parsing when possible\n\n if (input.$link !== undefined) {\n const cid = parseLexLink(input)\n if (cid) return cid\n if (options.strict) throw new TypeError(`Invalid $link object`)\n } else if (input.$bytes !== undefined) {\n const bytes = parseLexBytes(input)\n if (bytes) return bytes\n if (options.strict) throw new TypeError(`Invalid $bytes object`)\n } else if (input.$type !== undefined) {\n // @NOTE Since blobs are \"just\" regular lex objects with a special shape,\n // and because an object that does not conform to the blob shape would still\n // result in undefined being returned, we only attempt to parse blobs when\n // the strict option is enabled.\n if (options.strict) {\n if (input.$type === 'blob') {\n const blob = parseTypedBlobRef(input, options)\n if (blob) return blob\n throw new TypeError(`Invalid blob object`)\n } else if (typeof input.$type !== 'string') {\n throw new TypeError(`Invalid $type property (${typeof input.$type})`)\n } else if (input.$type.length === 0) {\n throw new TypeError(`Empty $type property`)\n }\n }\n }\n\n // @NOTE We ignore legacy blob representation here. They can be handled at the\n // application level if needed.\n\n return undefined\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/lex-json",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Lexicon encoding utilities for AT Lexicon data in JSON format",
|
|
6
6
|
"keywords": [
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"tslib": "^2.8.1",
|
|
42
|
-
"@atproto/lex-data": "^0.0.
|
|
42
|
+
"@atproto/lex-data": "^0.0.15"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"vitest": "^4.0.16"
|
package/src/blob.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
BlobRef,
|
|
3
2
|
BlobRefCheckOptions,
|
|
4
3
|
LexMap,
|
|
5
|
-
|
|
4
|
+
TypedBlobRef,
|
|
5
|
+
isTypedBlobRef,
|
|
6
6
|
} from '@atproto/lex-data'
|
|
7
7
|
import { parseLexLink } from './link.js'
|
|
8
8
|
|
|
@@ -26,7 +26,7 @@ import { parseLexLink } from './link.js'
|
|
|
26
26
|
* @example
|
|
27
27
|
* ```typescript
|
|
28
28
|
* // Parse a blob reference from JSON
|
|
29
|
-
* const blobRef =
|
|
29
|
+
* const blobRef = parseTypedBlobRef({
|
|
30
30
|
* $type: 'blob',
|
|
31
31
|
* ref: { $link: 'bafyreib2rxk3rybloqtqwbo' },
|
|
32
32
|
* mimeType: 'image/png',
|
|
@@ -36,14 +36,14 @@ import { parseLexLink } from './link.js'
|
|
|
36
36
|
* // blobRef.ref is a Cid instance
|
|
37
37
|
*
|
|
38
38
|
* // Returns undefined for non-blob objects
|
|
39
|
-
* const result =
|
|
39
|
+
* const result = parseTypedBlobRef({ foo: 'bar' })
|
|
40
40
|
* // result is undefined
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
|
-
export function
|
|
43
|
+
export function parseTypedBlobRef(
|
|
44
44
|
input: LexMap,
|
|
45
45
|
options?: BlobRefCheckOptions,
|
|
46
|
-
):
|
|
46
|
+
): TypedBlobRef | undefined {
|
|
47
47
|
if (input.$type !== 'blob') return undefined
|
|
48
48
|
|
|
49
49
|
const ref = input?.ref
|
|
@@ -59,10 +59,10 @@ export function parseBlobRef(
|
|
|
59
59
|
if (!cid) return undefined
|
|
60
60
|
|
|
61
61
|
const blob = { ...input, ref: cid }
|
|
62
|
-
if (
|
|
62
|
+
if (isTypedBlobRef(blob, options)) return blob
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
if (
|
|
65
|
+
if (isTypedBlobRef(input)) {
|
|
66
66
|
return input
|
|
67
67
|
}
|
|
68
68
|
|
package/src/lex-json.test.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import { describe, expect,
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
2
|
import { LexValue, lexEquals, parseCid } from '@atproto/lex-data'
|
|
3
3
|
import { JsonValue } from './json.js'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
jsonToLex,
|
|
6
|
+
lexParse,
|
|
7
|
+
lexParseJsonBytes,
|
|
8
|
+
lexStringify,
|
|
9
|
+
lexToJson,
|
|
10
|
+
} from './lex-json.js'
|
|
5
11
|
|
|
6
12
|
export const validVectors: Array<{
|
|
7
13
|
name: string
|
|
@@ -364,58 +370,143 @@ export const invalidVectors: Array<{
|
|
|
364
370
|
|
|
365
371
|
describe(lexParse, () => {
|
|
366
372
|
describe('valid vectors', () => {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
373
|
+
describe('strict mode', () => {
|
|
374
|
+
for (const { name, json, lex } of validVectors) {
|
|
375
|
+
test(name, () => {
|
|
370
376
|
expect(
|
|
371
|
-
lexEquals(lex, lexParse(JSON.stringify(json), { strict:
|
|
377
|
+
lexEquals(lex, lexParse(JSON.stringify(json), { strict: true })),
|
|
372
378
|
).toBe(true)
|
|
379
|
+
})
|
|
380
|
+
}
|
|
381
|
+
})
|
|
382
|
+
describe('non-strict mode', () => {
|
|
383
|
+
for (const { name, json, lex } of validVectors) {
|
|
384
|
+
test(name, () => {
|
|
373
385
|
expect(
|
|
374
|
-
lexEquals(lex, lexParse(JSON.stringify(json), { strict:
|
|
386
|
+
lexEquals(lex, lexParse(JSON.stringify(json), { strict: false })),
|
|
375
387
|
).toBe(true)
|
|
376
388
|
})
|
|
377
|
-
}
|
|
378
|
-
}
|
|
389
|
+
}
|
|
390
|
+
})
|
|
379
391
|
})
|
|
380
392
|
|
|
381
393
|
describe('acceptable vectors', () => {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
394
|
+
describe('strict mode', () => {
|
|
395
|
+
for (const { note, json } of acceptableVectors) {
|
|
396
|
+
test(note, () => {
|
|
397
|
+
expect(() =>
|
|
398
|
+
lexParse(JSON.stringify(json), { strict: true }),
|
|
399
|
+
).toThrow()
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
describe('non-strict mode', () => {
|
|
404
|
+
for (const { note, json } of acceptableVectors) {
|
|
405
|
+
test(note, () => {
|
|
385
406
|
expect(() =>
|
|
386
407
|
lexParse(JSON.stringify(json), { strict: false }),
|
|
387
408
|
).not.toThrow()
|
|
388
409
|
})
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
})
|
|
389
413
|
|
|
390
|
-
|
|
414
|
+
describe('invalid vectors', () => {
|
|
415
|
+
describe('strict mode', () => {
|
|
416
|
+
for (const { note, json } of invalidVectors) {
|
|
417
|
+
test(note, () => {
|
|
391
418
|
expect(() =>
|
|
392
419
|
lexParse(JSON.stringify(json), { strict: true }),
|
|
393
420
|
).toThrow()
|
|
394
421
|
})
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
describe('non-strict mode', () => {
|
|
425
|
+
for (const { note, json } of invalidVectors) {
|
|
426
|
+
test(note, () => {
|
|
427
|
+
expect(() =>
|
|
428
|
+
lexParse(JSON.stringify(json), { strict: false }),
|
|
429
|
+
).not.toThrow()
|
|
430
|
+
})
|
|
431
|
+
}
|
|
432
|
+
})
|
|
433
|
+
})
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
describe(lexParseJsonBytes, () => {
|
|
437
|
+
describe('valid vectors', () => {
|
|
438
|
+
describe('strict mode', () => {
|
|
439
|
+
describe('with pretty-printed JSON', () => {
|
|
440
|
+
for (const { name, json, lex } of validVectors) {
|
|
441
|
+
test(name, () => {
|
|
442
|
+
const jsonBytes = Buffer.from(JSON.stringify(json, undefined, 4))
|
|
443
|
+
expect(
|
|
444
|
+
lexEquals(lex, lexParseJsonBytes(jsonBytes, { strict: true })),
|
|
445
|
+
).toBe(true)
|
|
446
|
+
})
|
|
447
|
+
}
|
|
395
448
|
})
|
|
396
|
-
|
|
449
|
+
describe('with compact JSON', () => {
|
|
450
|
+
for (const { name, json, lex } of validVectors) {
|
|
451
|
+
test(name, () => {
|
|
452
|
+
const jsonBytes = Buffer.from(JSON.stringify(json))
|
|
453
|
+
expect(
|
|
454
|
+
lexEquals(lex, lexParseJsonBytes(jsonBytes, { strict: true })),
|
|
455
|
+
).toBe(true)
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
describe('non-strict mode', () => {
|
|
462
|
+
for (const { name, json, lex } of validVectors) {
|
|
463
|
+
test(name, () => {
|
|
464
|
+
const jsonBytes = Buffer.from(JSON.stringify(json))
|
|
465
|
+
expect(
|
|
466
|
+
lexEquals(lex, lexParseJsonBytes(jsonBytes, { strict: false })),
|
|
467
|
+
).toBe(true)
|
|
468
|
+
})
|
|
469
|
+
}
|
|
470
|
+
})
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
describe('acceptable vectors', () => {
|
|
474
|
+
describe('strict mode', () => {
|
|
475
|
+
for (const { note, json } of acceptableVectors) {
|
|
476
|
+
test(note, () => {
|
|
477
|
+
const jsonBytes = Buffer.from(JSON.stringify(json))
|
|
478
|
+
expect(() => lexParseJsonBytes(jsonBytes, { strict: true })).toThrow()
|
|
479
|
+
})
|
|
480
|
+
}
|
|
481
|
+
})
|
|
482
|
+
describe('non-strict mode', () => {
|
|
483
|
+
for (const { note, json } of acceptableVectors) {
|
|
484
|
+
test(note, () => {
|
|
485
|
+
const jsonBytes = Buffer.from(JSON.stringify(json))
|
|
486
|
+
expect(() =>
|
|
487
|
+
lexParseJsonBytes(jsonBytes, { strict: false }),
|
|
488
|
+
).not.toThrow()
|
|
489
|
+
})
|
|
490
|
+
}
|
|
491
|
+
})
|
|
397
492
|
})
|
|
398
493
|
|
|
399
494
|
describe('invalid vectors', () => {
|
|
400
495
|
describe('strict mode', () => {
|
|
401
496
|
for (const { note, json } of invalidVectors) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
lexParse(JSON.stringify(json), { strict: true }),
|
|
406
|
-
).toThrow()
|
|
407
|
-
})
|
|
497
|
+
test(note, () => {
|
|
498
|
+
const jsonBytes = Buffer.from(JSON.stringify(json))
|
|
499
|
+
expect(() => lexParseJsonBytes(jsonBytes, { strict: true })).toThrow()
|
|
408
500
|
})
|
|
409
501
|
}
|
|
410
502
|
})
|
|
411
503
|
describe('non-strict mode', () => {
|
|
412
504
|
for (const { note, json } of invalidVectors) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
})
|
|
505
|
+
test(note, () => {
|
|
506
|
+
const jsonBytes = Buffer.from(JSON.stringify(json))
|
|
507
|
+
expect(() =>
|
|
508
|
+
lexParseJsonBytes(jsonBytes, { strict: false }),
|
|
509
|
+
).not.toThrow()
|
|
419
510
|
})
|
|
420
511
|
}
|
|
421
512
|
})
|
|
@@ -425,10 +516,8 @@ describe(lexParse, () => {
|
|
|
425
516
|
describe(lexStringify, () => {
|
|
426
517
|
describe('valid vectors', () => {
|
|
427
518
|
for (const { name, json, lex } of validVectors) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
expect(JSON.parse(lexStringify(lex))).toEqual(json)
|
|
431
|
-
})
|
|
519
|
+
test(name, () => {
|
|
520
|
+
expect(JSON.parse(lexStringify(lex))).toStrictEqual(json)
|
|
432
521
|
})
|
|
433
522
|
}
|
|
434
523
|
})
|
|
@@ -436,63 +525,66 @@ describe(lexStringify, () => {
|
|
|
436
525
|
|
|
437
526
|
describe(jsonToLex, () => {
|
|
438
527
|
describe('valid vectors', () => {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
528
|
+
describe('strict mode', () => {
|
|
529
|
+
for (const { name, json, lex } of validVectors) {
|
|
530
|
+
test(name, () => {
|
|
442
531
|
expect(lexEquals(jsonToLex(json, { strict: true }), lex)).toBe(true)
|
|
443
532
|
expect(lexEquals(lex, jsonToLex(json, { strict: true }))).toBe(true)
|
|
444
533
|
})
|
|
534
|
+
}
|
|
535
|
+
})
|
|
445
536
|
|
|
446
|
-
|
|
537
|
+
describe('non-strict mode', () => {
|
|
538
|
+
for (const { name, json, lex } of validVectors) {
|
|
539
|
+
test(name, () => {
|
|
447
540
|
expect(lexEquals(jsonToLex(json, { strict: false }), lex)).toBe(true)
|
|
448
541
|
expect(lexEquals(lex, jsonToLex(json, { strict: false }))).toBe(true)
|
|
449
542
|
})
|
|
450
|
-
}
|
|
451
|
-
}
|
|
543
|
+
}
|
|
544
|
+
})
|
|
452
545
|
})
|
|
453
546
|
|
|
454
547
|
describe('acceptable vectors', () => {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
548
|
+
describe('strict mode', () => {
|
|
549
|
+
for (const { note, json } of acceptableVectors) {
|
|
550
|
+
test(note, () => {
|
|
458
551
|
expect(() => jsonToLex(json, { strict: true })).toThrow()
|
|
459
552
|
})
|
|
553
|
+
}
|
|
554
|
+
})
|
|
460
555
|
|
|
461
|
-
|
|
556
|
+
describe('non-strict mode', () => {
|
|
557
|
+
for (const { note, json } of acceptableVectors) {
|
|
558
|
+
test(note, () => {
|
|
462
559
|
expect(() => jsonToLex(json, { strict: false })).not.toThrow()
|
|
463
560
|
})
|
|
464
|
-
}
|
|
465
|
-
}
|
|
561
|
+
}
|
|
562
|
+
})
|
|
466
563
|
})
|
|
467
564
|
|
|
468
565
|
describe('invalid vectors', () => {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
566
|
+
describe('strict mode', () => {
|
|
567
|
+
for (const { note, json } of invalidVectors) {
|
|
568
|
+
test(note, () => {
|
|
472
569
|
expect(() => jsonToLex(json, { strict: true })).toThrow()
|
|
473
570
|
})
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
it('does not throw in non-strict mode', () => {
|
|
571
|
+
}
|
|
572
|
+
})
|
|
573
|
+
describe('non-strict mode', () => {
|
|
574
|
+
for (const { note, json } of invalidVectors) {
|
|
575
|
+
test(note, () => {
|
|
481
576
|
expect(() => jsonToLex(json, { strict: false })).not.toThrow()
|
|
482
577
|
})
|
|
483
|
-
}
|
|
484
|
-
}
|
|
578
|
+
}
|
|
579
|
+
})
|
|
485
580
|
})
|
|
486
581
|
})
|
|
487
582
|
|
|
488
583
|
describe(lexToJson, () => {
|
|
489
584
|
describe('valid vectors', () => {
|
|
490
585
|
for (const { name, json, lex } of validVectors) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
expect(lexToJson(lex)).toEqual(json)
|
|
494
|
-
expect(lexToJson(lex)).toEqual(json)
|
|
495
|
-
})
|
|
586
|
+
test(name, () => {
|
|
587
|
+
expect(lexToJson(lex)).toStrictEqual(json)
|
|
496
588
|
})
|
|
497
589
|
}
|
|
498
590
|
})
|
|
@@ -501,10 +593,19 @@ describe(lexToJson, () => {
|
|
|
501
593
|
describe('json > lex > json', () => {
|
|
502
594
|
describe('valid vectors', () => {
|
|
503
595
|
for (const { name, json } of validVectors) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
596
|
+
test(name, () => {
|
|
597
|
+
expect(lexToJson(jsonToLex(json))).toStrictEqual(json)
|
|
598
|
+
})
|
|
599
|
+
}
|
|
600
|
+
})
|
|
601
|
+
})
|
|
602
|
+
|
|
603
|
+
describe('json (binary) > lex > json', () => {
|
|
604
|
+
describe('valid vectors', () => {
|
|
605
|
+
for (const { name, json } of validVectors) {
|
|
606
|
+
test(name, () => {
|
|
607
|
+
const jsonBytes = Buffer.from(JSON.stringify(json, undefined, 4))
|
|
608
|
+
expect(lexToJson(lexParseJsonBytes(jsonBytes))).toStrictEqual(json)
|
|
508
609
|
})
|
|
509
610
|
}
|
|
510
611
|
})
|
|
@@ -513,12 +614,9 @@ describe('json > lex > json', () => {
|
|
|
513
614
|
describe('lex > json > lex', () => {
|
|
514
615
|
describe('valid vectors', () => {
|
|
515
616
|
for (const { name, lex } of validVectors) {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
expect(lexEquals(jsonToLex(lexToJson(lex)), lex)).toBe(true)
|
|
520
|
-
expect(lexEquals(lex, jsonToLex(lexToJson(lex)))).toBe(true)
|
|
521
|
-
})
|
|
617
|
+
test(name, () => {
|
|
618
|
+
expect(lexEquals(jsonToLex(lexToJson(lex)), lex)).toBe(true)
|
|
619
|
+
expect(lexEquals(lex, jsonToLex(lexToJson(lex)))).toBe(true)
|
|
522
620
|
})
|
|
523
621
|
}
|
|
524
622
|
})
|
package/src/lex-json.ts
CHANGED
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
LexMap,
|
|
6
6
|
LexValue,
|
|
7
7
|
isCid,
|
|
8
|
+
utf8FromBytes,
|
|
8
9
|
} from '@atproto/lex-data'
|
|
9
|
-
import {
|
|
10
|
+
import { parseTypedBlobRef } from './blob.js'
|
|
10
11
|
import { encodeLexBytes, parseLexBytes } from './bytes.js'
|
|
11
12
|
import { JsonObject, JsonValue } from './json.js'
|
|
12
13
|
import { encodeLexLink, parseLexLink } from './link.js'
|
|
@@ -117,6 +118,25 @@ export function lexParse<T extends LexValue = LexValue>(
|
|
|
117
118
|
})
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Parses a `Uint8Array` containing JSON data into a Lex value.
|
|
123
|
+
*/
|
|
124
|
+
export function lexParseJsonBytes(
|
|
125
|
+
jsonBytes: Uint8Array,
|
|
126
|
+
options?: LexParseOptions,
|
|
127
|
+
): LexValue {
|
|
128
|
+
// @TODO optimize this to avoid intermediate string. This requires a custom
|
|
129
|
+
// JSON parser that can operate on binary data, which is non-trivial, but
|
|
130
|
+
// could be a future improvement if performance is a concern. See the link
|
|
131
|
+
// below for an example of a JSON parser that operates on binary data in
|
|
132
|
+
// @ipld/dag-json
|
|
133
|
+
|
|
134
|
+
// https://github.com/ipld/js-dag-json/blob/57912da6e9d64a179f7d2384c3b6d7b07fbfb143/src/index.js#L161
|
|
135
|
+
|
|
136
|
+
const jsonString = utf8FromBytes(jsonBytes)
|
|
137
|
+
return lexParse(jsonString, options)
|
|
138
|
+
}
|
|
139
|
+
|
|
120
140
|
/**
|
|
121
141
|
* Converts a parsed JSON representation of Lexicon value to a {@link LexValue}.
|
|
122
142
|
*
|
|
@@ -328,7 +348,7 @@ function parseSpecialJsonObject(
|
|
|
328
348
|
// the strict option is enabled.
|
|
329
349
|
if (options.strict) {
|
|
330
350
|
if (input.$type === 'blob') {
|
|
331
|
-
const blob =
|
|
351
|
+
const blob = parseTypedBlobRef(input, options)
|
|
332
352
|
if (blob) return blob
|
|
333
353
|
throw new TypeError(`Invalid blob object`)
|
|
334
354
|
} else if (typeof input.$type !== 'string') {
|