socket-function 0.9.2 → 0.9.4

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.
Files changed (45) hide show
  1. package/.eslintrc.js +50 -50
  2. package/SocketFunction.ts +280 -280
  3. package/SocketFunctionTypes.ts +90 -90
  4. package/hot/HotReloadController.ts +105 -105
  5. package/mobx/UrlParam.ts +39 -39
  6. package/mobx/observer.tsx +49 -49
  7. package/mobx/promiseToObservable.tsx +41 -41
  8. package/package.json +2 -2
  9. package/require/CSSShim.ts +19 -19
  10. package/require/RequireController.ts +252 -252
  11. package/require/buffer.js +2368 -2368
  12. package/require/compileFlags.ts +44 -44
  13. package/require/require.html +13 -13
  14. package/require/require.js +462 -462
  15. package/spec.txt +115 -115
  16. package/src/CallFactory.ts +389 -389
  17. package/src/JSONLACKS/JSONLACKS.generated.js +17 -17
  18. package/src/JSONLACKS/JSONLACKS.pegjs +247 -247
  19. package/src/JSONLACKS/JSONLACKS.ts +429 -429
  20. package/src/args.ts +21 -21
  21. package/src/batching.ts +170 -170
  22. package/src/caching.ts +324 -318
  23. package/src/callHTTPHandler.ts +203 -203
  24. package/src/callManager.ts +134 -134
  25. package/src/certStore.ts +29 -29
  26. package/src/fixLargeNetworkCalls.ts +8 -8
  27. package/src/formatting/colors.ts +78 -78
  28. package/src/formatting/format.ts +160 -160
  29. package/src/formatting/logColors.ts +17 -17
  30. package/src/misc.ts +303 -302
  31. package/src/nodeCache.ts +92 -92
  32. package/src/nodeProxy.ts +54 -54
  33. package/src/profiling/getOwnTime.ts +142 -142
  34. package/src/profiling/measure.ts +273 -273
  35. package/src/profiling/stats.ts +212 -212
  36. package/src/profiling/tcpLagProxy.ts +63 -63
  37. package/src/storagePath.ts +10 -10
  38. package/src/tlsParsing.ts +96 -96
  39. package/src/types.ts +8 -8
  40. package/src/webSocketServer.ts +250 -250
  41. package/test/client.css +2 -2
  42. package/test/client.ts +46 -46
  43. package/test/server.ts +43 -43
  44. package/test/shared.ts +52 -52
  45. package/tsconfig.json +26 -26
