Skip to content

Commit

Permalink
feat: better extension mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
ajaishankar committed Oct 8, 2024
1 parent eb0efcd commit 2be5ac0
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 15 deletions.
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -485,8 +485,6 @@ const StringExtensions1 = Extensions.for(types.StringType, {
data.length >= length ||
ctx.issue(`Value must be at least ${length} characters`),
max: ...,
contains: ...,
matches: ...,
});

// or async - say check username availability
Expand Down Expand Up @@ -578,13 +576,22 @@ schema.refine((ctx, data) => {
})
```

### Extensions and optional/nullable

Optional and nullable need to be specified last with extensions.

```ts
z.string().min(2).optional() // correct: string | undefined
z.string().optional().min(2) // incorrect: string
```

## Inspiration (prior art)

[zod](https://valibot.dev/) - obviously, and [valibot](https://valibot.dev/).

pukka favors simplicity over type purity.
pukka favors simplicity over feature bloat.

That is, no lazy recursive types, intersection types, pick, merge, input vs output schemas, pipes, preprocess, transform etc.
That is, no lazy recursive types, intersection types, function types, pick, merge, input vs output schemas, pipes, preprocess, transform etc.

If all you want to do is validate forms and api payloads in a natural, typesafe way, do give pukka a try.

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pukka",
"version": "1.4.0",
"version": "1.5.0",
"description": "Typescript schema-first zod compatible hyper validation",
"repository": {
"type": "git",
Expand Down
15 changes: 13 additions & 2 deletions src/__tests__/extend.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it, test } from "vitest";
import { Type } from "../base";
import { describe, expect, expectTypeOf, it, test } from "vitest";
import { type Infer, Type } from "../base";
import { Extensions } from "../extend";
import { type Path, registerIssues } from "../issue";
import { pukka } from "../pukka";
Expand Down Expand Up @@ -68,6 +68,17 @@ describe("extend", () => {
expect(a.safeParse(undefined).success).toBe(true);
});

test("optional/nullable has to be specified last", () => {
const a = z.string().min(2).description("a").optional(); // works
const b = z.string().optional().min(2).description("b"); // nukes optional type :(

expectTypeOf("").toMatchTypeOf<Infer<typeof a>>();
expectTypeOf(undefined).toMatchTypeOf<Infer<typeof a>>();

expectTypeOf("").toMatchTypeOf<Infer<typeof b>>();
expectTypeOf(undefined).not.toMatchTypeOf<Infer<typeof b>>();
});

it("should capture params", async () => {
const name = z.string().min(2);
expect(params(name, StringExtensions, "min")).toEqual([2]);
Expand Down
18 changes: 12 additions & 6 deletions src/extend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
Validator,
} from "./base";
import { internalType } from "./internal";
import type { Simplify } from "./util";

type TypeConstructor<T extends BaseType = BaseType> = new (...args: any[]) => T;

Expand All @@ -28,11 +29,17 @@ type Extensions<T> = {
};
};

export type Extended<T extends BaseType, X extends Extensions<Infer<T>>> = T & {
/**
* Chainable extension methods with optional message override param
*/
type ChainableExtensions<
T extends BaseType,
X extends Extensions<Infer<T>>,
> = Simplify<{
[K in Exclude<keyof X, keyof T>]: (
...args: [...Parameters<X[K]>, override?: MessageOverride<Infer<T>>]
) => Extended<T, X>;
};
) => T & ChainableExtensions<T, X>;
}>;

/**
* Helper to register a type with a constructor taking a single object parameter
Expand Down Expand Up @@ -108,15 +115,14 @@ function applyExtensions<
}

type I = InstanceType<C>;
type Ext = Extended<I, X>;

return <F extends (...args: any[]) => I>(fn: F) =>
fn as (...args: Parameters<F>) => I & Ext;
fn as (...args: Parameters<F>) => I & ChainableExtensions<I, X>;
}

function getExtensionParams<
T extends BaseType,
X extends Extensions<Infer<T>>,
X extends Extensions<NonNullable<Infer<T>>>,
M extends keyof X,
>(type: T, ext: X, name: M) {
type P = Parameters<X[M]>;
Expand Down

0 comments on commit 2be5ac0

Please sign in to comment.