From 2e5991c507b49fff766e5f86892882cfa7607976 Mon Sep 17 00:00:00 2001 From: KATT Date: Tue, 3 Oct 2023 10:21:59 +0200 Subject: [PATCH] serialized --- src/createTsonAsync.ts | 8 +- src/handlers/tsonPromise.test.ts | 152 ++++++++++++++++++++++++++++++- src/serializeAsync.ts | 45 ++++++++- src/types.ts | 9 ++ 4 files changed, 208 insertions(+), 6 deletions(-) diff --git a/src/createTsonAsync.ts b/src/createTsonAsync.ts index 32b0f9de..3914fcaf 100644 --- a/src/createTsonAsync.ts +++ b/src/createTsonAsync.ts @@ -1,6 +1,10 @@ -import { createAsyncTsonSerializer } from "./serializeAsync.js"; +import { + createAsyncTsonSerializer, + createAsyncTsonStringifier, +} from "./serializeAsync.js"; import { TsonAsyncOptions } from "./types.js"; export const createTsonAsync = (opts: TsonAsyncOptions) => ({ - serializeAsync: createAsyncTsonSerializer(opts), + serialize: createAsyncTsonSerializer(opts), + stringify: createAsyncTsonStringifier(opts), }); diff --git a/src/handlers/tsonPromise.test.ts b/src/handlers/tsonPromise.test.ts index c32e2ad0..8dbb9d20 100644 --- a/src/handlers/tsonPromise.test.ts +++ b/src/handlers/tsonPromise.test.ts @@ -1,6 +1,8 @@ import { expect, test } from "vitest"; import { createTsonAsync, tsonPromise } from "../index.js"; +import { TsonAsyncValueTuple } from "../serializeAsync.js"; +import { TsonSerialized, TsonSerializedValue } from "../types.js"; const createPromise = (result: () => T) => { return new Promise((resolve) => { @@ -18,7 +20,7 @@ test("serialize promise", async () => { const promise = Promise.resolve(42); - const [head, iterator] = tson.serializeAsync(promise); + const [head, iterator] = tson.serialize(promise); expect(head).toMatchInlineSnapshot(` { @@ -63,7 +65,7 @@ test("serialize promise that returns a promise", async () => { }), }; - const [head, iterator] = tson.serializeAsync(obj); + const [head, iterator] = tson.serialize(obj); expect(head).toMatchInlineSnapshot(` { @@ -115,7 +117,7 @@ test("promise that rejects", async () => { const promise = Promise.reject(new Error("foo")); - const [head, iterator] = tson.serializeAsync(promise); + const [head, iterator] = tson.serialize(promise); expect(head).toMatchInlineSnapshot(` { @@ -144,3 +146,147 @@ test("promise that rejects", async () => { ] `); }); + +test("stringifier - no promises", async () => { + const obj = { + foo: "bar", + }; + + const tson = createTsonAsync({ + nonce: () => "__tson", + types: [tsonPromise], + }); + + const buffer: string[] = []; + + for await (const value of tson.stringify(obj, 4)) { + buffer.push(value); + } + + // expect(buffer).toHaveLength(5); + expect(buffer).toMatchInlineSnapshot(` + [ + "[", + " {\\"json\\":{\\"foo\\":\\"bar\\"},\\"nonce\\":\\"__tson\\"}", + " ,", + " [", + " ]", + "]", + ] + `); + + expect(JSON.parse(buffer.join(""))).toMatchInlineSnapshot(` + [ + { + "json": { + "foo": "bar", + }, + "nonce": "__tson", + }, + [], + ] + `); +}); + +test("stringifier - with promise", async () => { + const obj = createPromise(() => "bar" as const); + + const tson = createTsonAsync({ + nonce: () => "__tson", + types: [tsonPromise], + }); + + const buffer: string[] = []; + + for await (const value of tson.stringify(obj, 4)) { + buffer.push(value); + } + + expect(buffer).toMatchInlineSnapshot(` + [ + "[", + " {\\"json\\":[\\"Promise\\",0,\\"__tson\\"],\\"nonce\\":\\"__tson\\"}", + " ,", + " [", + " [0,0,\\"bar\\"]", + " ]", + "]", + ] + `); +}); + +test("stringifier - promise in promise", async () => { + const obj = { + promise: createPromise(() => { + return { + anotherPromise: createPromise(() => { + return 42; + }), + }; + }), + }; + + const tson = createTsonAsync({ + nonce: () => "__tson", + types: [tsonPromise], + }); + + const buffer: string[] = []; + + for await (const value of tson.stringify(obj, 2)) { + buffer.push(value); + } + + const full = JSON.parse(buffer.join("")) as [ + TsonSerialized, + TsonAsyncValueTuple[], + ]; + + const [head, values] = full; + expect(head).toMatchInlineSnapshot(` + { + "json": { + "promise": [ + "Promise", + 0, + "__tson", + ], + }, + "nonce": "__tson", + } + `); + + expect(values).toMatchInlineSnapshot(` + [ + [ + 0, + 0, + { + "anotherPromise": [ + "Promise", + 1, + "__tson", + ], + }, + ], + [ + 1, + 0, + 42, + ], + ] + `); + + expect(buffer).toMatchInlineSnapshot(` + [ + "[", + " {\\"json\\":{\\"promise\\":[\\"Promise\\",0,\\"__tson\\"]},\\"nonce\\":\\"__tson\\"}", + " ,", + " [", + " [0,0,{\\"anotherPromise\\":[\\"Promise\\",1,\\"__tson\\"]}]", + " ,[1,0,42]", + " ]", + "]", + ] + `); +}); diff --git a/src/serializeAsync.ts b/src/serializeAsync.ts index caaec0d1..96090c0e 100644 --- a/src/serializeAsync.ts +++ b/src/serializeAsync.ts @@ -8,6 +8,8 @@ import { TsonAllTypes, TsonAsyncIndex, TsonAsyncOptions, + TsonAsyncStringifier, + TsonAsyncStringifierIterator, TsonNonce, TsonSerialized, TsonSerializedValue, @@ -22,7 +24,7 @@ type WalkFn = (value: unknown) => unknown; const PROMISE_RESOLVED = 0 as const; const PROMISE_REJECTED = 1 as const; -type TsonAsyncValueTuple = [ +export type TsonAsyncValueTuple = [ TsonAsyncIndex, typeof PROMISE_REJECTED | typeof PROMISE_RESOLVED, unknown, @@ -183,3 +185,44 @@ export function createAsyncTsonSerializer( ]; }; } + +export function createAsyncTsonStringifier( + opts: TsonAsyncOptions, +): TsonAsyncStringifier { + const indent = (length: number) => " ".repeat(length); + const stringifier: (value: unknown, space?: number) => AsyncIterable = + async function* stringify(value, space = 0) { + // head looks like + + // [ + // { json: {}, nonce: "..." } + // ,[ + + const [head, iterator] = createAsyncTsonSerializer(opts)(value); + + // first line of the json: init the array, ignored when parsing> + yield "["; + // second line: the shape of the json - used when parsing> + yield indent(space * 1) + JSON.stringify(head); + + // third line: comma before values, ignored when parsing + yield indent(space * 1) + ","; + // fourth line: the values array, ignored when parsing + yield indent(space * 1) + "["; + + let isFirstStreamedValue = true; + for await (const value of iterator) { + let string = !isFirstStreamedValue ? "," : ""; + isFirstStreamedValue = false; + string += JSON.stringify(value); + yield indent(space * 2) + string; + + continue; + } + + yield indent(space * 1) + "]"; // end value array + yield "]"; // end response + }; + + return stringifier as TsonAsyncStringifier; +} diff --git a/src/types.ts b/src/types.ts index 0ea3219a..4df8c856 100644 --- a/src/types.ts +++ b/src/types.ts @@ -168,3 +168,12 @@ export type TsonStringifyFn = ( ) => TsonStringified; export type TsonParseFn = (string: TsonStringified) => TValue; + +export type TsonAsyncStringifierIterator = AsyncIterable & { + [serialized]: TValue; +}; + +export type TsonAsyncStringifier = ( + value: TValue, + space?: number, +) => TsonAsyncStringifierIterator;