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

Commit

Permalink
feat: split up deserialization and start on support for SSE (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
KATT authored Oct 13, 2023
1 parent d84d2e9 commit 3bd9782
Show file tree
Hide file tree
Showing 18 changed files with 383 additions and 156 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Serialize almost[^1] anything!
- 🌊 Serialize & stream things like `Promise`s or async iterators

> [!IMPORTANT]
> _Though well-tested, this package might undergo big changes, stay tuned!_
> _Though well-tested, this package might undergo big changes and **does not** follow semver whilst on `0.x.y`-version, stay tuned!_
### 👀 Example

Expand Down
2 changes: 1 addition & 1 deletion examples/async/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@examples/minimal",
"name": "@examples/json-stream",
"version": "10.38.5",
"private": true,
"description": "An example project for tupleson",
Expand Down
4 changes: 2 additions & 2 deletions examples/async/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import http from "node:http";
import { createTsonStringifyAsync } from "tupleson";
import { createTsonStreamAsync } from "tupleson";

import { tsonOptions } from "./shared.js";

const tsonStringifyAsync = createTsonStringifyAsync(tsonOptions);
const tsonStringifyAsync = createTsonStreamAsync(tsonOptions);

const randomNumber = (min: number, max: number) =>
Math.floor(Math.random() * (max - min + 1) + min);
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@release-it/conventional-changelog": "^7.0.2",
"@tsconfig/strictest": "^2.0.2",
"@types/eslint": "^8.44.3",
"@types/event-source-polyfill": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^6.7.3",
"@typescript-eslint/parser": "^6.7.3",
"@vitest/coverage-v8": "^0.34.6",
Expand All @@ -61,6 +62,7 @@
"eslint-plugin-regexp": "^1.15.0",
"eslint-plugin-vitest": "^0.3.1",
"eslint-plugin-yml": "^1.9.0",
"event-source-polyfill": "^1.0.31",
"jsonc-eslint-parser": "^2.3.0",
"knip": "^2.31.0",
"markdownlint": "^0.31.1",
Expand Down
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/async/asyncTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export type TsonAsyncStringifierIterable<TValue> = AsyncIterable<string> & {
[serialized]: TValue;
};

export type BrandSerialized<TType, TValue> = TType & {
[serialized]: TValue;
};

export type TsonAsyncStringifier = <TValue>(
value: TValue,
space?: number,
Expand Down
14 changes: 11 additions & 3 deletions src/async/createTsonAsync.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { TsonAsyncOptions } from "./asyncTypes.js";
import { createTsonParseAsync } from "./deserializeAsync.js";
import { createTsonStringifyAsync } from "./serializeAsync.js";
import {
createTsonSSEResponse,
createTsonStreamAsync,
} from "./serializeAsync.js";

/**
* Only used for testing - when using the async you gotta pick which one you want
* @internal
*/
export const createTsonAsync = (opts: TsonAsyncOptions) => ({
parse: createTsonParseAsync(opts),
stringify: createTsonStringifyAsync(opts),
parseJsonStream: createTsonParseAsync(opts),
stringifyJsonStream: createTsonStreamAsync(opts),
toSSEResponse: createTsonSSEResponse(opts),
});
56 changes: 31 additions & 25 deletions src/async/deserializeAsync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
TsonAsyncOptions,
TsonParseAsyncOptions,
TsonType,
createTsonAsync,
createTsonParseAsync,
tsonAsyncIterable,
tsonBigint,
Expand All @@ -19,6 +18,7 @@ import {
waitFor,
} from "../internals/testUtils.js";
import { TsonSerialized } from "../sync/syncTypes.js";
import { createTsonAsync } from "./createTsonAsync.js";
import { mapIterable, readableStreamToAsyncIterable } from "./iterableUtils.js";

test("deserialize variable chunk length", async () => {
Expand All @@ -32,7 +32,7 @@ test("deserialize variable chunk length", async () => {
yield '[\n{"json":{"foo":"bar"},"nonce":"__tson"}';
yield "\n,\n[\n]\n]";
})();
const result = await tson.parse(iterable);
const result = await tson.parseJsonStream(iterable);
expect(result).toEqual({ foo: "bar" });
}

Expand All @@ -41,7 +41,7 @@ test("deserialize variable chunk length", async () => {
await sleep(1);
yield '[\n{"json":{"foo":"bar"},"nonce":"__tson"}\n,\n[\n]\n]';
})();
const result = await tson.parse(iterable);
const result = await tson.parseJsonStream(iterable);
expect(result).toEqual({ foo: "bar" });
}

Expand All @@ -54,7 +54,7 @@ test("deserialize variable chunk length", async () => {
yield "[\n]\n";
yield "]";
})();
const result = await tson.parse(iterable);
const result = await tson.parseJsonStream(iterable);
expect(result).toEqual({ foo: "bar" });
}
});
Expand All @@ -71,9 +71,9 @@ test("deserialize async iterable", async () => {
foo: "bar",
};

const strIterable = tson.stringify(obj);
const strIterable = tson.stringifyJsonStream(obj);

const result = await tson.parse(strIterable);
const result = await tson.parseJsonStream(strIterable);

expect(result).toEqual(obj);
}
Expand All @@ -84,9 +84,9 @@ test("deserialize async iterable", async () => {
foo: Promise.resolve("bar"),
};

const strIterable = tson.stringify(obj);
const strIterable = tson.stringifyJsonStream(obj);

const result = await tson.parse(strIterable);
const result = await tson.parseJsonStream(strIterable);

expect(await result.foo).toEqual("bar");
}
Expand Down Expand Up @@ -120,9 +120,9 @@ test("stringify async iterable + promise", async () => {
promise: Promise.resolve(42),
};

const strIterable = tson.stringify(input);
const strIterable = tson.stringifyJsonStream(input);

const output = await tson.parse(strIterable, parseOptions);
const output = await tson.parseJsonStream(strIterable, parseOptions);

expect(output.foo).toEqual("bar");

Expand Down Expand Up @@ -166,7 +166,7 @@ test("e2e: stringify async iterable and promise over the network", async () => {
const tson = createTsonAsync(opts);

const obj = createMockObj();
const strIterarable = tson.stringify(obj, 4);
const strIterarable = tson.stringifyJsonStream(obj, 4);

for await (const value of strIterarable) {
res.write(value);
Expand All @@ -192,7 +192,7 @@ test("e2e: stringify async iterable and promise over the network", async () => {
(v) => textDecoder.decode(v),
);

const parsed = await tson.parse<MockObj>(stringIterator);
const parsed = await tson.parseJsonStream<MockObj>(stringIterator);

expect(parsed.foo).toEqual("bar");

Expand Down Expand Up @@ -267,7 +267,7 @@ test("iterator error", async () => {
const tson = createTsonAsync(opts);

const obj = createMockObj();
const strIterarable = tson.stringify(obj, 4);
const strIterarable = tson.stringifyJsonStream(obj, 4);

for await (const value of strIterarable) {
res.write(value);
Expand All @@ -291,7 +291,7 @@ test("iterator error", async () => {
(v) => textDecoder.decode(v),
);

const parsed = await tson.parse<MockObj>(stringIterator);
const parsed = await tson.parseJsonStream<MockObj>(stringIterator);
expect(await parsed.promise).toEqual(42);

const results = [];
Expand Down Expand Up @@ -381,7 +381,7 @@ test("values missing when stream ends", async () => {
assert(err);

expect(err.message).toMatchInlineSnapshot(
'"Stream interrupted: Stream ended unexpectedly"',
'"Stream interrupted: Stream ended unexpectedly (state 1)"',
);
}

Expand All @@ -390,15 +390,15 @@ test("values missing when stream ends", async () => {
const err = await waitError(result.promise);

expect(err).toMatchInlineSnapshot(
"[TsonStreamInterruptedError: Stream interrupted: Stream ended unexpectedly]",
"[TsonStreamInterruptedError: Stream interrupted: Stream ended unexpectedly (state 1)]",
);
}

expect(parseOptions.onStreamError).toHaveBeenCalledTimes(1);
expect(parseOptions.onStreamError.mock.calls).toMatchInlineSnapshot(`
[
[
[TsonStreamInterruptedError: Stream interrupted: Stream ended unexpectedly],
[TsonStreamInterruptedError: Stream interrupted: Stream ended unexpectedly (state 1)],
],
]
`);
Expand Down Expand Up @@ -432,14 +432,14 @@ test("async: missing values of promise", async () => {

await createTsonAsync({
types: [tsonPromise],
}).parse(generator(), parseOptions);
}).parseJsonStream(generator(), parseOptions);

await waitFor(() => {
expect(parseOptions.onStreamError).toHaveBeenCalledTimes(1);
});

expect(parseOptions.onStreamError.mock.calls[0]![0]!).toMatchInlineSnapshot(
"[TsonStreamInterruptedError: Stream interrupted: Stream ended unexpectedly]",
"[TsonStreamInterruptedError: Stream interrupted: Stream ended unexpectedly (state 1)]",
);
});

Expand Down Expand Up @@ -523,7 +523,7 @@ test("1 iterator completed but another never finishes", async () => {
`);

expect(err.message).toMatchInlineSnapshot(
'"Stream interrupted: Stream ended unexpectedly"',
'"Stream interrupted: Stream ended unexpectedly (state 1)"',
);
}

Expand All @@ -532,7 +532,7 @@ test("1 iterator completed but another never finishes", async () => {
expect(parseOptions.onStreamError.mock.calls).toMatchInlineSnapshot(`
[
[
[TsonStreamInterruptedError: Stream interrupted: Stream ended unexpectedly],
[TsonStreamInterruptedError: Stream interrupted: Stream ended unexpectedly (state 1)],
],
]
`);
Expand Down Expand Up @@ -578,7 +578,7 @@ test("e2e: simulated server crash", async () => {
const tson = createTsonAsync(opts);

const obj = createMockObj();
const strIterarable = tson.stringify(obj, 4);
const strIterarable = tson.stringifyJsonStream(obj, 4);

void crashedDeferred.promise.then(() => {
// destroy the response stream
Expand Down Expand Up @@ -607,7 +607,10 @@ test("e2e: simulated server crash", async () => {
(v) => textDecoder.decode(v),
);

const parsed = await tson.parse<MockObj>(stringIterator, parseOptions);
const parsed = await tson.parseJsonStream<MockObj>(
stringIterator,
parseOptions,
);
{
// check the iterator
const results = [];
Expand Down Expand Up @@ -678,7 +681,7 @@ test("e2e: client aborted request", async () => {
const tson = createTsonAsync(opts);

const obj = createMockObj();
const strIterarable = tson.stringify(obj, 4);
const strIterarable = tson.stringifyJsonStream(obj, 4);

for await (const value of strIterarable) {
serverSentChunks.push(value.trimEnd());
Expand Down Expand Up @@ -707,7 +710,10 @@ test("e2e: client aborted request", async () => {
(v) => textDecoder.decode(v),
);

const parsed = await tson.parse<MockObj>(stringIterator, parseOptions);
const parsed = await tson.parseJsonStream<MockObj>(
stringIterator,
parseOptions,
);
{
// check the iterator
const results = [];
Expand Down
Loading

0 comments on commit 3bd9782

Please sign in to comment.