0.1.0 #1

Merged
Thilawyn merged 24 commits from next into master 2024-01-05 00:39:33 +01:00
24 changed files with 905 additions and 3 deletions

110
.drone.jsonnet Normal file
View File

@@ -0,0 +1,110 @@
local bun_image = "oven/bun:1";
local node_image = "node:20";
local install_step = {
name: "install",
image: bun_image,
commands: ["bun install --frozen-lockfile"],
};
local lint_step = {
name: "lint",
image: bun_image,
commands: ["bun lint:tsc"],
};
local build_step = {
name: "build",
image: bun_image,
commands: ["bun run build"],
};
local pack_step = {
name: "pack",
image: node_image,
commands: ["npm pack --dry-run"],
};
local publish_step = {
name: "publish",
image: node_image,
environment: {
NPM_TOKEN: { from_secret: "npm_token" }
},
commands: [
"npm set @thilawyn:registry https://git.jvalver.de/api/packages/thilawyn/npm/",
"npm config set -- //git.jvalver.de/api/packages/thilawyn/npm/:_authToken $NPM_TOKEN",
"npm publish",
],
};
[
// Lint the whole project when not in master, not in a PR nor on a tag
{
kind: "pipeline",
type: "docker",
name: "lint",
trigger: {
ref: {
exclude: [
"refs/heads/master",
"refs/pull/**",
"refs/tags/**",
]
}
},
steps: [
install_step,
lint_step,
],
},
// Build the package without publishing for pull requests
{
kind: "pipeline",
type: "docker",
name: "build",
trigger: {
ref: {
include: ["refs/pull/**"]
}
},
steps: [
install_step,
lint_step,
build_step,
pack_step,
],
},
// Build and publish the package for master and tags
{
kind: "pipeline",
type: "docker",
name: "build-publish",
trigger: {
ref: {
include: [
"refs/heads/master",
"refs/tags/**",
]
}
},
steps: [
install_step,
lint_step,
build_step,
publish_step,
],
},
]

1
.gitignore vendored
View File

@@ -129,4 +129,3 @@ dist
.yarn/build-state.yml .yarn/build-state.yml
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
@thilawyn:registry=https://git.jvalver.de/api/packages/thilawyn/npm/

View File

@@ -1,3 +1,3 @@
# schemable-classes # schemable-class
Create TypeScript classes out of Zod schemas Create TypeScript classes out of Zod schemas

BIN
bun.lockb Executable file

Binary file not shown.

2
bunfig.toml Normal file
View File

@@ -0,0 +1,2 @@
[install.scopes]
"@thilawyn" = "https://git.jvalver.de/api/packages/thilawyn/npm/"

59
package.json Normal file
View File

@@ -0,0 +1,59 @@
{
"name": "@thilawyn/schemable-class",
"version": "0.1.0",
"type": "module",
"publishConfig": {
"registry": "https://git.jvalver.de/api/packages/thilawyn/npm/"
},
"files": [
"./dist"
],
"exports": {
".": {
"import": {
"types": "./dist/schemable.d.mts",
"default": "./dist/schemable.mjs"
},
"require": {
"types": "./dist/schemable.d.cts",
"default": "./dist/schemable.cjs"
}
},
"./jsonifiable": {
"import": {
"types": "./dist/jsonifiable.d.mts",
"default": "./dist/jsonifiable.mjs"
},
"require": {
"types": "./dist/jsonifiable.d.cts",
"default": "./dist/jsonifiable.cjs"
}
}
},
"scripts": {
"build": "rollup -c rollup.config.ts",
"lint:tsc": "tsc --noEmit",
"clean:cache": "rm -f tsconfig.tsbuildinfo",
"clean:dist": "rm -rf dist",
"clean:node": "rm -rf node_modules"
},
"dependencies": {
"decimal.js": "^10.4.3",
"effect": "^2.0.0-next.62",
"lodash-es": "^4.17.21",
"type-fest": "^4.9.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/lodash-es": "^4.17.12",
"bun-types": "latest",
"npm-check-updates": "^16.14.12",
"npm-sort": "^0.0.4",
"rollup": "^4.9.1",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-ts": "^3.4.5",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
}
}

