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

Commit

Permalink
types: clean up interfaces in iterableTypes.ts
Browse files Browse the repository at this point in the history
feat: guards are distinct from type handlers
chore: rename some things
types: general type improvements
chore: add more iterable utils
  • Loading branch information
helmturner committed Nov 25, 2023
1 parent 6b9752a commit 98a8f81
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 144 deletions.
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"Streamify",
"stringifier",
"superjson",
"Thunkable",
"tson",
"tsup",
"tupleson",
Expand Down
6 changes: 1 addition & 5 deletions src/async/asyncTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,5 @@ export interface TsonAsyncOptions {
/**
* The list of types to use
*/
types: (
| TsonAsyncType<any, any>
| TsonType<any, any>
| TsonType<any, never>
)[];
types: (TsonAsyncType<any, any> | TsonType<any, any>)[];
}
13 changes: 5 additions & 8 deletions src/async/deserializeAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { assert } from "../internals/assert.js";
import { isTsonTuple } from "../internals/isTsonTuple.js";
import { mapOrReturn } from "../internals/mapOrReturn.js";
import {
TsonMarshaller,
TsonNonce,
TsonSerialized,
TsonTransformerSerializeDeserialize,
} from "../sync/syncTypes.js";
import { TsonAbortError, TsonStreamInterruptedError } from "./asyncErrors.js";
import {
Expand All @@ -27,9 +27,7 @@ import { TsonAsyncValueTuple } from "./serializeAsync.js";
type WalkFn = (value: unknown) => unknown;
type WalkerFactory = (nonce: TsonNonce) => WalkFn;

type AnyTsonTransformerSerializeDeserialize =
| TsonAsyncType<any, any>
| TsonTransformerSerializeDeserialize<any, any>;
type AnyTsonMarshaller = TsonAsyncType<any, any> | TsonMarshaller<any, any>;

export interface TsonParseAsyncOptions {
/**
Expand All @@ -56,16 +54,15 @@ type TsonParseAsync = <TValue>(
type TsonDeserializeIterableValue = TsonAsyncValueTuple | TsonSerialized;
type TsonDeserializeIterable = AsyncIterable<TsonDeserializeIterableValue>;
function createTsonDeserializer(opts: TsonAsyncOptions) {
const typeByKey: Record<string, AnyTsonTransformerSerializeDeserialize> = {};
const typeByKey: Record<string, AnyTsonMarshaller> = {};

for (const handler of opts.types) {
if (handler.key) {
if (typeByKey[handler.key]) {
throw new Error(`Multiple handlers for key ${handler.key} found`);
}

typeByKey[handler.key] =
handler as AnyTsonTransformerSerializeDeserialize;
typeByKey[handler.key] = handler as AnyTsonMarshaller;
}
}

Expand Down Expand Up @@ -183,7 +180,7 @@ function createTsonDeserializer(opts: TsonAsyncOptions) {
const walk = walker(head.nonce);

try {
const walked = walk(head.json);
const walked = walk(head.tson);

return walked;
} finally {
Expand Down
74 changes: 74 additions & 0 deletions src/async/iterableUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
NodeJSReadableStreamEsque,
WebReadableStreamEsque,
} from "../internals/esque.js";
import { AsyncGenerator, Generator } from "../iterableTypes.js";

export async function* readableStreamToAsyncIterable<T>(
stream:
Expand Down Expand Up @@ -150,3 +151,76 @@ function addIfProvided<TKey extends "data" | "event" | "id" | "retry">(

return `${key}: ${value as any}\n`;
}

export interface AsyncIterableEsque<T = unknown> {
[Symbol.asyncIterator](): AsyncIterator<T>;
}

export function isAsyncIterableEsque(
maybeAsyncIterable: unknown,
): maybeAsyncIterable is AsyncIterableEsque & AsyncIterable<unknown> {
return (
!!maybeAsyncIterable &&
(typeof maybeAsyncIterable === "object" ||
typeof maybeAsyncIterable === "function") &&
Symbol.asyncIterator in maybeAsyncIterable
);
}

export interface IterableEsque {
[Symbol.iterator](): unknown;
}

export function isIterableEsque(
maybeIterable: unknown,
): maybeIterable is IterableEsque {
return (
!!maybeIterable &&
(typeof maybeIterable === "object" ||
typeof maybeIterable === "function") &&
Symbol.iterator in maybeIterable
);
}

type GeneratorFnEsque = (() => AsyncGenerator) | (() => Generator);

export function isAsyncGeneratorFn(
maybeAsyncGeneratorFn: unknown,
): maybeAsyncGeneratorFn is GeneratorFnEsque {
return (
typeof maybeAsyncGeneratorFn === "function" &&
["AsyncGeneratorFunction", "GeneratorFunction"].includes(
maybeAsyncGeneratorFn.constructor.name,
)
);
}

export type PromiseEsque = PromiseLike<unknown>;

export function isPromiseEsque(
maybePromise: unknown,
): maybePromise is PromiseEsque {
return (
!!maybePromise &&
typeof maybePromise === "object" &&
"then" in maybePromise &&
typeof maybePromise.then === "function"
);
}

export type ThunkEsque = () => unknown;

export function isThunkEsque(maybeThunk: unknown): maybeThunk is ThunkEsque {
return (
!!maybeThunk && typeof maybeThunk === "function" && maybeThunk.length === 0
);
}

export type Thunkable =
| AsyncIterableEsque
| GeneratorFnEsque
| IterableEsque
| PromiseEsque
| ThunkEsque;

export type MaybePromise<T> = Promise<T> | T;
41 changes: 21 additions & 20 deletions src/async/serializeAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function walkerFactory(nonce: TsonNonce, types: TsonAsyncOptions["types"]) {

const iterators = new Map<TsonAsyncIndex, AsyncIterator<unknown>>();

const iterator = {
const iterable: AsyncIterable<TsonAsyncValueTuple> = {
async *[Symbol.asyncIterator]() {
// race all active iterators and yield next value as they come
// when one iterator is done, remove it from the list
Expand Down Expand Up @@ -94,24 +94,25 @@ function walkerFactory(nonce: TsonNonce, types: TsonAsyncOptions["types"]) {
walk: WalkFn,
) => TsonSerializedValue;

const $serialize: Serializer = handler.serializeIterator
? (value): TsonTuple => {
const idx = asyncIndex++ as TsonAsyncIndex;

const iterator = handler.serializeIterator({
value,
});
iterators.set(idx, iterator[Symbol.asyncIterator]());

return [handler.key as TsonTypeHandlerKey, idx, nonce];
}
: handler.serialize
? (value, nonce, walk): TsonTuple => [
handler.key as TsonTypeHandlerKey,
walk(handler.serialize(value)),
nonce,
]
: (value, _nonce, walk) => walk(value);
const $serialize: Serializer =
"serializeIterator" in handler
? (value): TsonTuple => {
const idx = asyncIndex++ as TsonAsyncIndex;

const iterator = handler.serializeIterator({
value,
});
iterators.set(idx, iterator[Symbol.asyncIterator]());

return [handler.key as TsonTypeHandlerKey, idx, nonce];
}
: "serialize" in handler
? (value, nonce, walk): TsonTuple => [
handler.key as TsonTypeHandlerKey,
walk(handler.serialize(value)),
nonce,
]
: (value, _nonce, walk) => walk(value);
return {
...handler,
$serialize,
Expand Down Expand Up @@ -185,7 +186,7 @@ function walkerFactory(nonce: TsonNonce, types: TsonAsyncOptions["types"]) {
return cacheAndReturn(mapOrReturn(value, walk));
};

return [walk, iterator] as const;
return [walk, iterable] as const;
}

type TsonAsyncSerializer = <T>(
Expand Down
121 changes: 23 additions & 98 deletions src/iterableTypes.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,32 @@
/**
* @file
* @see https://github.com/microsoft/TypeScript/issues/32682
* @variation {Async} AsyncIterator, AsyncIterable, and AsyncIterableIterator can
* be substituted for Iterator, Iterable, and IterableIterator respectively in
* the below description.
* @description
* Native constructs which use Iterables discard the value of the return type
* and call `next()` with no arguments. While it is possible to instantiate
* an Iterable's Iterator manually, that iterator must be prepared to take
* `undefined` as an argument to `next()`, and expect that the return value
* may be discarded. Otherwise, it would break the Iterable contract.
*
* In other words, Iterators leave it to the consumer to decide what to do
* on each iteration (via the 'next' and 'return' methods), while Iterables
* enforce a contract that the consumer must follow, where these methods are
* optional.
*
* To preserve correctness, an Iterable's `Next` and `Return` types
* MUST be joined with undefined when passed as type parameters to the
* Iterator returned by its [Symbol.iterator] method.
*
* For IterableIterators, a TypeScript construct which extends the Iterator
* interface, the additional type parameters SHOULD NOT be joined with
* undefined when passed to the Iterator which the interface extends. By testing
* for the presence of a parameter in the `next()` method, an iterator can
* determine whether is being called manually or by a native construct. It is
* perfectly valid for an IterableIterator to require a parameter in it's own
* `next()` method, but not in the `next()` method of the iterator returned
* by its [Symbol.iterator].
*
* As of Feb 4, 2022 (v4.6.1), the TS team had shelved adding the 2nd and 3rd
* type parameters to these interfaces, but had not ruled it out for the future.
* This file originally contained types for the `iterable` and `asyncIterable`
* (as well as `iterableIterator` and `asyncIterableIterator`) types, but
* ultimately they were decided against, as they were not ultimately useful.
* The extensions essentially provided useless information, given that the
* `next` and `return` methods cannot be relied upon to be present. For that,
* Generators are a better choice, as they expose the `next` and `return`
* methods through the GeneratorFunction syntax.
* @see https://github.com/microsoft/TypeScript/issues/32682 for information
* about the types that were removed.
*/

/* eslint-disable @typescript-eslint/no-empty-interface */

/**
* A stronger type for Iterable
* A stronger type for Iterator
*/
export interface Iterable<
T = unknown,
TOptionalReturn = unknown,
TOptionalNext = unknown,
> {
[Symbol.iterator](): Iterator<
T,
TOptionalReturn | undefined,
TOptionalNext | undefined
>;
}
export interface Iterator<T = unknown, TReturn = unknown, TNextArg = unknown>
extends globalThis.Iterator<T, TReturn, TNextArg> {}

/**
* A stronger type for IterableIterator.
* A stronger type for AsyncIterator
*/
export interface IterableIterator<
export interface AsyncIterator<
T = unknown,
TOptionalReturn = unknown,
TOptionalNext = unknown,
> extends Iterator<T, TOptionalReturn, TOptionalNext> {
[Symbol.iterator](): IterableIterator<
T,
TOptionalReturn | undefined,
TOptionalNext | undefined
>;
}
TReturn = unknown,
TNextArg = unknown,
> extends globalThis.AsyncIterator<T, TReturn, TNextArg> {}

/**
* A stronger type for Generator
Expand All @@ -70,54 +35,14 @@ export interface Generator<
T = unknown,
TOptionalReturn = unknown,
TOptionalNext = unknown,
> {
[Symbol.iterator](): Iterator<
T,
TOptionalReturn | undefined,
TOptionalNext | undefined
>;
}

/**
* A stronger type for AsyncIterable
*/
export interface AsyncIterable<
T = unknown,
TOptionalReturn = unknown,
TOptionalNext = unknown,
> {
[Symbol.asyncIterator](): AsyncIterator<
T,
TOptionalReturn | undefined,
TOptionalNext | undefined
>;
}

/**
* A stronger type for AsyncIterableIterator.
*/
export interface AsyncIterableIterator<
T = unknown,
TOptionalReturn = unknown,
TOptionalNext = unknown,
> extends AsyncIterator<T, TOptionalReturn, TOptionalNext> {
[Symbol.asyncIterator](): AsyncIterableIterator<
T,
TOptionalReturn | undefined,
TOptionalNext | undefined
>;
}
> extends globalThis.Generator<T, TOptionalReturn, TOptionalNext> {}
/**
* A stronger type for AsyncGenerator
*/
export interface AsyncGenerator<
T = unknown,
TOptionalReturn = unknown,
TOptionalNext = unknown,
> {
[Symbol.asyncIterator](): AsyncIterator<
T,
TOptionalReturn | undefined,
TOptionalNext | undefined
>;
}
> extends globalThis.AsyncGenerator<T, TOptionalReturn, TOptionalNext> {}

/* eslint-enable @typescript-eslint/no-empty-interface */
8 changes: 4 additions & 4 deletions src/sync/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ export function createTsonSerialize(opts: TsonOptions): TsonSerializeFn {
) {
return cacheAndReturn([
primitiveHandler.key,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
walk(primitiveHandler.serialize!(value)),

walk(primitiveHandler.serialize(value)),
nonce,
] as TsonTuple);
}
Expand All @@ -101,8 +101,8 @@ export function createTsonSerialize(opts: TsonOptions): TsonSerializeFn {
if (handler.test(value)) {
return cacheAndReturn([
handler.key,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
walk(handler.serialize!(value)),

walk(handler.serialize(value)),
nonce,
] as TsonTuple);
}
Expand Down
Loading

0 comments on commit 98a8f81

Please sign in to comment.