json-web-streams 0.0.1 → 1.0.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
@@ -1,5 +1,11 @@
1
1
  # json-web-streams
2
2
 
3
+ [![Build Status](https://github.com/zengm-games/json-web-streams/actions/workflows/test.yaml/badge.svg)](https://github.com/zengm-games/json-web-streams/actions/workflows/test.yaml)
4
+ [![GitHub Repo stars](https://img.shields.io/github/stars/zengm-games/json-web-streams)](https://github.com/zengm-games/json-web-streams)
5
+ [![GitHub](https://img.shields.io/github/license/zengm-games/json-web-streams)](https://github.com/zengm-games/json-web-streams)
6
+ [![npm](https://img.shields.io/npm/v/json-web-streams)](https://www.npmjs.com/package/json-web-streams)
7
+ [![npm](https://img.shields.io/npm/dm/json-web-streams)](https://www.npmjs.com/package/json-web-streams)
8
+
3
9
  - **Stream large JSON files** without loading everything into memory
4
10
  - Built on the **Web Streams API** so it runs in web browsers, Node.js, and more
5
11
  - Query with **JSONPath** to extract only the data you need
@@ -0,0 +1,28 @@
1
+ import { JSONParseStreamRaw } from "./JSONParseStreamRaw.js";
2
+ import { JSONPath } from "./jsonPathToPathArray.js";
3
+ import { StandardSchemaV1 } from "@standard-schema/spec";
4
+
5
+ //#region src/JSONParseStream.d.ts
6
+ type JSONParseStreamOutput<T> = T extends {
7
+ path: infer P extends JSONPath;
8
+ schema: infer S extends StandardSchemaV1;
9
+ } ? {
10
+ path: P;
11
+ value: StandardSchemaV1.InferOutput<S>;
12
+ wildcardKeys?: string[];
13
+ } : T extends JSONPath ? {
14
+ path: T;
15
+ value: unknown;
16
+ wildcardKeys?: string[];
17
+ } : never;
18
+ declare class JSONParseStream<T extends readonly (JSONPath | {
19
+ path: JSONPath;
20
+ schema: StandardSchemaV1;
21
+ })[]> extends TransformStream<string, JSONParseStreamOutput<T[number]>> {
22
+ _parser: JSONParseStreamRaw;
23
+ constructor(jsonPaths: T, options?: {
24
+ multi?: boolean;
25
+ });
26
+ }
27
+ //#endregion
28
+ export { JSONParseStream };
@@ -0,0 +1,116 @@
1
+ import { JSONParseStreamRaw } from "./JSONParseStreamRaw.js";
2
+ import { jsonPathToPathArray } from "./jsonPathToPathArray.js";
3
+
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
+ 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";
21
+ };
22
+ var JSONParseStream = class extends TransformStream {
23
+ _parser;
24
+ constructor(jsonPaths, options) {
25
+ let parser;
26
+ const multi = options?.multi ?? false;
27
+ const jsonPathInfos = jsonPaths.map((row) => {
28
+ let path;
29
+ let schema;
30
+ if (typeof row === "string") path = row;
31
+ else {
32
+ path = row.path;
33
+ schema = row.schema;
34
+ }
35
+ const pathArray = jsonPathToPathArray(path);
36
+ let wildcardIndexes;
37
+ for (const [i, component] of pathArray.entries()) if (component.type === "wildcard") {
38
+ if (wildcardIndexes === void 0) wildcardIndexes = [];
39
+ wildcardIndexes.push(i);
40
+ }
41
+ return {
42
+ path,
43
+ pathArray,
44
+ schema,
45
+ wildcardIndexes
46
+ };
47
+ });
48
+ super({
49
+ start(controller) {
50
+ parser = new JSONParseStreamRaw({
51
+ multi,
52
+ 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
+ }
80
+ }
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
+ }
95
+ }
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;
99
+ }
100
+ }
101
+ });
102
+ },
103
+ transform(chunk) {
104
+ parser.write(chunk);
105
+ },
106
+ flush(controller) {
107
+ parser.checkEnd();
108
+ controller.terminate();
109
+ }
110
+ });
111
+ this._parser = parser;
112
+ }
113
+ };
114
+
115
+ //#endregion
116
+ export { JSONParseStream };
@@ -1,4 +1,4 @@
1
- //#region src/JSONParserText.d.ts
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
4
  type TokenizerState = "START" | "TRUE1" | "TRUE2" | "TRUE3" | "FALSE1" | "FALSE2" | "FALSE3" | "FALSE4" | "NULL1" | "NULL2" | "NULL3" | "NUMBER-" | "NUMBER0" | "NUMBER" | "STRING1" | "STRING2" | "STRING3" | "STRING4" | "STRING5" | "STRING6";
@@ -10,8 +10,8 @@ type Stack = {
10
10
  value: Value | undefined;
11
11
  mode: Mode | undefined;
12
12
  }[];
13
- type OnValue = (value: Value, stack: Stack) => void;
14
- declare class JSONParserText {
13
+ type OnValue = (value: Value) => void;
14
+ declare class JSONParseStreamRaw {
15
15
  tokenizerState: TokenizerState;
16
16
  state: Token | ParserState;
17
17
  mode: Mode | undefined;
@@ -44,4 +44,4 @@ declare class JSONParserText {
44
44
  checkEnd(): void;
45
45
  }
46
46
  //#endregion
47
- export { JSONParserText };
47
+ export { JSONParseStreamRaw };
@@ -1,11 +1,11 @@
1
- //#region src/JSONParserText.ts
1
+ //#region src/JSONParseStreamRaw.ts
2
2
  const WHITESPACE = new Set([
3
3
  " ",
4
4
  " ",
5
5
  "\n",
6
6
  "\r"
7
7
  ]);
8
- var JSONParserText = class {
8
+ var JSONParseStreamRaw = class {
9
9
  tokenizerState = "START";
10
10
  state = "VALUE";
11
11
  mode;
@@ -34,7 +34,6 @@ var JSONParserText = class {
34
34
  for (let i = 0, l = text.length; i < l; i++) {
35
35
  const n = text[i];
36
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
37
  if (this.tokenizerState === "START") if (n === "{") this.onToken("LEFT_BRACE", "{", i);
39
38
  else if (n === "}") this.onToken("RIGHT_BRACE", "}", i);
40
39
  else if (n === "[") this.onToken("LEFT_BRACKET", "[", i);
@@ -194,11 +193,8 @@ var JSONParserText = class {
194
193
  }
195
194
  emit(value) {
196
195
  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
- }]);
196
+ if (value === void 0) return;
197
+ this.onValue(value);
202
198
  }
203
199
  onToken(token, value, i) {
204
200
  if (this.stack.length === 0) {
@@ -255,7 +251,6 @@ var JSONParserText = class {
255
251
  } else if (!this.seenRootObject) throw new Error("No data in input");
256
252
  }
257
253
  };
258
- var JSONParserText_default = JSONParserText;
259
254
 
260
255
  //#endregion
261
- export { JSONParserText_default as default };
256
+ export { JSONParseStreamRaw };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- import { JSONPath } from "./jsonPathToQueryPath.js";
2
- import { createJSONParserStream } from "./JSONParserStream.js";
3
- export { type JSONPath, createJSONParserStream };
1
+ import { JSONParseStreamRaw } from "./JSONParseStreamRaw.js";
2
+ import { JSONPath } from "./jsonPathToPathArray.js";
3
+ import { JSONParseStream } from "./JSONParseStream.js";
4
+ export { JSONParseStream, JSONParseStreamRaw, type JSONPath };
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
- import { createJSONParserStream } from "./JSONParserStream.js";
1
+ import { JSONParseStreamRaw } from "./JSONParseStreamRaw.js";
2
+ import { JSONParseStream } from "./JSONParseStream.js";
2
3
 
3
- export { createJSONParserStream };
4
+ export { JSONParseStream, JSONParseStreamRaw };
@@ -1,4 +1,4 @@
1
- //#region src/jsonPathToQueryPath.d.ts
1
+ //#region src/jsonPathToPathArray.d.ts
2
2
 
3
3
  type JSONPath = "$" | `$${"." | "["}${string}`;
4
4
  //#endregion
@@ -1,12 +1,12 @@
1
1
  import parser from "jsonpath-rfc9535/parser";
2
2
 
3
- //#region src/jsonPathToQueryPath.ts
4
- const jsonPathToQueryPath = (jsonPath) => {
3
+ //#region src/jsonPathToPathArray.ts
4
+ const jsonPathToPathArray = (path) => {
5
5
  let parsed;
6
6
  try {
7
- parsed = parser(jsonPath);
7
+ parsed = parser(path);
8
8
  } catch (error) {
9
- throw new Error(`Error parsing JSONPath "${jsonPath}"`, { cause: error });
9
+ throw new Error(`Error parsing JSONPath "${path}"`, { cause: error });
10
10
  }
11
11
  return parsed.segments.flatMap((segment) => {
12
12
  if (segment.type === "ChildSegment") {
@@ -30,4 +30,4 @@ const jsonPathToQueryPath = (jsonPath) => {
30
30
  };
31
31
 
32
32
  //#endregion
33
- export { jsonPathToQueryPath };
33
+ export { jsonPathToPathArray };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-web-streams",
3
- "version": "0.0.1",
3
+ "version": "1.0.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",
@@ -26,6 +26,7 @@
26
26
  "lint": "npm-run-all --parallel lint:oxlint lint:tsc",
27
27
  "lint:oxlint": "oxlint --type-aware",
28
28
  "lint:tsc": "tsc",
29
+ "prepack": "node --run build",
29
30
  "prepare": "husky",
30
31
  "test": "vitest"
31
32
  },
@@ -1,18 +0,0 @@
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 };
@@ -1,108 +0,0 @@
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 };