json-web-streams 1.0.0 → 1.2.0

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/README.md CHANGED
@@ -35,16 +35,14 @@ import { JSONParseStream } from "json-web-streams";
35
35
 
36
36
  // data.json contains: [{ "x": 1 }, { "x": 2 }, { "x": 3 }]
37
37
  const response = await fetch("https://example.com/data.json");
38
- await response.body
38
+
39
+ const stream = response.body
39
40
  .pipeThrough(new TextDecoderStream())
40
- .pipeThrough(new JSONParseStream(["$[*]"]))
41
- .pipeTo(
42
- new WritableStream({
43
- write({ value }) {
44
- console.log(value);
45
- },
46
- }),
47
- );
41
+ .pipeThrough(new JSONParseStream(["$[*]"]));
42
+
43
+ for await (const { value } of stream) {
44
+ console.log(value);
45
+ }
48
46
 
49
47
  // Output:
50
48
  // {"x": 1}
@@ -52,28 +50,16 @@ await response.body
52
50
  // {"x": 3}
53
51
  ```
54
52
 
55
- > [!TIP]
56
- > 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:
57
- >
58
- > ```ts
59
- > const stream = response.body
60
- > .pipeThrough(new TextDecoderStream())
61
- > .pipeThrough(new JSONParseStream(["$[*]"]));
62
- > for await (const { value } of stream) {
63
- > console.log(value);
64
- > }
65
- > ```
66
-
67
53
  ## API
68
54
 
69
55
  ```ts
70
56
  const jsonParseStream = new JSONParseStream(
71
- jsonPaths: (JSONPath | { path: JSONPath; schema: StandardSchemaV1 })[],
57
+ jsonPaths: (JSONPath | { path: JSONPath; key?: Key; schema?: StandardSchemaV1 })[],
72
58
  options?: { multi?: boolean },
73
59
  );
74
60
  ```
75
61
 
76
- ### `jsonPaths: (JSONPath | { path: JSONPath; schema: StandardSchemaV1 })[]`
62
+ ### `jsonPaths: (JSONPath | { path: JSONPath; key?: Key; schema?: StandardSchemaV1 })[]`
77
63
 
78
64
  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.
79
65
 
@@ -102,7 +88,15 @@ but you can have as many as you want:
102
88
  >
103
89
  > 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"`.
104
90
 