43
rollup.config.ts Normal file
View File

@@ -0,0 +1,43 @@
import { nodeResolve } from "@rollup/plugin-node-resolve"
import { defineConfig } from "rollup"
import cleanup from "rollup-plugin-cleanup"
import ts from "rollup-plugin-ts"
import pkg from "./package.json" assert { type: "json" }
export const createBundleConfig = (
input: string,
name: keyof typeof pkg.exports,
) =>
defineConfig({
input,
output: [
{
file: pkg.exports[name].import.default,
format: "esm",
},
{
file: pkg.exports[name].require.default,
format: "cjs",
},
],
external: id => !/^[./]/.test(id),
plugins: [
nodeResolve(),
ts(),
cleanup({
comments: "jsdoc",
extensions: ["ts"],
}),
],
})
export default [
createBundleConfig("src/index.ts", "."),
createBundleConfig("src/jsonifiable/index.ts", "./jsonifiable"),
]

90
src/SchemableClass.ts Normal file
View File

@@ -0,0 +1,90 @@
import { Class } from "type-fest"
import { z } from "zod"
/**
* Configuration for creating a schemable object with validation schemas.
* @template Values - The type representing the expected values.
* @template Input - The type representing the input values.
* @template SchemaT - The type representing the base validation schema.
* @template SchemaUnknownKeys - The type representing the unknown keys behavior in the base validation schema.
* @template SchemaCatchall - The type representing the catchall behavior in the base validation schema.
* @template SchemaWithDefaultValuesT - The type representing the validation schema with default values.
* @template SchemaWithDefaultValuesUnknownKeys - The type representing the unknown keys behavior in the validation schema with default values.
* @template SchemaWithDefaultValuesCatchall - The type representing the catchall behavior in the validation schema with default values.
*/
export type SchemableConfig<
Values extends {} = {},
Input extends {} = {},
SchemaT extends z.ZodRawShape = z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam = z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny = z.ZodTypeAny,
SchemaWithDefaultValuesT extends z.ZodRawShape = z.ZodRawShape,
SchemaWithDefaultValuesUnknownKeys extends z.UnknownKeysParam = z.UnknownKeysParam,
SchemaWithDefaultValuesCatchall extends z.ZodTypeAny = z.ZodTypeAny,
> = {
readonly values: Values
readonly input: Input
readonly schema: z.ZodObject<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
Values
>
readonly schemaWithDefaultValues: z.ZodObject<
SchemaWithDefaultValuesT,
SchemaWithDefaultValuesUnknownKeys,
SchemaWithDefaultValuesCatchall,
Values,
Input
>
}
/**
* Represents a class with validation schemas.
* @template $Config - The configuration type for the schemable object.
*/
export type SchemableClass<
$Config extends SchemableConfig
> = (
Class<
SchemableObject<$Config>,
SchemableClassConstructorParams<$Config>
> & {
readonly $schemableConfig: $Config
readonly schema: $Config["schema"]
readonly schemaWithDefaultValues: $Config["schemaWithDefaultValues"]
}
)
/**
* Represents the constructor parameters for the schemable object class.
* @template $Config - The configuration type for the schemable object.
*/
export type SchemableClassConstructorParams<
$Config extends SchemableConfig
> = (
Parameters<
(data: $Config["values"]) => void
>
)
/**
* Represents an object with validation schemas.
* @template $Config - The configuration type for the schemable object.
*/
export type SchemableObject<
$Config extends SchemableConfig
> = (
{
readonly $schemableConfig: $Config
readonly schema: $Config["schema"]
readonly schemaWithDefaultValues: $Config["schemaWithDefaultValues"]
} & $Config["values"]
)

