Skip to content

Commit

Permalink
feat: implement include and select magic schema generation
Browse files Browse the repository at this point in the history
  • Loading branch information
DJankauskas committed Jul 14, 2023
1 parent 3950788 commit 1eace8f
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 79 deletions.
3 changes: 0 additions & 3 deletions packages/cli/test_project/src/index.ts
@@ -1,8 +1,5 @@
import { z, Stl } from "stainless";
import { ExternalInterface, TestEnumAdditional } from "./additional";
import { StringSchema } from "ts-to-zod";
import { ExternalInterface as __symbol_ExternalInterface, Test as __class_Test, InThisFile as __symbol_InThisFile, EnumTest as __enum_EnumTest } from "stl-api/gen/src/index";
import { TestEnumAdditional as __enum_TestEnumAdditional } from "stl-api/gen/src/additional";
import { ExternalInterface as __symbol_ExternalInterface, InThisFile as __symbol_InThisFile } from "@stl-api/gen/src/index";
import { Test as __class_Test, EnumTest as __enum_EnumTest } from "@stl-api/gen/src/index";
import { TestEnumAdditional as __enum_TestEnumAdditional } from "@stl-api/gen/src/additional";
Expand Down
31 changes: 31 additions & 0 deletions packages/stainless/src/magicTypes.ts
@@ -0,0 +1,31 @@
import { SchemaType, TypeSchema, input, output } from "ts-to-zod";
import {
IncludableInput,
IncludableOutput,
SelectableInput,
SelectableOutput,
} from "./z";
import { IncludablePaths } from "./includes";
import { SelectTree } from "./parseSelect";

export class Includable<T extends TypeSchema<any>> extends SchemaType<
IncludableInput<input<T>>,
IncludableOutput<output<T>>
> {}

type IncludesIO<T, Depth extends 0 | 1 | 2 | 3 | 4 | 5> = IncludablePaths<output<T>, Depth>[];

export class Includes<
T extends TypeSchema<any>,
Depth extends 0 | 1 | 2 | 3 | 4 | 5 = 3
> extends SchemaType<IncludesIO<T, Depth>, IncludesIO<T, Depth>> {}

export class Selectable<T extends TypeSchema<any>> extends SchemaType<
SelectableInput<input<T>>,
SelectableOutput<output<T>>
> {}

