diff --git a/bun.lockb b/bun.lockb index 4e23eb8..a8be37e 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 547eb5f..2ab619a 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/rollup.config.ts b/rollup.config.ts index 8625946..292a90c 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -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"), ] diff --git a/src/SchemableClass.ts b/src/SchemableClass.ts index f85216c..f54a32f 100644 --- a/src/SchemableClass.ts +++ b/src/SchemableClass.ts @@ -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, + + 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, +> = { + [Key in Exclude]: Values[Key] +} & { + [Key in keyof DefaultValues]?: Key extends keyof Values + ? Values[Key] + : never +} diff --git a/src/defineDefaultValues.ts b/src/defineDefaultValues.ts new file mode 100644 index 0000000..634d83a --- /dev/null +++ b/src/defineDefaultValues.ts @@ -0,0 +1,8 @@ +import { Opaque } from "type-fest" + + +export type DefinedDefaultValuesTag = "@thilawyn/schemable-class/DefinedDefaultValues" + +export function defineDefaultValues(values: T) { + return values as Opaque +} diff --git a/src/extendSchemableClass.ts b/src/extendSchemableClass.ts new file mode 100644 index 0000000..bb392e1 --- /dev/null +++ b/src/extendSchemableClass.ts @@ -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, + + SchemaT extends z.ZodRawShape, + SchemaUnknownKeys extends z.UnknownKeysParam, + SchemaCatchall extends z.ZodTypeAny, + SchemaValues extends ExtendSchemaValues, + + DefaultValues extends Partial, +>( + 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 + }, +) { + type Class = ( + C extends ConcreteClass + ? ConcreteClass + : AbstractClass + ) + + 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, "schema" | "defaultValues" | keyof ExtendSchemaValues> & + { + readonly schema: z.ZodObject, + readonly defaultValues: DefaultValues, + } & + SchemaValues, + + Parameters<(values: SchemaValues) => void> + > & + + Omit, "schema" | "defaultValues"> & + { + readonly schema: z.ZodObject, + readonly defaultValues: DefaultValues, + } + ) +} diff --git a/src/index.ts b/src/index.ts index 17d8b56..4b4c248 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ export * from "./SchemableClass" +export * from "./defineDefaultValues" +export * from "./extendSchemableClass" export * from "./makeSchemableClass" export * from "./newSchemable" diff --git a/src/jsonifiable/JsonifiableSchemableClass.ts b/src/jsonifiable/JsonifiableSchemableClass.ts index c0dc3c0..da145d6 100644 --- a/src/jsonifiable/JsonifiableSchemableClass.ts +++ b/src/jsonifiable/JsonifiableSchemableClass.ts @@ -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, + + 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 + jsonifyEffect(): Effect.Effect, 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, $Config["jsonifiedValues"]> -} diff --git a/src/jsonifiable/dejsonifySchemable.ts b/src/jsonifiable/dejsonifySchemable.ts index 35ceafb..b8e7f80 100644 --- a/src/jsonifiable/dejsonifySchemable.ts +++ b/src/jsonifiable/dejsonifySchemable.ts @@ -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, + + 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, -) => - new class_(class_.dejsonifySchema.parse(values, params)) as InstanceType +) { + return new class_( + class_.dejsonifySchema.parse(values, params) + ) as InstanceType +} -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, + + 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, -) => - new class_(await class_.dejsonifySchema.parseAsync(values, params)) as InstanceType +) { + return new class_( + await class_.dejsonifySchema.parseAsync(values, params) + ) as InstanceType +} -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, + + 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, -) => pipe( - parseZodTypeEffect< - z.output, - z.input - >( - class_.dejsonifySchema, - values, - params, - ), + class_: C | JsonifiableSchemableClass< + SchemaT, + SchemaUnknownKeys, + SchemaCatchall, - Effect.map(values => new class_(values) as InstanceType), -) + Values, + DefaultValues, + + JsonifySchemaT, + JsonifySchemaUnknownKeys, + JsonifySchemaCatchall, + + DejsonifySchemaT, + DejsonifySchemaUnknownKeys, + DejsonifySchemaCatchall, + + JsonifiedValues, + + "Class" + >, + + values: JsonifiedValues, + params?: Partial, +) { + return pipe( + parseZodTypeEffect(class_.dejsonifySchema, values, params), + Effect.map(values => new class_(values) as InstanceType), + ) +} diff --git a/src/jsonifiable/makeJsonifiableSchemableClass.ts b/src/jsonifiable/makeJsonifiableSchemableClass.ts index a956b0b..691a3f2 100644 --- a/src/jsonifiable/makeJsonifiableSchemableClass.ts +++ b/src/jsonifiable/makeJsonifiableSchemableClass.ts @@ -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, 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 = ( + C extends ConcreteClass + ? ConcreteClass + : AbstractClass + ) 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 - - return jsonifiableClass as unknown as ( + } as unknown as ( Class< - InstanceType & JsonifiableSchemableObject, + InstanceType & { + readonly jsonifySchema: z.ZodObject, + readonly dejsonifySchema: z.ZodObject, + + jsonify(): JsonifiedValues + jsonifyPromise(): Promise + jsonifyEffect(): Effect.Effect, JsonifiedValues> + }, + ConstructorParameters > & - StaticMembers & - StaticMembers> - ) + StaticMembers & { + readonly jsonifySchema: z.ZodObject, + readonly dejsonifySchema: z.ZodObject, + } + ) } diff --git a/src/jsonifiable/schema/bigint.ts b/src/jsonifiable/schema/bigint.ts index b0f0093..76a605d 100644 --- a/src/jsonifiable/schema/bigint.ts +++ b/src/jsonifiable/schema/bigint.ts @@ -1,18 +1,28 @@ +import { Opaque } from "type-fest" import { z } from "zod" +import { identity } from "../../util" -export const jsonifyBigIntSchema = (schema: S) => - schema.transform(v => v.toString()) +export type JsonifiedBigInt = Opaque -export const dejsonifyBigIntSchema = (schema: S) => - z - .string() - .transform(v => { - try { - return BigInt(v) - } - catch (e) { - return v - } - }) - .pipe(schema) + +export function jsonifyBigIntSchema(schema: S) { + return schema.transform(v => v.toString() as JsonifiedBigInt) +} + +export function dejsonifyBigIntSchema(schema: S) { + return z + .custom(identity) + .pipe(z + .string() + .transform(v => { + try { + return BigInt(v) + } + catch (e) { + return v + } + }) + .pipe(schema) + ) +} diff --git a/src/jsonifiable/schema/date.ts b/src/jsonifiable/schema/date.ts index b5ac677..c70b2ed 100644 --- a/src/jsonifiable/schema/date.ts +++ b/src/jsonifiable/schema/date.ts @@ -1,18 +1,28 @@ +import { Opaque } from "type-fest" import { z } from "zod" +import { identity } from "../../util" -export const jsonifyDateSchema = (schema: S) => - schema.transform(v => v.toString()) +export type JsonifiedDate = Opaque -export const dejsonifyDateSchema = (schema: S) => - z - .string() - .transform(v => { - try { - return new Date(v) - } - catch (e) { - return v - } - }) - .pipe(schema) + +export function jsonifyDateSchema(schema: S) { + return schema.transform(v => v.toString() as JsonifiedDate) +} + +export function dejsonifyDateSchema(schema: S) { + return z + .custom(identity) + .pipe(z + .string() + .transform(v => { + try { + return new Date(v) + } + catch (e) { + return v + } + }) + .pipe(schema) + ) +} diff --git a/src/jsonifiable/schema/decimal.ts b/src/jsonifiable/schema/decimal.ts index 58c492d..be387ee 100644 --- a/src/jsonifiable/schema/decimal.ts +++ b/src/jsonifiable/schema/decimal.ts @@ -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 = >(schema: S) => - schema.transform(v => v.toJSON()) +export type JsonifiedDecimal = Opaque -export const dejsonifyDecimalSchema = >(schema: S) => - z - .string() - .transform(v => { - try { - return new Decimal(v) - } - catch (e) { - return v - } - }) - .pipe(schema) + +export function jsonifyDecimalSchema< + S extends z.ZodType +>(schema: S) { + return schema.transform(v => v.toJSON() as JsonifiedDecimal) +} + +export function dejsonifyDecimalSchema< + S extends z.ZodType +>(schema: S) { + return z + .custom(identity) + .pipe(z + .string() + .transform(v => { + try { + return new Decimal(v) + } + catch (e) { + return v + } + }) + .pipe(schema) + ) +} diff --git a/src/jsonifiable/schema/schemable.ts b/src/jsonifiable/schema/schemable.ts index c6e2a2d..b0a72bc 100644 --- a/src/jsonifiable/schema/schemable.ts +++ b/src/jsonifiable/schema/schemable.ts @@ -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, z.ZodTypeDef, InstanceType>, +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, + + 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, z.ZodTypeDef, InstanceType>, >( - 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, z.ZodTypeDef, InstanceType>, + 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, + + 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, z.ZodTypeDef, InstanceType>, >( - 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) +} diff --git a/src/legacy/SchemableClass.ts b/src/legacy/SchemableClass.ts new file mode 100644 index 0000000..f85216c --- /dev/null +++ b/src/legacy/SchemableClass.ts @@ -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"] +) diff --git a/src/legacy/index.ts b/src/legacy/index.ts new file mode 100644 index 0000000..17d8b56 --- /dev/null +++ b/src/legacy/index.ts @@ -0,0 +1,3 @@ +export * from "./SchemableClass" +export * from "./makeSchemableClass" +export * from "./newSchemable" diff --git a/src/legacy/jsonifiable/JsonifiableSchemableClass.ts b/src/legacy/jsonifiable/JsonifiableSchemableClass.ts new file mode 100644 index 0000000..c0dc3c0 --- /dev/null +++ b/src/legacy/jsonifiable/JsonifiableSchemableClass.ts @@ -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, $Config["jsonifiedValues"]> +} diff --git a/src/legacy/jsonifiable/dejsonifySchemable.ts b/src/legacy/jsonifiable/dejsonifySchemable.ts new file mode 100644 index 0000000..35ceafb --- /dev/null +++ b/src/legacy/jsonifiable/dejsonifySchemable.ts @@ -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, +) => + new class_(class_.dejsonifySchema.parse(values, params)) as InstanceType + + +export const dejsonifySchemablePromise = async < + C extends JsonifiableSchemableClass<$Config>, + $Config extends JsonifiableSchemableConfig, +>( + class_: C | JsonifiableSchemableClass<$Config>, + values: $Config["jsonifiedValues"], + params?: Partial, +) => + new class_(await class_.dejsonifySchema.parseAsync(values, params)) as InstanceType + + +export const dejsonifySchemableEffect = < + C extends JsonifiableSchemableClass<$Config>, + $Config extends JsonifiableSchemableConfig, +>( + class_: C | JsonifiableSchemableClass<$Config>, + values: $Config["jsonifiedValues"], + params?: Partial, +) => pipe( + parseZodTypeEffect< + z.output, + z.input + >( + class_.dejsonifySchema, + values, + params, + ), + + Effect.map(values => new class_(values) as InstanceType), +) diff --git a/src/legacy/jsonifiable/index.ts b/src/legacy/jsonifiable/index.ts new file mode 100644 index 0000000..74e7d23 --- /dev/null +++ b/src/legacy/jsonifiable/index.ts @@ -0,0 +1,4 @@ +export * from "./JsonifiableSchemableClass" +export * from "./dejsonifySchemable" +export * from "./makeJsonifiableSchemableClass" +export * from "./schema" diff --git a/src/legacy/jsonifiable/makeJsonifiableSchemableClass.ts b/src/legacy/jsonifiable/makeJsonifiableSchemableClass.ts new file mode 100644 index 0000000..a956b0b --- /dev/null +++ b/src/legacy/jsonifiable/makeJsonifiableSchemableClass.ts @@ -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 + + return jsonifiableClass as unknown as ( + Class< + InstanceType & JsonifiableSchemableObject, + ConstructorParameters + > & + StaticMembers & + StaticMembers> + ) + +} diff --git a/src/legacy/jsonifiable/schema/bigint.ts b/src/legacy/jsonifiable/schema/bigint.ts new file mode 100644 index 0000000..b0f0093 --- /dev/null +++ b/src/legacy/jsonifiable/schema/bigint.ts @@ -0,0 +1,18 @@ +import { z } from "zod" + + +export const jsonifyBigIntSchema = (schema: S) => + schema.transform(v => v.toString()) + +export const dejsonifyBigIntSchema = (schema: S) => + z + .string() + .transform(v => { + try { + return BigInt(v) + } + catch (e) { + return v + } + }) + .pipe(schema) diff --git a/src/legacy/jsonifiable/schema/date.ts b/src/legacy/jsonifiable/schema/date.ts new file mode 100644 index 0000000..b5ac677 --- /dev/null +++ b/src/legacy/jsonifiable/schema/date.ts @@ -0,0 +1,18 @@ +import { z } from "zod" + + +export const jsonifyDateSchema = (schema: S) => + schema.transform(v => v.toString()) + +export const dejsonifyDateSchema = (schema: S) => + z + .string() + .transform(v => { + try { + return new Date(v) + } + catch (e) { + return v + } + }) + .pipe(schema) diff --git a/src/legacy/jsonifiable/schema/decimal.ts b/src/legacy/jsonifiable/schema/decimal.ts new file mode 100644 index 0000000..58c492d --- /dev/null +++ b/src/legacy/jsonifiable/schema/decimal.ts @@ -0,0 +1,19 @@ +import { Decimal } from "decimal.js" +import { z } from "zod" + + +export const jsonifyDecimalSchema = >(schema: S) => + schema.transform(v => v.toJSON()) + +export const dejsonifyDecimalSchema = >(schema: S) => + z + .string() + .transform(v => { + try { + return new Decimal(v) + } + catch (e) { + return v + } + }) + .pipe(schema) diff --git a/src/legacy/jsonifiable/schema/index.ts b/src/legacy/jsonifiable/schema/index.ts new file mode 100644 index 0000000..08ecef8 --- /dev/null +++ b/src/legacy/jsonifiable/schema/index.ts @@ -0,0 +1,4 @@ +export * from "./bigint" +export * from "./date" +export * from "./decimal" +export * from "./schemable" diff --git a/src/legacy/jsonifiable/schema/schemable.ts b/src/legacy/jsonifiable/schema/schemable.ts new file mode 100644 index 0000000..c6e2a2d --- /dev/null +++ b/src/legacy/jsonifiable/schema/schemable.ts @@ -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, z.ZodTypeDef, InstanceType>, +>( + 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, z.ZodTypeDef, InstanceType>, +>( + class_: C | JsonifiableSchemableClass<$Config>, + schema: S, +) => + class_.dejsonifySchema.transform(v => new class_(v)).pipe(schema) diff --git a/src/legacy/makeSchemableClass.ts b/src/legacy/makeSchemableClass.ts new file mode 100644 index 0000000..88bcf72 --- /dev/null +++ b/src/legacy/makeSchemableClass.ts @@ -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, + input: undefined as unknown as z.input, + 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) { + Object.assign(this, data) + } + } as SchemableClass + +} diff --git a/src/legacy/newSchemable.ts b/src/legacy/newSchemable.ts new file mode 100644 index 0000000..da0be36 --- /dev/null +++ b/src/legacy/newSchemable.ts @@ -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] + +type NewSchemableArgs = + HasRequiredKeys 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 + + +/** + * 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 + + +/** + * 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, + z.input + >( + class_.schemaWithDefaultValues, + values || {}, + params, + ), + + Effect.map(values => new class_(values) as InstanceType), +) diff --git a/src/legacy/tests.ts b/src/legacy/tests.ts new file mode 100644 index 0000000..c19daf7 --- /dev/null +++ b/src/legacy/tests.ts @@ -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) diff --git a/src/legacy/util.ts b/src/legacy/util.ts new file mode 100644 index 0000000..2cf7b08 --- /dev/null +++ b/src/legacy/util.ts @@ -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 = { + [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 + ? 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, + ...args: Parameters +) => pipe( + Effect.promise(() => schema.safeParseAsync(...args)), + + Effect.flatMap(response => + response.success + ? Effect.succeed(response.data) + : Effect.fail(response.error) + ), +) diff --git a/src/makeSchemableClass.ts b/src/makeSchemableClass.ts index 88bcf72..0345eb7 100644 --- a/src/makeSchemableClass.ts +++ b/src/makeSchemableClass.ts @@ -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, +>( + extend: C, + + { schema, defaultValues }: { + schema: z.ZodObject + defaultValues: Opaque + }, +) { + type Class = ( + C extends ConcreteClass + ? ConcreteClass + : AbstractClass + ) + + return class extends (extend as unknown as ConcreteClass) { + 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 & + { + readonly schema: z.ZodObject, + readonly defaultValues: DefaultValues, + } & + Values, + + Parameters<(values: Values) => void> + > & + + StaticMembers & + { + readonly schema: z.ZodObject, + 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, >( - { - schema: schemaWithDefaultValues - }: { - schema: z.ZodObject< - SchemaWithDefaultValuesT, - SchemaWithDefaultValuesUnknownKeys, - SchemaWithDefaultValuesCatchall, - SchemaWithDefaultValuesOutput, - SchemaWithDefaultValuesInput - > + props: { + schema: z.ZodObject + defaultValues: Opaque } ) { - - const schema = zodObjectRemoveDefaults(schemaWithDefaultValues) - - const $schemableConfig = { - values: undefined as unknown as z.output, - input: undefined as unknown as z.input, - 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) { - Object.assign(this, data) - } - } as SchemableClass - + return makeSchemableClassFrom(Object, props) } diff --git a/src/newSchemable.ts b/src/newSchemable.ts index da0be36..98a5498 100644 --- a/src/newSchemable.ts +++ b/src/newSchemable.ts @@ -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] +type ParamsArgs = [] | [params: Partial] type NewSchemableArgs = HasRequiredKeys 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, >( - class_: C | SchemableClass<$Config>, - ...[values, params]: NewSchemableArgs<$Config["input"]> -) => - new class_(class_.schemaWithDefaultValues.parse(values || {}, params)) as InstanceType + class_: C | SchemableClass< + SchemaT, + SchemaUnknownKeys, + SchemaCatchall, + Values, + DefaultValues, + "Class" + >, + + ...[values, params]: NewSchemableArgs< + SchemableClassInput + > +) { + return new class_( + class_.schema.parse({ ...class_.defaultValues, ...values }, params) + ) as InstanceType +} -/** - * 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, >( - class_: C | SchemableClass<$Config>, - ...[values, params]: NewSchemableArgs<$Config["input"]> -) => - new class_(await class_.schemaWithDefaultValues.parseAsync(values || {}, params)) as InstanceType + class_: C | SchemableClass< + SchemaT, + SchemaUnknownKeys, + SchemaCatchall, + Values, + DefaultValues, + "Class" + >, + + ...[values, params]: NewSchemableArgs< + SchemableClassInput + > +) { + return new class_( + await class_.schema.parseAsync({ ...class_.defaultValues, ...values }, params) + ) as InstanceType +} -/** - * 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, >( - class_: C | SchemableClass<$Config>, - ...[values, params]: NewSchemableArgs<$Config["input"]> -) => pipe( - parseZodTypeEffect< - z.output, - z.input - >( - class_.schemaWithDefaultValues, - values || {}, - params, - ), + class_: C | SchemableClass< + SchemaT, + SchemaUnknownKeys, + SchemaCatchall, + Values, + DefaultValues, + "Class" + >, - Effect.map(values => new class_(values) as InstanceType), -) + ...[values, params]: NewSchemableArgs< + SchemableClassInput + > +) { + return pipe( + parseZodTypeEffect( + class_.schema, + { ...class_.defaultValues, ...values }, + params, + ), + + Effect.map(values => new class_(values) as InstanceType), + ) +} diff --git a/src/observable/index.ts b/src/observable/index.ts new file mode 100644 index 0000000..6f219a5 --- /dev/null +++ b/src/observable/index.ts @@ -0,0 +1 @@ +export * from "./makeSchemableClassObservable" diff --git a/src/observable/makeSchemableClassObservable.ts b/src/observable/makeSchemableClassObservable.ts new file mode 100644 index 0000000..7b5fcb7 --- /dev/null +++ b/src/observable/makeSchemableClassObservable.ts @@ -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 extends z.ZodRawShape, + SchemaUnknownKeys extends z.UnknownKeysParam, + SchemaCatchall extends z.ZodTypeAny, + + Values extends {}, + DefaultValues extends Partial, +>( + extend: C | SchemableClass +) { + return class extends (extend as AbstractConstructor) { + constructor(...args: any[]) { + super(...args) + + makeObservable(this, + mapValues(this.schema.shape, () => observable) + ) + } + } as unknown as C +} diff --git a/src/tests.ts b/src/tests.ts index c19daf7..6015710 100644 --- a/src/tests.ts +++ b/src/tests.ts @@ -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()) diff --git a/src/util.ts b/src/util.ts index 2cf7b08..a1c11d4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -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(value: T) { + return value +} + + +export type ClassType = "AbstractClass" | "Class" + +export type Class< + Type extends ClassType, + T, + Arguments extends unknown[] = any[], +> = ( + Type extends "AbstractClass" + ? AbstractClass + : Type extends "Class" + ? ConcreteClass + : never +) + + /** * Represents the static members of a class. * @template C - The class type. @@ -12,52 +32,6 @@ export type StaticMembers = { } -/** - * 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 - ? 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. *