3
src/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from "./SchemableClass"
export * from "./makeSchemableClass"
export * from "./newSchemable"

View File

@@ -0,0 +1,66 @@
import { Effect } from "effect"
import { Class } from "type-fest"
import { JsonifiableObject } from "type-fest/source/jsonifiable"
import { z } from "zod"
import { SchemableClassConstructorParams, SchemableConfig } from ".."
export type JsonifiableSchemableConfig<
$SchemableConfig extends SchemableConfig = SchemableConfig,
JsonifiedValues extends JsonifiableObject = {},
JsonifySchemaT extends z.ZodRawShape = z.ZodRawShape,
JsonifySchemaUnknownKeys extends z.UnknownKeysParam = z.UnknownKeysParam,
JsonifySchemaCatchall extends z.ZodTypeAny = z.ZodTypeAny,
DejsonifySchemaT extends z.ZodRawShape = z.ZodRawShape,
DejsonifySchemaUnknownKeys extends z.UnknownKeysParam = z.UnknownKeysParam,
DejsonifySchemaCatchall extends z.ZodTypeAny = z.ZodTypeAny,
> = {
readonly $schemableConfig: $SchemableConfig
readonly jsonifiedValues: JsonifiedValues
readonly jsonifySchema: z.ZodObject<
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
JsonifiedValues,
$SchemableConfig["values"]
>
readonly dejsonifySchema: z.ZodObject<
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
$SchemableConfig["values"],
JsonifiedValues
>
}
export type JsonifiableSchemableClass<
$Config extends JsonifiableSchemableConfig
> = (
Class<
JsonifiableSchemableObject<$Config>,
SchemableClassConstructorParams<$Config["$schemableConfig"]>
> & {
readonly $jsonifiableSchemableConfig: $Config
readonly jsonifySchema: $Config["jsonifySchema"]
readonly dejsonifySchema: $Config["dejsonifySchema"]
}
)
export type JsonifiableSchemableObject<
$Config extends JsonifiableSchemableConfig
> = {
readonly $jsonifiableSchemableConfig: $Config
readonly jsonifySchema: $Config["jsonifySchema"]
readonly dejsonifySchema: $Config["dejsonifySchema"]
jsonify(): $Config["jsonifiedValues"]
jsonifyPromise(): Promise<$Config["jsonifiedValues"]>
jsonifyEffect(): Effect.Effect<never, z.ZodError<$Config["$schemableConfig"]["values"]>, $Config["jsonifiedValues"]>
}

View File

@@ -0,0 +1,47 @@
import { Effect, pipe } from "effect"
import { z } from "zod"
import { JsonifiableSchemableClass, JsonifiableSchemableConfig } from "."
import { parseZodTypeEffect } from "../util"
export const dejsonifySchemable = <
C extends JsonifiableSchemableClass<$Config>,
$Config extends JsonifiableSchemableConfig,
>(
class_: C | JsonifiableSchemableClass<$Config>,
values: $Config["jsonifiedValues"],
params?: Partial<z.ParseParams>,
) =>
new class_(class_.dejsonifySchema.parse(values, params)) as InstanceType<C>
export const dejsonifySchemablePromise = async <
C extends JsonifiableSchemableClass<$Config>,
$Config extends JsonifiableSchemableConfig,
>(
class_: C | JsonifiableSchemableClass<$Config>,
values: $Config["jsonifiedValues"],
params?: Partial<z.ParseParams>,
) =>
new class_(await class_.dejsonifySchema.parseAsync(values, params)) as InstanceType<C>
export const dejsonifySchemableEffect = <
C extends JsonifiableSchemableClass<$Config>,
$Config extends JsonifiableSchemableConfig,
>(
class_: C | JsonifiableSchemableClass<$Config>,
values: $Config["jsonifiedValues"],
params?: Partial<z.ParseParams>,
) => pipe(
parseZodTypeEffect<
z.output<typeof class_.dejsonifySchema>,
z.input<typeof class_.dejsonifySchema>
>(
class_.dejsonifySchema,
values,
params,
),
Effect.map(values => new class_(values) as InstanceType<C>),
)