105
- 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.
91
+ The values of the `jsonPaths` array can either be `JSONPath` strings, or objects like `{ path: JSONPath; key?: Key; schema?: StandardSchemaV1 }` to specify the additional optional `key` and `schema` properties.
92
+
93
+ #### `key?: Key`
94
+
95
+ If `key` is defined, it will be propagated through to the `key` property on output objects. So it can be any type, but typically is probably something like a number, string, or Symbol that you want to use to discriminate between types of outputs when you have multiple entries in the `jsonPaths` array. When `key` is not defined, then the value of `path` is used instead. See the [`JSONParseStream` output](#jsonparsestream-output) section below for examples.
96
+
97
+ #### `schema?: StandardSchemaV1`
98
+
99
+ `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.
106
100
 
107
101
  ### `options?: { multi?: boolean }`
108
102
 
@@ -137,60 +131,76 @@ Output from `JSONParseStream` has this format:
137
131
  ```ts
138
132
  type JSONParseStreamOutput<T = unknown> = {
139
133
  value: T;
140
- path: JSONPath;
134
+ key: Key;
141
135
  wildcardKeys?: string[];
142
136
  };
143
137
  ```
144
138
 
145
139
  `value` is the value selected by one of your JSONPath queries.
146
140
 
147
- `path` is the JSONPath query (from the `jsonPaths` parameter of `JSONParseStream`) that matched `value`.
141
+ `key` is the JSONPath query (from the `jsonPaths` parameter of `JSONParseStream`) that matched `value`, or the value from the `key` property inside `jsonPaths` if that was supplied.
148
142
 
149
- 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:
143
+ If you only have one JSONPath query, you can ignore `key`. But if you have more than one, `key` may be helpful when processing stream output to distinguish between different types of values. For example:
150
144
 
151
145
  <!-- prettier-ignore -->
152
146
  ```ts
153
- await new ReadableStream({
147
+ const stream = new ReadableStream({
154
148
  start(controller) {
155
149
  controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
156
150
  controller.close();
157
151
  },
158
152
  })
159
- .pipeThrough(new JSONParseStream(["$.bar[*]", "$.foo[*]"]))
160
- .pipeTo(
161
- new WritableStream({
162
- write(record) {
163
- if (record.path === "$.bar[*]") {
164
- // Do something with the values from bar
165
- } else {
166
- // Do something with the values from foo
167
- }
168
- },
169
- }),
170
- );
153
+ .pipeThrough(new JSONParseStream(["$.bar[*]", "$.foo[*]"]));
154
+
155
+ for await (const record of stream) {
156
+ if (record.key === "$.bar[*]") {
157
+ // Do something with the values from bar
158
+ } else {
159
+ // Do something with the values from foo
160
+ }
161
+ }
162
+ ```
163
+
164
+ Or with a manually defined key:
165
+
166
+ <!-- prettier-ignore -->
167
+ ```ts
168
+ const stream = new ReadableStream({
169
+ start(controller) {
170
+ controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
171
+ controller.close();
172
+ },
173
+ })
174
+ .pipeThrough(new JSONParseStream([{ key: "bar", path: "$.bar[*]" }, "$.foo[*]"]));
175
+
176
+ for await (const record of stream) {
177
+ if (record.key === "bar") {
178
+ // Do something with the values from bar
179
+ } else {
180
+ // Do something with the values from foo
181
+ }
182
+ }
171
183
  ```
172
184
 
173
185
  `wildcardKeys` is defined when you have a wildcard in an object (not an array) somewhere in your JSONPath. For example:
174
186
 
175
187
  <!-- prettier-ignore -->
176
188
  ```ts
177
- await new ReadableStream({
189
+ const stream = new ReadableStream({
178
190
  start(controller) {
179
191
  controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
180
192
  controller.close();
181
193
  },
182
194
  })
183
- .pipeThrough(new JSONParseStream(["$[*]"]))
184
- .pipeTo(
185
- new WritableStream({
186
- write(record) {
187
- console.log(record);
188
- },
189
- }),
190
- );
195
+ .pipeThrough(new JSONParseStream(["$[*]"]));
196
+
197
+ for await (const record of stream) {
198
+ console.log(record);
199
+ }
200
+
191
201
  // Output:
192
- // { path: "$[*]", value: [1, 2], wildcardKeys: ["foo"] },
193
- // { path: "$[*]", value: ["a", "b", "c"], wildcardKeys: ["bar"] },
202
+ // { key: "$[*]", value: [1, 2], wildcardKeys: ["foo"] },
203
+ // { key: "$[*]", value: ["a", "b", "c"], wildcardKeys: ["bar"] },
194
204
  ```
195
205
 
196
206
  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.
@@ -198,7 +208,7 @@ The purpose of `wildcardKeys` is to allow you to easily distinguish different ty
198
208
  > [!WARNING]
199
209
  > 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.
200
210
  >
201
- > 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.
211
+ > Some schema validation libraries do a deep clone of objects they validate. In that case, you won't have this issue.
202
212
 
203
213
  #### Schema validation and types for `JSONParseStream` output
204
214
 
@@ -210,7 +220,7 @@ To use schema validation for a JSONPath query, then pass an object `{ path: JSON
210
220
  ```ts
211
221
  import * as z from "zod";
212
222
 
213
- await new ReadableStream({
223
+ const stream = new ReadableStream({
214
224
  start(controller) {
215
225
  controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
216
226
  controller.close();
@@ -221,32 +231,57 @@ await new ReadableStream({
221
231
  { path: "$.foo[*]", schema: z.number() },
222
232
  { path: "$.bar[*]", schema: z.string() },
223
233
  ]),
224
- )
225
- .pipeTo(
226
- new WritableStream({
227
- write(record) {
228
- if (record.path === "$.foo[*]") {
229
- // Type of record.value is number
230
- } else {
231
- // Type of record.value is string
232
- }
233
- },
234
- }),
235
234
  );
236
- ```
237
235
 
238
- > [!TIP]
239
- > If you only want to validate some values, you can mix `{ path: JSONPath; schema: StandardSchemaV1 }` and `JSONPath` in the `jsonPaths` array.
236
+ for await (const record of stream) {
237
+ if (record.key === "$.foo[*]") {
238
+ // Type of record.value is `number`
239
+ } else {
240
+ // Type of record.value is `string`
241
+ }
242
+ }
243
+ ```
240
244
 
241
245
  For JSONPath queries with no schema, emitted values will have the `unknown` type.
242
246
 
247
+ The type of the `key` property will be either the string literal `path` from the input paramter (such as `"$.foo[*]`) or whatever you put in the `key` property of the input, so you can use it to discriminate between object types in the output. For example:
248
+
249
+ <!-- prettier-ignore -->
250
+ ```ts
251
+ import * as z from "zod";
252
+
253
+ const stream = new ReadableStream({
254
+ start(controller) {
255
+ controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
256
+ controller.close();
257
+ },
258
+ })
259
+ .pipeThrough(
260
+ new JSONParseStream([
261
+ { key: "foo", path: "$.foo[*]", schema: z.number() },
262
+ { key: "bar", path: "$.bar[*]", schema: z.string() },
263
+ ]),
264
+ );
265
+
266
+ for await (const record of stream) {
267
+ if (record.key === "foo") {
268
+ // Type of record.value is `number`
269
+ } else {
270
+ // Type of record.value is `string`
271
+ }
272
+ }
273
+ ```
274
+
275
+ > [!TIP]
276
+ > If you only want to validate values or override keys for some queries, you can mix `{ path: JSONPath; key?: Key; schema?: StandardSchemaV1 }` and `JSONPath` in the `jsonPaths` array.
277
+
243
278
  ## JSONPath
244
279
 
245
280
  json-web-streams supports a subset of JSONPath. Currently the supported components are:
246
281
 
247
282
  - **The root node**, represented by the symbol `$` which must be the first character of any JSONPath query.
248
283
 
249
- - **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.
284
+ - **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"]`, 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.
250
285
 
251
286
  - **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.
252
287
 
@@ -264,7 +299,7 @@ Or if the array is at the root if the object like this data:
264
299
  [{ "x": 1 }, { "x": 2 }, { "x": 3 }]
265
300
  ```
266
301
 
267
- 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`).
302
+ then you'd write something like `$[*]` to emit each object (`{ x: 1}`, `{x: 2}`, `{x: 3}`), or `$[*].x` to emit just the numbers (`1`, `2`, `3`).
268
303
 
269
304
  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 `$`.
270
305
 
@@ -285,11 +320,11 @@ Let's say you have this JSON:
285
320
  { "foo": [1, 2], "bar": ["a", "b", "c"] }
286
321
  ```
287
322
 
288
- 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`:
323
+ 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 `.key`:
289
324
 
290
325
  <!-- prettier-ignore -->
291
326
  ```ts
292
- await new ReadableStream({
327
+ const stream = new ReadableStream({
293
328
  start(controller) {
294
329
  controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
295
330
  controller.close();
@@ -297,25 +332,22 @@ await new ReadableStream({
297
332
  })
298
333
  .pipeThrough(
299
334
  new JSONParseStream(["$.foo[*]", "$.bar[*]"]),
300
- )
301
- .pipeTo(
302
- new WritableStream({
303
- write(record) {
304
- if (record.path === "$.foo[*]") {
305
- // 1, 2
306
- } else {
307
- // a, b, c
308
- }
309
- },
310
- }),
311
335
  );
336
+
337
+ for await (const record of stream) {
338
+ if (record.key === "$.foo[*]") {
339
+ // 1, 2
340
+ } else {
341
+ // a, b, c
342
+ }
343
+ }
312
344
  ```
313
345
 
314
346
  Or you could use one JSONPath query with a wildcard, and then use `.wildcardKeys` to distinguish the objects:
315
347
 
316
348
  <!-- prettier-ignore -->
317
349
  ```ts
318
- await new ReadableStream({
350
+ const stream = new ReadableStream({
319
351
  start(controller) {
320
352
  controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
321
353
  controller.close();
@@ -323,18 +355,15 @@ await new ReadableStream({
323
355
  })
324
356
  .pipeThrough(
325
357
  new JSONParseStream(["$[*][*]"]),
326
- )
327
- .pipeTo(
328
- new WritableStream({
329
- write(record) {
330
- if (record.wildcardKeys[0] === "foo") {
331
- // 1, 2
332
- } else {
333
- // a, b, c
334
- }
335
- },
336
- }),
337
358
  );
359
+
360
+ for await (const record of stream) {
361
+ if (record.wildcardKeys[0] === "foo") {
362
+ // 1, 2
363
+ } else {
364
+ // a, b, c
365
+ }
366
+ }
338
367
  ```
339
368
 
340
369
  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.
@@ -347,7 +376,7 @@ In this example, Zod schemas enforce that `record.value` is either a `string` or
347
376
  ```ts
348
377
  import * as z from "zod";
349
378
 
350
- await new ReadableStream({
379
+ const stream = new ReadableStream({
351
380
  start(controller) {
352
381
  controller.enqueue('{ "foo": [1, 2], "bar": ["a", "b", "c"] }');
353
382
  controller.close();
@@ -358,16 +387,13 @@ await new ReadableStream({
358
387
  { path: "$.foo[*]", schema: z.number() },
359
388
  { path: "$.bar[*]", schema: z.string() },
360
389
  ]),
361
- )
362
- .pipeTo(
363
- new WritableStream({
364
- write(record) {
365
- if (record.path === "$.foo[*]") {
366
- // 1, 2
367
- } else {
368
- // a, b, c
369
- }
370
- },
371
- }),
372
390
  );
391
+
392
+ for await (const record of stream) {
393
+ if (record.key === "$.foo[*]") {
394
+ // Type of record.value is `number` rather than `unknown`
395
+ } else {
396
+ // Type of record.value is `string` rather than `unknown`
397
+ }
398
+ }
373
399
  ```
@@ -4,20 +4,25 @@ import { StandardSchemaV1 } from "@standard-schema/spec";
4
4
 
5
5
  //#region src/JSONParseStream.d.ts
6
6
  type JSONParseStreamOutput<T> = T extends {
7
+ key?: infer K | undefined;
7
8
  path: infer P extends JSONPath;
8
- schema: infer S extends StandardSchemaV1;
9
+ schema?: infer S extends StandardSchemaV1 | undefined;
9
10
  } ? {
10
- path: P;
11
- value: StandardSchemaV1.InferOutput<S>;
11
+ value: S extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<S> : unknown;
12
12
  wildcardKeys?: string[];
13
- } : T extends JSONPath ? {
14
- path: T;
13
+ } & (undefined extends K ? {
14
+ key: P;
15
+ } : {
16
+ key: K;
17
+ }) : T extends JSONPath ? {
18
+ key: T;
15
19
  value: unknown;
16
20
  wildcardKeys?: string[];
17
21
  } : never;
18
- declare class JSONParseStream<T extends readonly (JSONPath | {
22
+ declare class JSONParseStream<const Key extends unknown, T extends readonly (JSONPath | {
23
+ key?: Key;
19
24
  path: JSONPath;
20
- schema: StandardSchemaV1;
25
+ schema?: StandardSchemaV1;
21
26
  })[]> extends TransformStream<string, JSONParseStreamOutput<T[number]>> {
22
27
  _parser: JSONParseStreamRaw;
23
28
  constructor(jsonPaths: T, options?: {
@@ -2,33 +2,21 @@ import { JSONParseStreamRaw } from "./JSONParseStreamRaw.js";
2
2
  import { jsonPathToPathArray } from "./jsonPathToPathArray.js";
3
3
 
4
4
  //#region src/JSONParseStream.ts
5
- const stackToPathComponent = (stackComponent) => {
6
- if (stackComponent.mode === "OBJECT" && stackComponent.key !== void 0) return {
7
- type: "key",
8
- value: stackComponent.key
9
- };
10
- if (stackComponent.mode === "ARRAY") return { type: "wildcard" };
11
- throw new Error(`Unexpected mode "${stackComponent.mode}"`);
12
- };
13
5
  const isEqual = (x, y) => {
14
- if (!y) return false;
15
- if (x.type === y?.type) {
16
- if (x.type === "wildcard") return true;
17
- if (x.value === y.value) return true;
18
- return false;
19
- }
20
- return x.type === "wildcard";
6
+ if (x.type === "wildcard") return y.mode === "ARRAY" || y.mode === "OBJECT";
7
+ return y.mode === "OBJECT" && x.value === y.key;
21
8
  };
22
9
  var JSONParseStream = class extends TransformStream {
23
10
  _parser;
24
11
  constructor(jsonPaths, options) {
25
12
  let parser;
26
- const multi = options?.multi ?? false;
27
13
  const jsonPathInfos = jsonPaths.map((row) => {
14
+ let key;
28
15
  let path;
29
16
  let schema;
30
17
  if (typeof row === "string") path = row;
31
18
  else {
19
+ key = row.key;
32
20
  path = row.path;
33
21
  schema = row.schema;
34
22
  }
@@ -39,63 +27,71 @@ var JSONParseStream = class extends TransformStream {
39
27
  wildcardIndexes.push(i);
40
28
  }
41
29
  return {
30
+ key,
42
31
  path,
43
32
  pathArray,
44
- schema,
33
+ validate: schema?.["~standard"].validate,
45
34
  wildcardIndexes
46
35
  };
47
36
  });
37
+ const makeLevel = (jsonPathInfos$1, depth, matchesAbove) => {
38
+ return {
39
+ jsonPathInfos: jsonPathInfos$1,
40
+ matchesHere: jsonPathInfos$1.some((info) => info.pathArray.length === depth),
41
+ matchesAbove,
42
+ matchesBelow: jsonPathInfos$1.some((info) => info.pathArray.length > depth)
43
+ };
44
+ };
45
+ const levels = [makeLevel(jsonPathInfos, 0, false)];
46
+ const updateLevel = (depth) => {
47
+ const parent = levels[depth - 1];
48
+ levels[depth] = makeLevel(parent.jsonPathInfos.filter((info) => info.pathArray.length >= depth && isEqual(info.pathArray[depth - 1], parser)), depth, parent.matchesAbove || parent.matchesHere);
49
+ };
48
50
  super({
49
51
  start(controller) {
50
52
  parser = new JSONParseStreamRaw({
51
- multi,
53
+ multi: options?.multi,
54
+ parseWholeValue: () => !levels[parser.stack.length].matchesBelow,
55
+ onKey: updateLevel,
56
+ onPush: updateLevel,
57
+ onPop: (depth) => {
58
+ levels.length = depth + 1;
59
+ },
52
60
  onValue: (value) => {
53
- const { key: parserKey, mode: parserMode, stack: parserStack, value: parserValue } = parser;
54
- const stackLength = parserStack.length;
55
- const stackPathArray = new Array(stackLength);
56
- for (let i = 1; i < stackLength; i++) stackPathArray[i - 1] = stackToPathComponent(parserStack[i]);
57
- if (parserStack.length > 0) stackPathArray[stackLength - 1] = stackToPathComponent({
58
- value: parserValue,
59
- key: parserKey,
60
- mode: parserMode
61
- });
62
- let keep = false;
63
- for (const { path, pathArray, schema, wildcardIndexes } of jsonPathInfos) {
64
- if (stackPathArray.length < pathArray.length) continue;
65
- if (pathArray.every((x, j) => isEqual(x, stackPathArray[j]))) if (stackPathArray.length === pathArray.length) {
66
- let valueToEmit;
67
- if (schema) {
68
- const result = schema["~standard"].validate(value);
69
- if (result instanceof Promise) throw new TypeError("Schema validation must be synchronous");
70
- if (result.issues) throw new Error(JSON.stringify(result.issues, null, 2));
71
- valueToEmit = result.value;
72
- } else valueToEmit = value;
73
- let wildcardKeys;
74
- if (wildcardIndexes) for (const index of wildcardIndexes) {
75
- const pathComponent = stackPathArray[index];
76
- if (pathComponent?.type === "key") {
77
- if (!wildcardKeys) wildcardKeys = [];
78
- wildcardKeys.push(pathComponent.value);
79
- }
61
+ const depth = parser.stack.length;
62
+ const level = levels[depth];
63
+ if (level.matchesHere) for (const { key, path, pathArray, validate, wildcardIndexes } of level.jsonPathInfos) {
64
+ if (pathArray.length !== depth) continue;
65
+ let valueToEmit;
66
+ if (validate) {
67
+ const result = validate(value);
68
+ if (result instanceof Promise) throw new TypeError("Schema validation must be synchronous");
69
+ if (result.issues) throw new Error(JSON.stringify(result.issues, null, 2));
70
+ valueToEmit = result.value;
71
+ } else valueToEmit = value;
72
+ let wildcardKeys;
73
+ if (wildcardIndexes) for (const index of wildcardIndexes) {
74
+ const stackComponent = parser.stack[index + 1] ?? parser;
75
+ if (stackComponent.mode === "OBJECT" && stackComponent.key !== void 0) {
76
+ if (!wildcardKeys) wildcardKeys = [];
77
+ wildcardKeys.push(stackComponent.key);
80
78
  }
81
- if (wildcardKeys) controller.enqueue({
82
- path,
83
- value: valueToEmit,
84
- wildcardKeys
85
- });
86
- else controller.enqueue({
87
- path,
88
- value: valueToEmit
89
- });
90
- } else keep = true;
91
- else {
92
- const type = typeof value;
93
- if (type === "string" || type === "number" || type === "boolean" || value === null) keep = true;
94
79
  }
80
+ if (wildcardKeys) controller.enqueue({
81
+ key: key ?? path,
82
+ value: valueToEmit,
83
+ wildcardKeys
84
+ });
85
+ else controller.enqueue({
86
+ key: key ?? path,
87
+ value: valueToEmit
88
+ });
95
89
  }
96
- if (!keep) {
97
- for (const row of parserStack) row.value = void 0;
98
- if (typeof parserValue === "object" && parserValue !== null && parserKey !== void 0) parserValue[parserKey] = void 0;
90
+ if (level.matchesAbove) return;
91
+ const type = typeof value;
92
+ if (!(type === "string" || type === "number" || type === "boolean" || value === null)) {
93
+ for (const row of parser.stack) row.value = void 0;
94
+ if (typeof parser.value === "object" && parser.value !== null && parser.key !== void 0) parser.value[parser.key] = void 0;
99
95
  }
100
96
  }
101
97
  });
@@ -103,9 +99,8 @@ var JSONParseStream = class extends TransformStream {
103
99
  transform(chunk) {
104
100
  parser.write(chunk);
105
101
  },
106
- flush(controller) {
102
+ flush() {
107
103
  parser.checkEnd();
108
- controller.terminate();
109
104
  }
110
105
  });
111
106
  this._parser = parser;
@@ -1,7 +1,7 @@
1
1
  //#region src/JSONParseStreamRaw.d.ts
2
2
  type Token = "LEFT_BRACE" | "RIGHT_BRACE" | "LEFT_BRACKET" | "RIGHT_BRACKET" | "COLON" | "COMMA" | "TRUE" | "FALSE" | "NULL" | "STRING" | "NUMBER";
3
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";
4
+ type TokenizerState = "START" | "TRUE1" | "TRUE2" | "TRUE3" | "FALSE1" | "FALSE2" | "FALSE3" | "FALSE4" | "NULL1" | "NULL2" | "NULL3" | "NUMBER-" | "NUMBER0" | "NUMBER" | "NUMBER_DOT" | "NUMBER_FRACTION" | "NUMBER_E" | "NUMBER_E_SIGN" | "NUMBER_EXPONENT" | "STRING1" | "STRING2" | "STRING3" | "STRING4" | "STRING5" | "STRING6";
5
5
  type Mode = "OBJECT" | "ARRAY";
6
6
  type Key = string | number;
7
7
  type Value = any;
@@ -10,6 +10,7 @@ type Stack = {
10
10
  value: Value | undefined;
11
11
  mode: Mode | undefined;
12
12
  }[];
13
+ type OnPopPush = (stackLength: number) => void;
13
14
  type OnValue = (value: Value) => void;
14
15
  declare class JSONParseStreamRaw {
15
16
  tokenizerState: TokenizerState;
@@ -20,27 +21,50 @@ declare class JSONParseStreamRaw {
20
21
  key: Key | undefined;
21
22
  value: Value;
22
23
  position: number;
24
+ onKey: OnPopPush | undefined;
25
+ onPop: OnPopPush | undefined;
26
+ onPush: OnPopPush | undefined;
23
27
  onValue: OnValue;
24
28
  unicode: string | undefined;
25
- highSurrogate: number | undefined;
26
29
  seenRootObject: boolean;
27
- multi: boolean;
30
+ multi: boolean | undefined;
28
31
  multiIndex: number;
32
+ parseWholeValue: (() => boolean) | undefined;
33
+ captureDisabled: boolean;
34
+ capturePieces: string[] | undefined;
35
+ captureStart: number;
36
+ captureDepth: number;
37
+ captureInString: boolean;
38
+ captureEscaped: boolean;
29
39
  constructor({
30
40
  multi,
31
- onValue
41
+ onKey,
42
+ onPop,
43
+ onPush,
44
+ onValue,
45
+ parseWholeValue
32
46
  }: {
33
- multi: boolean;
47
+ multi?: boolean;
48
+ onKey?: OnPopPush;
49
+ onPop?: OnPopPush;
50
+ onPush?: OnPopPush;
34
51
  onValue: OnValue;
52
+ parseWholeValue?: () => boolean;
35
53
  });
36
54
  charError(char: string, i: number): void;
37
55
  parseError(token: Token, value: Value, i: number): void;
56
+ shouldCapture(): boolean;
57
+ startCapture(text: string, i: number): number;
58
+ scanCapture(text: string, start: number): number;
59
+ endCapture(json: string): void;
60
+ parseCaptureStrict(json: string): void;
38
61
  write(text: string): void;
39
- push(): void;
62
+ push(mode: Mode, value: object): void;
40
63
  pop(): void;
64
+ setKey(key: string): void;
41
65
  emit(value: Value): void;
42
66
  onToken(token: Token, value: Value, i: number): void;
43
- numberReviver(text: string, i: number): void;
67
+ endNumber(i: number): void;
44
68
  checkEnd(): void;
45
69
  }
46
70
  //#endregion
@@ -1,10 +1,5 @@
1
1
  //#region src/JSONParseStreamRaw.ts
2
- const WHITESPACE = new Set([
3
- " ",
4
- " ",
5
- "\n",
6
- "\r"
7
- ]);
2
+ const isWhitespace = (n) => n === " " || n === "\n" || n === "\r" || n === " ";
8
3
  var JSONParseStreamRaw = class {
9
4
  tokenizerState = "START";
10
5
  state = "VALUE";
@@ -14,15 +9,28 @@ var JSONParseStreamRaw = class {
14
9
  key;
15
10
  value;
16
11
  position = 0;
12
+ onKey;
13
+ onPop;
14
+ onPush;
17
15
  onValue;
18
16
  unicode;
19
- highSurrogate;
20
17
  seenRootObject = false;
21
18
  multi;
22
19
  multiIndex = 0;
23
- constructor({ multi, onValue }) {
24
- this.onValue = onValue;
20
+ parseWholeValue;
21
+ captureDisabled = false;
22
+ capturePieces;
23
+ captureStart = 0;
24
+ captureDepth = 0;
25
+ captureInString = false;
26
+ captureEscaped = false;
27
+ constructor({ multi, onKey, onPop, onPush, onValue, parseWholeValue }) {
25
28
  this.multi = multi;
29
+ this.onKey = onKey;
30
+ this.onPop = onPop;
31
+ this.onPush = onPush;
32
+ this.onValue = onValue;
33
+ this.parseWholeValue = parseWholeValue;
26
34
  }
27
35
  charError(char, i) {
28
36
  throw new Error(`Unexpected ${JSON.stringify(char)} at position ${this.position + i} in state ${this.tokenizerState}`);
@@ -30,13 +38,91 @@ var JSONParseStreamRaw = class {
30
38
  parseError(token, value, i) {
31
39
  throw new Error(`Unexpected ${token}${value ? `(${JSON.stringify(value)})` : ""} at position ${this.position + i} in state ${this.state}`);
32
40
  }
41
+ shouldCapture() {
42
+ return this.parseWholeValue !== void 0 && !this.captureDisabled && (this.state === "VALUE" || this.state === "VALUE_AFTER_COMMA") && this.parseWholeValue();
43
+ }
44
+ startCapture(text, i) {
45
+ this.captureStart = this.position + i;
46
+ this.captureDepth = 0;
47
+ this.captureInString = false;
48
+ this.captureEscaped = false;
49
+ const end = this.scanCapture(text, i);
50
+ if (end === -1) {
51
+ this.capturePieces = [text.slice(i)];
52
+ return text.length - 1;
53
+ }
54
+ this.endCapture(text.slice(i, end + 1));
55
+ return end;
56
+ }
57
+ scanCapture(text, start) {
58
+ const l = text.length;
59
+ let j = start;
60
+ if (this.captureEscaped && j < l) {
61
+ this.captureEscaped = false;
62
+ j += 1;
63
+ }
64
+ let depth = this.captureDepth;
65
+ let inString = this.captureInString;
66
+ for (; j < l; j++) {
67
+ const code = text.charCodeAt(j);
68
+ if (inString) {
69
+ if (code === 92) {
70
+ j += 1;
71
+ if (j === l) this.captureEscaped = true;
72
+ } else if (code === 34) inString = false;
73
+ } else if (code === 34) inString = true;
74
+ else if (code === 123 || code === 91) depth += 1;
75
+ else if (code === 125 || code === 93) {
76
+ depth -= 1;
77
+ if (depth === 0) return j;
78
+ }
79
+ }
80
+ this.captureDepth = depth;
81
+ this.captureInString = inString;
82
+ return -1;
83
+ }
84
+ endCapture(json) {
85
+ this.capturePieces = void 0;
86
+ let value;
87
+ try {
88
+ value = JSON.parse(json);
89
+ } catch {
90
+ this.parseCaptureStrict(json);
91
+ return;
92
+ }
93
+ if (this.stack.length === 0) this.seenRootObject = true;
94
+ if (this.value) this.value[this.key] = value;
95
+ this.emit(value);
96
+ }
97
+ parseCaptureStrict(json) {
98
+ const position = this.position;
99
+ this.position = this.captureStart;
100
+ this.captureDisabled = true;
101
+ this.write(json);
102
+ this.captureDisabled = false;
103
+ this.position = position;
104
+ }
33
105
  write(text) {
34
- for (let i = 0, l = text.length; i < l; i++) {
106
+ let i = 0;
107
+ if (this.capturePieces !== void 0) {
108
+ const end = this.scanCapture(text, 0);
109
+ if (end === -1) {
110
+ this.capturePieces.push(text);
111
+ this.position += text.length;
112
+ return;
113
+ }
114
+ this.capturePieces.push(text.slice(0, end + 1));
115
+ this.endCapture(this.capturePieces.join(""));
116
+ i = end + 1;
117
+ }
118
+ for (const l = text.length; i < l; i++) {
35
119
  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.tokenizerState === "START") if (n === "{") this.onToken("LEFT_BRACE", "{", i);
120
+ if (!this.multi && this.stack.length === 0 && this.seenRootObject && !isWhitespace(n)) return this.charError(n, i);
121
+ if (this.tokenizerState === "START") if (n === "{") if (this.shouldCapture()) i = this.startCapture(text, i);
122
+ else this.onToken("LEFT_BRACE", "{", i);
38
123
  else if (n === "}") this.onToken("RIGHT_BRACE", "}", i);
39
- else if (n === "[") this.onToken("LEFT_BRACKET", "[", i);
124
+ else if (n === "[") if (this.shouldCapture()) i = this.startCapture(text, i);
125
+ else this.onToken("LEFT_BRACKET", "[", i);
40
126
  else if (n === "]") this.onToken("RIGHT_BRACKET", "]", i);
41
127
  else if (n === ":") this.onToken("COLON", ":", i);
42
128
  else if (n === ",") this.onToken("COMMA", ",", i);
@@ -55,15 +141,21 @@ var JSONParseStreamRaw = class {
55
141
  } else if (n >= "1" && n <= "9") {
56
142
  this.string = n;
57
143
  this.tokenizerState = "NUMBER";
58
- } else if (WHITESPACE.has(n)) {} else if (n === "␞" && this.multi && this.stack.length === 0) {} else return this.charError(n, i);
144
+ } else if (isWhitespace(n)) {} else if (n === "␞" && this.multi && this.stack.length === 0) {} else return this.charError(n, i);
59
145
  else if (this.tokenizerState === "STRING1") if (n === "\"") {
60
146
  this.tokenizerState = "START";
61
147
  this.onToken("STRING", this.string, i);
62
148
  this.string = void 0;
63
149
  } else if (n === "\\") this.tokenizerState = "STRING2";
64
150
  else {
65
- if (n.charCodeAt(0) <= 31) this.charError(n, i);
66
- this.string += n;
151
+ let j = i;
152
+ for (; j < l; j++) {
153
+ const code = text.charCodeAt(j);
154
+ if (code === 34 || code === 92) break;
155
+ if (code <= 31) return this.charError(text[j], j);
156
+ }
157
+ this.string += text.slice(i, j);
158
+ i = j - 1;
67
159
  }
68
160
  else if (this.tokenizerState === "STRING2") if (n === "\"") {
69
161
  this.string += "\"";
@@ -94,59 +186,75 @@ var JSONParseStreamRaw = class {
94
186
  this.tokenizerState = "STRING3";
95
187
  } else return this.charError(n, i);
96
188
  else if (this.tokenizerState === "STRING3" || this.tokenizerState === "STRING4" || this.tokenizerState === "STRING5" || this.tokenizerState === "STRING6") {
189
+ if (!(n >= "0" && n <= "9" || n >= "a" && n <= "f" || n >= "A" && n <= "F")) return this.charError(n, i);
97
190
  this.unicode += n;
98
191
  if (this.tokenizerState === "STRING3") this.tokenizerState = "STRING4";
99
192
  else if (this.tokenizerState === "STRING4") this.tokenizerState = "STRING5";
100
193
  else if (this.tokenizerState === "STRING5") this.tokenizerState = "STRING6";
101
194
  else if (this.tokenizerState === "STRING6") {
102
195
  const intVal = Number.parseInt(this.unicode, 16);
103
- if (Number.isNaN(intVal)) return this.charError(n, i);
104
196
  this.unicode = void 0;
105
- if (this.highSurrogate !== void 0 && intVal >= 56320 && intVal < 57344) {
106
- this.string += String.fromCharCode(this.highSurrogate, intVal);
107
- this.highSurrogate = void 0;
108
- } else if (this.highSurrogate === void 0 && intVal >= 55296 && intVal < 56320) this.highSurrogate = intVal;
109
- else {
110
- if (this.highSurrogate !== void 0) {
111
- this.string += String.fromCharCode(this.highSurrogate);
112
- this.highSurrogate = void 0;
113
- }
114
- this.string += String.fromCharCode(intVal);
115
- }
197
+ this.string += String.fromCharCode(intVal);
116
198
  this.tokenizerState = "STRING1";
117
199
  }
118
- } else if (this.tokenizerState === "NUMBER" || this.tokenizerState === "NUMBER-" || this.tokenizerState === "NUMBER0") {
119
- if (this.tokenizerState === "NUMBER0" && n >= "0" && n <= "9") return this.charError("0", i - 1);
120
- switch (n) {
121
- case "0":
122
- this.string += n;
123
- this.tokenizerState = this.tokenizerState === "NUMBER-" ? "NUMBER0" : "NUMBER";
124
- break;
125
- case "1":
126
- case "2":
127
- case "3":
128
- case "4":
129
- case "5":
130
- case "6":
131
- case "7":
132
- case "8":
133
- case "9":
134
- case ".":
135
- case "e":
136
- case "E":
137
- case "+":
138
- case "-":
139
- this.string += n;
140
- this.tokenizerState = "NUMBER";
141
- break;
142
- default:
143
- this.tokenizerState = "START";
144
- this.numberReviver(this.string, i);
145
- this.string = void 0;
146
- i--;
147
- break;
148
- }
149
- } else if (this.tokenizerState === "TRUE1") if (n === "r") this.tokenizerState = "TRUE2";
200
+ } else if (this.tokenizerState === "NUMBER") if (n >= "0" && n <= "9") this.string += n;
201
+ else if (n === ".") {
202
+ this.string += n;
203
+ this.tokenizerState = "NUMBER_DOT";
204
+ } else if (n === "e" || n === "E") {
205
+ this.string += n;
206
+ this.tokenizerState = "NUMBER_E";
207
+ } else {
208
+ this.endNumber(i);
209
+ i--;
210
+ }
211
+ else if (this.tokenizerState === "NUMBER_FRACTION") if (n >= "0" && n <= "9") this.string += n;
212
+ else if (n === "e" || n === "E") {
213
+ this.string += n;
214
+ this.tokenizerState = "NUMBER_E";
215
+ } else {
216
+ this.endNumber(i);
217
+ i--;
218
+ }
219
+ else if (this.tokenizerState === "NUMBER0") if (n >= "0" && n <= "9") return this.charError("0", i - 1);
220
+ else if (n === ".") {
221
+ this.string += n;
222
+ this.tokenizerState = "NUMBER_DOT";
223
+ } else if (n === "e" || n === "E") {
224
+ this.string += n;
225
+ this.tokenizerState = "NUMBER_E";
226
+ } else {
227
+ this.endNumber(i);
228
+ i--;
229
+ }
230
+ else if (this.tokenizerState === "NUMBER-") if (n === "0") {
231
+ this.string += n;
232
+ this.tokenizerState = "NUMBER0";
233
+ } else if (n >= "1" && n <= "9") {
234
+ this.string += n;
235
+ this.tokenizerState = "NUMBER";
236
+ } else return this.charError(n, i);
237
+ else if (this.tokenizerState === "NUMBER_DOT") if (n >= "0" && n <= "9") {
238
+ this.string += n;
239
+ this.tokenizerState = "NUMBER_FRACTION";
240
+ } else return this.charError(n, i);
241
+ else if (this.tokenizerState === "NUMBER_E") if (n >= "0" && n <= "9") {
242
+ this.string += n;
243
+ this.tokenizerState = "NUMBER_EXPONENT";
244
+ } else if (n === "+" || n === "-") {
245
+ this.string += n;
246
+ this.tokenizerState = "NUMBER_E_SIGN";
247
+ } else return this.charError(n, i);
248
+ else if (this.tokenizerState === "NUMBER_E_SIGN") if (n >= "0" && n <= "9") {
249
+ this.string += n;
250
+ this.tokenizerState = "NUMBER_EXPONENT";
251
+ } else return this.charError(n, i);
252
+ else if (this.tokenizerState === "NUMBER_EXPONENT") if (n >= "0" && n <= "9") this.string += n;
253
+ else {
254
+ this.endNumber(i);
255
+ i--;
256
+ }
257
+ else if (this.tokenizerState === "TRUE1") if (n === "r") this.tokenizerState = "TRUE2";
150
258
  else return this.charError(n, i);
151
259
  else if (this.tokenizerState === "TRUE2") if (n === "u") this.tokenizerState = "TRUE3";
152
260
  else return this.charError(n, i);
@@ -175,12 +283,17 @@ var JSONParseStreamRaw = class {
175
283
  }
176
284
  this.position += text.length;
177
285
  }
178
- push() {
286
+ push(mode, value) {
179
287
  this.stack.push({
180
288
  value: this.value,
181
289
  key: this.key,
182
290
  mode: this.mode
183
291
  });
292
+ if (this.value) this.value[this.key] = value;
293
+ this.value = value;
294
+ this.mode = mode;
295
+ this.key = mode === "ARRAY" ? 0 : void 0;
296
+ this.onPush?.(this.stack.length);
184
297
  }
185
298
  pop() {
186
299
  const value = this.value;
@@ -189,8 +302,13 @@ var JSONParseStreamRaw = class {
189
302
  this.key = parent.key;
190
303
  this.mode = parent.mode;
191
304
  this.emit(value);
305
+ this.onPop?.(this.stack.length);
192
306
  if (!this.mode) this.state = "VALUE";
193
307
  }
308
+ setKey(key) {
309
+ this.key = key;
310
+ this.onKey?.(this.stack.length);
311
+ }
194
312
  emit(value) {
195
313
  if (this.mode) this.state = "COMMA";
196
314
  if (value === void 0) return;
@@ -204,18 +322,10 @@ var JSONParseStreamRaw = class {
204
322
  if (this.value) this.value[this.key] = value;
205
323
  this.emit(value);
206
324
  } else if (token === "LEFT_BRACE") {
207
- this.push();
208
- if (this.value) this.value = this.value[this.key] = {};
209
- else this.value = {};
210
- this.key = void 0;
325
+ this.push("OBJECT", {});
211
326
  this.state = "KEY";
212
- this.mode = "OBJECT";
213
327
  } else if (token === "LEFT_BRACKET") {
214
- this.push();
215
- if (this.value) this.value = this.value[this.key] = [];
216
- else this.value = [];
217
- this.key = 0;
218
- this.mode = "ARRAY";
328
+ this.push("ARRAY", []);
219
329
  this.state = "VALUE";
220
330
  } else if (token === "RIGHT_BRACE") if (this.mode === "OBJECT" && this.state !== "VALUE_AFTER_COMMA") this.pop();
221
331
  else return this.parseError(token, value, i);
@@ -223,7 +333,7 @@ var JSONParseStreamRaw = class {
223
333
  else return this.parseError(token, value, i);
224
334
  else return this.parseError(token, value, i);
225
335
  else if (this.state === "KEY" || this.state === "KEY_AFTER_COMMA") if (token === "STRING") {
226
- this.key = value;
336
+ this.setKey(value);
227
337
  this.state = "COLON";
228
338
  } else if (token === "RIGHT_BRACE" && this.state !== "KEY_AFTER_COMMA") this.pop();
229
339
  else return this.parseError(token, value, i);
@@ -238,17 +348,22 @@ var JSONParseStreamRaw = class {
238
348
  else return this.parseError(token, value, i);
239
349
  else return this.parseError(token, value, i);
240
350
  }
241
- numberReviver(text, i) {
242
- const number = JSON.parse(text);
243
- if (Number.isNaN(number)) return this.charError(text, i);
351
+ endNumber(i) {
352
+ const number = Number(this.string);
353
+ this.string = void 0;
354
+ this.tokenizerState = "START";
244
355
  this.onToken("NUMBER", number, i);
245
356
  }
246
357
  checkEnd() {
358
+ if (this.capturePieces !== void 0) {
359
+ const json = this.capturePieces.join("");
360
+ this.capturePieces = void 0;
361
+ this.parseCaptureStrict(json);
362
+ }
247
363
  if (this.stack.length > 0) throw new Error(`Unexpected end of input at position ${this.position} in state ${this.state}`);
248
- if (this.state === "VALUE" && this.tokenizerState === "NUMBER" && this.string !== void 0) {
249
- this.numberReviver(this.string, this.position - 1);
250
- this.string = void 0;
251
- } else if (!this.seenRootObject) throw new Error("No data in input");
364
+ if (this.tokenizerState === "NUMBER" || this.tokenizerState === "NUMBER0" || this.tokenizerState === "NUMBER_FRACTION" || this.tokenizerState === "NUMBER_EXPONENT") this.endNumber(this.position - 1);
365
+ if (this.tokenizerState !== "START") throw new Error(`Unexpected end of input at position ${this.position} in state ${this.tokenizerState}`);
366
+ if (!this.seenRootObject) throw new Error("No data in input");
252
367
  }
253
368
  };
254
369
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { JSONParseStreamRaw } from "./JSONParseStreamRaw.js";
2
1
  import { JSONPath } from "./jsonPathToPathArray.js";
3
2
  import { JSONParseStream } from "./JSONParseStream.js";
4
- export { JSONParseStream, JSONParseStreamRaw, type JSONPath };
3
+ export { JSONParseStream, type JSONPath };
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import { JSONParseStreamRaw } from "./JSONParseStreamRaw.js";
2
1
  import { JSONParseStream } from "./JSONParseStream.js";
3
2
 
4
- export { JSONParseStream, JSONParseStreamRaw };
3
+ export { JSONParseStream };
@@ -8,22 +8,23 @@ const jsonPathToPathArray = (path) => {
8
8
  } catch (error) {
9
9
  throw new Error(`Error parsing JSONPath "${path}"`, { cause: error });
10
10
  }
11
- return parsed.segments.flatMap((segment) => {
11
+ return parsed.segments.map((segment) => {
12
12
  if (segment.type === "ChildSegment") {
13
13
  const node = segment.node;
14
14
  if (node.type === "MemberNameShorthand") return {
15
15
  type: "key",
16
16
  value: node.value
17
17
  };
18
- else if (node.type === "BracketedSelection") return node.selectors.map((selector) => {
18
+ else if (node.type === "BracketedSelection") {
19
+ if (node.selectors.length !== 1) throw new Error(`Multiple selectors in brackets are not supported in JSONPath "${path}"`);
20
+ const selector = node.selectors[0];
19
21
  if (selector.type === "NameSelector") return {
20
22
  type: "key",
21
23
  value: selector.value
22
24
  };
23
25
  else if (selector.type === "WildcardSelector") return { type: "wildcard" };
24
26
  else throw new Error(`Unsupported node: ${JSON.stringify(node)}`);
25
- });
26
- else if (node.type === "WildcardSelector") return { type: "wildcard" };
27
+ } else if (node.type === "WildcardSelector") return { type: "wildcard" };
27
28
  else throw new Error(`${segment.type} node type not supported`);
28
29
  } else throw new Error(`${segment.type} segment type not supported`);
29
30
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-web-streams",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
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
5
  "keywords": [
6
6
  "web streams",
@@ -14,6 +14,7 @@
14
14
  },
15
15
  "license": "Apache-2.0",
16
16
  "author": "Jeremy Scheff <jeremy@zengm.com> (https://zengm.com/)",
17
+ "sideEffects": false,
17
18
  "type": "module",
18
19
  "main": "dist/index.js",
19
20
  "types": "dist/index.d.ts",
@@ -21,6 +22,7 @@
21
22
  "dist"
22
23
  ],
23
24
  "scripts": {
25
+ "bench": "vitest bench --run",
24
26
  "build": "tsdown --unbundle",
25
27
  "format": "prettier --write .",
26
28
  "lint": "npm-run-all --parallel lint:oxlint lint:tsc",
@@ -28,7 +30,7 @@
28
30
  "lint:tsc": "tsc",
29
31
  "prepack": "node --run build",
30
32
  "prepare": "husky",
31
- "test": "vitest"
33
+ "test": "vitest --run"
32
34
  },
33
35
  "lint-staged": {
34
36
  "*.{js,cjs,mjs,jsx,json,scss,ts,cts,mts,tsx,md}": "prettier --write"
@@ -39,21 +41,21 @@
39
41
  },
40
42
  "devDependencies": {
41
43
  "@ianvs/prettier-plugin-sort-imports": "^4.7.0",
42
- "@types/node": "^24.8.1",
44
+ "@types/node": "^24.9.1",
43
45
  "husky": "^9.1.7",
44
- "lint-staged": "^16.2.4",
46
+ "lint-staged": "^16.2.6",
45
47
  "npm-run-all2": "^8.0.4",
46
- "oxlint": "^1.23.0",
47
- "oxlint-tsgolint": "^0.2.0",
48
+ "oxlint": "^1.24.0",
49
+ "oxlint-tsgolint": "^0.3.0",
48
50
  "prettier": "^3.6.2",
49
51
  "prettier-plugin-packagejson": "^2.5.19",
50
- "tsdown": "^0.15.7",
52
+ "tsdown": "^0.15.9",
51
53
  "typescript": "^5.9.3",
52
- "vitest": "^3.2.4",
54
+ "vitest": "^4.0.3",
53
55
  "zod": "^4.1.12"
54
56
  },
55
57
  "engines": {
56
58
  "node": ">=22",
57
- "pnpm": "^10.0.0"
59
+ "pnpm": "^11.0.0"
58
60
  }
59
61
  }