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

Commit

Permalink
chore: refactor sync api
Browse files Browse the repository at this point in the history
feat!: new guard api
  • Loading branch information
helmturner committed Nov 25, 2023
1 parent 98a8f81 commit f588184
Show file tree
Hide file tree
Showing 16 changed files with 167 additions and 172 deletions.
7 changes: 4 additions & 3 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@
"knip",
"lcov",
"markdownlintignore",
"marshaller",
"npmpackagejsonlintrc",
"openai",
"outro",
"packagejson",
"quickstart",
"Streamified",
"Streamify",
"streamified",
"streamify",
"stringifier",
"superjson",
"Thunkable",
"thunkable",
"tson",
"tsup",
"tupleson",
Expand Down
12 changes: 6 additions & 6 deletions src/async/asyncHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ export async function* mapIterable<T, TValue>(
export async function reduceIterable<
T,
TInitialValue extends Promise<any> = Promise<T>,
TKey extends PropertyKey = number,
TKeyFn extends (prev: TKey) => TKey = (prev: TKey) => TKey,
TKey extends PropertyKey | bigint = bigint,
TKeyFn extends (prev?: TKey) => TKey = (prev?: TKey) => TKey,
>(
iterable: AsyncIterable<T>,
iterable: Iterable<T>,
fn: (acc: Awaited<TInitialValue>, v: T, i: TKey) => Awaited<TInitialValue>,
initialValue: TInitialValue = Promise.resolve() as TInitialValue,
initialKey: TKey = 0 as TKey,
incrementKey: TKeyFn = ((prev) => (prev as number) + 1) as TKeyFn,
incrementKey: TKeyFn = ((prev?: bigint) =>
prev === undefined ? 0n : prev + 1n) as TKeyFn,
): Promise<Awaited<TInitialValue>> {
let acc = initialValue;
let i = initialKey;
let i = incrementKey();

for await (const value of iterable) {
acc = fn(await acc, value, i);
Expand Down
2 changes: 1 addition & 1 deletion src/async/deserializeAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ function createTsonDeserializer(opts: TsonAsyncOptions) {
const walk = walker(head.nonce);

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

return walked;
} finally {
Expand Down
36 changes: 29 additions & 7 deletions src/async/iterableUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export interface AsyncIterableEsque<T = unknown> {

export function isAsyncIterableEsque(
maybeAsyncIterable: unknown,
): maybeAsyncIterable is AsyncIterableEsque & AsyncIterable<unknown> {
): maybeAsyncIterable is AsyncIterableEsque {
return (
!!maybeAsyncIterable &&
(typeof maybeAsyncIterable === "object" ||
Expand All @@ -167,8 +167,8 @@ export function isAsyncIterableEsque(
);
}

export interface IterableEsque {
[Symbol.iterator](): unknown;
export interface IterableEsque<T = unknown> {
[Symbol.iterator](): Iterator<T>;
}

export function isIterableEsque(
Expand All @@ -182,11 +182,11 @@ export function isIterableEsque(
);
}

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

export function isAsyncGeneratorFn(
export function isMaybeAsyncGeneratorFn(
maybeAsyncGeneratorFn: unknown,
): maybeAsyncGeneratorFn is GeneratorFnEsque {
): maybeAsyncGeneratorFn is SyncOrAsyncGeneratorFnEsque {
return (
typeof maybeAsyncGeneratorFn === "function" &&
["AsyncGeneratorFunction", "GeneratorFunction"].includes(
Expand All @@ -195,6 +195,28 @@ export function isAsyncGeneratorFn(
);
}

export type GeneratorFnEsque = () => Generator;

export function isGeneratorFnEsque(
maybeGeneratorFn: unknown,
): maybeGeneratorFn is GeneratorFnEsque {
return (
typeof maybeGeneratorFn === "function" &&
maybeGeneratorFn.constructor.name === "GeneratorFunction"
);
}

export type AsyncGeneratorFnEsque = () => AsyncGenerator;

export function isAsyncGeneratorFnEsque(
maybeAsyncGeneratorFn: unknown,
): maybeAsyncGeneratorFn is AsyncGeneratorFnEsque {
return (
typeof maybeAsyncGeneratorFn === "function" &&
maybeAsyncGeneratorFn.constructor.name === "AsyncGeneratorFunction"
);
}

export type PromiseEsque = PromiseLike<unknown>;

export function isPromiseEsque(
Expand All @@ -218,9 +240,9 @@ export function isThunkEsque(maybeThunk: unknown): maybeThunk is ThunkEsque {

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

export type MaybePromise<T> = Promise<T> | T;
4 changes: 2 additions & 2 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { TsonOptions, TsonType, createTson } from "./index.js";
import { expectError, waitError } from "./internals/testUtils.js";

test("multiple handlers for primitive string found", () => {
const stringHandler: TsonType<string, never> = {
const stringHandler: TsonType<string, string> = {

Check failure on line 8 in src/index.test.ts

View workflow job for this annotation

GitHub Actions / type_check

Type '{ primitive: "string"; }' is not assignable to type 'TsonType<string, string>'.
primitive: "string",
};
const opts: TsonOptions = {
Expand Down Expand Up @@ -98,7 +98,7 @@ test("async: duplicate keys", async () => {
});

test("async: multiple handlers for primitive string found", async () => {
const stringHandler: TsonType<string, never> = {
const stringHandler: TsonType<string, string> = {

Check failure on line 101 in src/index.test.ts

View workflow job for this annotation

GitHub Actions / type_check

Type '{ primitive: "string"; }' is not assignable to type 'TsonType<string, string>'.
primitive: "string",
};

Expand Down
3 changes: 3 additions & 0 deletions src/internals/isComplexValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isComplexValue(arg: unknown): arg is object {
return arg !== null && (typeof arg === "object" || typeof arg === "function");
}
4 changes: 3 additions & 1 deletion src/internals/isPlainObject.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const isPlainObject = (obj: unknown): obj is Record<string, unknown> => {
export const isPlainObject = (
obj: unknown,
): obj is Record<PropertyKey, unknown> => {
if (!obj || typeof obj !== "object") {
return false;
}
Expand Down
9 changes: 1 addition & 8 deletions src/iterableTypes.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import * as v from "vitest";

import {
AsyncGenerator,
AsyncIterable,
AsyncIterableIterator,
Generator,
Iterable,
IterableIterator,
} from "./iterableTypes.js";
import { AsyncGenerator, Generator } from "./iterableTypes.js";

v.describe("Async Iterable Types", () => {
v.it("should be interchangeable with the original type signatures", () => {
Expand Down
10 changes: 0 additions & 10 deletions src/sync/deserialize.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { isTsonTuple } from "../internals/isTsonTuple.js";
import { mapOrReturn } from "../internals/mapOrReturn.js";
import { TsonAssert, TsonGuard } from "../tsonAssert.js";
import {
TsonDeserializeFn,
TsonMarshaller,
Expand All @@ -17,7 +16,6 @@ type AnyTsonMarshaller = TsonMarshaller<any, any>;

export function createTsonDeserialize(opts: TsonOptions): TsonDeserializeFn {
const typeByKey: Record<string, AnyTsonMarshaller> = {};
const assertions: TsonGuard<unknown>[] = [];
for (const handler of opts.types) {
if (handler.key) {
if (typeByKey[handler.key]) {
Expand All @@ -26,20 +24,12 @@ export function createTsonDeserialize(opts: TsonOptions): TsonDeserializeFn {

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

if ("assertion" in handler) {
assertions.push(handler);
continue;
}
}

const walker: WalkerFactory = (nonce) => {
const walk: WalkFn = (value) => {
if (isTsonTuple(value, nonce)) {
const [type, serializedValue] = value;
for (const assert of assertions) {
assert.assertion(value);
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const transformer = typeByKey[type]!;
Expand Down
3 changes: 2 additions & 1 deletion src/sync/handlers/tsonNumberGuard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { expectError } from "../../internals/testUtils.js";

test("number", () => {
const t = createTson({
types: [tsonNumberGuard],
guards: [tsonNumberGuard],
types: [],
});

const bad = [
Expand Down
27 changes: 15 additions & 12 deletions src/sync/handlers/tsonNumberGuard.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { tsonAssert } from "../../tsonAssert.js";
import { TsonGuard } from "../../tsonAssert.js";

/**
* Prevents `NaN` and `Infinity` from being serialized
*/

export const tsonAssertNotInfinite = tsonAssert((v) => {
if (typeof v !== "number") {
return;
}
export const tsonNumberGuard: TsonGuard<Exclude<unknown, number>> = {
assert(v: unknown) {
if (typeof v !== "number") {
return;
}

if (isNaN(v)) {
throw new Error("Encountered NaN");
}
if (isNaN(v)) {
throw new Error("Encountered NaN");
}

if (!isFinite(v)) {
throw new Error("Encountered Infinity");
}
});
if (!isFinite(v)) {
throw new Error("Encountered Infinity");
}
},
key: "tsonAssertNotInfinite",
};
7 changes: 2 additions & 5 deletions src/sync/handlers/tsonUnknownObjectGuard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ import { expectError } from "../../internals/testUtils.js";
test("guard unwanted objects", () => {
// Sets are okay, but not Maps
const t = createTson({
types: [
tsonSet,
// defined last so it runs last
tsonUnknownObjectGuard,
],
guards: [tsonUnknownObjectGuard],
types: [tsonSet],
});

{
Expand Down
15 changes: 9 additions & 6 deletions src/sync/handlers/tsonUnknownObjectGuard.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TsonError } from "../../errors.js";
import { isPlainObject } from "../../internals/isPlainObject.js";
import { TsonGuard, tsonAssert } from "../../tsonAssert.js";
import { TsonGuard } from "../../tsonAssert.js";

export class TsonUnknownObjectGuardError extends TsonError {
/**
Expand All @@ -24,8 +24,11 @@ export class TsonUnknownObjectGuardError extends TsonError {
* Make sure to define this last in the list of types.
* @throws {TsonUnknownObjectGuardError} if an unknown object is found
*/
export const tsonUnknownObjectGuard = tsonAssert((v) => {
if (v && typeof v === "object" && !Array.isArray(v) && !isPlainObject(v)) {
throw new TsonUnknownObjectGuardError(v);
}
});
export const tsonUnknownObjectGuard: TsonGuard<NonNullable<unknown>> = {
assert(v: unknown) {
if (v && typeof v === "object" && !Array.isArray(v) && !isPlainObject(v)) {
throw new TsonUnknownObjectGuardError(v);
}
},
key: "tsonUnknownObjectGuard",
};
Loading

0 comments on commit f588184

Please sign in to comment.