4
src/jsonifiable/index.ts Normal file
View File

@@ -0,0 +1,4 @@
export * from "./JsonifiableSchemableClass"
export * from "./dejsonifySchemable"
export * from "./makeJsonifiableSchemableClass"
export * from "./schema"

View File

@@ -0,0 +1,98 @@
import { Class } from "type-fest"
import { JsonifiableObject } from "type-fest/source/jsonifiable"
import { z } from "zod"
import { JsonifiableSchemableClass, JsonifiableSchemableConfig, JsonifiableSchemableObject } from "."
import { SchemableClass, SchemableConfig } from ".."
import { StaticMembers, parseZodTypeEffect } from "../util"
export function makeJsonifiableSchemableClass<
C extends SchemableClass<$SchemableConfig>,
$SchemableConfig extends SchemableConfig,
JsonifiedValues extends JsonifiableObject,
JsonifySchemaT extends z.ZodRawShape,
JsonifySchemaUnknownKeys extends z.UnknownKeysParam,
JsonifySchemaCatchall extends z.ZodTypeAny,
DejsonifySchemaT extends z.ZodRawShape,
DejsonifySchemaUnknownKeys extends z.UnknownKeysParam,
DejsonifySchemaCatchall extends z.ZodTypeAny,
>(
class_: C | SchemableClass<$SchemableConfig>,
props: {
jsonifySchema: (props: {
schema: $SchemableConfig["schema"]
s: $SchemableConfig["schema"]["shape"]
}) => z.ZodObject<
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
JsonifiedValues,
$SchemableConfig["values"]
>
dejsonifySchema: (props: {
schema: $SchemableConfig["schema"]
s: $SchemableConfig["schema"]["shape"]
}) => z.ZodObject<
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
$SchemableConfig["values"],
JsonifiedValues
>
},
) {
const jsonifySchema = props.jsonifySchema({
schema: class_.schema,
s: class_.schema.shape,
})
const dejsonifySchema = props.dejsonifySchema({
schema: class_.schema,
s: class_.schema.shape,
})
const $jsonifiableSchemableConfig = {
$schemableConfig: class_.$schemableConfig,
jsonifiedValues: undefined as unknown as JsonifiedValues,
jsonifySchema: undefined as unknown as typeof jsonifySchema,
dejsonifySchema: undefined as unknown as typeof dejsonifySchema,
} as const satisfies JsonifiableSchemableConfig
const jsonifiableClass = class JsonifiableSchemableObject extends class_ {
static readonly $jsonifiableSchemableConfig = $jsonifiableSchemableConfig
static readonly jsonifySchema = jsonifySchema
static readonly dejsonifySchema = dejsonifySchema
readonly $jsonifiableSchemableConfig = $jsonifiableSchemableConfig
readonly jsonifySchema = jsonifySchema
readonly dejsonifySchema = dejsonifySchema
jsonify() {
return this.jsonifySchema.parse(this)
}
jsonifyPromise() {
return this.jsonifySchema.parseAsync(this)
}
jsonifyEffect() {
return parseZodTypeEffect(this.jsonifySchema, this)
}
} satisfies JsonifiableSchemableClass<typeof $jsonifiableSchemableConfig>
return jsonifiableClass as unknown as (
Class<
InstanceType<C> & JsonifiableSchemableObject<typeof $jsonifiableSchemableConfig>,
ConstructorParameters<C>
> &
StaticMembers<C> &
StaticMembers<JsonifiableSchemableClass<typeof $jsonifiableSchemableConfig>>
)
}

View File

