0.1.1 #2

Merged
Thilawyn merged 47 commits from next into master 2024-01-17 20:47:13 +01:00
35 changed files with 1661 additions and 465 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{
"name": "@thilawyn/schemable-class",
"version": "0.1.0",
"version": "0.1.1",
"type": "module",
"publishConfig": {
"registry": "https://git.jvalver.de/api/packages/thilawyn/npm/"
@@ -28,6 +28,16 @@
"types": "./dist/jsonifiable.d.cts",
"default": "./dist/jsonifiable.cjs"
}
},
"./observable": {
"import": {
"types": "./dist/observable.d.mts",
"default": "./dist/observable.mjs"
},
"require": {
"types": "./dist/observable.d.cts",
"default": "./dist/observable.cjs"
}
}
},
"scripts": {
@@ -39,8 +49,9 @@
},
"dependencies": {
"decimal.js": "^10.4.3",
"effect": "^2.0.0-next.62",
"effect": "^2.1.1",
"lodash-es": "^4.17.21",
"mobx": "^6.12.0",
"type-fest": "^4.9.0",
"zod": "^3.22.4"
},
@@ -50,9 +61,10 @@
"bun-types": "latest",
"npm-check-updates": "^16.14.12",
"npm-sort": "^0.0.4",
"rollup": "^4.9.1",
"rollup": "^4.9.5",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-ts": "^3.4.5",
"ts-functional-pipe": "^3.1.2",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
}

View File

