Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
serialized
Browse files Browse the repository at this point in the history
  • Loading branch information
KATT committed Oct 3, 2023
1 parent 592cc1d commit 2e5991c
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 6 deletions.
8 changes: 6 additions & 2 deletions src/createTsonAsync.ts
Original file line number Diff line number Diff line change
@@ -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),
});
152 changes: 149 additions & 3 deletions src/handlers/tsonPromise.test.ts
Original file line number Diff line number Diff line change
@@ -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";

Check failure on line 5 in src/handlers/tsonPromise.test.ts

View workflow job for this annotation

GitHub Actions / type_check

'TsonSerializedValue' is declared but its value is never read.

Check failure on line 5 in src/handlers/tsonPromise.test.ts

View workflow job for this annotation

GitHub Actions / lint

'TsonSerializedValue' is defined but never used

const createPromise = <T>(result: () => T) => {
return new Promise<T>((resolve) => {
Expand All @@ -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(`
{
Expand Down Expand Up @@ -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(`
{
Expand Down Expand Up @@ -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(`
{
Expand Down Expand Up @@ -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]",
" ]",
"]",
]
`);
});
45 changes: 44 additions & 1 deletion src/serializeAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
TsonAllTypes,
TsonAsyncIndex,
TsonAsyncOptions,
TsonAsyncStringifier,
TsonAsyncStringifierIterator,

Check failure on line 12 in src/serializeAsync.ts

View workflow job for this annotation

GitHub Actions / build

'TsonAsyncStringifierIterator' is declared but its value is never read.

Check failure on line 12 in src/serializeAsync.ts

View workflow job for this annotation

GitHub Actions / type_check

'TsonAsyncStringifierIterator' is declared but its value is never read.

Check failure on line 12 in src/serializeAsync.ts

View workflow job for this annotation

GitHub Actions / lint

'TsonAsyncStringifierIterator' is defined but never used
TsonNonce,
TsonSerialized,
TsonSerializedValue,
Expand All @@ -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,
Expand Down Expand Up @@ -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<string> =
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;
}
9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,12 @@ export type TsonStringifyFn = <TValue>(
) => TsonStringified<TValue>;

export type TsonParseFn = <TValue>(string: TsonStringified<TValue>) => TValue;

export type TsonAsyncStringifierIterator<TValue> = AsyncIterable<string> & {
[serialized]: TValue;
};

export type TsonAsyncStringifier = <TValue>(
value: TValue,
space?: number,
) => TsonAsyncStringifierIterator<TValue>;

0 comments on commit 2e5991c

Please sign in to comment.