@@ -0,0 +1,18 @@
import { z } from "zod"
export const jsonifyBigIntSchema = <S extends z.ZodBigInt>(schema: S) =>
schema.transform(v => v.toString())
export const dejsonifyBigIntSchema = <S extends z.ZodBigInt>(schema: S) =>
z
.string()
.transform(v => {
try {
return BigInt(v)
}
catch (e) {
return v
}
})
.pipe(schema)

View File

@@ -0,0 +1,18 @@
import { z } from "zod"
export const jsonifyDateSchema = <S extends z.ZodDate>(schema: S) =>
schema.transform(v => v.toString())
export const dejsonifyDateSchema = <S extends z.ZodDate>(schema: S) =>
z
.string()
.transform(v => {
try {
return new Date(v)
}
catch (e) {
return v
}
})
.pipe(schema)

View File

@@ -0,0 +1,19 @@
import { Decimal } from "decimal.js"
import { z } from "zod"
export const jsonifyDecimalSchema = <S extends z.ZodType<Decimal, z.ZodTypeDef, Decimal>>(schema: S) =>
schema.transform(v => v.toJSON())
export const dejsonifyDecimalSchema = <S extends z.ZodType<Decimal, z.ZodTypeDef, Decimal>>(schema: S) =>
z
.string()
.transform(v => {
try {
return new Decimal(v)
}
catch (e) {
return v
}
})
.pipe(schema)

View File

@@ -0,0 +1,4 @@
export * from "./bigint"
export * from "./date"
export * from "./decimal"
export * from "./schemable"

View File

@@ -0,0 +1,25 @@
import { z } from "zod"
import { JsonifiableSchemableClass, JsonifiableSchemableConfig } from ".."
// TODO: try to find a way to get rid of the 'class_' arg
export const jsonifySchemableSchema = <
C extends JsonifiableSchemableClass<$Config>,
$Config extends JsonifiableSchemableConfig,
S extends z.ZodType<InstanceType<C>, z.ZodTypeDef, InstanceType<C>>,
>(
class_: C | JsonifiableSchemableClass<$Config>,
schema: S,
) =>
schema.pipe(class_.jsonifySchema)
// TODO: try to find a way to get rid of the 'class_' arg
export const dejsonifySchemableSchema = <
C extends JsonifiableSchemableClass<$Config>,
$Config extends JsonifiableSchemableConfig,
S extends z.ZodType<InstanceType<C>, z.ZodTypeDef, InstanceType<C>>,
>(
class_: C | JsonifiableSchemableClass<$Config>,
schema: S,
) =>
class_.dejsonifySchema.transform(v => new class_(v)).pipe(schema)

49
src/makeSchemableClass.ts Normal file
View File

@@ -0,0 +1,49 @@
import { z } from "zod"
import { SchemableClass, SchemableConfig } from "."
import { zodObjectRemoveDefaults } from "./util"
export function makeSchemableClass<
SchemaWithDefaultValuesT extends z.ZodRawShape,
SchemaWithDefaultValuesUnknownKeys extends z.UnknownKeysParam,
SchemaWithDefaultValuesCatchall extends z.ZodTypeAny,
SchemaWithDefaultValuesOutput extends SchemaWithDefaultValuesInput, // TODO: apply "StripSchemaInputDefaults"?
SchemaWithDefaultValuesInput extends {},
>(
{
schema: schemaWithDefaultValues
}: {
schema: z.ZodObject<
SchemaWithDefaultValuesT,
SchemaWithDefaultValuesUnknownKeys,
SchemaWithDefaultValuesCatchall,
SchemaWithDefaultValuesOutput,
SchemaWithDefaultValuesInput
>
}
) {
const schema = zodObjectRemoveDefaults(schemaWithDefaultValues)
const $schemableConfig = {
values: undefined as unknown as z.output<typeof schemaWithDefaultValues>,
input: undefined as unknown as z.input<typeof schemaWithDefaultValues>,
schema: undefined as unknown as typeof schema,
schemaWithDefaultValues: undefined as unknown as typeof schemaWithDefaultValues,
} as const satisfies SchemableConfig
return class SchemableObject {
static readonly $schemableConfig = $schemableConfig
static readonly schema = schema
static readonly schemaWithDefaultValues = schemaWithDefaultValues
readonly $schemableConfig = $schemableConfig
readonly schema = schema
readonly schemaWithDefaultValues = schemaWithDefaultValues
constructor(data: z.output<typeof schema>) {
Object.assign(this, data)
}
} as SchemableClass<typeof $schemableConfig>
}

