This repository has been archived by the owner on Jul 5, 2024. It is now read-only.
generated from JoshuaKGoldberg/create-typescript-app
-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: serializing async iterable (#26)
- Loading branch information
Showing
12 changed files
with
933 additions
and
235 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import { expect, test, vi } from "vitest"; | ||
|
||
import { | ||
createTsonAsync, | ||
tsonAsyncIterator, | ||
tsonBigint, | ||
tsonPromise, | ||
} from "../index.js"; | ||
import { assert } from "../internals/assert.js"; | ||
import { | ||
mapIterable, | ||
readableStreamToAsyncIterable, | ||
} from "../internals/iterableUtils.js"; | ||
import { createTestServer } from "../internals/testUtils.js"; | ||
import { TsonAsyncOptions } from "./asyncTypes.js"; | ||
|
||
test("deserialize async iterable", async () => { | ||
const tson = createTsonAsync({ | ||
nonce: () => "__tson", | ||
types: [tsonAsyncIterator, tsonPromise, tsonBigint], | ||
}); | ||
|
||
{ | ||
// plain obj | ||
const obj = { | ||
foo: "bar", | ||
}; | ||
|
||
const strIterable = tson.stringify(obj); | ||
|
||
const result = await tson.parse(strIterable); | ||
|
||
expect(result).toEqual(obj); | ||
} | ||
|
||
{ | ||
// promise | ||
const obj = { | ||
foo: Promise.resolve("bar"), | ||
}; | ||
|
||
const strIterable = tson.stringify(obj); | ||
|
||
const result = await tson.parse(strIterable); | ||
|
||
expect(await result.foo).toEqual("bar"); | ||
} | ||
}); | ||
|
||
test("stringify async iterable + promise", async () => { | ||
const onErr = vi.fn(); | ||
const tson = createTsonAsync({ | ||
nonce: () => "__tson", | ||
onStreamError: onErr, | ||
types: [tsonAsyncIterator, tsonPromise, tsonBigint], | ||
}); | ||
|
||
async function* iterable() { | ||
await new Promise((resolve) => setTimeout(resolve, 1)); | ||
yield 1n; | ||
await new Promise((resolve) => setTimeout(resolve, 1)); | ||
yield 2n; | ||
yield 3n; | ||
|
||
await new Promise((resolve) => setTimeout(resolve, 2)); | ||
yield 4n; | ||
yield 5n; | ||
} | ||
|
||
const input = { | ||
foo: "bar", | ||
iterable: iterable(), | ||
promise: Promise.resolve(42), | ||
}; | ||
|
||
const strIterable = tson.stringify(input); | ||
|
||
const output = await tson.parse(strIterable); | ||
|
||
expect(output.foo).toEqual("bar"); | ||
|
||
expect(await output.promise).toEqual(42); | ||
|
||
const result = []; | ||
|
||
for await (const value of output.iterable) { | ||
result.push(value); | ||
} | ||
|
||
expect(result).toEqual([1n, 2n, 3n, 4n, 5n]); | ||
}); | ||
|
||
test("e2e: stringify and parse promise with a promise over a network connection", async () => { | ||
function createMockObj() { | ||
async function* generator() { | ||
for (const number of [1, 2, 3, 4, 5]) { | ||
await new Promise((resolve) => setTimeout(resolve, 1)); | ||
yield BigInt(number); | ||
} | ||
} | ||
|
||
return { | ||
foo: "bar", | ||
iterable: generator(), | ||
// promise: Promise.resolve(42), | ||
}; | ||
} | ||
|
||
type MockObj = ReturnType<typeof createMockObj>; | ||
|
||
const opts: TsonAsyncOptions = { | ||
nonce: () => "__tson", | ||
types: [tsonPromise, tsonAsyncIterator, tsonBigint], | ||
}; | ||
|
||
const server = await createTestServer({ | ||
handleRequest: async (_req, res) => { | ||
const tson = createTsonAsync(opts); | ||
|
||
const obj = createMockObj(); | ||
const strIterarable = tson.stringify(obj, 4); | ||
|
||
// set proper header for chunked responses | ||
// res.setHeader("Transfer-Encoding", "chunked"); | ||
|
||
for await (const value of strIterarable) { | ||
res.write(value); | ||
} | ||
|
||
res.end(); | ||
}, | ||
}); | ||
|
||
// ------------- client ------------------- | ||
const tson = createTsonAsync(opts); | ||
|
||
// do a streamed fetch request | ||
const response = await fetch(server.url); | ||
|
||
assert(response.body); | ||
|
||
const textDecoder = new TextDecoder(); | ||
|
||
const spy = vi.fn(); | ||
const stringIterator = mapIterable( | ||
mapIterable(readableStreamToAsyncIterable(response.body), (v) => | ||
textDecoder.decode(v), | ||
), | ||
(val) => { | ||
spy(val.trimEnd()); | ||
return val; | ||
}, | ||
); | ||
|
||
const parsedRaw = await tson.parse(stringIterator); | ||
const parsed = parsedRaw as MockObj; | ||
|
||
expect(parsed.foo).toEqual("bar"); | ||
// expect(await parsed.promise).toEqual(42); | ||
|
||
const results = []; | ||
|
||
for await (const value of parsed.iterable) { | ||
results.push(value); | ||
} | ||
|
||
expect(results).toEqual([1n, 2n, 3n, 4n, 5n]); | ||
|
||
server.close(); | ||
}); |
Oops, something went wrong.