@@ -1,430 +1,430 @@
1
-
2
- import { enableMeasurements, measureBlock, measureCode, measureCodeSync, measureFnc } from "../profiling/measure";
3
- // enableMeasurements();
4
-
5
- import debugbreak from "debugbreak";
6
- import fs from "fs";
7
-
8
- import parser from "./JSONLACKS.generated.js";
9
- import { recursiveFreeze } from "../misc";
10
- import { canHaveChildren } from "../types";
11
- import { delay } from "../batching";
12
-
13
- const SERIALIZE_OBJECT_BATCH_COUNT = 1000;
14
- const PARSE_BYTE_CHUNK_SIZE = 1024 * 1024 * 10;
15
-
16
- // Supports json and also:
17
- // - Non quoted field names "{ x: 1 }"?
18
- // - Trailing commas
19
- // NOTE: Comma only syntax is not supported, ex, "[,,]", which is an array of length 2 in javascript
20
- // - Comments on input, but not on output
21
- // - References
22
- // - Buffers (just Buffers, not typed arrays)
23
- // The stringify function always creates valid json, as the syntax for references and buffers
24
- // will just be special property names and values.
25
- // NOTE: We don't support Date serialization. Never store Dates, store "number".
26
-
27
- export interface JSONLACKS_ParseConfig {
28
- // Defaults to true. Enables parsing of:
29
- // - Trailing commas
30
- // - Non-quoted field names (ex, "{ x: 1 }")
31
- // - Comments (strips them, but doesn't throw)
32
- extended?: boolean;
33
- }
34
- export interface JSONLACKS_StringifyConfig {
35
- // If specified, we are allowed to mutate the provided object. Speeds up serialization.
36
- allowObjectMutation?: boolean;
37
- }
38
-
39
- interface HydrateState {
40
- references: Map<string, unknown>,
41
- visited: Set<unknown>,
42
- }
43
-
44
- export class JSONLACKS {
45
- public static readonly LACKS_KEY = "__JSONLACKS__98cfb4a05fa34d828661cae15b8779ce__";
46
-
47
- /** If set to true parses non-quoted field names, comments, trailing commas, etc */
48
- public static EXTENDED_PARSER = true;
49
- public static IGNORE_MISSING_REFERENCES = false;
50
-
51
- @measureFnc
52
- public static stringify(obj: unknown, config?: JSONLACKS_StringifyConfig): string {
53
- let serialized = JSONLACKS.escapeSpecialObjects(obj, config);
54
- return measureBlock(function JSONstringify() { return JSON.stringify(serialized); });
55
- }
56
- /** Is useful when serializing an array to a file with one object per line */
57
- @measureFnc
58
- public static async stringifyFile(obj: unknown[], config?: JSONLACKS_StringifyConfig): Promise<Buffer> {
59
- let serialized = JSONLACKS.escapeSpecialObjects(obj, config) as unknown[];
60
- return await measureBlock(async function JSONstringifyAndJoin() {
61
- let buffers: Buffer[] = [];
62
- for (let i = 0; i < serialized.length; i += SERIALIZE_OBJECT_BATCH_COUNT) {
63
- let str = serialized.slice(i, i + SERIALIZE_OBJECT_BATCH_COUNT).map(x => JSON.stringify(x) + "\n").join("");
64
- buffers.push(Buffer.from(str));
65
- await delay("immediate");
66
- }
67
- // Break up into chunks, as string => Buffer i
68
- return Buffer.concat(buffers);
69
- });
70
- }
71
- // TIMING: Seems to be about 40X slower than JSON.parse unless extended is set to false,
72
- // then it is about 2X slower (although it depends on the size and complexity of the objects!)
73
- @measureFnc
74
- public static parse<T>(text: string, config?: JSONLACKS_ParseConfig, hydrateState?: HydrateState): T {
75
- let obj: unknown;
76
-
77
- let extendedParsing = config?.extended ?? JSONLACKS.EXTENDED_PARSER;
78
-
79
- if (extendedParsing) {
80
- obj = measureBlock(function JSONextendedParse() { return parser.parse(text); });
81
- } else {
82
- obj = measureBlock(function JSONparse() { return JSON.parse(text); });
83
- }
84
-
85
- return JSONLACKS.hydrateSpecialObjects(obj, hydrateState) as T;
86
- }
87
- @measureFnc
88
- public static async parseLines<T>(buffer: Buffer, config?: JSONLACKS_ParseConfig): Promise<T[]> {
89
- let output: T[] = [];
90
- let pos = 0;
91
- let hydrateState: HydrateState = {
92
- references: new Map(),
93
- visited: new Set(),
94
- };
95
- function parseChunk() {
96
- let start = pos;
97
- let lastNewLine = 0;
98
- while (pos < buffer.length && (!lastNewLine || (pos - start) < PARSE_BYTE_CHUNK_SIZE)) {
99
- let byte = buffer[pos];
100
- if (byte === 10) {
101
- lastNewLine = pos;
102
- }
103
- pos++;
104
- }
105
- if (pos === buffer.length) {
106
- lastNewLine = pos;
107
- }
108
- pos = lastNewLine + 1;
109
-
110
- let text = buffer.slice(start, lastNewLine).toString("utf8");
111
- let lines = text
112
- .replaceAll("\r", "")
113
- .split("\n")
114
- .filter(x => x && !x.startsWith("//"))
115
- ;
116
- let linesJSON = "[";
117
- for (let i = 0; i < lines.length; i++) {
118
- if (i !== 0) linesJSON += ",";
119
- linesJSON += lines[i];
120
- }
121
- linesJSON += "]";
122
- let parts = JSONLACKS.parse(linesJSON, config, hydrateState) as T[];
123
- for (let part of parts) {
124
- output.push(part);
125
- }
126
- }
127
- while (pos < buffer.length) {
128
- parseChunk();
129
- // Wait, to allow other thread to do work. We wait a long time... because we parse 10MB at once,
130
- // so... this gives us 2s of delay per 1GB of parsing, which should only be a fraction of our parse time
131
- if (pos < buffer.length) {
132
- await delay(20);
133
- }
134
- }
135
- return output;
136
- }
137
-
138
- @measureFnc
139
- private static escapeSpecialObjects(obj: unknown, config?: JSONLACKS_StringifyConfig): unknown {
140
- // I think iterating twice for references is the fastest way to do it?
141
- let objects = new Set<unknown>();
142
- // NOTE: Use unique values for references, to allow concatenating files without having to
143
- // deal with escaping references.
144
- let refPrefix = Date.now() + Math.random() + "";
145
- let nextRefId = 0;
146
- function getNextRefKey() {
147
- return refPrefix + "_" + nextRefId++;
148
- }
149
- let refObjects = new Map<unknown, string>();
150
- findReferences(obj);
151
- function findReferences(obj: unknown) {
152
- if (!canHaveChildren(obj)) return;
153
- if (refObjects.has(obj)) return;
154
- if (objects.has(obj)) {
155
- let refKey = getNextRefKey();
156
- refObjects.set(obj, refKey);
157
- return;
158
- }
159
- objects.add(obj);
160
- for (let key in obj) {
161
- findReferences(obj[key]);
162
- }
163
- }
164
-
165
- let refsSeen = new Set<unknown>();
166
- return iterate(obj);
167
- function iterate(obj: unknown, refHandled?: boolean): unknown {
168
- if (!canHaveChildren(obj)) return obj;
169
- if (!refHandled) {
170
- let refKey = refObjects.get(obj);
171
- if (refKey) {
172
- if (refsSeen.has(obj)) {
173
- return {
174
- [JSONLACKS.LACKS_KEY]: "ref",
175
- id: refKey,
176
- };
177
- }
178
- refsSeen.add(obj);
179
- return {
180
- [JSONLACKS.LACKS_KEY]: "define",
181
- id: refKey,
182
- value: iterate(obj, true),
183
- };
184
- }
185
- }
186
-
187
- if (JSONLACKS.LACKS_KEY in obj) {
188
- let restOfObj = { ...obj };
189
- let escapedValue = restOfObj[JSONLACKS.LACKS_KEY];
190
- delete restOfObj[JSONLACKS.LACKS_KEY];
191
- return {
192
- [JSONLACKS.LACKS_KEY]: "removedSpecialKey",
193
- escapedValue: iterate(escapedValue),
194
- restOfObj: iterate(restOfObj),
195
- };
196
- }
197
-
198
- if (obj instanceof Buffer) {
199
- return {
200
- [JSONLACKS.LACKS_KEY]: "Buffer",
201
- data: obj.toString("base64"),
202
- };
203
- }
204
-
205
- let cloned = config?.allowObjectMutation;
206
- function cloneObj() {
207
- if (cloned) return;
208
- cloned = true;
209
- if (Array.isArray(obj)) {
210
- obj = [...obj];
211
- } else {
212
- obj = { ...obj as any };
213
- }
214
- };
215
- for (let key in obj) {
216
- let originalValue = obj[key];
217
- let value = iterate(originalValue);
218
- if (value !== originalValue) {
219
- cloneObj();
220
- obj[key] = value;
221
- }
222
- }
223
- return obj;
224
- }
225
- }
226
-
227
- @measureFnc
228
- private static hydrateSpecialObjects(obj: unknown, hydrateState?: HydrateState): unknown {
229
- let references = hydrateState?.references || new Map<string, unknown>();
230
- let visited = hydrateState?.visited || new Set<unknown>();
231
- return iterate(obj);
232
- function iterate(obj: unknown) {
233
- if (!canHaveChildren(obj)) return obj;
234
- if (visited.has(obj)) return obj;
235
- visited.add(obj);
236
- let type = obj[JSONLACKS.LACKS_KEY];
237
- if (!type) {
238
- for (let key in obj) {
239
- let originalValue = obj[key];
240
- let value = iterate(originalValue);
241
- if (value !== originalValue) {
242
- obj[key] = value;
243
- }
244
- }
245
- return obj;
246
- }
247
- if (type === "removedSpecialKey") {
248
- let restOfObj = iterate(obj.restOfObj) as any;
249
- let escapedValue = iterate(obj.escapedValue) as any;
250
- return { ...restOfObj, [JSONLACKS.LACKS_KEY]: escapedValue };
251
- }
252
- if (type === "Buffer") {
253
- return Buffer.from(obj.data as string, "base64");
254
- }
255
- if (type === "define") {
256
- references.set(obj.id as string, iterate(obj.value));
257
- return obj.value;
258
- }
259
- if (type === "ref") {
260
- let id = obj.id as string;
261
- if (!JSONLACKS.IGNORE_MISSING_REFERENCES && !references.has(id)) {
262
- debugbreak(2);
263
- debugger;
264
- throw new Error(`Reference to undefined id "${id}"`);
265
- }
266
- return references.get(id);
267
- }
268
- throw new Error(`Unknown lacks type "${type}"`);
269
- }
270
- }
271
- }
272
-
273
- async function benchmark() {
274
- const loops = 1000 * 100;
275
- let inputs: string[] = [];
276
- for (let i = 0; i < loops; i++) {
277
- inputs.push(JSON.stringify({
278
- i,
279
- hello_there: i,
280
- example: 5,
281
- list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
282
- }) + " ");
283
- }
284
- JSON.parse(inputs[0]);
285
- parser.parse(inputs[0]);
286
-
287
- measureCodeSync(function measure() {
288
- measureBlock(function JSON_PARSE() {
289
- for (let i = 0; i < loops; i++) {
290
- JSON.parse(inputs[i]);
291
- }
292
- });
293
- measureBlock(function JSON_PARSE_FREEZE() {
294
- for (let i = 0; i < loops; i++) {
295
- let obj = JSON.parse(inputs[i]);
296
- recursiveFreeze(obj);
297
- }
298
- });
299
-
300
- measureBlock(function EXTENDED_PARSE() {
301
- for (let i = 0; i < loops; i++) {
302
- parser.parse(inputs[i]);
303
- }
304
- });
305
-
306
- measureBlock(function JSONLACKS_PARSE() {
307
- for (let i = 0; i < loops; i++) {
308
- JSONLACKS.parse(inputs[i]);
309
- }
310
- });
311
- measureBlock(function JSONLACKS_PARSE_SIMPLE() {
312
- for (let i = 0; i < loops; i++) {
313
- JSONLACKS.parse(inputs[i], { extended: false });
314
- }
315
- });
316
-
317
- }, {
318
- thresholdInTable: 0
319
- });
320
-
321
- }
322
- //benchmark().catch(console.error).finally(() => process.exit());
323
-
324
-
325
- async function testJSONLACKS() {
326
- {
327
- let obj = {};
328
- let str = JSONLACKS.stringify({
329
- a: obj,
330
- b: obj,
331
- });
332
- let parsed = JSONLACKS.parse(str) as any;
333
- if (parsed.a !== parsed.b) {
334
- throw new Error("Failed to maintain references");
335
- }
336
- if (typeof parsed.a !== "object") {
337
- throw new Error("Object become corrupted");
338
- }
339
- }
340
-
341
- // Test LACKS_KEY is escaped correctly
342
- {
343
- let obj = { [JSONLACKS.LACKS_KEY]: "hello" };
344
- let str = JSONLACKS.stringify(obj);
345
- let parsed = JSONLACKS.parse(str) as any;
346
- if (JSON.stringify(obj) !== JSON.stringify(parsed)) {
347
- throw new Error("Failed to escape LACKS_KEY");
348
- }
349
- }
350
- // Test buffers are preserved
351
- {
352
- let obj = { a: Buffer.from("hello") };
353
- let str = JSONLACKS.stringify(obj);
354
- let parsed = JSONLACKS.parse(str) as any;
355
- if (obj.a.toString() !== parsed.a.toString()) {
356
- throw new Error(`Failed to preserve buffers`);
357
- }
358
- }
359
-
360
- // Test references 2
361
- {
362
- let base = {};
363
- let arr = [base, base];
364
- let obj = { x: arr, y: arr };
365
- let result = JSONLACKS.parse(JSONLACKS.stringify(obj), { extended: false }) as typeof obj;
366
- if (JSON.stringify(result) !== JSON.stringify(obj)) {
367
- throw new Error(`Corrupted values, expected ${JSON.stringify(obj)}, got ${JSON.stringify(result)}`);
368
- }
369
- if (result.x !== result.y) {
370
- throw new Error(`Failed to maintain references`);
371
- }
372
- if (result.x[0] !== result.x[1]) {
373
- throw new Error(`Failed to maintain references`);
374
- }
375
- }
376
- }
377
- //testJSONLACKS().catch(console.error).finally(() => process.exit());
378
-
379
- async function generateAndVerifyParser() {
380
- const pegjs = await import("pegjs");
381
-
382
- var grammar = fs.readFileSync(__dirname + "/JSONLACKS.pegjs", "utf8");
383
- var parserSource = pegjs.generate(grammar, { output: "source", format: "commonjs" });
384
- fs.writeFileSync(__dirname + "/JSONLACKS.generated.js", parserSource);
385
-
386
- var module = { exports: {} };
387
- eval(parserSource);
388
- const parser = module.exports as any;
389
- function verify(text: string) {
390
- var parsed = parser.parse(text);
391
- var result = JSON.stringify(parsed);
392
- let realResult = JSON.stringify(eval("(" + text + ")"));
393
- if (result !== realResult) {
394
- throw new Error(`Failed to parse: ${text} should be ${realResult}, was ${result}`);
395
- }
396
- }
397
-
398
- verify(`
399
- {
400
- "hello_there": 1,
401
- }
402
- `);
403
- verify(`
404
- {
405
- hello_there: 1,
406
- }
407
- `);
408
- verify(`
409
- {
410
- hello_there: 1,
411
- more: 1,
412
- }
413
- `);
414
- verify(`{ list: [1,], }`);
415
- verify(`{ list: [1,], list2: [1,], }`);
416
-
417
- verify(`{ /* test */ list: [1,], list2: [1,], }`);
418
- verify(`{ /* test */ /* test 2 */ list: [1,], list2: [1,], }`);
419
- verify(`
420
- {
421
- /* test */
422
- /* test 2 */
423
- list: [1,],
424
- // Single line comment
425
- list2: [1,],
426
- // list3: [3],
427
- }
428
- `);
429
- }
1
+
2
+ import { enableMeasurements, measureBlock, measureCode, measureCodeSync, measureFnc } from "../profiling/measure";
3
+ // enableMeasurements();
4
+
5
+ import debugbreak from "debugbreak";
6
+ import fs from "fs";
7
+
8
+ import parser from "./JSONLACKS.generated.js";
9
+ import { recursiveFreeze } from "../misc";
10
+ import { canHaveChildren } from "../types";
11
+ import { delay } from "../batching";
12
+
13
+ const SERIALIZE_OBJECT_BATCH_COUNT = 1000;
14
+ const PARSE_BYTE_CHUNK_SIZE = 1024 * 1024 * 10;
15
+
16
+ // Supports json and also:
17
+ // - Non quoted field names "{ x: 1 }"?
18
+ // - Trailing commas
19
+ // NOTE: Comma only syntax is not supported, ex, "[,,]", which is an array of length 2 in javascript
20
+ // - Comments on input, but not on output
21
+ // - References
22
+ // - Buffers (just Buffers, not typed arrays)
23
+ // The stringify function always creates valid json, as the syntax for references and buffers
24
+ // will just be special property names and values.
25
+ // NOTE: We don't support Date serialization. Never store Dates, store "number".
26
+
27
+ export interface JSONLACKS_ParseConfig {
28
+ // Defaults to true. Enables parsing of:
29
+ // - Trailing commas
30
+ // - Non-quoted field names (ex, "{ x: 1 }")
31
+ // - Comments (strips them, but doesn't throw)
32
+ extended?: boolean;
33
+ }
34
+ export interface JSONLACKS_StringifyConfig {
35
+ // If specified, we are allowed to mutate the provided object. Speeds up serialization.
36
+ allowObjectMutation?: boolean;
37
+ }
38
+
39
+ interface HydrateState {
40
+ references: Map<string, unknown>,
41
+ visited: Set<unknown>,
42
+ }
43
+
44
+ export class JSONLACKS {
45
+ public static readonly LACKS_KEY = "__JSONLACKS__98cfb4a05fa34d828661cae15b8779ce__";
46
+
47
+ /** If set to true parses non-quoted field names, comments, trailing commas, etc */
48
+ public static EXTENDED_PARSER = true;
49
+ public static IGNORE_MISSING_REFERENCES = false;
50
+
51
+ @measureFnc
52
+ public static stringify(obj: unknown, config?: JSONLACKS_StringifyConfig): string {
53
+ let serialized = JSONLACKS.escapeSpecialObjects(obj, config);
54
+ return measureBlock(function JSONstringify() { return JSON.stringify(serialized); });
55
+ }
56
+ /** Is useful when serializing an array to a file with one object per line */
57
+ @measureFnc
58
+ public static async stringifyFile(obj: unknown[], config?: JSONLACKS_StringifyConfig): Promise<Buffer> {
59
+ let serialized = JSONLACKS.escapeSpecialObjects(obj, config) as unknown[];
60
+ return await measureBlock(async function JSONstringifyAndJoin() {
61
+ let buffers: Buffer[] = [];
62
+ for (let i = 0; i < serialized.length; i += SERIALIZE_OBJECT_BATCH_COUNT) {
63
+ let str = serialized.slice(i, i + SERIALIZE_OBJECT_BATCH_COUNT).map(x => JSON.stringify(x) + "\n").join("");
64
+ buffers.push(Buffer.from(str));
65
+ await delay("immediate");
66
+ }
67
+ // Break up into chunks, as string => Buffer i
68
+ return Buffer.concat(buffers);
69
+ });
70
+ }
71
+ // TIMING: Seems to be about 40X slower than JSON.parse unless extended is set to false,
72
+ // then it is about 2X slower (although it depends on the size and complexity of the objects!)
73
+ @measureFnc
74
+ public static parse<T>(text: string, config?: JSONLACKS_ParseConfig, hydrateState?: HydrateState): T {
75
+ let obj: unknown;
76
+
77
+ let extendedParsing = config?.extended ?? JSONLACKS.EXTENDED_PARSER;
78
+
79
+ if (extendedParsing) {
80
+ obj = measureBlock(function JSONextendedParse() { return parser.parse(text); });
81
+ } else {
82
+ obj = measureBlock(function JSONparse() { return JSON.parse(text); });
83
+ }
84
+
85
+ return JSONLACKS.hydrateSpecialObjects(obj, hydrateState) as T;
86
+ }
87
+ @measureFnc
88
+ public static async parseLines<T>(buffer: Buffer, config?: JSONLACKS_ParseConfig): Promise<T[]> {
89
+ let output: T[] = [];
90
+ let pos = 0;
91
+ let hydrateState: HydrateState = {
92
+ references: new Map(),
93
+ visited: new Set(),
94
+ };
95
+ function parseChunk() {
96
+ let start = pos;
97
+ let lastNewLine = 0;
98
+ while (pos < buffer.length && (!lastNewLine || (pos - start) < PARSE_BYTE_CHUNK_SIZE)) {
99
+ let byte = buffer[pos];
100
+ if (byte === 10) {
101
+ lastNewLine = pos;
102
+ }
103
+ pos++;
104
+ }
105
+ if (pos === buffer.length) {
106
+ lastNewLine = pos;
107
+ }
108
+ pos = lastNewLine + 1;
109
+
110
+ let text = buffer.slice(start, lastNewLine).toString("utf8");
111
+ let lines = text
112
+ .replaceAll("\r", "")
113
+ .split("\n")
114
+ .filter(x => x && !x.startsWith("//"))
115
+ ;
116
+ let linesJSON = "[";
117
+ for (let i = 0; i < lines.length; i++) {
118
+ if (i !== 0) linesJSON += ",";
119
+ linesJSON += lines[i];
120
+ }
121
+ linesJSON += "]";
122
+ let parts = JSONLACKS.parse(linesJSON, config, hydrateState) as T[];
123
+ for (let part of parts) {
124
+ output.push(part);
125
+ }
126
+ }
127
+ while (pos < buffer.length) {
128
+ parseChunk();
129
+ // Wait, to allow other thread to do work. We wait a long time... because we parse 10MB at once,
130
+ // so... this gives us 2s of delay per 1GB of parsing, which should only be a fraction of our parse time
131
+ if (pos < buffer.length) {
132
+ await delay(20);
133
+ }
134
+ }
135
+ return output;
136
+ }
137
+
138
+ @measureFnc
139
+ private static escapeSpecialObjects(obj: unknown, config?: JSONLACKS_StringifyConfig): unknown {
140
+ // I think iterating twice for references is the fastest way to do it?
141
+ let objects = new Set<unknown>();
142
+ // NOTE: Use unique values for references, to allow concatenating files without having to
143
+ // deal with escaping references.
144
+ let refPrefix = Date.now() + Math.random() + "";
145
+ let nextRefId = 0;
146
+ function getNextRefKey() {
147
+ return refPrefix + "_" + nextRefId++;
148
+ }
149
+ let refObjects = new Map<unknown, string>();
150
+ findReferences(obj);
151
+ function findReferences(obj: unknown) {
152
+ if (!canHaveChildren(obj)) return;
153
+ if (refObjects.has(obj)) return;
154
+ if (objects.has(obj)) {
155
+ let refKey = getNextRefKey();
156
+ refObjects.set(obj, refKey);
157
+ return;
158
+ }
159
+ objects.add(obj);
160
+ for (let key in obj) {
161
+ findReferences(obj[key]);
162
+ }
163
+ }
164
+
165
+ let refsSeen = new Set<unknown>();
166
+ return iterate(obj);
167
+ function iterate(obj: unknown, refHandled?: boolean): unknown {
168
+ if (!canHaveChildren(obj)) return obj;
169
+ if (!refHandled) {
170
+ let refKey = refObjects.get(obj);
171
+ if (refKey) {
172
+ if (refsSeen.has(obj)) {
173
+ return {
174
+ [JSONLACKS.LACKS_KEY]: "ref",
175
+ id: refKey,
176
+ };
177
+ }
178
+ refsSeen.add(obj);
179
+ return {
180
+ [JSONLACKS.LACKS_KEY]: "define",
181
+ id: refKey,
182
+ value: iterate(obj, true),
183
+ };
184
+ }
185
+ }
186
+
187
+ if (JSONLACKS.LACKS_KEY in obj) {
188
+ let restOfObj = { ...obj };
189
+ let escapedValue = restOfObj[JSONLACKS.LACKS_KEY];
190
+ delete restOfObj[JSONLACKS.LACKS_KEY];
191
+ return {
192
+ [JSONLACKS.LACKS_KEY]: "removedSpecialKey",
193
+ escapedValue: iterate(escapedValue),
194
+ restOfObj: iterate(restOfObj),
195
+ };
196
+ }
197
+
198
+ if (obj instanceof Buffer) {
199
+ return {
200
+ [JSONLACKS.LACKS_KEY]: "Buffer",
201
+ data: obj.toString("base64"),
202
+ };
203
+ }
204
+
205
+ let cloned = config?.allowObjectMutation;
206
+ function cloneObj() {
207
+ if (cloned) return;
208
+ cloned = true;
209
+ if (Array.isArray(obj)) {
210
+ obj = [...obj];
211
+ } else {
212
+ obj = { ...obj as any };
213
+ }
214
+ };
215
+ for (let key in obj) {
216
+ let originalValue = obj[key];
217
+ let value = iterate(originalValue);
218
+ if (value !== originalValue) {
219
+ cloneObj();
220
+ obj[key] = value;
221
+ }
222
+ }
223
+ return obj;
224
+ }
225
+ }
226
+
227
+ @measureFnc
228
+ private static hydrateSpecialObjects(obj: unknown, hydrateState?: HydrateState): unknown {
229
+ let references = hydrateState?.references || new Map<string, unknown>();
230
+ let visited = hydrateState?.visited || new Set<unknown>();
231
+ return iterate(obj);
232
+ function iterate(obj: unknown) {
233
+ if (!canHaveChildren(obj)) return obj;
234
+ if (visited.has(obj)) return obj;
235
+ visited.add(obj);
236
+ let type = obj[JSONLACKS.LACKS_KEY];
237
+ if (!type) {
238
+ for (let key in obj) {
239
+ let originalValue = obj[key];
240
+ let value = iterate(originalValue);
241
+ if (value !== originalValue) {
242
+ obj[key] = value;
243
+ }
244
+ }
245
+ return obj;
246
+ }
247
+ if (type === "removedSpecialKey") {
248
+ let restOfObj = iterate(obj.restOfObj) as any;
249
+ let escapedValue = iterate(obj.escapedValue) as any;
250
+ return { ...restOfObj, [JSONLACKS.LACKS_KEY]: escapedValue };
251
+ }
252
+ if (type === "Buffer") {
253
+ return Buffer.from(obj.data as string, "base64");
254
+ }
255
+ if (type === "define") {
256
+ references.set(obj.id as string, iterate(obj.value));
257
+ return obj.value;
258
+ }
259
+ if (type === "ref") {
260
+ let id = obj.id as string;
261
+ if (!JSONLACKS.IGNORE_MISSING_REFERENCES && !references.has(id)) {
262
+ debugbreak(2);
263
+ debugger;
264
+ throw new Error(`Reference to undefined id "${id}"`);
265
+ }
266
+ return references.get(id);
267
+ }
268
+ throw new Error(`Unknown lacks type "${type}"`);
269
+ }
270
+ }
271
+ }
272
+
273
+ async function benchmark() {
274
+ const loops = 1000 * 100;
275
+ let inputs: string[] = [];
276
+ for (let i = 0; i < loops; i++) {
277
+ inputs.push(JSON.stringify({
278
+ i,
279
+ hello_there: i,
280
+ example: 5,
281
+ list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
282
+ }) + " ");
283
+ }
284
+ JSON.parse(inputs[0]);
285
+ parser.parse(inputs[0]);
286
+
287
+ measureCodeSync(function measure() {
288
+ measureBlock(function JSON_PARSE() {
289
+ for (let i = 0; i < loops; i++) {
290
+ JSON.parse(inputs[i]);
291
+ }
292
+ });
293
+ measureBlock(function JSON_PARSE_FREEZE() {
294
+ for (let i = 0; i < loops; i++) {
295
+ let obj = JSON.parse(inputs[i]);
296
+ recursiveFreeze(obj);
297
+ }
298
+ });
299
+
300
+ measureBlock(function EXTENDED_PARSE() {
301
+ for (let i = 0; i < loops; i++) {
302
+ parser.parse(inputs[i]);
303
+ }
304
+ });
305
+
306
+ measureBlock(function JSONLACKS_PARSE() {
307
+ for (let i = 0; i < loops; i++) {
308
+ JSONLACKS.parse(inputs[i]);
309
+ }
310
+ });
311
+ measureBlock(function JSONLACKS_PARSE_SIMPLE() {
312
+ for (let i = 0; i < loops; i++) {
313
+ JSONLACKS.parse(inputs[i], { extended: false });
314
+ }
315
+ });
316
+
317
+ }, {
318
+ thresholdInTable: 0
319
+ });
320
+
321
+ }
322
+ //benchmark().catch(console.error).finally(() => process.exit());
323
+
324
+
325
+ async function testJSONLACKS() {
326
+ {
327
+ let obj = {};
328
+ let str = JSONLACKS.stringify({
329
+ a: obj,
330
+ b: obj,
331
+ });
332
+ let parsed = JSONLACKS.parse(str) as any;
333
+ if (parsed.a !== parsed.b) {
334
+ throw new Error("Failed to maintain references");
335
+ }
336
+ if (typeof parsed.a !== "object") {
337
+ throw new Error("Object become corrupted");
338
+ }
339
+ }
340
+
341
+ // Test LACKS_KEY is escaped correctly
342
+ {
343
+ let obj = { [JSONLACKS.LACKS_KEY]: "hello" };
344
+ let str = JSONLACKS.stringify(obj);
345
+ let parsed = JSONLACKS.parse(str) as any;
346
+ if (JSON.stringify(obj) !== JSON.stringify(parsed)) {
347
+ throw new Error("Failed to escape LACKS_KEY");
348
+ }
349
+ }
350
+ // Test buffers are preserved
351
+ {
352
+ let obj = { a: Buffer.from("hello") };
353
+ let str = JSONLACKS.stringify(obj);
354
+ let parsed = JSONLACKS.parse(str) as any;
355
+ if (obj.a.toString() !== parsed.a.toString()) {
356
+ throw new Error(`Failed to preserve buffers`);
357
+ }
358
+ }
359
+
360
+ // Test references 2
361
+ {
362
+ let base = {};
363
+ let arr = [base, base];
364
+ let obj = { x: arr, y: arr };
365
+ let result = JSONLACKS.parse(JSONLACKS.stringify(obj), { extended: false }) as typeof obj;
366
+ if (JSON.stringify(result) !== JSON.stringify(obj)) {
367
+ throw new Error(`Corrupted values, expected ${JSON.stringify(obj)}, got ${JSON.stringify(result)}`);
368
+ }
369
+ if (result.x !== result.y) {
370
+ throw new Error(`Failed to maintain references`);
371
+ }
372
+ if (result.x[0] !== result.x[1]) {
373
+ throw new Error(`Failed to maintain references`);
374
+ }
375
+ }
376
+ }
377
+ //testJSONLACKS().catch(console.error).finally(() => process.exit());
378
+
379
+ async function generateAndVerifyParser() {
380
+ const pegjs = await import("pegjs");
381
+
382
+ var grammar = fs.readFileSync(__dirname + "/JSONLACKS.pegjs", "utf8");
383
+ var parserSource = pegjs.generate(grammar, { output: "source", format: "commonjs" });
384
+ fs.writeFileSync(__dirname + "/JSONLACKS.generated.js", parserSource);
385
+
386
+ var module = { exports: {} };
387
+ eval(parserSource);
388
+ const parser = module.exports as any;
389
+ function verify(text: string) {
390
+ var parsed = parser.parse(text);
391
+ var result = JSON.stringify(parsed);
392
+ let realResult = JSON.stringify(eval("(" + text + ")"));
393
+ if (result !== realResult) {
394
+ throw new Error(`Failed to parse: ${text} should be ${realResult}, was ${result}`);
395
+ }
396
+ }
397
+
398
+ verify(`
399
+ {
400
+ "hello_there": 1,
401
+ }
402
+ `);
403
+ verify(`
404
+ {
405
+ hello_there: 1,
406
+ }
407
+ `);
408
+ verify(`
409
+ {
410
+ hello_there: 1,
411
+ more: 1,
412
+ }
413
+ `);
414
+ verify(`{ list: [1,], }`);
415
+ verify(`{ list: [1,], list2: [1,], }`);
416
+
417
+ verify(`{ /* test */ list: [1,], list2: [1,], }`);
418
+ verify(`{ /* test */ /* test 2 */ list: [1,], list2: [1,], }`);
419
+ verify(`
420
+ {
421
+ /* test */
422
+ /* test 2 */
423
+ list: [1,],
424
+ // Single line comment
425
+ list2: [1,],
426
+ // list3: [3],
427
+ }
428
+ `);
429
+ }
430
430
  //generateAndVerifyParser().catch(console.error).finally(() => process.exit());