77
src/newSchemable.ts Normal file
View File

@@ -0,0 +1,77 @@
import { Effect, pipe } from "effect"
import { HasRequiredKeys } from "type-fest"
import { z } from "zod"
import { SchemableClass, SchemableConfig } from "."
import { parseZodTypeEffect } from "./util"
type ParamsArgs = [] | [Partial<z.ParseParams>]
type NewSchemableArgs<Input extends object> =
HasRequiredKeys<Input> extends true
? [Input, ...ParamsArgs]
: [] | [Input, ...ParamsArgs]
/**
* Creates a new instance of a SchemableClass with default values.
*
* @param class_ - The SchemableClass.
* @param values - The values to be parsed and used to create the instance.
* @param params - Optional parameters for parsing.
* @returns A new instance of the specified SchemableClass.
*/
export const newSchemable = <
C extends SchemableClass<$Config>,
$Config extends SchemableConfig,
>(
class_: C | SchemableClass<$Config>,
...[values, params]: NewSchemableArgs<$Config["input"]>
) =>
new class_(class_.schemaWithDefaultValues.parse(values || {}, params)) as InstanceType<C>
/**
* Creates a new instance of a SchemableClass with default values asynchronously.
*
* @param class_ - The SchemableClass.
* @param values - The values to be parsed and used to create the instance.
* @param params - Optional parameters for parsing.
* @returns A Promise resolving to a new instance of the specified SchemableClass.
*/
export const newSchemablePromise = async <
C extends SchemableClass<$Config>,
$Config extends SchemableConfig,
>(
class_: C | SchemableClass<$Config>,
...[values, params]: NewSchemableArgs<$Config["input"]>
) =>
new class_(await class_.schemaWithDefaultValues.parseAsync(values || {}, params)) as InstanceType<C>
/**
* Creates a new instance of a SchemableClass with default values as an Effect.
*
* @param class_ - The SchemableClass.
* @param values - The values to be parsed and used to create the instance.
* @param params - Optional parameters for parsing.
* @returns An Effect producing a new instance of the specified SchemableClass.
*/
export const newSchemableEffect = <
C extends SchemableClass<$Config>,
$Config extends SchemableConfig,
>(
class_: C | SchemableClass<$Config>,
...[values, params]: NewSchemableArgs<$Config["input"]>
) => pipe(
parseZodTypeEffect<
z.output<typeof class_.schemaWithDefaultValues>,
z.input<typeof class_.schemaWithDefaultValues>
>(
class_.schemaWithDefaultValues,
values || {},
params,
),
Effect.map(values => new class_(values) as InstanceType<C>),
)

64
src/tests.ts Normal file
View File