export class Selects<
T extends TypeSchema<object>,
Depth extends 0 | 1 | 2 | 3 | 4 | 5 = 3
> extends SchemaType<string, SelectTree | null | undefined> {}
31 changes: 16 additions & 15 deletions packages/stainless/src/stl.ts
Expand Up @@ -7,6 +7,7 @@ export { SelectTree, parseSelect } from "./parseSelect";
export { z };
export { createClient } from "./client";
export { createRecursiveProxy } from "./createRecursiveProxy";
export { Includable, Includes, Selectable, Selects } from "./magicTypes";
export {
type StainlessClient,
ClientPromise,
Expand Down Expand Up @@ -915,19 +916,19 @@ export class Stl<Plugins extends AnyPlugins> {
* @param schema this is generated by the `stl` CLI and should not be passed or modified manually
* @returns schema representing type `T`
*
* Invocations of this method are detected by the `stl` CLI,
* which injects a Zod schema representing the input type `T`
* Invocations of this method are detected by the `stl` CLI,
* which injects a Zod schema representing the input type `T`
* along with any necessary imports into the file. Schema generation must
* be re-run to keep the schema up-to-date whenever `T` or any types
* it relies on change. This can be automated via the `stl` CLI watch mode.
*
* For more information on using the `stl` CLI, see the
* For more information on using the `stl` CLI, see the
* [CLI docs](https://stainlessapi.com/stl/cli.md).
* For more details on advanced conversion functionality,
* For more details on advanced conversion functionality,
* including adding custom validation and transformation logic
* to generated schemas, see the
* [magic type schema docs](https://stainlessapi.com/stl/schemas/schemas-from-types.md).
*
* to generated schemas, see the
* [magic type schema docs](https://stainlessapi.com/stl/schemas/schemas-from-types.md).
*
* ## Example
* ```ts
* // invoke like this
Expand All @@ -951,21 +952,21 @@ export class Stl<Plugins extends AnyPlugins> {
* @param config optional configuraton
* @param handler the function that handles requests to this endpoint
* @returns endpoint instance
*
*
* ## Schema generation
* Invocations of this method are detected by the `stl` CLI,
* Invocations of this method are detected by the `stl` CLI,
* which generates Zod schemas for the given param and response types.
* Schema generation must be re-run to keep the schema up-to-date whenever
* Schema generation must be re-run to keep the schema up-to-date whenever
* any of these types, or the types they rely on, change. This can be automated
* via the `stl` CLI watch mode.
*
* For more information on using the `stl` CLI, see the
* For more information on using the `stl` CLI, see the
* [CLI docs](https://stainlessapi.com/stl/cli.md).
* For more details on advanced conversion functionality,
* For more details on advanced conversion functionality,
* including adding custom validation and transformation logic
* to generated schemas, see the
* [magic type schema docs](https://stainlessapi.com/stl/schemas/schemas-from-types.md).
*
* to generated schemas, see the
* [magic type schema docs](https://stainlessapi.com/stl/schemas/schemas-from-types.md).
*
* ## Example
* ```ts
* // ~/api/users/retrieve.ts
Expand Down
3 changes: 2 additions & 1 deletion packages/ts-to-zod/package.json
Expand Up @@ -26,7 +26,8 @@
"prettier": "^2.8.8",
"rimraf": "^5.0.1",
"ts-jest": "^29.1.0",
"typescript": "^5.1.3"
"typescript": "^5.1.3",
"stainless": "workspace:*"
},
"dependencies": {
"@types/lodash": "^4.14.195",
Expand Down
12 changes: 9 additions & 3 deletions packages/ts-to-zod/src/__tests__/transform-refine.test.ts
Expand Up @@ -11,7 +11,7 @@ import {
ArraySchema,
StringSchema,
} from "../index";
import z from "zod";
import { z, Includes, Selectable } from "stainless";

class ToString<I extends TypeSchema<any>> extends Transform<I, string> {
transform(value: output<I>): string {
Expand Down Expand Up @@ -72,6 +72,9 @@ type T = {
>;
datetime: StringSchema<{ datetime: { offset: true } }>;
catchall: ObjectSchema<{ a: number }, { catchall: string }>;
includes: Includes<Aliased>;
deepIncludes: Includes<Aliased, 5>;
selectable: Selectable<Aliased>;
};

it(`transform`, async () =>
Expand All @@ -83,15 +86,18 @@ it(`transform`, async () =>
{
"src/__tests__/transform-refine.test.codegen.d.ts": "import { z } from "zod";
import { ParseFloat, ToString, Coerce, ParsePet, Even } from "./transform-refine.test";
const Aliased: z.ZodTypeAny;
const T: z.ZodTypeAny;
",
"src/__tests__/transform-refine.test.codegen.js": "const { z } = require("zod");
const { ParseFloat, ToString, Coerce, ParsePet, Even } = require("./transform-refine.test");
const T = z.object({ a: z.date().transform(new ToString().transform).transform(new ParseFloat().transform), b: z.string().transform(new Coerce().transform), c: z.string().refine(new ParsePet().refine, new ParsePet().message), d: z.number().superRefine(new Even().superRefine), date: z.date().min(new Date("2023-01-10")), number: z.number().finite().safe("too big").gt(5, "5 and below too small!"), object: z.object({}).passthrough(), aliasedObject: z.object({ x: z.string() }).strict(), array: z.array(z.number().nullable()).nonempty().min(5, "at least five elements needed"), datetime: z.string().datetime({ offset: true }), catchall: z.object({ a: z.number() }).catchall(z.string()) });
const Aliased = z.object({ x: z.string() });
const T = z.object({ a: z.date().transform(new ToString().transform).transform(new ParseFloat().transform), b: z.string().transform(new Coerce().transform), c: z.string().refine(new ParsePet().refine, new ParsePet().message), d: z.number().superRefine(new Even().superRefine), date: z.date().min(new Date("2023-01-10")), number: z.number().finite().safe("too big").gt(5, "5 and below too small!"), object: z.object({}).passthrough(), aliasedObject: z.object({ x: z.string() }).strict(), array: z.array(z.number().nullable()).nonempty().min(5, "at least five elements needed"), datetime: z.string().datetime({ offset: true }), catchall: z.object({ a: z.number() }).catchall(z.string()), includes: z.includes(z.lazy(() => Aliased), 3), deepIncludes: z.includes(z.lazy(() => Aliased), 5), selectable: z.lazy(() => Aliased).selectable() });
",
"src/__tests__/transform-refine.test.codegen.mjs": "import { z } from "zod";
import { ParseFloat, ToString, Coerce, ParsePet, Even } from "./transform-refine.test";
const T = z.object({ a: z.date().transform(new ToString().transform).transform(new ParseFloat().transform), b: z.string().transform(new Coerce().transform), c: z.string().refine(new ParsePet().refine, new ParsePet().message), d: z.number().superRefine(new Even().superRefine), date: z.date().min(new Date("2023-01-10")), number: z.number().finite().safe("too big").gt(5, "5 and below too small!"), object: z.object({}).passthrough(), aliasedObject: z.object({ x: z.string() }).strict(), array: z.array(z.number().nullable()).nonempty().min(5, "at least five elements needed"), datetime: z.string().datetime({ offset: true }), catchall: z.object({ a: z.number() }).catchall(z.string()) });
const Aliased = z.object({ x: z.string() });
const T = z.object({ a: z.date().transform(new ToString().transform).transform(new ParseFloat().transform), b: z.string().transform(new Coerce().transform), c: z.string().refine(new ParsePet().refine, new ParsePet().message), d: z.number().superRefine(new Even().superRefine), date: z.date().min(new Date("2023-01-10")), number: z.number().finite().safe("too big").gt(5, "5 and below too small!"), object: z.object({}).passthrough(), aliasedObject: z.object({ x: z.string() }).strict(), array: z.array(z.number().nullable()).nonempty().min(5, "at least five elements needed"), datetime: z.string().datetime({ offset: true }), catchall: z.object({ a: z.number() }).catchall(z.string()), includes: z.includes(z.lazy(() => Aliased), 3), deepIncludes: z.includes(z.lazy(() => Aliased), 5), selectable: z.lazy(() => Aliased).selectable() });
",
}
`));

0 comments on commit 1eace8f

Please sign in to comment.