Skip to content

Commit 541eca8

Browse files
feat(overmind): change async to new package name
1 parent 3df057d commit 541eca8

File tree

12 files changed

+377
-5
lines changed

12 files changed

+377
-5
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
src
2+
jest.config.js
3+
rollup.config.js
4+
tsconfig.json
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# a-json
2+
3+
Async `JSON` parse and stringify
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module.exports = {
2+
collectCoverage: true,
3+
collectCoverageFrom: ['src/**/*.{t,j}s?(x)', '!src/**/*.d.ts'],
4+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
5+
transform: {
6+
'^.+\\.tsx?$': 'ts-jest',
7+
},
8+
testRegex: '\\.test\\.tsx?$',
9+
testPathIgnorePatterns: [
10+
'/dist/',
11+
'/es/',
12+
'/lib/',
13+
'<rootDir>/node_modules/',
14+
],
15+
transformIgnorePatterns: ['<rootDir>/node_modules/'],
16+
coveragePathIgnorePatterns: ['<rootDir>/node_modules/'],
17+
haste: {
18+
// This option is needed or else globbing ignores <rootDir>/node_modules.
19+
providesModuleNodeModules: ['non-blocking-json'],
20+
},
21+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "non-blocking-json",
3+
"version": "1.0.0",
4+
"description": "Async JSON parse and stringify",
5+
"author": "Christian Alfoni <[email protected]>",
6+
"license": "MIT",
7+
"repository": "git+https://github.com/cerebral/overmind.git",
8+
"main": "lib/index.js",
9+
"module": "es/index.js",
10+
"types": "lib/index.d.ts",
11+
"scripts": {
12+
"build": "npm run build:lib & npm run build:es",
13+
"build:lib": "tsc --outDir lib --module commonjs",
14+
"build:es": "tsc --outDir es --module es2015",
15+
"clean": "rimraf es lib coverage",
16+
"typecheck": "tsc --noEmit",
17+
"test": "jest",
18+
"test:watch": "jest --watch --updateSnapshot --coverage false",
19+
"prebuild": "npm run clean",
20+
"postbuild": "rimraf {lib,es}/**/__tests__",
21+
"posttest": "npm run typecheck"
22+
},
23+
"keywords": [
24+
"events",
25+
"eventemitter",
26+
"eventhub"
27+
],
28+
"files": [
29+
"lib",
30+
"es"
31+
],
32+
"devDependencies": {
33+
"@types/node": "^12.11.6",
34+
"tslib": "^1.10.0"
35+
}
36+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { stringify, parse } from ".";
2+
3+
test("should stringify objects", async () => {
4+
const obj = {
5+
foo: "bar",
6+
bar: {
7+
isAwesome: true
8+
},
9+
baz: ["foo", 123, true]
10+
};
11+
const result = await stringify(obj);
12+
expect(result).toBe(JSON.stringify(obj));
13+
});
14+
15+
test("should parse string", async () => {
16+
const obj = JSON.stringify({
17+
foo: "bar",
18+
bar: {
19+
isAwesome: true
20+
},
21+
baz: ["foo", false, 123]
22+
});
23+
const result = await parse(obj);
24+
expect(result).toEqual(JSON.parse(obj));
25+
});
26+
27+
test("should stringify objects in arrays", async () => {
28+
const obj = {
29+
demos: [
30+
{
31+
title: "Simple app"
32+
}
33+
]
34+
};
35+
const result = await stringify(obj);
36+
expect(result).toBe(JSON.stringify(obj));
37+
});
38+
39+
test("should stringify complex stuff", async () => {
40+
const string = '{"type":"effect","data":{"result":[{"title":"statecharts","fileName":"statecharts.md"}],"isPending":false,"error":false}}'
41+
const obj = JSON.parse(string)
42+
const result = await stringify(obj)
43+
expect(result).toBe(string)
44+
})
45+
46+
test("should handle more scenarios", async () => {
47+
const string = '{"type":"effect","data":{"namespacePath":[],"actionId":"onInitialize","executionId":0,"actionName":"onInitialize","operatorId":0,"isRunning":true,"path":[],"type":"action","effectId":14,"name":"","method":"request","args":["/backend/apis"],"isPending":true,"error":false}}'
48+
const obj = JSON.parse(string)
49+
const result = await stringify(obj)
50+
expect(result).toBe(string)
51+
})
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
// Symbol used as placeholders for objects and arrays
2+
const IS_ARRAY_START = Symbol("IS_ARRAY_START");
3+
const IS_ARRAY_END = Symbol("IS_ARRAY_END");
4+
const IS_OBJECT_START = Symbol("IS_OBJECT_START");
5+
const IS_OBJECT_END = Symbol("IS_OBJECT_END");
6+
7+
let ASYNC_BATCH_COUNT = 3000;
8+
let TRACK_PERF = false;
9+
10+
export const tweak = (batchCount, trackPerf) => {
11+
ASYNC_BATCH_COUNT = batchCount;
12+
TRACK_PERF = Boolean(trackPerf);
13+
};
14+
15+
const next = (typeof window !== 'undefined' && window.requestAnimationFrame) || setTimeout;
16+
17+
/*
18+
This function takes a value which results in a Map of
19+
path to a value and placeholder IS_ARRAY/IS_OBJECT, or the static
20+
value. It takes a "replacer" function which allows you to intercept
21+
the created result
22+
*/
23+
function getPaths(value, paths, replacer, currentPath = []) {
24+
const replacedValue = replacer ? replacer(currentPath, value) : value;
25+
if (typeof replacedValue === "function" || typeof replacedValue === "symbol" || replacedValue === undefined) {
26+
} else if (Array.isArray(replacedValue)) {
27+
paths.set(currentPath.slice(), IS_ARRAY_START);
28+
for (let key in replacedValue) {
29+
currentPath.push(key);
30+
getPaths(replacedValue[key], paths, replacer ? replacer.bind(replacedValue) : replacer, currentPath);
31+
currentPath.pop();
32+
}
33+
paths.set(currentPath.slice(), IS_ARRAY_END);
34+
} else if (replacedValue !== null && typeof replacedValue === "object") {
35+
paths.set(currentPath.slice(), IS_OBJECT_START);
36+
for (let key in replacedValue) {
37+
currentPath.push(key);
38+
getPaths(replacedValue[key], paths, replacer ? replacer.bind(replacedValue) : replacer, currentPath);
39+
currentPath.pop();
40+
}
41+
paths.set(currentPath.slice(), IS_OBJECT_END);
42+
} else {
43+
paths.set(currentPath.slice(), replacedValue);
44+
}
45+
46+
return paths;
47+
}
48+
49+
/*
50+
When parsing we replace all objects and arrays with a reference first,
51+
then we async resolve them. This function identifies a ref
52+
*/
53+
function isRef(ref) {
54+
if (typeof ref === "string" && ref.match(/\$\$REF_[0-9]/)) {
55+
return ref;
56+
}
57+
58+
return null;
59+
}
60+
61+
/*
62+
This function takes value with an optional replacer. It will stringify
63+
the values async by first creating a MAP of all paths, as explained above.
64+
Then it iterates these paths and produces the JSON string
65+
*/
66+
export function stringify(value, replacer?) {
67+
return new Promise<string>(resolve => {
68+
next(async () => {
69+
const paths = getPaths(value, new Map(), replacer ? replacer.bind(global || window) : replacer);
70+
const structs = [];
71+
let prevKey = [];
72+
let prevValue = null
73+
let string = "";
74+
let x = 0;
75+
let start
76+
77+
if (TRACK_PERF) {
78+
start = performance.now()
79+
}
80+
81+
for (let [key, value] of paths) {
82+
if (x % ASYNC_BATCH_COUNT === 0) {
83+
if (TRACK_PERF) {
84+
const end = performance.now()
85+
console.log("PERF - ", end - start)
86+
start = end
87+
}
88+
await new Promise(resolve => next(resolve));
89+
}
90+
91+
x++;
92+
93+
const currentKey = prevKey;
94+
95+
prevKey = key;
96+
97+
if (value === IS_OBJECT_END) {
98+
structs.pop()
99+
string += '}'
100+
continue
101+
}
102+
if (value === IS_ARRAY_END) {
103+
structs.pop()
104+
string += ']'
105+
continue
106+
}
107+
108+
109+
prevValue = value
110+
111+
if (key.length && key.length <= currentKey.length) {
112+
string += ",";
113+
}
114+
115+
let currentStruct = structs[structs.length - 1];
116+
117+
if (value === IS_OBJECT_START) {
118+
structs.push(value);
119+
string += key.length && currentStruct === IS_OBJECT_START ? `"${key[key.length - 1]}":{` : "{";
120+
continue;
121+
}
122+
123+
if (value === IS_ARRAY_START) {
124+
structs.push(value);
125+
string += key.length && currentStruct === IS_OBJECT_START ? `"${key[key.length - 1]}":[` : "[";
126+
continue;
127+
}
128+
129+
currentStruct = structs[structs.length - 1];
130+
if (currentStruct === IS_OBJECT_START) {
131+
string += `"${key[key.length - 1]}":${JSON.stringify(value)}`;
132+
continue;
133+
}
134+
if (currentStruct === IS_ARRAY_START) {
135+
string += JSON.stringify(value);
136+
continue;
137+
}
138+
}
139+
140+
resolve(string);
141+
});
142+
});
143+
}
144+
145+
/*
146+
This function takes a JSON string and async parses it. It does this by looking
147+
tracking all "{", "[" and then when finding their end parts, "}" and "]", replaces
148+
the object/array with a reference. Each reference is then async parsed and
149+
built back together
150+
*/
151+
export async function parse(value) {
152+
let refId = 0;
153+
const references = {};
154+
const openingObjectBrackets = [];
155+
const openingArrayBrackets = [];
156+
157+
let isInString = false;
158+
let start
159+
160+
if (TRACK_PERF) {
161+
start = performance.now()
162+
}
163+
164+
for (let charIndex = 0; charIndex < value.length; charIndex++) {
165+
if (charIndex % ASYNC_BATCH_COUNT === 0) {
166+
if (TRACK_PERF) {
167+
const end = performance.now()
168+
console.log("PERF - ", end - start)
169+
start = end
170+
}
171+
await new Promise(resolve => next(resolve));
172+
}
173+
if (value[charIndex] === '"') {
174+
isInString = !isInString;
175+
continue;
176+
}
177+
178+
if (isInString) {
179+
continue;
180+
}
181+
182+
if (value[charIndex] === "{") {
183+
openingObjectBrackets.push(charIndex);
184+
} else if (value[charIndex] === "[") {
185+
openingArrayBrackets.push(charIndex);
186+
} else if (value[charIndex] === "}") {
187+
const openingBracketIndex = openingObjectBrackets.pop();
188+
const id = `$$REF_${refId++}`;
189+
references[id] = value.substr(
190+
openingBracketIndex,
191+
charIndex - openingBracketIndex + 1
192+
);
193+
value =
194+
value.substr(0, openingBracketIndex) +
195+
`"${id}"` +
196+
value.substr(charIndex + 1);
197+
198+
charIndex = openingBracketIndex + id.length + 1;
199+
} else if (value[charIndex] === "]") {
200+
const openingBracketIndex = openingArrayBrackets.pop();
201+
const id = `$$REF_${refId++}`;
202+
references[id] = value.substr(
203+
openingBracketIndex,
204+
charIndex - openingBracketIndex + 1
205+
);
206+
value =
207+
value.substr(0, openingBracketIndex) +
208+
`"${id}"` +
209+
value.substr(charIndex + 1);
210+
charIndex = openingBracketIndex + id.length + 1;
211+
}
212+
}
213+
214+
async function produceResult(value, i) {
215+
let parsedValue = JSON.parse(value);
216+
217+
const keys = Object.keys(parsedValue);
218+
219+
if (i % ASYNC_BATCH_COUNT === 0) {
220+
await new Promise(resolve => next(resolve));
221+
}
222+
223+
return Promise.all(
224+
keys.map(async (key, index) => {
225+
const value = parsedValue[key];
226+
i++;
227+
if (isRef(value)) {
228+
parsedValue[key] = await produceResult(references[value], i + index);
229+
}
230+
})
231+
).then(() => parsedValue);
232+
}
233+
234+
let initialValue = JSON.parse(value);
235+
if (isRef(initialValue)) {
236+
initialValue = references[initialValue];
237+
}
238+
239+
return produceResult(initialValue, 0);
240+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es6",
4+
"module": "commonjs",
5+
"rootDir": "src",
6+
"outDir": ".code",
7+
"newLine": "LF",
8+
"lib": ["dom", "es2017"],
9+
"moduleResolution": "node",
10+
"importHelpers": true,
11+
"declaration": true,
12+
"pretty": true,
13+
"sourceMap": true,
14+
"inlineSources": true
15+
},
16+
"exclude": ["node_modules", "dist", "es", "lib", "src/**/*.test.ts"]
17+
}

packages/node_modules/overmind-devtools-client/DevtoolBackend.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const WebSocket = require('ws')
2-
const { stringify, parse } = require('a-json')
2+
const { stringify, parse } = require('non-blocking-json')
33

44
class DevtoolBackend {
55
constructor(options) {

packages/node_modules/overmind-devtools-client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"emotion": "^9.2.12",
2121
"lodash.throttle": "^4.1.1",
2222
"overmind-react": "next",
23-
"a-json": "next",
23+
"non-blocking-json": "next",
2424
"react": "16.9.0",
2525
"react-dom": "16.9.0",
2626
"react-split-pane": "^0.1.87",

0 commit comments

Comments
 (0)