@@ -0,0 +1,64 @@
import { z } from "zod"
import { makeSchemableClass, newSchemable } from "."
import { dejsonifyBigIntSchema, dejsonifySchemable, dejsonifySchemableSchema, jsonifyBigIntSchema, jsonifySchemableSchema, makeJsonifiableSchemableClass } from "./jsonifiable"
const GroupSchema = z.object({
/** Group ID */
id: z.bigint(),
/** Group name */
name: z.string(),
})
const GroupSchemableObject = makeSchemableClass({ schema: GroupSchema })
const GroupJsonifiableSchemableObject = makeJsonifiableSchemableClass(GroupSchemableObject, {
jsonifySchema: ({ schema, s }) => schema.extend({
id: jsonifyBigIntSchema(s.id)
}),
dejsonifySchema: ({ schema, s }) => schema.extend({
id: dejsonifyBigIntSchema(s.id)
}),
})
class Group extends GroupJsonifiableSchemableObject {}
const UserSchema = z.object({
/** User ID */
id: z.bigint(),
/** Name string */
name: z.string(),
/** User group */
group: z.instanceof(Group),
})
const UserSchemableObject = makeSchemableClass({ schema: UserSchema })
const UserJsonifiableSchemableObject = makeJsonifiableSchemableClass(UserSchemableObject, {
jsonifySchema: ({ schema, s }) => schema.extend({
id: jsonifyBigIntSchema(s.id),
group: jsonifySchemableSchema(Group, s.group),
}),
dejsonifySchema: ({ schema, s }) => schema.extend({
id: dejsonifyBigIntSchema(s.id),
group: dejsonifySchemableSchema(Group, s.group),
}),
})
class User extends UserJsonifiableSchemableObject {}
const group1 = new Group({ id: 1n, name: "Group 1" })
const user1 = new User({ id: 1n, name: "User 1", group: group1 })
const user2 = newSchemable(User, { id: 2n, name: "User 2", group: group1 })
const jsonifiedUser2 = user2.jsonify()
const dejsonifiedUser2 = dejsonifySchemable(User, jsonifiedUser2)
console.log(dejsonifiedUser2)

82
src/util.ts Normal file
View File

@@ -0,0 +1,82 @@
import { Effect, pipe } from "effect"
import { mapValues } from "lodash-es"
import { z } from "zod"
/**
* Represents the static members of a class.
* @template C - The class type.
*/
export type StaticMembers<C> = {
[Key in keyof C as Key extends "prototype" ? never : Key]: C[Key]
}
/**
* Removes default values from a ZodObject schema and returns a new schema.
*
* @param schema - The ZodObject schema to process.
* @returns A new ZodObject schema with default values removed.
*/
export const zodObjectRemoveDefaults = <
T extends z.ZodRawShape,
UnknownKeys extends z.UnknownKeysParam,
Catchall extends z.ZodTypeAny,
Output extends {},
Input extends {},
>(
schema: z.ZodObject<
T,
UnknownKeys,
Catchall,
Output,
Input
>
) =>
schema.extend(zodShapeRemoveDefaults(schema.shape))
/**
* Removes default values from a ZodObject shape and returns a new shape.
*
* @param shape - The ZodObject shape to process.
* @returns A new shape with default values removed.
*/
export const zodShapeRemoveDefaults = <
Shape extends z.ZodRawShape
>(
shape: Shape
): {
[K in keyof Shape]:
Shape[K] extends z.ZodDefault<infer T>
? T
: Shape[K]
} =>
mapValues(shape, el =>
el instanceof z.ZodDefault
? el.removeDefault()
: el
)
/**
* Parses a value using a ZodType schema wrapped in an Effect monad.
*
* @param schema - The ZodType schema to use for parsing.
* @param args - The arguments to pass to the `safeParseAsync` method of the schema.
* @returns An Effect monad representing the parsing result.
*/
export const parseZodTypeEffect = <
Output,
Input,
>(
schema: z.ZodType<Output, z.ZodTypeDef, Input>,
...args: Parameters<typeof schema.safeParseAsync>
) => pipe(
Effect.promise(() => schema.safeParseAsync(...args)),
Effect.flatMap(response =>
response.success
? Effect.succeed(response.data)
: Effect.fail(response.error)
),
)

24
tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
// "allowImportingTsExtensions": true,
// "noEmit": true,
"declaration": true,
"composite": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"types": [
"bun-types"
]
},
"include": ["src"]
}