@@ -40,4 +40,5 @@ export const createBundleConfig = (
export default [
createBundleConfig("src/index.ts", "."),
createBundleConfig("src/jsonifiable/index.ts", "./jsonifiable"),
createBundleConfig("src/observable/index.ts", "./observable"),
]

View File

@@ -1,90 +1,54 @@
import { Class } from "type-fest"
import { z } from "zod"
import { Class, ClassType } from "./util"
/**
* 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
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
Type extends ClassType = "AbstractClass"
> = (
Class<
SchemableObject<$Config>,
SchemableClassConstructorParams<$Config>
Type,
{
readonly schema: z.ZodObject<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
Values
>
readonly defaultValues: DefaultValues
} & Values,
Parameters<(values: Values) => never>
> & {
readonly $schemableConfig: $Config
readonly schema: $Config["schema"]
readonly schemaWithDefaultValues: $Config["schemaWithDefaultValues"]
readonly schema: z.ZodObject<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
Values
>
readonly defaultValues: DefaultValues
}
)
/**
* 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"]
)
export type SchemableClassInput<
Values extends {},
DefaultValues extends Partial<Values>,
> = {
[Key in Exclude<keyof Values, keyof DefaultValues>]: Values[Key]
} & {
[Key in keyof DefaultValues]?: Key extends keyof Values
? Values[Key]
: never
}

View File

@@ -0,0 +1,8 @@
import { Opaque } from "type-fest"
export type DefinedDefaultValuesTag = "@thilawyn/schemable-class/DefinedDefaultValues"
export function defineDefaultValues<T>(values: T) {
return values as Opaque<T, DefinedDefaultValuesTag>
}

View File

@@ -0,0 +1,95 @@
import { AbstractClass, Class as ConcreteClass, Opaque } from "type-fest"
import { z } from "zod"
import { DefinedDefaultValuesTag, SchemableClass } from "."
import { StaticMembers } from "./util"
export function extendSchemableClass<
C extends SchemableClass<
ExtendSchemaT,
ExtendSchemaUnknownKeys,
ExtendSchemaCatchall,
ExtendSchemaValues,
ExtendDefaultValues
>,
ExtendSchemaT extends z.ZodRawShape,
ExtendSchemaUnknownKeys extends z.UnknownKeysParam,
ExtendSchemaCatchall extends z.ZodTypeAny,
ExtendSchemaValues extends {},
ExtendDefaultValues extends Partial<ExtendSchemaValues>,
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
SchemaValues extends ExtendSchemaValues,
DefaultValues extends Partial<SchemaValues>,
>(
extend: C | SchemableClass<
ExtendSchemaT,
ExtendSchemaUnknownKeys,
ExtendSchemaCatchall,
ExtendSchemaValues,
ExtendDefaultValues
>,
props: {
schema: (props: {
schema: z.ZodObject<
ExtendSchemaT,
ExtendSchemaUnknownKeys,
ExtendSchemaCatchall,
ExtendSchemaValues,
ExtendSchemaValues
>
shape: ExtendSchemaT
}) => z.ZodObject<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
SchemaValues,
SchemaValues
>
defaultValues: (defaultValues: ExtendDefaultValues) => Opaque<DefaultValues, DefinedDefaultValuesTag>
},
) {
type Class<T, Arguments extends unknown[]> = (
C extends ConcreteClass<any>
? ConcreteClass<T, Arguments>
: AbstractClass<T, Arguments>
)
const schema = props.schema({
schema: extend.schema,
shape: extend.schema.shape,
})
const defaultValues = props.defaultValues(extend.defaultValues)
return class extends extend {
static readonly schema = schema
readonly schema = schema
static readonly defaultValues = defaultValues
readonly defaultValues = defaultValues
} as unknown as (
Class<
Omit<InstanceType<C>, "schema" | "defaultValues" | keyof ExtendSchemaValues> &
{
readonly schema: z.ZodObject<SchemaT, SchemaUnknownKeys, SchemaCatchall, SchemaValues, SchemaValues>,
readonly defaultValues: DefaultValues,
} &
SchemaValues,
Parameters<(values: SchemaValues) => void>
> &
Omit<StaticMembers<C>, "schema" | "defaultValues"> &
{
readonly schema: z.ZodObject<SchemaT, SchemaUnknownKeys, SchemaCatchall, SchemaValues, SchemaValues>,
readonly defaultValues: DefaultValues,
}
)
}

View File

@@ -1,3 +1,5 @@
export * from "./SchemableClass"
export * from "./defineDefaultValues"
export * from "./extendSchemableClass"
export * from "./makeSchemableClass"
export * from "./newSchemable"

View File

@@ -1,66 +1,80 @@
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
>
}
import { SchemableClass } from ".."
import { Class, ClassType } from "../util"
export type JsonifiableSchemableClass<
$Config extends JsonifiableSchemableConfig
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
JsonifySchemaT extends z.ZodRawShape,
JsonifySchemaUnknownKeys extends z.UnknownKeysParam,
JsonifySchemaCatchall extends z.ZodTypeAny,
DejsonifySchemaT extends z.ZodRawShape,
DejsonifySchemaUnknownKeys extends z.UnknownKeysParam,
DejsonifySchemaCatchall extends z.ZodTypeAny,
JsonifiedValues extends JsonifiableObject,
Type extends ClassType = "AbstractClass"
> = (
SchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
Type
> &
Class<
JsonifiableSchemableObject<$Config>,
SchemableClassConstructorParams<$Config["$schemableConfig"]>
Type,
{
readonly jsonifySchema: z.ZodObject<
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
JsonifiedValues,
Values
>
readonly dejsonifySchema: z.ZodObject<
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
Values,
JsonifiedValues
>
jsonify(): JsonifiedValues
jsonifyPromise(): Promise<JsonifiedValues>
jsonifyEffect(): Effect.Effect<never, z.ZodError<Values>, JsonifiedValues>
},
any[]
> & {
readonly $jsonifiableSchemableConfig: $Config
readonly jsonifySchema: $Config["jsonifySchema"]
readonly dejsonifySchema: $Config["dejsonifySchema"]
readonly jsonifySchema: z.ZodObject<
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
JsonifiedValues,
Values
>
readonly dejsonifySchema: z.ZodObject<
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
Values,
JsonifiedValues
>
}
)
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

@@ -1,47 +1,213 @@
import { Effect, pipe } from "effect"
import { JsonifiableObject } from "type-fest/source/jsonifiable"
import { z } from "zod"
import { JsonifiableSchemableClass, JsonifiableSchemableConfig } from "."
import { JsonifiableSchemableClass } from "."
import { parseZodTypeEffect } from "../util"
export const dejsonifySchemable = <
C extends JsonifiableSchemableClass<$Config>,
$Config extends JsonifiableSchemableConfig,
export function dejsonifySchemable<
C extends JsonifiableSchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
JsonifiedValues,
"Class"
>,
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
JsonifySchemaT extends z.ZodRawShape,
JsonifySchemaUnknownKeys extends z.UnknownKeysParam,
JsonifySchemaCatchall extends z.ZodTypeAny,
DejsonifySchemaT extends z.ZodRawShape,
DejsonifySchemaUnknownKeys extends z.UnknownKeysParam,
DejsonifySchemaCatchall extends z.ZodTypeAny,
JsonifiedValues extends JsonifiableObject,
>(
class_: C | JsonifiableSchemableClass<$Config>,
values: $Config["jsonifiedValues"],
class_: C | JsonifiableSchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
JsonifiedValues,
"Class"
>,
values: JsonifiedValues,
params?: Partial<z.ParseParams>,
) =>
new class_(class_.dejsonifySchema.parse(values, params)) as InstanceType<C>
) {
return new class_(
class_.dejsonifySchema.parse(values, params)
) as InstanceType<C>
}
export const dejsonifySchemablePromise = async <
C extends JsonifiableSchemableClass<$Config>,
$Config extends JsonifiableSchemableConfig,
export async function dejsonifySchemablePromise<
C extends JsonifiableSchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
JsonifiedValues,
"Class"
>,
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
JsonifySchemaT extends z.ZodRawShape,
JsonifySchemaUnknownKeys extends z.UnknownKeysParam,
JsonifySchemaCatchall extends z.ZodTypeAny,
DejsonifySchemaT extends z.ZodRawShape,
DejsonifySchemaUnknownKeys extends z.UnknownKeysParam,
DejsonifySchemaCatchall extends z.ZodTypeAny,
JsonifiedValues extends JsonifiableObject,
>(
class_: C | JsonifiableSchemableClass<$Config>,
values: $Config["jsonifiedValues"],
class_: C | JsonifiableSchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
JsonifiedValues,
"Class"
>,
values: JsonifiedValues,
params?: Partial<z.ParseParams>,
) =>
new class_(await class_.dejsonifySchema.parseAsync(values, params)) as InstanceType<C>
) {
return new class_(
await class_.dejsonifySchema.parseAsync(values, params)
) as InstanceType<C>
}
export const dejsonifySchemableEffect = <
C extends JsonifiableSchemableClass<$Config>,
$Config extends JsonifiableSchemableConfig,
export function dejsonifySchemableEffect<
C extends JsonifiableSchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
JsonifiedValues,
"Class"
>,
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
JsonifySchemaT extends z.ZodRawShape,
JsonifySchemaUnknownKeys extends z.UnknownKeysParam,
JsonifySchemaCatchall extends z.ZodTypeAny,
DejsonifySchemaT extends z.ZodRawShape,
DejsonifySchemaUnknownKeys extends z.UnknownKeysParam,
DejsonifySchemaCatchall extends z.ZodTypeAny,
JsonifiedValues extends JsonifiableObject,
>(
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,
),
class_: C | JsonifiableSchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Effect.map(values => new class_(values) as InstanceType<C>),
)
Values,
DefaultValues,
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
JsonifiedValues,
"Class"
>,
values: JsonifiedValues,
params?: Partial<z.ParseParams>,
) {
return pipe(
parseZodTypeEffect(class_.dejsonifySchema, values, params),
Effect.map(values => new class_(values) as InstanceType<C>),
)
}

View File

@@ -1,16 +1,26 @@
import { Class } from "type-fest"
import { Effect } from "effect"
import { AbstractClass, Class as ConcreteClass } from "type-fest"
import { JsonifiableObject } from "type-fest/source/jsonifiable"
import { z } from "zod"
import { JsonifiableSchemableClass, JsonifiableSchemableConfig, JsonifiableSchemableObject } from "."
import { SchemableClass, SchemableConfig } from ".."
import { SchemableClass } from ".."
import { StaticMembers, parseZodTypeEffect } from "../util"
export function makeJsonifiableSchemableClass<
C extends SchemableClass<$SchemableConfig>,
$SchemableConfig extends SchemableConfig,
C extends SchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues
>,
JsonifiedValues extends JsonifiableObject,
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
JsonifySchemaT extends z.ZodRawShape,
JsonifySchemaUnknownKeys extends z.UnknownKeysParam,
@@ -19,59 +29,77 @@ export function makeJsonifiableSchemableClass<
DejsonifySchemaT extends z.ZodRawShape,
DejsonifySchemaUnknownKeys extends z.UnknownKeysParam,
DejsonifySchemaCatchall extends z.ZodTypeAny,
JsonifiedValues extends JsonifiableObject,
>(
class_: C | SchemableClass<$SchemableConfig>,
extend: C | SchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues
>,
props: {
jsonifySchema: (props: {
schema: $SchemableConfig["schema"]
s: $SchemableConfig["schema"]["shape"]
schema: z.ZodObject<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
Values
>
shape: SchemaT
}) => z.ZodObject<
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
JsonifiedValues,
$SchemableConfig["values"]
Values
>
dejsonifySchema: (props: {
schema: $SchemableConfig["schema"]
s: $SchemableConfig["schema"]["shape"]
schema: z.ZodObject<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
Values
>
shape: SchemaT
}) => z.ZodObject<
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
$SchemableConfig["values"],
Values,
JsonifiedValues
>
},
) {
type Class<T, Arguments extends unknown[]> = (
C extends ConcreteClass<any>
? ConcreteClass<T, Arguments>
: AbstractClass<T, Arguments>
)
const jsonifySchema = props.jsonifySchema({
schema: class_.schema,
s: class_.schema.shape,
schema: extend.schema,
shape: extend.schema.shape,
})
const dejsonifySchema = props.dejsonifySchema({
schema: class_.schema,
s: class_.schema.shape,
schema: extend.schema,
shape: extend.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
return class extends extend {
static readonly jsonifySchema = jsonifySchema
readonly jsonifySchema = jsonifySchema
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
static readonly dejsonifySchema = dejsonifySchema
readonly dejsonifySchema = dejsonifySchema
jsonify() {
return this.jsonifySchema.parse(this)
@@ -84,15 +112,23 @@ export function makeJsonifiableSchemableClass<
jsonifyEffect() {
return parseZodTypeEffect(this.jsonifySchema, this)
}
} satisfies JsonifiableSchemableClass<typeof $jsonifiableSchemableConfig>
return jsonifiableClass as unknown as (
} as unknown as (
Class<
InstanceType<C> & JsonifiableSchemableObject<typeof $jsonifiableSchemableConfig>,
InstanceType<C> & {
readonly jsonifySchema: z.ZodObject<JsonifySchemaT, JsonifySchemaUnknownKeys, JsonifySchemaCatchall, JsonifiedValues, Values>,
readonly dejsonifySchema: z.ZodObject<DejsonifySchemaT, DejsonifySchemaUnknownKeys, DejsonifySchemaCatchall, Values, JsonifiedValues>,
jsonify(): JsonifiedValues
jsonifyPromise(): Promise<JsonifiedValues>
jsonifyEffect(): Effect.Effect<never, z.ZodError<Values>, JsonifiedValues>
},
ConstructorParameters<C>
> &
StaticMembers<C> &
StaticMembers<JsonifiableSchemableClass<typeof $jsonifiableSchemableConfig>>
)
StaticMembers<C> & {
readonly jsonifySchema: z.ZodObject<JsonifySchemaT, JsonifySchemaUnknownKeys, JsonifySchemaCatchall, JsonifiedValues, Values>,
readonly dejsonifySchema: z.ZodObject<DejsonifySchemaT, DejsonifySchemaUnknownKeys, DejsonifySchemaCatchall, Values, JsonifiedValues>,
}
)
}

View File

@@ -1,18 +1,28 @@
import { Opaque } from "type-fest"
import { z } from "zod"
import { identity } from "../../util"
export const jsonifyBigIntSchema = <S extends z.ZodBigInt>(schema: S) =>
schema.transform(v => v.toString())
export type JsonifiedBigInt = Opaque<string, "@thilawyn/schemable-class/JsonifiedBigInt">
export const dejsonifyBigIntSchema = <S extends z.ZodBigInt>(schema: S) =>
z
.string()
.transform(v => {
try {
return BigInt(v)
}
catch (e) {
return v
}
})
.pipe(schema)
export function jsonifyBigIntSchema<S extends z.ZodBigInt>(schema: S) {
return schema.transform(v => v.toString() as JsonifiedBigInt)
}
export function dejsonifyBigIntSchema<S extends z.ZodBigInt>(schema: S) {
return z
.custom<JsonifiedBigInt>(identity)
.pipe(z
.string()
.transform(v => {
try {
return BigInt(v)
}
catch (e) {
return v
}
})
.pipe(schema)
)
}

View File

@@ -1,18 +1,28 @@
import { Opaque } from "type-fest"
import { z } from "zod"
import { identity } from "../../util"
export const jsonifyDateSchema = <S extends z.ZodDate>(schema: S) =>
schema.transform(v => v.toString())
export type JsonifiedDate = Opaque<string, "@thilawyn/schemable-class/JsonifiedDate">
export const dejsonifyDateSchema = <S extends z.ZodDate>(schema: S) =>
z
.string()
.transform(v => {
try {
return new Date(v)
}
catch (e) {
return v
}
})
.pipe(schema)
export function jsonifyDateSchema<S extends z.ZodDate>(schema: S) {
return schema.transform(v => v.toString() as JsonifiedDate)
}
export function dejsonifyDateSchema<S extends z.ZodDate>(schema: S) {
return z
.custom<JsonifiedDate>(identity)
.pipe(z
.string()
.transform(v => {
try {
return new Date(v)
}
catch (e) {
return v
}
})
.pipe(schema)
)
}

View File

@@ -1,19 +1,33 @@
import { Decimal } from "decimal.js"
import { Opaque } from "type-fest"
import { z } from "zod"
import { identity } from "../../util"
export const jsonifyDecimalSchema = <S extends z.ZodType<Decimal, z.ZodTypeDef, Decimal>>(schema: S) =>
schema.transform(v => v.toJSON())
export type JsonifiedDecimal = Opaque<string, "@thilawyn/schemable-class/JsonifiedDecimal">
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)
export function jsonifyDecimalSchema<
S extends z.ZodType<Decimal, z.ZodTypeDef, Decimal>
>(schema: S) {
return schema.transform(v => v.toJSON() as JsonifiedDecimal)
}
export function dejsonifyDecimalSchema<
S extends z.ZodType<Decimal, z.ZodTypeDef, Decimal>
>(schema: S) {
return z
.custom<JsonifiedDecimal>(identity)
.pipe(z
.string()
.transform(v => {
try {
return new Decimal(v)
}
catch (e) {
return v
}
})
.pipe(schema)
)
}

View File

@@ -1,25 +1,139 @@
import { JsonifiableObject } from "type-fest/source/jsonifiable"
import { z } from "zod"
import { JsonifiableSchemableClass, JsonifiableSchemableConfig } from ".."
import { JsonifiableSchemableClass } 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>>,
export function jsonifySchemableSchema<
C extends JsonifiableSchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
JsonifiedValues,
"Class"
>,
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
JsonifySchemaT extends z.ZodRawShape,
JsonifySchemaUnknownKeys extends z.UnknownKeysParam,
JsonifySchemaCatchall extends z.ZodTypeAny,
DejsonifySchemaT extends z.ZodRawShape,
DejsonifySchemaUnknownKeys extends z.UnknownKeysParam,
DejsonifySchemaCatchall extends z.ZodTypeAny,
JsonifiedValues extends JsonifiableObject,
S extends z.ZodType<InstanceType<C>, z.ZodTypeDef, InstanceType<C>>,
>(
class_: C | JsonifiableSchemableClass<$Config>,
schema: S,
) =>
schema.pipe(class_.jsonifySchema)
class_: C | JsonifiableSchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
// 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>>,
Values,
DefaultValues,
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
JsonifiedValues,
"Class"
>,
schema: S,
) {
return schema.pipe(class_.jsonifySchema)
}
export function dejsonifySchemableSchema<
C extends JsonifiableSchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
JsonifiedValues,
"Class"
>,
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
JsonifySchemaT extends z.ZodRawShape,
JsonifySchemaUnknownKeys extends z.UnknownKeysParam,
JsonifySchemaCatchall extends z.ZodTypeAny,
DejsonifySchemaT extends z.ZodRawShape,
DejsonifySchemaUnknownKeys extends z.UnknownKeysParam,
DejsonifySchemaCatchall extends z.ZodTypeAny,
JsonifiedValues extends JsonifiableObject,
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)
class_: C | JsonifiableSchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
JsonifySchemaT,
JsonifySchemaUnknownKeys,
JsonifySchemaCatchall,
DejsonifySchemaT,
DejsonifySchemaUnknownKeys,
DejsonifySchemaCatchall,
JsonifiedValues,
"Class"
>,
schema: S,
) {
return class_.dejsonifySchema.transform(v => new class_(v)).pipe(schema)
}

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/legacy/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>),
)

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)

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>
}

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/legacy/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/legacy/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)
),
)

View File

@@ -1,49 +1,82 @@
import { AbstractClass, Class as ConcreteClass, Opaque } from "type-fest"
import { z } from "zod"
import { SchemableClass, SchemableConfig } from "."
import { zodObjectRemoveDefaults } from "./util"
import { DefinedDefaultValuesTag } from "."
import { StaticMembers } from "./util"
export function makeSchemableClassFrom<
C extends AbstractClass<{
schema?: never
defaultValues?: never
}, []> & {
schema?: never
defaultValues?: never
},
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
>(
extend: C,
{ schema, defaultValues }: {
schema: z.ZodObject<SchemaT, SchemaUnknownKeys, SchemaCatchall, Values, Values>
defaultValues: Opaque<DefaultValues, DefinedDefaultValuesTag>
},
) {
type Class<T, Arguments extends unknown[]> = (
C extends ConcreteClass<any>
? ConcreteClass<T, Arguments>
: AbstractClass<T, Arguments>
)
return class extends (extend as unknown as ConcreteClass<any, []>) {
static readonly schema = schema
readonly schema = schema
static readonly defaultValues = defaultValues
readonly defaultValues = defaultValues
constructor(values: Values) {
super()
Object.assign(this, values)
}
} as unknown as (
Class<
InstanceType<C> &
{
readonly schema: z.ZodObject<SchemaT, SchemaUnknownKeys, SchemaCatchall, Values, Values>,
readonly defaultValues: DefaultValues,
} &
Values,
Parameters<(values: Values) => void>
> &
StaticMembers<C> &
{
readonly schema: z.ZodObject<SchemaT, SchemaUnknownKeys, SchemaCatchall, Values, Values>,
readonly defaultValues: DefaultValues,
}
)
}
export function makeSchemableClass<
SchemaWithDefaultValuesT extends z.ZodRawShape,
SchemaWithDefaultValuesUnknownKeys extends z.UnknownKeysParam,
SchemaWithDefaultValuesCatchall extends z.ZodTypeAny,
SchemaWithDefaultValuesOutput extends SchemaWithDefaultValuesInput, // TODO: apply "StripSchemaInputDefaults"?
SchemaWithDefaultValuesInput extends {},
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
>(
{
schema: schemaWithDefaultValues
}: {
schema: z.ZodObject<
SchemaWithDefaultValuesT,
SchemaWithDefaultValuesUnknownKeys,
SchemaWithDefaultValuesCatchall,
SchemaWithDefaultValuesOutput,
SchemaWithDefaultValuesInput
>
props: {
schema: z.ZodObject<SchemaT, SchemaUnknownKeys, SchemaCatchall, Values, Values>
defaultValues: Opaque<DefaultValues, DefinedDefaultValuesTag>
}
) {
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>
return makeSchemableClassFrom(Object, props)
}

View File

@@ -1,77 +1,127 @@
import { Effect, pipe } from "effect"
import { HasRequiredKeys } from "type-fest"
import { z } from "zod"
import { SchemableClass, SchemableConfig } from "."
import { SchemableClass, SchemableClassInput } from "."
import { parseZodTypeEffect } from "./util"
type ParamsArgs = [] | [Partial<z.ParseParams>]
type ParamsArgs = [] | [params: Partial<z.ParseParams>]
type NewSchemableArgs<Input extends object> =
HasRequiredKeys<Input> extends true
? [Input, ...ParamsArgs]
: [] | [Input, ...ParamsArgs]
? [values: Input, ...args: ParamsArgs]
: [] | [values: Input, ...args: 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,
export function newSchemable<
C extends SchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
"Class"
>,
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
>(
class_: C | SchemableClass<$Config>,
...[values, params]: NewSchemableArgs<$Config["input"]>
) =>
new class_(class_.schemaWithDefaultValues.parse(values || {}, params)) as InstanceType<C>
class_: C | SchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
"Class"
>,
...[values, params]: NewSchemableArgs<
SchemableClassInput<Values, DefaultValues>
>
) {
return new class_(
class_.schema.parse({ ...class_.defaultValues, ...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,
export async function newSchemablePromise<
C extends SchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
"Class"
>,
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
>(
class_: C | SchemableClass<$Config>,
...[values, params]: NewSchemableArgs<$Config["input"]>
) =>
new class_(await class_.schemaWithDefaultValues.parseAsync(values || {}, params)) as InstanceType<C>
class_: C | SchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
"Class"
>,
...[values, params]: NewSchemableArgs<
SchemableClassInput<Values, DefaultValues>
>
) {
return new class_(
await class_.schema.parseAsync({ ...class_.defaultValues, ...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,
export function newSchemableEffect<
C extends SchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
"Class"
>,
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
>(
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,
),
class_: C | SchemableClass<
SchemaT,
SchemaUnknownKeys,
SchemaCatchall,
Values,
DefaultValues,
"Class"
>,
Effect.map(values => new class_(values) as InstanceType<C>),
)
...[values, params]: NewSchemableArgs<
SchemableClassInput<Values, DefaultValues>
>
) {
return pipe(
parseZodTypeEffect(
class_.schema,
{ ...class_.defaultValues, ...values },
params,
),
Effect.map(values => new class_(values) as InstanceType<C>),
)
}

1
src/observable/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from "./makeSchemableClassObservable"

View File

@@ -0,0 +1,29 @@
import { mapValues } from "lodash-es"
import { makeObservable, observable } from "mobx"
import { AbstractConstructor } from "type-fest"
import { z } from "zod"
import { SchemableClass } from ".."
export function makeSchemableClassObservable<
C extends SchemableClass<SchemaT, SchemaUnknownKeys, SchemaCatchall, Values, DefaultValues>,
SchemaT extends z.ZodRawShape,
SchemaUnknownKeys extends z.UnknownKeysParam,
SchemaCatchall extends z.ZodTypeAny,
Values extends {},
DefaultValues extends Partial<Values>,
>(
extend: C | SchemableClass<SchemaT, SchemaUnknownKeys, SchemaCatchall, Values, DefaultValues>
) {
return class extends (extend as AbstractConstructor<any>) {
constructor(...args: any[]) {
super(...args)
makeObservable(this,
mapValues(this.schema.shape, () => observable)
)
}
} as unknown as C
}

View File

@@ -1,64 +1,63 @@
import { pipeInto } from "ts-functional-pipe"
import { z } from "zod"
import { makeSchemableClass, newSchemable } from "."
import { dejsonifyBigIntSchema, dejsonifySchemable, dejsonifySchemableSchema, jsonifyBigIntSchema, jsonifySchemableSchema, makeJsonifiableSchemableClass } from "./jsonifiable"
import { defineDefaultValues, extendSchemableClass, makeSchemableClass, newSchemable } from "."
import { dejsonifyBigIntSchema, dejsonifySchemable, jsonifyBigIntSchema, makeJsonifiableSchemableClass } from "./jsonifiable"
import { makeSchemableClassObservable } from "./observable"
const GroupSchema = z.object({
/** Group ID */
id: z.bigint(),
const UserLevel = z.enum(["User", "Admin"])
/** Group name */
name: z.string(),
})
const GroupSchemableObject = makeSchemableClass({ schema: GroupSchema })
class User extends pipeInto(
makeSchemableClass({
schema: z.object({
id: z.bigint(),
name: z.string(),
level: UserLevel,
}),
const GroupJsonifiableSchemableObject = makeJsonifiableSchemableClass(GroupSchemableObject, {
jsonifySchema: ({ schema, s }) => schema.extend({
id: jsonifyBigIntSchema(s.id)
defaultValues: defineDefaultValues({
level: "User" as const
}),
}),
dejsonifySchema: ({ schema, s }) => schema.extend({
id: dejsonifyBigIntSchema(s.id)
v => makeSchemableClassObservable(v),
v => makeJsonifiableSchemableClass(v, {
jsonifySchema: ({ schema, shape }) => schema.extend({
id: jsonifyBigIntSchema(shape.id)
}),
dejsonifySchema: ({ schema, shape }) => schema.extend({
id: dejsonifyBigIntSchema(shape.id)
}),
}),
) {}
User.schema
const user1 = newSchemable(User, { id: 1n, name: "User" })
user1.schema
const jsonifiedUser1 = user1.jsonify()
console.log(jsonifiedUser1)
console.log(dejsonifySchemable(User, jsonifiedUser1))
const UserWithPhone = extendSchemableClass(User, {
schema: ({ schema }) => schema.extend({
phone: z.string()
}),
defaultValues: defaultValues => defineDefaultValues({
...defaultValues,
phone: "+33600000000",
}),
})
class Group extends GroupJsonifiableSchemableObject {}
UserWithPhone.defaultValues
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)
// const user2 = newSchemable(UserWithPhone, { id: 1n, name: "User" })
// console.log(user2.jsonify())

View File

@@ -1,8 +1,28 @@
import { Effect, pipe } from "effect"
import { mapValues } from "lodash-es"
import { AbstractClass, Class as ConcreteClass } from "type-fest"
import { z } from "zod"
export function identity<T>(value: T) {
return value
}
export type ClassType = "AbstractClass" | "Class"
export type Class<
Type extends ClassType,
T,
Arguments extends unknown[] = any[],
> = (
Type extends "AbstractClass"
? AbstractClass<T, Arguments>
: Type extends "Class"
? ConcreteClass<T, Arguments>
: never
)
/**
* Represents the static members of a class.
* @template C - The class type.
@@ -12,52 +32,6 @@ export type StaticMembers<C> = {
}
/**
* 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.
*