json-web-streams 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,209 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction, and
10
+ distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by the
13
+ copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all other
16
+ entities that control, are controlled by, or are under common control with
17
+ that entity. For the purposes of this definition, "control" means (i) the
18
+ power, direct or indirect, to cause the direction or management of such
19
+ entity, whether by contract or otherwise, or (ii) ownership of
20
+ fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
21
+ ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity exercising
24
+ permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation source,
28
+ and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical transformation
31
+ or translation of a Source form, including but not limited to compiled
32
+ object code, generated documentation, and conversions to
33
+ other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or Object
36
+ form, made available under the License, as indicated by a copyright notice
37
+ that is included in or attached to the work (an example is provided in the
38
+ Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object form,
41
+ that is based on (or derived from) the Work and for which the editorial
42
+ revisions, annotations, elaborations, or other modifications represent,
43
+ as a whole, an original work of authorship. For the purposes of this
44
+ License, Derivative Works shall not include works that remain separable
45
+ from, or merely link (or bind by name) to the interfaces of, the Work and
46
+ Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including the original
49
+ version of the Work and any modifications or additions to that Work or
50
+ Derivative Works thereof, that is intentionally submitted to Licensor for
51
+ inclusion in the Work by the copyright owner or by an individual or
52
+ Legal Entity authorized to submit on behalf of the copyright owner.
53
+ For the purposes of this definition, "submitted" means any form of
54
+ electronic, verbal, or written communication sent to the Licensor or its
55
+ representatives, including but not limited to communication on electronic
56
+ mailing lists, source code control systems, and issue tracking systems
57
+ that are managed by, or on behalf of, the Licensor for the purpose of
58
+ discussing and improving the Work, but excluding communication that is
59
+ conspicuously marked or otherwise designated in writing by the copyright
60
+ owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity on
63
+ behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License.
67
+
68
+ Subject to the terms and conditions of this License, each Contributor
69
+ hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
70
+ royalty-free, irrevocable copyright license to reproduce, prepare
71
+ Derivative Works of, publicly display, publicly perform, sublicense,
72
+ and distribute the Work and such Derivative Works in
73
+ Source or Object form.
74
+
75
+ 3. Grant of Patent License.
76
+
77
+ Subject to the terms and conditions of this License, each Contributor
78
+ hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
79
+ royalty-free, irrevocable (except as stated in this section) patent
80
+ license to make, have made, use, offer to sell, sell, import, and
81
+ otherwise transfer the Work, where such license applies only to those
82
+ patent claims licensable by such Contributor that are necessarily
83
+ infringed by their Contribution(s) alone or by combination of their
84
+ Contribution(s) with the Work to which such Contribution(s) was submitted.
85
+ If You institute patent litigation against any entity (including a
86
+ cross-claim or counterclaim in a lawsuit) alleging that the Work or a
87
+ Contribution incorporated within the Work constitutes direct or
88
+ contributory patent infringement, then any patent licenses granted to
89
+ You under this License for that Work shall terminate as of the date such
90
+ litigation is filed.
91
+
92
+ 4. Redistribution.
93
+
94
+ You may reproduce and distribute copies of the Work or Derivative Works
95
+ thereof in any medium, with or without modifications, and in Source or
96
+ Object form, provided that You meet the following conditions:
97
+
98
+ 1. You must give any other recipients of the Work or Derivative Works a
99
+ copy of this License; and
100
+
101
+ 2. You must cause any modified files to carry prominent notices stating
102
+ that You changed the files; and
103
+
104
+ 3. You must retain, in the Source form of any Derivative Works that You
105
+ distribute, all copyright, patent, trademark, and attribution notices from
106
+ the Source form of the Work, excluding those notices that do not pertain
107
+ to any part of the Derivative Works; and
108
+
109
+ 4. If the Work includes a "NOTICE" text file as part of its distribution,
110
+ then any Derivative Works that You distribute must include a readable copy
111
+ of the attribution notices contained within such NOTICE file, excluding
112
+ those notices that do not pertain to any part of the Derivative Works,
113
+ in at least one of the following places: within a NOTICE text file
114
+ distributed as part of the Derivative Works; within the Source form or
115
+ documentation, if provided along with the Derivative Works; or, within a
116
+ display generated by the Derivative Works, if and wherever such
117
+ third-party notices normally appear. The contents of the NOTICE file are
118
+ for informational purposes only and do not modify the License.
119
+ You may add Your own attribution notices within Derivative Works that You
120
+ distribute, alongside or as an addendum to the NOTICE text from the Work,
121
+ provided that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and may
125
+ provide additional or different license terms and conditions for use,
126
+ reproduction, or distribution of Your modifications, or for any such
127
+ Derivative Works as a whole, provided Your use, reproduction, and
128
+ distribution of the Work otherwise complies with the conditions
129
+ stated in this License.
130
+
131
+ 5. Submission of Contributions.
132
+
133
+ Unless You explicitly state otherwise, any Contribution intentionally
134
+ submitted for inclusion in the Work by You to the Licensor shall be under
135
+ the terms and conditions of this License, without any additional
136
+ terms or conditions. Notwithstanding the above, nothing herein shall
137
+ supersede or modify the terms of any separate license agreement you may
138
+ have executed with Licensor regarding such Contributions.
139
+
140
+ 6. Trademarks.
141
+
142
+ This License does not grant permission to use the trade names, trademarks,
143
+ service marks, or product names of the Licensor, except as required for
144
+ reasonable and customary use in describing the origin of the Work and
145
+ reproducing the content of the NOTICE file.
146
+
147
+ 7. Disclaimer of Warranty.
148
+
149
+ Unless required by applicable law or agreed to in writing, Licensor
150
+ provides the Work (and each Contributor provides its Contributions)
151
+ on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
152
+ either express or implied, including, without limitation, any warranties
153
+ or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS
154
+ FOR A PARTICULAR PURPOSE. You are solely responsible for determining the
155
+ appropriateness of using or redistributing the Work and assume any risks
156
+ associated with Your exercise of permissions under this License.
157
+
158
+ 8. Limitation of Liability.
159
+
160
+ In no event and under no legal theory, whether in tort
161
+ (including negligence), contract, or otherwise, unless required by
162
+ applicable law (such as deliberate and grossly negligent acts) or agreed
163
+ to in writing, shall any Contributor be liable to You for damages,
164
+ including any direct, indirect, special, incidental, or consequential
165
+ damages of any character arising as a result of this License or out of
166
+ the use or inability to use the Work (including but not limited to damages
167
+ for loss of goodwill, work stoppage, computer failure or malfunction,
168
+ or any and all other commercial damages or losses), even if such
169
+ Contributor has been advised of the possibility of such damages.
170
+
171
+ 9. Accepting Warranty or Additional Liability.
172
+
173
+ While redistributing the Work or Derivative Works thereof, You may choose
174
+ to offer, and charge a fee for, acceptance of support, warranty,
175
+ indemnity, or other liability obligations and/or rights consistent with
176
+ this License. However, in accepting such obligations, You may act only
177
+ on Your own behalf and on Your sole responsibility, not on behalf of any
178
+ other Contributor, and only if You agree to indemnify, defend, and hold
179
+ each Contributor harmless for any liability incurred by, or claims
180
+ asserted against, such Contributor by reason of your accepting any such
181
+ warranty or additional liability.
182
+
183
+ END OF TERMS AND CONDITIONS
184
+
185
+ APPENDIX: How to apply the Apache License to your work
186
+
187
+ To apply the Apache License to your work, attach the following boilerplate
188
+ notice, with the fields enclosed by brackets "[]" replaced with your own
189
+ identifying information. (Don't include the brackets!) The text should be
190
+ enclosed in the appropriate comment syntax for the file format. We also
191
+ recommend that a file or class name and description of purpose be included
192
+ on the same "printed page" as the copyright notice for easier
193
+ identification within third-party archives.
194
+
195
+ Copyright 2017 Jeremy Scheff
196
+
197
+
198
+ Licensed under the Apache License, Version 2.0 (the "License");
199
+ you may not use this file except in compliance with the License.
200
+ You may obtain a copy of the License at
201
+
202
+ http://www.apache.org/licenses/LICENSE-2.0
203
+
204
+ Unless required by applicable law or agreed to in writing, software
205
+ distributed under the License is distributed on an "AS IS" BASIS,
206
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
207
+ or implied. See the License for the specific language governing
208
+ permissions and limitations under the License.
209
+
package/README.md ADDED
@@ -0,0 +1,367 @@
1
+ # json-web-streams
2
+
3
+ - **Stream large JSON files** without loading everything into memory
4
+ - Built on the **Web Streams API** so it runs in web browsers, Node.js, and more
5
+ - Query with **JSONPath** to extract only the data you need
6
+ - Integrated **schema validation** with full **TypeScript** support
7
+ - Tested on the [JSON Parsing Test Suite](https://github.com/nst/JSONTestSuite) and other edge cases
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ npm install json-web-streams
13
+ ```
14
+
15
+ ## Getting started
16
+
17
+ Imagine you have some JSON like:
18
+
19
+ ```json
20
+ [{ "x": 1 }, { "x": 2 }, { "x": 3 }]
21
+ ```
22
+
23
+ You can just `JSON.parse` it and then do whatever you want. But what if it's so large that parsing it all at once is slow or impossible?
24
+
25
+ By using json-web-streams, you can stream through the JSON object without having to read it all into memory. Here's an example that prints out each object as it is parsed:
26
+
27
+ ```ts
28
+ import { JSONParseStream } from "json-web-streams";
29
+
30
+ // data.json contains: [{ "x": 1 }, { "x": 2 }, { "x": 3 }]
31
+ const response = await fetch("https://example.com/data.json");
32
+ await response.body
33
+ .pipeThrough(new TextDecoderStream())
34
+ .pipeThrough(new JSONParseStream(["$[*]"]))
35
+ .pipeTo(
36
+ new WritableStream({
37
+ write({ value }) {
38
+ console.log(value);
39
+ },
40
+ }),
41
+ );
42
+
43
+ // Output:
44
+ // {"x": 1}
45
+ // {"x": 2}
46
+ // {"x": 3}
47
+ ```
48
+
49
+ > [!TIP]
50
+ > If you don't have to support Safari, [most other environments](https://caniuse.com/mdn-api_readablestream_--asynciterator) let you use a nicer syntax for consuming stream output as an async iterator:
51
+ >
52
+ > ```ts
53
+ > const stream = response.body
54
+ > .pipeThrough(new TextDecoderStream())
55
+ > .pipeThrough(new JSONParseStream(["$[*]"]));
56
+ > for await (const { value } of stream) {
57
+ > console.log(value);
58
+ > }
59
+ > ```
60
+
61
+ ## API
62
+
63
+ ```ts
64
+ const jsonParseStream = new JSONParseStream(
65
+ jsonPaths: (JSONPath | { path: JSONPath; schema: StandardSchemaV1 })[],
66
+ options?: { multi?: boolean },
67
+ );
68
+ ```
69
+
70
+ ### `jsonPaths: (JSONPath | { path: JSONPath; schema: StandardSchemaV1 })[]`
71
+
72
+ The first argument to `JSONParseStream` is an array specifying what objects to emit from the stream. The `JSONPath` type is a string containing a JSONPath query. JSONPath is a query language for JSON to let you pick out specific values from a JSON object.
73
+
74
+ (The `JSONPath` type is just a slightly more restrictive version of a string. I wish it could completely parse and validate the JSONPath syntax, but currently it just enforces some little things like that it must start with a `$`.)
75
+
76
+ In many cases, you'll just have one JSONPath query, like:
77
+
78
+ ```json
79
+ ["$.foo[*]"]
80
+ ```
81
+
82
+ but you can have as many as you want:
83
+
84
+ ```json
85
+ ["$.foo[*]", "$.bar", "$.bar.baz"]
86
+ ```
87
+
88
+ > [!IMPORTANT]
89
+ > **json-web-streams only supports a subset of JSONPath.** See the [JSONPath](#jsonpath) section below for more details and examples. But to briefly explain what a typical JSONPath query means:
90
+ >
91
+ > `$.foo[*]` can be broken into three parts:
92
+ >
93
+ > `$` refers to the root node of the JSON object\
94
+ > `.foo` is similar to accessing an object property in JS, so this is the `foo` property of the root node\
95
+ > `[*]` means "every value in this array or object"
96
+ >
97
+ > In total this query means "emit every value in the array/object in the `foo` property of the overall JSON object". So for this JSON `{ foo: ["A", "B", "C"] }` it would emit the three values `"A"`, `"B"`, and `"C"`.
98
+
99
+ The values of the `jsonPaths` array can either be `JSONPath` strings, or objects like `{ path: JSONPath; schema: StandardSchemaV1 }` where `schema` is a schema validator from any library supporting the [Standard Schema specification](https://github.com/standard-schema/standard-schema) such as Zod, Valibot, or ArkType. When you supply a schema like this, each value will be validated before it is emitted by the stream, and emitted values will have correct TypeScript types rather than being `unknown`. For more details, see the [Schema validation and types for `JSONParseStream` output](#schema-validation-and-types-for-jsonparsestream-output) section below.
100
+
101
+ ### `options?: { multi?: boolean }`
102
+
103
+ There are various [JSON streaming formats](https://en.wikipedia.org/wiki/JSON_streaming) that make it more convenient to stream JSON by omitting the opening/closing tags and instead emitting multiple JSON objects sequentially. Some of these formats are:
104
+
105
+ - JSON Lines (JSONL) aka Newline Delimited JSON (NDJSON) - JSON objects are separated by \n
106
+ - JSON Text Sequences aka json-seq - JSON objects are separated by the unicode record separator character ␞
107
+ - Concatenated JSON - JSON objects are simply concatenated with nothing in between.
108
+
109
+ Setting `multi` to `true` enables support for all of those streaming JSON formats. It's actually a little more permissive - it allows any combination of whitespace and the unicode record separator between JSON objects.
110
+
111
+ > [!TIP]
112
+ > If you want to emit every one of these individual JSON objects, use the JSONPath query `$` which means "emit the entire object", so in `multi` mode it will emit each of the individual objects.
113
+
114
+ ### `JSONParseStream` input
115
+
116
+ `new JSONParseStream(jsonPaths)` returns a [TransformStream](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream), meaning that it receives some input (e.g. from a ReadableStream) and emits some output (e.g. to a WritableStream).
117
+
118
+ Inputs to the `JSONParseStream` stream must be strings. If you have a stream emitting some binary encoded text (such as from `fetch`), pipe it through `TextDecoderStream` first:
119
+
120
+ ```ts
121
+ const response = await fetch("https://example.com/data.json");
122
+ const stream = response.body
123
+ .pipeThrough(new TextDecoderStream())
124
+ .pipeThrough(new JSONParseStream(["$.foo[*]"]));
125
+ ```
126
+
127
+ ### `JSONParseStream` output
128
+
129
+ Output from `JSONParseStream` has this format:
130
+
131
+ ```ts
132
+ type JSONParseStreamOutput<T = unknown> = {
133
+ value: T;
134
+ path: JSONPath;
135
+ wildcardKeys?: string[];
136
+ };
137
+ ```
138
+
139
+ `value` is the value selected by one of your JSONPath queries.
140
+
141
+ `path` is the JSONPath query (from the `jsonPaths` parameter of `JSONParseStream`) that matched `value`.
142
+
143
+ If you only have one JSONPath query, you can ignore `path`. But if you have more than one, `path` may be helpful when processing stream output to distinguish between different types of values. For example:
144
+
145
+ <!-- prettier-ignore -->
146
+ ```ts
147
+ await new ReadableStream({
148
+ start(controller) {
149
+ controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
150
+ controller.close();
151
+ },
152
+ })
153
+ .pipeThrough(new JSONParseStream(["$.bar[*]", "$.foo[*]"]))
154
+ .pipeTo(
155
+ new WritableStream({
156
+ write(record) {
157
+ if (record.path === "$.bar[*]") {
158
+ // Do something with the values from bar
159
+ } else {
160
+ // Do something with the values from foo
161
+ }
162
+ },
163
+ }),
164
+ );
165
+ ```
166
+
167
+ `wildcardKeys` is defined when you have a wildcard in an object (not an array) somewhere in your JSONPath. For example:
168
+
169
+ <!-- prettier-ignore -->
170
+ ```ts
171
+ await new ReadableStream({
172
+ start(controller) {
173
+ controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
174
+ controller.close();
175
+ },
176
+ })
177
+ .pipeThrough(new JSONParseStream(["$[*]"]))
178
+ .pipeTo(
179
+ new WritableStream({
180
+ write(record) {
181
+ console.log(record);
182
+ },
183
+ }),
184
+ );
185
+ // Output:
186
+ // { path: "$[*]", value: [1, 2], wildcardKeys: ["foo"] },
187
+ // { path: "$[*]", value: ["a", "b", "c"], wildcardKeys: ["bar"] },
188
+ ```
189
+
190
+ The purpose of `wildcardKeys` is to allow you to easily distinguish different types of objects. `wildcardKeys` has one entry for each wildcard object in your JSONPath query.
191
+
192
+ > [!WARNING]
193
+ > It is possible to have two JSONPath queries that output overlapping objects, like if your data is `{ "foo": [1, 2] }` and you query for both `$` and `$.foo`. This will emit two objects: `{ foo: [1, 2] }` and `[1, 2]`. Due to how json-web-streams works internally, both of those objects share the same array instance, meaning that if the array in one is mutated it will affect the other.
194
+ >
195
+ > Some schema validation libraries do a deep clone of objects they validate. In that case, you won't have this issue. Otherwise, in the rare case that you query for overlapping objects, you will have to handle this problem, such as by deep cloning one of the objects.
196
+
197
+ #### Schema validation and types for `JSONParseStream` output
198
+
199
+ If you want to validate the objects as they stream in, json-web-streams integrates with any schema validation library that supports the [Standard Schema specification](https://github.com/standard-schema/standard-schema), such as Zod, Valibot, and ArkType.
200
+
201
+ To use schema validation for a JSONPath query, then pass an object `{ path: JSONPath; schema: StandardSchemaV1 }` rather just a string `JSONPath`. Then each value will be validated before being output by the stream, and the correct TypeScript types will be propagated through the stream as well.
202
+
203
+ <!-- prettier-ignore -->
204
+ ```ts
205
+ import * as z from "zod";
206
+
207
+ await new ReadableStream({
208
+ start(controller) {
209
+ controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
210
+ controller.close();
211
+ },
212
+ })
213
+ .pipeThrough(
214
+ new JSONParseStream([
215
+ { path: "$.foo[*]", schema: z.number() },
216
+ { path: "$.bar[*]", schema: z.string() },
217
+ ]),
218
+ )
219
+ .pipeTo(
220
+ new WritableStream({
221
+ write(record) {
222
+ if (record.path === "$.foo[*]") {
223
+ // Type of record.value is number
224
+ } else {
225
+ // Type of record.value is string
226
+ }
227
+ },
228
+ }),
229
+ );
230
+ ```
231
+
232
+ > [!TIP]
233
+ > If you only want to validate some values, you can mix `{ path: JSONPath; schema: StandardSchemaV1 }` and `JSONPath` in the `jsonPaths` array.
234
+
235
+ For JSONPath queries with no schema, emitted values will have the `unknown` type.
236
+
237
+ ## JSONPath
238
+
239
+ json-web-streams supports a subset of JSONPath. Currently the supported components are:
240
+
241
+ - **The root node**, represented by the symbol `$` which must be the first character of any JSONPath query.
242
+
243
+ - **Name selectors** which are like accessing a property in a JS object. For instance if you have an object like `{ "foo": { bar: 5 } }`, then `$.foo.bar` refers to the value `5`. You can also write this in the more verbose bracket notation like `$["foo"]["bar"]` or `$["foo", "bar"]`, which is useful if your key names include characters that need escaping. You can also mix them like `$.foo["bar"]` or use single quotes like `$['foo']['bar']` - all of these JSONPath queries have the same meaning.
244
+
245
+ - **Wildcard selectors** which select every value in an array or object. With this JSON `{ "foo": { "a": 1, "b": 2, "c": 3 } }`, the JSONPath query `$.foo[*]` would emit the three individual numbers `1`, `2`, and `3`. If the inner object was changed to an array like `{ "foo": [1, 2, 3] }`, the same JSONPath query would emit the same values.
246
+
247
+ You can combine these selectors as deep as you want. For instance, if instead you have an array of objects you can select values inside those individual objects with a query like `$.foo[*].bar`. Applying that to this data:
248
+
249
+ ```json
250
+ { "foo": [{ "bar": 1 }, { "bar": 2 }, { "bar": 3 }] }
251
+ ```
252
+
253
+ would emit `1`, `2`, and `3`.
254
+
255
+ Or if the array is at the root if the object like this data:
256
+
257
+ ```json
258
+ [{ "x": 1 }, { "x": 2 }, { "x": 3 }]
259
+ ```
260
+
261
+ then you'd write something like `$[*]` to emit each object (`{ x: 1}`, `{x: 2}`, `{x: 3}`), or `$[*].key` to emit just the numbers (`1`, `2`, `3`).
262
+
263
+ To emit the whole object at once (okay in that case you wouldn't use this library, but maybe just for testing, or for `multi` mode) you just use `$`.
264
+
265
+ > [!TIP]
266
+ > If you want to play around with JSONPath queries to make sure you understand what they are doing, [jsonpath.com](https://jsonpath.com/) is a great website that lets you easily run a JSONPath query on some data.
267
+
268
+ ## JSONParseStream examples
269
+
270
+ There are several examples above in code blocks throughout the README, and here are a few more!
271
+
272
+ ### `wildcardKeys` vs. multiple entries in `jsonPaths`
273
+
274
+ Sometimes there are multiple ways to achieve your goal.
275
+
276
+ Let's say you have this JSON:
277
+
278
+ ```json
279
+ { "foo": [1, 2], "bar": ["a", "b", "c"] }
280
+ ```
281
+
282
+ You want to get all the values in `foo` and all the values in `bar`. You could define them as two separate JSONPath queries and then distinguish the output with `.path`:
283
+
284
+ <!-- prettier-ignore -->
285
+ ```ts
286
+ await new ReadableStream({
287
+ start(controller) {
288
+ controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
289
+ controller.close();
290
+ },
291
+ })
292
+ .pipeThrough(
293
+ new JSONParseStream(["$.foo[*]", "$.bar[*]"]),
294
+ )
295
+ .pipeTo(
296
+ new WritableStream({
297
+ write(record) {
298
+ if (record.path === "$.foo[*]") {
299
+ // 1, 2
300
+ } else {
301
+ // a, b, c
302
+ }
303
+ },
304
+ }),
305
+ );
306
+ ```
307
+
308
+ Or you could use one JSONPath query with a wildcard, and then use `.wildcardKeys` to distinguish the objects:
309
+
310
+ <!-- prettier-ignore -->
311
+ ```ts
312
+ await new ReadableStream({
313
+ start(controller) {
314
+ controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
315
+ controller.close();
316
+ },
317
+ })
318
+ .pipeThrough(
319
+ new JSONParseStream(["$[*][*]"]),
320
+ )
321
+ .pipeTo(
322
+ new WritableStream({
323
+ write(record) {
324
+ if (record.wildcardKeys[0] === "foo") {
325
+ // 1, 2
326
+ } else {
327
+ // a, b, c
328
+ }
329
+ },
330
+ }),
331
+ );
332
+ ```
333
+
334
+ Using multiple JSONPath queries is a little more explicit, but using wildcard keys is more concise, especially if you had more than just two types of objects. And instead of known keys like `foo` and `bar` your JSON had some unknown keys, then using a wildcard would be your only option.
335
+
336
+ But a nice thing about multiple JSONPath queries is that you can add schema validation to ensure your data is the correct format and give you nice TypeScript types. Whereas if you are using `wildcardKeys` to distinguish types, there is currently no way to use that information in schema validation.
337
+
338
+ In this example, Zod schemas enforce that `record.value` is either a `string` or `number` as appropriate, rather than `unknown`:
339
+
340
+ <!-- prettier-ignore -->
341
+ ```ts
342
+ import * as z from "zod";
343
+
344
+ await new ReadableStream({
345
+ start(controller) {
346
+ controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
347
+ controller.close();
348
+ },
349
+ })
350
+ .pipeThrough(
351
+ new JSONParseStream([
352
+ { path: "$.foo[*]", schema: z.number() },
353
+ { path: "$.bar[*]", schema: z.string() },
354
+ ]),
355
+ )
356
+ .pipeTo(
357
+ new WritableStream({
358
+ write(record) {
359
+ if (record.path === "$.foo[*]") {
360
+ // 1, 2
361
+ } else {
362
+ // a, b, c
363
+ }
364
+ },
365
+ }),
366
+ );
367
+ ```
@@ -0,0 +1,18 @@
1
+ import { JSONParserText } from "./JSONParserText.js";
2
+ import { JSONPath } from "./jsonPathToQueryPath.js";
3
+ import { StandardSchemaV1 } from "@standard-schema/spec";
4
+
5
+ //#region src/JSONParserStream.d.ts
6
+ declare const createJSONParserStream: <JSONPathsObject extends Partial<Record<JSONPath, StandardSchemaV1 | null | undefined>> = Record<JSONPath, never>>(jsonPaths: JSONPathsObject) => JSONParserStream<JSONPathsObject>;
7
+ declare class JSONParserStream<JSONPathsObject extends Partial<Record<JSONPath, StandardSchemaV1 | null | undefined>>> extends TransformStream<string, {
8
+ jsonPath: JSONPath;
9
+ value: unknown;
10
+ wildcardKeys?: string[];
11
+ }> {
12
+ _parser: JSONParserText;
13
+ constructor(jsonPaths: JSONPathsObject, options?: {
14
+ multi?: boolean;
15
+ });
16
+ }
17
+ //#endregion
18
+ export { createJSONParserStream };
@@ -0,0 +1,108 @@
1
+ import JSONParserText_default from "./JSONParserText.js";
2
+ import { jsonPathToQueryPath } from "./jsonPathToQueryPath.js";
3
+
4
+ //#region src/JSONParserStream.ts
5
+ const stackToQueryPath = (stack) => {
6
+ return stack.slice(1).map((row) => {
7
+ if (row.mode === "OBJECT" && row.key !== void 0) return {
8
+ type: "key",
9
+ value: row.key
10
+ };
11
+ if (row.mode === "ARRAY") return { type: "wildcard" };
12
+ throw new Error(`Unexpected mode "${row.mode}"`);
13
+ });
14
+ };
15
+ const isEqual = (x, y) => {
16
+ if (!y) return false;
17
+ if (x.type === y?.type) {
18
+ if (x.type === "wildcard") return true;
19
+ if (x.value === y.value) return true;
20
+ return false;
21
+ }
22
+ return x.type === "wildcard";
23
+ };
24
+ const createJSONParserStream = (jsonPaths) => {
25
+ return new JSONParserStream(jsonPaths);
26
+ };
27
+ var JSONParserStream = class extends TransformStream {
28
+ _parser;
29
+ constructor(jsonPaths, options) {
30
+ let parser;
31
+ const multi = options?.multi ?? false;
32
+ const queryInfos = /* @__PURE__ */ new Map();
33
+ for (const [key, schema] of Object.entries(jsonPaths)) {
34
+ const jsonPath = key;
35
+ const queryPath = jsonPathToQueryPath(jsonPath);
36
+ let wildcardIndexes;
37
+ for (const [i, component] of queryPath.entries()) if (component.type === "wildcard") {
38
+ if (wildcardIndexes === void 0) wildcardIndexes = [];
39
+ wildcardIndexes.push(i);
40
+ }
41
+ queryInfos.set(jsonPath, {
42
+ jsonPath,
43
+ queryPath,
44
+ schema,
45
+ wildcardIndexes
46
+ });
47
+ }
48
+ super({
49
+ start(controller) {
50
+ parser = new JSONParserText_default({
51
+ multi,
52
+ onValue: (value, stack) => {
53
+ const path = stackToQueryPath(stack);
54
+ let keep = false;
55
+ for (const { jsonPath, queryPath, schema, wildcardIndexes } of queryInfos.values()) if (queryPath.every((x, j) => isEqual(x, path[j]))) if (path.length === queryPath.length) {
56
+ let valueToEmit;
57
+ if (schema) {
58
+ const result = schema["~standard"].validate(value);
59
+ if (result instanceof Promise) throw new Error("async schema validation is not supported");
60
+ if (result.issues) throw new Error(JSON.stringify(result.issues, null, 2));
61
+ valueToEmit = result.value;
62
+ } else valueToEmit = queryInfos.size === 1 ? value : structuredClone(value);
63
+ console.log("valueToEmit", value, valueToEmit);
64
+ let wildcardKeys;
65
+ if (wildcardIndexes) {
66
+ if (wildcardIndexes) for (const index of wildcardIndexes) {
67
+ const pathComponent = path[index];
68
+ if (pathComponent?.type === "key") {
69
+ if (!wildcardKeys) wildcardKeys = [];
70
+ wildcardKeys.push(pathComponent.value);
71
+ }
72
+ }
73
+ }
74
+ if (wildcardKeys) controller.enqueue({
75
+ jsonPath,
76
+ value: valueToEmit,
77
+ wildcardKeys
78
+ });
79
+ else controller.enqueue({
80
+ jsonPath,
81
+ value: valueToEmit
82
+ });
83
+ } else keep = true;
84
+ else {
85
+ const type = typeof value;
86
+ if (type === "string" || type === "number" || type === "boolean" || value === null) keep = true;
87
+ }
88
+ if (!keep) {
89
+ for (const row of parser.stack) row.value = void 0;
90
+ if (typeof parser.value === "object" && parser.value !== null && parser.key !== void 0) delete parser.value[parser.key];
91
+ }
92
+ }
93
+ });
94
+ },
95
+ transform(chunk) {
96
+ parser.write(chunk);
97
+ },
98
+ flush(controller) {
99
+ parser.checkEnd();
100
+ controller.terminate();
101
+ }
102
+ });
103
+ this._parser = parser;
104
+ }
105
+ };
106
+
107
+ //#endregion
108
+ export { createJSONParserStream };
@@ -0,0 +1,47 @@
1
+ //#region src/JSONParserText.d.ts
2
+ type Token = "LEFT_BRACE" | "RIGHT_BRACE" | "LEFT_BRACKET" | "RIGHT_BRACKET" | "COLON" | "COMMA" | "TRUE" | "FALSE" | "NULL" | "STRING" | "NUMBER";
3
+ type ParserState = "VALUE" | "KEY" | "VALUE_AFTER_COMMA" | "KEY_AFTER_COMMA";
4
+ type TokenizerState = "START" | "TRUE1" | "TRUE2" | "TRUE3" | "FALSE1" | "FALSE2" | "FALSE3" | "FALSE4" | "NULL1" | "NULL2" | "NULL3" | "NUMBER-" | "NUMBER0" | "NUMBER" | "STRING1" | "STRING2" | "STRING3" | "STRING4" | "STRING5" | "STRING6";
5
+ type Mode = "OBJECT" | "ARRAY";
6
+ type Key = string | number;
7
+ type Value = any;
8
+ type Stack = {
9
+ key: Key | undefined;
10
+ value: Value | undefined;
11
+ mode: Mode | undefined;
12
+ }[];
13
+ type OnValue = (value: Value, stack: Stack) => void;
14
+ declare class JSONParserText {
15
+ tokenizerState: TokenizerState;
16
+ state: Token | ParserState;
17
+ mode: Mode | undefined;
18
+ stack: Stack;
19
+ string: string | undefined;
20
+ key: Key | undefined;
21
+ value: Value;
22
+ position: number;
23
+ onValue: OnValue;
24
+ unicode: string | undefined;
25
+ highSurrogate: number | undefined;
26
+ seenRootObject: boolean;
27
+ multi: boolean;
28
+ multiIndex: number;
29
+ constructor({
30
+ multi,
31
+ onValue
32
+ }: {
33
+ multi: boolean;
34
+ onValue: OnValue;
35
+ });
36
+ charError(char: string, i: number): void;
37
+ parseError(token: Token, value: Value, i: number): void;
38
+ write(text: string): void;
39
+ push(): void;
40
+ pop(): void;
41
+ emit(value: Value): void;
42
+ onToken(token: Token, value: Value, i: number): void;
43
+ numberReviver(text: string, i: number): void;
44
+ checkEnd(): void;
45
+ }
46
+ //#endregion
47
+ export { JSONParserText };
@@ -0,0 +1,261 @@
1
+ //#region src/JSONParserText.ts
2
+ const WHITESPACE = new Set([
3
+ " ",
4
+ " ",
5
+ "\n",
6
+ "\r"
7
+ ]);
8
+ var JSONParserText = class {
9
+ tokenizerState = "START";
10
+ state = "VALUE";
11
+ mode;
12
+ stack = [];
13
+ string;
14
+ key;
15
+ value;
16
+ position = 0;
17
+ onValue;
18
+ unicode;
19
+ highSurrogate;
20
+ seenRootObject = false;
21
+ multi;
22
+ multiIndex = 0;
23
+ constructor({ multi, onValue }) {
24
+ this.onValue = onValue;
25
+ this.multi = multi;
26
+ }
27
+ charError(char, i) {
28
+ throw new Error(`Unexpected ${JSON.stringify(char)} at position ${this.position + i} in state ${this.tokenizerState}`);
29
+ }
30
+ parseError(token, value, i) {
31
+ throw new Error(`Unexpected ${token}${value ? `(${JSON.stringify(value)})` : ""} at position ${this.position + i} in state ${this.state}`);
32
+ }
33
+ write(text) {
34
+ for (let i = 0, l = text.length; i < l; i++) {
35
+ const n = text[i];
36
+ if (!this.multi && this.stack.length === 0 && this.seenRootObject && !WHITESPACE.has(n)) return this.charError(n, i);
37
+ if (this.multi && this.stack.length === 0 && this.seenRootObject) {}
38
+ if (this.tokenizerState === "START") if (n === "{") this.onToken("LEFT_BRACE", "{", i);
39
+ else if (n === "}") this.onToken("RIGHT_BRACE", "}", i);
40
+ else if (n === "[") this.onToken("LEFT_BRACKET", "[", i);
41
+ else if (n === "]") this.onToken("RIGHT_BRACKET", "]", i);
42
+ else if (n === ":") this.onToken("COLON", ":", i);
43
+ else if (n === ",") this.onToken("COMMA", ",", i);
44
+ else if (n === "t") this.tokenizerState = "TRUE1";
45
+ else if (n === "f") this.tokenizerState = "FALSE1";
46
+ else if (n === "n") this.tokenizerState = "NULL1";
47
+ else if (n === "\"") {
48
+ this.string = "";
49
+ this.tokenizerState = "STRING1";
50
+ } else if (n === "-") {
51
+ this.string = "-";
52
+ this.tokenizerState = "NUMBER-";
53
+ } else if (n === "0") {
54
+ this.string = n;
55
+ this.tokenizerState = "NUMBER0";
56
+ } else if (n >= "1" && n <= "9") {
57
+ this.string = n;
58
+ this.tokenizerState = "NUMBER";
59
+ } else if (WHITESPACE.has(n)) {} else if (n === "␞" && this.multi && this.stack.length === 0) {} else return this.charError(n, i);
60
+ else if (this.tokenizerState === "STRING1") if (n === "\"") {
61
+ this.tokenizerState = "START";
62
+ this.onToken("STRING", this.string, i);
63
+ this.string = void 0;
64
+ } else if (n === "\\") this.tokenizerState = "STRING2";
65
+ else {
66
+ if (n.charCodeAt(0) <= 31) this.charError(n, i);
67
+ this.string += n;
68
+ }
69
+ else if (this.tokenizerState === "STRING2") if (n === "\"") {
70
+ this.string += "\"";
71
+ this.tokenizerState = "STRING1";
72
+ } else if (n === "\\") {
73
+ this.string += "\\";
74
+ this.tokenizerState = "STRING1";
75
+ } else if (n === "/") {
76
+ this.string += "/";
77
+ this.tokenizerState = "STRING1";
78
+ } else if (n === "b") {
79
+ this.string += "\b";
80
+ this.tokenizerState = "STRING1";
81
+ } else if (n === "f") {
82
+ this.string += "\f";
83
+ this.tokenizerState = "STRING1";
84
+ } else if (n === "n") {
85
+ this.string += "\n";
86
+ this.tokenizerState = "STRING1";
87
+ } else if (n === "r") {
88
+ this.string += "\r";
89
+ this.tokenizerState = "STRING1";
90
+ } else if (n === "t") {
91
+ this.string += " ";
92
+ this.tokenizerState = "STRING1";
93
+ } else if (n === "u") {
94
+ this.unicode = "";
95
+ this.tokenizerState = "STRING3";
96
+ } else return this.charError(n, i);
97
+ else if (this.tokenizerState === "STRING3" || this.tokenizerState === "STRING4" || this.tokenizerState === "STRING5" || this.tokenizerState === "STRING6") {
98
+ this.unicode += n;
99
+ if (this.tokenizerState === "STRING3") this.tokenizerState = "STRING4";
100
+ else if (this.tokenizerState === "STRING4") this.tokenizerState = "STRING5";
101
+ else if (this.tokenizerState === "STRING5") this.tokenizerState = "STRING6";
102
+ else if (this.tokenizerState === "STRING6") {
103
+ const intVal = Number.parseInt(this.unicode, 16);
104
+ if (Number.isNaN(intVal)) return this.charError(n, i);
105
+ this.unicode = void 0;
106
+ if (this.highSurrogate !== void 0 && intVal >= 56320 && intVal < 57344) {
107
+ this.string += String.fromCharCode(this.highSurrogate, intVal);
108
+ this.highSurrogate = void 0;
109
+ } else if (this.highSurrogate === void 0 && intVal >= 55296 && intVal < 56320) this.highSurrogate = intVal;
110
+ else {
111
+ if (this.highSurrogate !== void 0) {
112
+ this.string += String.fromCharCode(this.highSurrogate);
113
+ this.highSurrogate = void 0;
114
+ }
115
+ this.string += String.fromCharCode(intVal);
116
+ }
117
+ this.tokenizerState = "STRING1";
118
+ }
119
+ } else if (this.tokenizerState === "NUMBER" || this.tokenizerState === "NUMBER-" || this.tokenizerState === "NUMBER0") {
120
+ if (this.tokenizerState === "NUMBER0" && n >= "0" && n <= "9") return this.charError("0", i - 1);
121
+ switch (n) {
122
+ case "0":
123
+ this.string += n;
124
+ this.tokenizerState = this.tokenizerState === "NUMBER-" ? "NUMBER0" : "NUMBER";
125
+ break;
126
+ case "1":
127
+ case "2":
128
+ case "3":
129
+ case "4":
130
+ case "5":
131
+ case "6":
132
+ case "7":
133
+ case "8":
134
+ case "9":
135
+ case ".":
136
+ case "e":
137
+ case "E":
138
+ case "+":
139
+ case "-":
140
+ this.string += n;
141
+ this.tokenizerState = "NUMBER";
142
+ break;
143
+ default:
144
+ this.tokenizerState = "START";
145
+ this.numberReviver(this.string, i);
146
+ this.string = void 0;
147
+ i--;
148
+ break;
149
+ }
150
+ } else if (this.tokenizerState === "TRUE1") if (n === "r") this.tokenizerState = "TRUE2";
151
+ else return this.charError(n, i);
152
+ else if (this.tokenizerState === "TRUE2") if (n === "u") this.tokenizerState = "TRUE3";
153
+ else return this.charError(n, i);
154
+ else if (this.tokenizerState === "TRUE3") if (n === "e") {
155
+ this.tokenizerState = "START";
156
+ this.onToken("TRUE", true, i);
157
+ } else return this.charError(n, i);
158
+ else if (this.tokenizerState === "FALSE1") if (n === "a") this.tokenizerState = "FALSE2";
159
+ else return this.charError(n, i);
160
+ else if (this.tokenizerState === "FALSE2") if (n === "l") this.tokenizerState = "FALSE3";
161
+ else return this.charError(n, i);
162
+ else if (this.tokenizerState === "FALSE3") if (n === "s") this.tokenizerState = "FALSE4";
163
+ else return this.charError(n, i);
164
+ else if (this.tokenizerState === "FALSE4") if (n === "e") {
165
+ this.tokenizerState = "START";
166
+ this.onToken("FALSE", false, i);
167
+ } else return this.charError(n, i);
168
+ else if (this.tokenizerState === "NULL1") if (n === "u") this.tokenizerState = "NULL2";
169
+ else return this.charError(n, i);
170
+ else if (this.tokenizerState === "NULL2") if (n === "l") this.tokenizerState = "NULL3";
171
+ else return this.charError(n, i);
172
+ else if (this.tokenizerState === "NULL3") if (n === "l") {
173
+ this.tokenizerState = "START";
174
+ this.onToken("NULL", null, i);
175
+ } else return this.charError(n, i);
176
+ }
177
+ this.position += text.length;
178
+ }
179
+ push() {
180
+ this.stack.push({
181
+ value: this.value,
182
+ key: this.key,
183
+ mode: this.mode
184
+ });
185
+ }
186
+ pop() {
187
+ const value = this.value;
188
+ const parent = this.stack.pop();
189
+ this.value = parent.value;
190
+ this.key = parent.key;
191
+ this.mode = parent.mode;
192
+ this.emit(value);
193
+ if (!this.mode) this.state = "VALUE";
194
+ }
195
+ emit(value) {
196
+ if (this.mode) this.state = "COMMA";
197
+ this.onValue(value, [...this.stack, {
198
+ value: this.value,
199
+ key: this.key,
200
+ mode: this.mode
201
+ }]);
202
+ }
203
+ onToken(token, value, i) {
204
+ if (this.stack.length === 0) {
205
+ if (!this.seenRootObject) this.seenRootObject = true;
206
+ }
207
+ if (this.state === "VALUE" || this.state === "VALUE_AFTER_COMMA") if (token === "STRING" || token === "NUMBER" || token === "TRUE" || token === "FALSE" || token === "NULL") {
208
+ if (this.value) this.value[this.key] = value;
209
+ this.emit(value);
210
+ } else if (token === "LEFT_BRACE") {
211
+ this.push();
212
+ if (this.value) this.value = this.value[this.key] = {};
213
+ else this.value = {};
214
+ this.key = void 0;
215
+ this.state = "KEY";
216
+ this.mode = "OBJECT";
217
+ } else if (token === "LEFT_BRACKET") {
218
+ this.push();
219
+ if (this.value) this.value = this.value[this.key] = [];
220
+ else this.value = [];
221
+ this.key = 0;
222
+ this.mode = "ARRAY";
223
+ this.state = "VALUE";
224
+ } else if (token === "RIGHT_BRACE") if (this.mode === "OBJECT" && this.state !== "VALUE_AFTER_COMMA") this.pop();
225
+ else return this.parseError(token, value, i);
226
+ else if (token === "RIGHT_BRACKET") if (this.mode === "ARRAY" && this.state !== "VALUE_AFTER_COMMA") this.pop();
227
+ else return this.parseError(token, value, i);
228
+ else return this.parseError(token, value, i);
229
+ else if (this.state === "KEY" || this.state === "KEY_AFTER_COMMA") if (token === "STRING") {
230
+ this.key = value;
231
+ this.state = "COLON";
232
+ } else if (token === "RIGHT_BRACE" && this.state !== "KEY_AFTER_COMMA") this.pop();
233
+ else return this.parseError(token, value, i);
234
+ else if (this.state === "COLON") if (token === "COLON") this.state = "VALUE";
235
+ else return this.parseError(token, value, i);
236
+ else if (this.state === "COMMA") if (token === "COMMA") {
237
+ if (this.mode === "ARRAY") {
238
+ this.key++;
239
+ this.state = "VALUE_AFTER_COMMA";
240
+ } else if (this.mode === "OBJECT") this.state = "KEY_AFTER_COMMA";
241
+ } else if (token === "RIGHT_BRACKET" && this.mode === "ARRAY" || token === "RIGHT_BRACE" && this.mode === "OBJECT") this.pop();
242
+ else return this.parseError(token, value, i);
243
+ else return this.parseError(token, value, i);
244
+ }
245
+ numberReviver(text, i) {
246
+ const number = JSON.parse(text);
247
+ if (Number.isNaN(number)) return this.charError(text, i);
248
+ this.onToken("NUMBER", number, i);
249
+ }
250
+ checkEnd() {
251
+ if (this.stack.length > 0) throw new Error(`Unexpected end of input at position ${this.position} in state ${this.state}`);
252
+ if (this.state === "VALUE" && this.tokenizerState === "NUMBER" && this.string !== void 0) {
253
+ this.numberReviver(this.string, this.position - 1);
254
+ this.string = void 0;
255
+ } else if (!this.seenRootObject) throw new Error("No data in input");
256
+ }
257
+ };
258
+ var JSONParserText_default = JSONParserText;
259
+
260
+ //#endregion
261
+ export { JSONParserText_default as default };
@@ -0,0 +1,3 @@
1
+ import { JSONPath } from "./jsonPathToQueryPath.js";
2
+ import { createJSONParserStream } from "./JSONParserStream.js";
3
+ export { type JSONPath, createJSONParserStream };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { createJSONParserStream } from "./JSONParserStream.js";
2
+
3
+ export { createJSONParserStream };
@@ -0,0 +1,5 @@
1
+ //#region src/jsonPathToQueryPath.d.ts
2
+
3
+ type JSONPath = "$" | `$${"." | "["}${string}`;
4
+ //#endregion
5
+ export { JSONPath };
@@ -0,0 +1,33 @@
1
+ import parser from "jsonpath-rfc9535/parser";
2
+
3
+ //#region src/jsonPathToQueryPath.ts
4
+ const jsonPathToQueryPath = (jsonPath) => {
5
+ let parsed;
6
+ try {
7
+ parsed = parser(jsonPath);
8
+ } catch (error) {
9
+ throw new Error(`Error parsing JSONPath "${jsonPath}"`, { cause: error });
10
+ }
11
+ return parsed.segments.flatMap((segment) => {
12
+ if (segment.type === "ChildSegment") {
13
+ const node = segment.node;
14
+ if (node.type === "MemberNameShorthand") return {
15
+ type: "key",
16
+ value: node.value
17
+ };
18
+ else if (node.type === "BracketedSelection") return node.selectors.map((selector) => {
19
+ if (selector.type === "NameSelector") return {
20
+ type: "key",
21
+ value: selector.value
22
+ };
23
+ else if (selector.type === "WildcardSelector") return { type: "wildcard" };
24
+ else throw new Error(`Unsupported node: ${JSON.stringify(node)}`);
25
+ });
26
+ else if (node.type === "WildcardSelector") return { type: "wildcard" };
27
+ else throw new Error(`${segment.type} node type not supported`);
28
+ } else throw new Error(`${segment.type} segment type not supported`);
29
+ });
30
+ };
31
+
32
+ //#endregion
33
+ export { jsonPathToQueryPath };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "json-web-streams",
3
+ "version": "0.0.1",
4
+ "description": "Streaming JSON parser built on top of the Web Streams API, so it works in web browsers, Node.js, and many other environments",
5
+ "keywords": [
6
+ "web streams",
7
+ "TransformStream",
8
+ "JSON"
9
+ ],
10
+ "homepage": "https://github.com/zengm-games/json-web-streams",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git://github.com/zengm-games/json-web-streams.git"
14
+ },
15
+ "license": "Apache-2.0",
16
+ "author": "Jeremy Scheff <jeremy@zengm.com> (https://zengm.com/)",
17
+ "type": "module",
18
+ "main": "dist/index.js",
19
+ "types": "dist/index.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsdown --unbundle",
25
+ "format": "prettier --write .",
26
+ "lint": "npm-run-all --parallel lint:oxlint lint:tsc",
27
+ "lint:oxlint": "oxlint --type-aware",
28
+ "lint:tsc": "tsc",
29
+ "prepare": "husky",
30
+ "test": "vitest"
31
+ },
32
+ "lint-staged": {
33
+ "*.{js,cjs,mjs,jsx,json,scss,ts,cts,mts,tsx,md}": "prettier --write"
34
+ },
35
+ "dependencies": {
36
+ "@standard-schema/spec": "^1.0.0",
37
+ "jsonpath-rfc9535": "^1.3.0"
38
+ },
39
+ "devDependencies": {
40
+ "@ianvs/prettier-plugin-sort-imports": "^4.7.0",
41
+ "@types/node": "^24.8.1",
42
+ "husky": "^9.1.7",
43
+ "lint-staged": "^16.2.4",
44
+ "npm-run-all2": "^8.0.4",
45
+ "oxlint": "^1.23.0",
46
+ "oxlint-tsgolint": "^0.2.0",
47
+ "prettier": "^3.6.2",
48
+ "prettier-plugin-packagejson": "^2.5.19",
49
+ "tsdown": "^0.15.7",
50
+ "typescript": "^5.9.3",
51
+ "vitest": "^3.2.4",
52
+ "zod": "^4.1.12"
53
+ },
54
+ "engines": {
55
+ "node": ">=22",
56
+ "pnpm": "^10.0.0"
57
+ }
58
+ }