0.1.0 #1
110
.drone.jsonnet
Normal file
110
.drone.jsonnet
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
local bun_image = "oven/bun:1";
|
||||||
|
local node_image = "node:20";
|
||||||
|
|
||||||
|
|
||||||
|
local install_step = {
|
||||||
|
name: "install",
|
||||||
|
image: bun_image,
|
||||||
|
commands: ["bun install --frozen-lockfile"],
|
||||||
|
};
|
||||||
|
|
||||||
|
local lint_step = {
|
||||||
|
name: "lint",
|
||||||
|
image: bun_image,
|
||||||
|
commands: ["bun lint:tsc"],
|
||||||
|
};
|
||||||
|
|
||||||
|
local build_step = {
|
||||||
|
name: "build",
|
||||||
|
image: bun_image,
|
||||||
|
commands: ["bun run build"],
|
||||||
|
};
|
||||||
|
|
||||||
|
local pack_step = {
|
||||||
|
name: "pack",
|
||||||
|
image: node_image,
|
||||||
|
commands: ["npm pack --dry-run"],
|
||||||
|
};
|
||||||
|
|
||||||
|
local publish_step = {
|
||||||
|
name: "publish",
|
||||||
|
image: node_image,
|
||||||
|
|
||||||
|
environment: {
|
||||||
|
NPM_TOKEN: { from_secret: "npm_token" }
|
||||||
|
},
|
||||||
|
|
||||||
|
commands: [
|
||||||
|
"npm set @thilawyn:registry https://git.jvalver.de/api/packages/thilawyn/npm/",
|
||||||
|
"npm config set -- //git.jvalver.de/api/packages/thilawyn/npm/:_authToken $NPM_TOKEN",
|
||||||
|
"npm publish",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
[
|
||||||
|
// Lint the whole project when not in master, not in a PR nor on a tag
|
||||||
|
{
|
||||||
|
kind: "pipeline",
|
||||||
|
type: "docker",
|
||||||
|
name: "lint",
|
||||||
|
|
||||||
|
trigger: {
|
||||||
|
ref: {
|
||||||
|
exclude: [
|
||||||
|
"refs/heads/master",
|
||||||
|
"refs/pull/**",
|
||||||
|
"refs/tags/**",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
steps: [
|
||||||
|
install_step,
|
||||||
|
lint_step,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Build the package without publishing for pull requests
|
||||||
|
{
|
||||||
|
kind: "pipeline",
|
||||||
|
type: "docker",
|
||||||
|
name: "build",
|
||||||
|
|
||||||
|
trigger: {
|
||||||
|
ref: {
|
||||||
|
include: ["refs/pull/**"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
steps: [
|
||||||
|
install_step,
|
||||||
|
lint_step,
|
||||||
|
build_step,
|
||||||
|
pack_step,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Build and publish the package for master and tags
|
||||||
|
{
|
||||||
|
kind: "pipeline",
|
||||||
|
type: "docker",
|
||||||
|
name: "build-publish",
|
||||||
|
|
||||||
|
trigger: {
|
||||||
|
ref: {
|
||||||
|
include: [
|
||||||
|
"refs/heads/master",
|
||||||
|
"refs/tags/**",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
steps: [
|
||||||
|
install_step,
|
||||||
|
lint_step,
|
||||||
|
build_step,
|
||||||
|
publish_step,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -129,4 +129,3 @@ dist
|
|||||||
.yarn/build-state.yml
|
.yarn/build-state.yml
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
|||||||
1
.npmrc
Normal file
1
.npmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@thilawyn:registry=https://git.jvalver.de/api/packages/thilawyn/npm/
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
# schemable-classes
|
# schemable-class
|
||||||
|
|
||||||
Create TypeScript classes out of Zod schemas
|
Create TypeScript classes out of Zod schemas
|
||||||
|
|||||||
2
bunfig.toml
Normal file
2
bunfig.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[install.scopes]
|
||||||
|
"@thilawyn" = "https://git.jvalver.de/api/packages/thilawyn/npm/"
|
||||||
59
package.json
Normal file
59
package.json
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"name": "@thilawyn/schemable-class",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "https://git.jvalver.de/api/packages/thilawyn/npm/"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"./dist"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": {
|
||||||
|
"types": "./dist/schemable.d.mts",
|
||||||
|
"default": "./dist/schemable.mjs"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./dist/schemable.d.cts",
|
||||||
|
"default": "./dist/schemable.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"./jsonifiable": {
|
||||||
|
"import": {
|
||||||
|
"types": "./dist/jsonifiable.d.mts",
|
||||||
|
"default": "./dist/jsonifiable.mjs"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./dist/jsonifiable.d.cts",
|
||||||
|
"default": "./dist/jsonifiable.cjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "rollup -c rollup.config.ts",
|
||||||
|
"lint:tsc": "tsc --noEmit",
|
||||||
|
"clean:cache": "rm -f tsconfig.tsbuildinfo",
|
||||||
|
"clean:dist": "rm -rf dist",
|
||||||
|
"clean:node": "rm -rf node_modules"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"decimal.js": "^10.4.3",
|
||||||
|
"effect": "^2.0.0-next.62",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"type-fest": "^4.9.0",
|
||||||
|
"zod": "^3.22.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"bun-types": "latest",
|
||||||
|
"npm-check-updates": "^16.14.12",
|
||||||
|
"npm-sort": "^0.0.4",
|
||||||
|
"rollup": "^4.9.1",
|
||||||
|
"rollup-plugin-cleanup": "^3.2.1",
|
||||||
|
"rollup-plugin-ts": "^3.4.5",
|
||||||
|
"tsx": "^4.7.0",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
43
rollup.config.ts
Normal file
43
rollup.config.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { nodeResolve } from "@rollup/plugin-node-resolve"
|
||||||
|
import { defineConfig } from "rollup"
|
||||||
|
import cleanup from "rollup-plugin-cleanup"
|
||||||
|
import ts from "rollup-plugin-ts"
|
||||||
|
import pkg from "./package.json" assert { type: "json" }
|
||||||
|
|
||||||
|
|
||||||
|
export const createBundleConfig = (
|
||||||
|
input: string,
|
||||||
|
name: keyof typeof pkg.exports,
|
||||||
|
) =>
|
||||||
|
defineConfig({
|
||||||
|
input,
|
||||||
|
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
file: pkg.exports[name].import.default,
|
||||||
|
format: "esm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: pkg.exports[name].require.default,
|
||||||
|
format: "cjs",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
external: id => !/^[./]/.test(id),
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
nodeResolve(),
|
||||||
|
ts(),
|
||||||
|
|
||||||
|
cleanup({
|
||||||
|
comments: "jsdoc",
|
||||||
|
extensions: ["ts"],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export default [
|
||||||
|
createBundleConfig("src/index.ts", "."),
|
||||||
|
createBundleConfig("src/jsonifiable/index.ts", "./jsonifiable"),
|
||||||
|
]
|
||||||
90
src/SchemableClass.ts
Normal file
90
src/SchemableClass.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { Class } from "type-fest"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for creating a schemable object with validation schemas.
|
||||||
|
* @template Values - The type representing the expected values.
|
||||||
|
* @template Input - The type representing the input values.
|
||||||
|
* @template SchemaT - The type representing the base validation schema.
|
||||||
|
* @template SchemaUnknownKeys - The type representing the unknown keys behavior in the base validation schema.
|
||||||
|
* @template SchemaCatchall - The type representing the catchall behavior in the base validation schema.
|
||||||
|
* @template SchemaWithDefaultValuesT - The type representing the validation schema with default values.
|
||||||
|
* @template SchemaWithDefaultValuesUnknownKeys - The type representing the unknown keys behavior in the validation schema with default values.
|
||||||
|
* @template SchemaWithDefaultValuesCatchall - The type representing the catchall behavior in the validation schema with default values.
|
||||||
|
*/
|
||||||
|
export type SchemableConfig<
|
||||||
|
Values extends {} = {},
|
||||||
|
Input extends {} = {},
|
||||||
|
|
||||||
|
SchemaT extends z.ZodRawShape = z.ZodRawShape,
|
||||||
|
SchemaUnknownKeys extends z.UnknownKeysParam = z.UnknownKeysParam,
|
||||||
|
SchemaCatchall extends z.ZodTypeAny = z.ZodTypeAny,
|
||||||
|
|
||||||
|
SchemaWithDefaultValuesT extends z.ZodRawShape = z.ZodRawShape,
|
||||||
|
SchemaWithDefaultValuesUnknownKeys extends z.UnknownKeysParam = z.UnknownKeysParam,
|
||||||
|
SchemaWithDefaultValuesCatchall extends z.ZodTypeAny = z.ZodTypeAny,
|
||||||
|
> = {
|
||||||
|
readonly values: Values
|
||||||
|
readonly input: Input
|
||||||
|
|
||||||
|
readonly schema: z.ZodObject<
|
||||||
|
SchemaT,
|
||||||
|
SchemaUnknownKeys,
|
||||||
|
SchemaCatchall,
|
||||||
|
Values,
|
||||||
|
Values
|
||||||
|
>
|
||||||
|
|
||||||
|
readonly schemaWithDefaultValues: z.ZodObject<
|
||||||
|
SchemaWithDefaultValuesT,
|
||||||
|
SchemaWithDefaultValuesUnknownKeys,
|
||||||
|
SchemaWithDefaultValuesCatchall,
|
||||||
|
Values,
|
||||||
|
Input
|
||||||
|
>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a class with validation schemas.
|
||||||
|
* @template $Config - The configuration type for the schemable object.
|
||||||
|
*/
|
||||||
|
export type SchemableClass<
|
||||||
|
$Config extends SchemableConfig
|
||||||
|
> = (
|
||||||
|
Class<
|
||||||
|
SchemableObject<$Config>,
|
||||||
|
SchemableClassConstructorParams<$Config>
|
||||||
|
> & {
|
||||||
|
readonly $schemableConfig: $Config
|
||||||
|
readonly schema: $Config["schema"]
|
||||||
|
readonly schemaWithDefaultValues: $Config["schemaWithDefaultValues"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the constructor parameters for the schemable object class.
|
||||||
|
* @template $Config - The configuration type for the schemable object.
|
||||||
|
*/
|
||||||
|
export type SchemableClassConstructorParams<
|
||||||
|
$Config extends SchemableConfig
|
||||||
|
> = (
|
||||||
|
Parameters<
|
||||||
|
(data: $Config["values"]) => void
|
||||||
|
>
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an object with validation schemas.
|
||||||
|
* @template $Config - The configuration type for the schemable object.
|
||||||
|
*/
|
||||||
|
export type SchemableObject<
|
||||||
|
$Config extends SchemableConfig
|
||||||
|
> = (
|
||||||
|
{
|
||||||
|
readonly $schemableConfig: $Config
|
||||||
|
readonly schema: $Config["schema"]
|
||||||
|
readonly schemaWithDefaultValues: $Config["schemaWithDefaultValues"]
|
||||||
|
} & $Config["values"]
|
||||||
|
)
|
||||||
3
src/index.ts
Normal file
3
src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./SchemableClass"
|
||||||
|
export * from "./makeSchemableClass"
|
||||||
|
export * from "./newSchemable"
|
||||||
66
src/jsonifiable/JsonifiableSchemableClass.ts
Normal file
66
src/jsonifiable/JsonifiableSchemableClass.ts
Normal 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"]>
|
||||||
|
}
|
||||||
47
src/jsonifiable/dejsonifySchemable.ts
Normal file
47
src/jsonifiable/dejsonifySchemable.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Effect, pipe } from "effect"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { JsonifiableSchemableClass, JsonifiableSchemableConfig } from "."
|
||||||
|
import { parseZodTypeEffect } from "../util"
|
||||||
|
|
||||||
|
|
||||||
|
export const dejsonifySchemable = <
|
||||||
|
C extends JsonifiableSchemableClass<$Config>,
|
||||||
|
$Config extends JsonifiableSchemableConfig,
|
||||||
|
>(
|
||||||
|
class_: C | JsonifiableSchemableClass<$Config>,
|
||||||
|
values: $Config["jsonifiedValues"],
|
||||||
|
params?: Partial<z.ParseParams>,
|
||||||
|
) =>
|
||||||
|
new class_(class_.dejsonifySchema.parse(values, params)) as InstanceType<C>
|
||||||
|
|
||||||
|
|
||||||
|
export const dejsonifySchemablePromise = async <
|
||||||
|
C extends JsonifiableSchemableClass<$Config>,
|
||||||
|
$Config extends JsonifiableSchemableConfig,
|
||||||
|
>(
|
||||||
|
class_: C | JsonifiableSchemableClass<$Config>,
|
||||||
|
values: $Config["jsonifiedValues"],
|
||||||
|
params?: Partial<z.ParseParams>,
|
||||||
|
) =>
|
||||||
|
new class_(await class_.dejsonifySchema.parseAsync(values, params)) as InstanceType<C>
|
||||||
|
|
||||||
|
|
||||||
|
export const dejsonifySchemableEffect = <
|
||||||
|
C extends JsonifiableSchemableClass<$Config>,
|
||||||
|
$Config extends JsonifiableSchemableConfig,
|
||||||
|
>(
|
||||||
|
class_: C | JsonifiableSchemableClass<$Config>,
|
||||||
|
values: $Config["jsonifiedValues"],
|
||||||
|
params?: Partial<z.ParseParams>,
|
||||||
|
) => pipe(
|
||||||
|
parseZodTypeEffect<
|
||||||
|
z.output<typeof class_.dejsonifySchema>,
|
||||||
|
z.input<typeof class_.dejsonifySchema>
|
||||||
|
>(
|
||||||
|
class_.dejsonifySchema,
|
||||||
|
values,
|
||||||
|
params,
|
||||||
|
),
|
||||||
|
|
||||||
|
Effect.map(values => new class_(values) as InstanceType<C>),
|
||||||
|
)
|
||||||
4
src/jsonifiable/index.ts
Normal file
4
src/jsonifiable/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./JsonifiableSchemableClass"
|
||||||
|
export * from "./dejsonifySchemable"
|
||||||
|
export * from "./makeJsonifiableSchemableClass"
|
||||||
|
export * from "./schema"
|
||||||
98
src/jsonifiable/makeJsonifiableSchemableClass.ts
Normal file
98
src/jsonifiable/makeJsonifiableSchemableClass.ts
Normal 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>>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
18
src/jsonifiable/schema/bigint.ts
Normal file
18
src/jsonifiable/schema/bigint.ts
Normal 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)
|
||||||
18
src/jsonifiable/schema/date.ts
Normal file
18
src/jsonifiable/schema/date.ts
Normal 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)
|
||||||
19
src/jsonifiable/schema/decimal.ts
Normal file
19
src/jsonifiable/schema/decimal.ts
Normal 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)
|
||||||
4
src/jsonifiable/schema/index.ts
Normal file
4
src/jsonifiable/schema/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./bigint"
|
||||||
|
export * from "./date"
|
||||||
|
export * from "./decimal"
|
||||||
|
export * from "./schemable"
|
||||||
25
src/jsonifiable/schema/schemable.ts
Normal file
25
src/jsonifiable/schema/schemable.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
import { JsonifiableSchemableClass, JsonifiableSchemableConfig } from ".."
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: try to find a way to get rid of the 'class_' arg
|
||||||
|
export const jsonifySchemableSchema = <
|
||||||
|
C extends JsonifiableSchemableClass<$Config>,
|
||||||
|
$Config extends JsonifiableSchemableConfig,
|
||||||
|
S extends z.ZodType<InstanceType<C>, z.ZodTypeDef, InstanceType<C>>,
|
||||||
|
>(
|
||||||
|
class_: C | JsonifiableSchemableClass<$Config>,
|
||||||
|
schema: S,
|
||||||
|
) =>
|
||||||
|
schema.pipe(class_.jsonifySchema)
|
||||||
|
|
||||||
|
// TODO: try to find a way to get rid of the 'class_' arg
|
||||||
|
export const dejsonifySchemableSchema = <
|
||||||
|
C extends JsonifiableSchemableClass<$Config>,
|
||||||
|
$Config extends JsonifiableSchemableConfig,
|
||||||
|
S extends z.ZodType<InstanceType<C>, z.ZodTypeDef, InstanceType<C>>,
|
||||||
|
>(
|
||||||
|
class_: C | JsonifiableSchemableClass<$Config>,
|
||||||
|
schema: S,
|
||||||
|
) =>
|
||||||
|
class_.dejsonifySchema.transform(v => new class_(v)).pipe(schema)
|
||||||
49
src/makeSchemableClass.ts
Normal file
49
src/makeSchemableClass.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
import { SchemableClass, SchemableConfig } from "."
|
||||||
|
import { zodObjectRemoveDefaults } from "./util"
|
||||||
|
|
||||||
|
|
||||||
|
export function makeSchemableClass<
|
||||||
|
SchemaWithDefaultValuesT extends z.ZodRawShape,
|
||||||
|
SchemaWithDefaultValuesUnknownKeys extends z.UnknownKeysParam,
|
||||||
|
SchemaWithDefaultValuesCatchall extends z.ZodTypeAny,
|
||||||
|
SchemaWithDefaultValuesOutput extends SchemaWithDefaultValuesInput, // TODO: apply "StripSchemaInputDefaults"?
|
||||||
|
SchemaWithDefaultValuesInput extends {},
|
||||||
|
>(
|
||||||
|
{
|
||||||
|
schema: schemaWithDefaultValues
|
||||||
|
}: {
|
||||||
|
schema: z.ZodObject<
|
||||||
|
SchemaWithDefaultValuesT,
|
||||||
|
SchemaWithDefaultValuesUnknownKeys,
|
||||||
|
SchemaWithDefaultValuesCatchall,
|
||||||
|
SchemaWithDefaultValuesOutput,
|
||||||
|
SchemaWithDefaultValuesInput
|
||||||
|
>
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
const schema = zodObjectRemoveDefaults(schemaWithDefaultValues)
|
||||||
|
|
||||||
|
const $schemableConfig = {
|
||||||
|
values: undefined as unknown as z.output<typeof schemaWithDefaultValues>,
|
||||||
|
input: undefined as unknown as z.input<typeof schemaWithDefaultValues>,
|
||||||
|
schema: undefined as unknown as typeof schema,
|
||||||
|
schemaWithDefaultValues: undefined as unknown as typeof schemaWithDefaultValues,
|
||||||
|
} as const satisfies SchemableConfig
|
||||||
|
|
||||||
|
return class SchemableObject {
|
||||||
|
static readonly $schemableConfig = $schemableConfig
|
||||||
|
static readonly schema = schema
|
||||||
|
static readonly schemaWithDefaultValues = schemaWithDefaultValues
|
||||||
|
|
||||||
|
readonly $schemableConfig = $schemableConfig
|
||||||
|
readonly schema = schema
|
||||||
|
readonly schemaWithDefaultValues = schemaWithDefaultValues
|
||||||
|
|
||||||
|
constructor(data: z.output<typeof schema>) {
|
||||||
|
Object.assign(this, data)
|
||||||
|
}
|
||||||
|
} as SchemableClass<typeof $schemableConfig>
|
||||||
|
|
||||||
|
}
|
||||||
77
src/newSchemable.ts
Normal file
77
src/newSchemable.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { Effect, pipe } from "effect"
|
||||||
|
import { HasRequiredKeys } from "type-fest"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { SchemableClass, SchemableConfig } from "."
|
||||||
|
import { parseZodTypeEffect } from "./util"
|
||||||
|
|
||||||
|
|
||||||
|
type ParamsArgs = [] | [Partial<z.ParseParams>]
|
||||||
|
|
||||||
|
type NewSchemableArgs<Input extends object> =
|
||||||
|
HasRequiredKeys<Input> extends true
|
||||||
|
? [Input, ...ParamsArgs]
|
||||||
|
: [] | [Input, ...ParamsArgs]
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of a SchemableClass with default values.
|
||||||
|
*
|
||||||
|
* @param class_ - The SchemableClass.
|
||||||
|
* @param values - The values to be parsed and used to create the instance.
|
||||||
|
* @param params - Optional parameters for parsing.
|
||||||
|
* @returns A new instance of the specified SchemableClass.
|
||||||
|
*/
|
||||||
|
export const newSchemable = <
|
||||||
|
C extends SchemableClass<$Config>,
|
||||||
|
$Config extends SchemableConfig,
|
||||||
|
>(
|
||||||
|
class_: C | SchemableClass<$Config>,
|
||||||
|
...[values, params]: NewSchemableArgs<$Config["input"]>
|
||||||
|
) =>
|
||||||
|
new class_(class_.schemaWithDefaultValues.parse(values || {}, params)) as InstanceType<C>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of a SchemableClass with default values asynchronously.
|
||||||
|
*
|
||||||
|
* @param class_ - The SchemableClass.
|
||||||
|
* @param values - The values to be parsed and used to create the instance.
|
||||||
|
* @param params - Optional parameters for parsing.
|
||||||
|
* @returns A Promise resolving to a new instance of the specified SchemableClass.
|
||||||
|
*/
|
||||||
|
export const newSchemablePromise = async <
|
||||||
|
C extends SchemableClass<$Config>,
|
||||||
|
$Config extends SchemableConfig,
|
||||||
|
>(
|
||||||
|
class_: C | SchemableClass<$Config>,
|
||||||
|
...[values, params]: NewSchemableArgs<$Config["input"]>
|
||||||
|
) =>
|
||||||
|
new class_(await class_.schemaWithDefaultValues.parseAsync(values || {}, params)) as InstanceType<C>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of a SchemableClass with default values as an Effect.
|
||||||
|
*
|
||||||
|
* @param class_ - The SchemableClass.
|
||||||
|
* @param values - The values to be parsed and used to create the instance.
|
||||||
|
* @param params - Optional parameters for parsing.
|
||||||
|
* @returns An Effect producing a new instance of the specified SchemableClass.
|
||||||
|
*/
|
||||||
|
export const newSchemableEffect = <
|
||||||
|
C extends SchemableClass<$Config>,
|
||||||
|
$Config extends SchemableConfig,
|
||||||
|
>(
|
||||||
|
class_: C | SchemableClass<$Config>,
|
||||||
|
...[values, params]: NewSchemableArgs<$Config["input"]>
|
||||||
|
) => pipe(
|
||||||
|
parseZodTypeEffect<
|
||||||
|
z.output<typeof class_.schemaWithDefaultValues>,
|
||||||
|
z.input<typeof class_.schemaWithDefaultValues>
|
||||||
|
>(
|
||||||
|
class_.schemaWithDefaultValues,
|
||||||
|
values || {},
|
||||||
|
params,
|
||||||
|
),
|
||||||
|
|
||||||
|
Effect.map(values => new class_(values) as InstanceType<C>),
|
||||||
|
)
|
||||||
64
src/tests.ts
Normal file
64
src/tests.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
import { makeSchemableClass, newSchemable } from "."
|
||||||
|
import { dejsonifyBigIntSchema, dejsonifySchemable, dejsonifySchemableSchema, jsonifyBigIntSchema, jsonifySchemableSchema, makeJsonifiableSchemableClass } from "./jsonifiable"
|
||||||
|
|
||||||
|
|
||||||
|
const GroupSchema = z.object({
|
||||||
|
/** Group ID */
|
||||||
|
id: z.bigint(),
|
||||||
|
|
||||||
|
/** Group name */
|
||||||
|
name: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const GroupSchemableObject = makeSchemableClass({ schema: GroupSchema })
|
||||||
|
|
||||||
|
const GroupJsonifiableSchemableObject = makeJsonifiableSchemableClass(GroupSchemableObject, {
|
||||||
|
jsonifySchema: ({ schema, s }) => schema.extend({
|
||||||
|
id: jsonifyBigIntSchema(s.id)
|
||||||
|
}),
|
||||||
|
|
||||||
|
dejsonifySchema: ({ schema, s }) => schema.extend({
|
||||||
|
id: dejsonifyBigIntSchema(s.id)
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
class Group extends GroupJsonifiableSchemableObject {}
|
||||||
|
|
||||||
|
|
||||||
|
const UserSchema = z.object({
|
||||||
|
/** User ID */
|
||||||
|
id: z.bigint(),
|
||||||
|
|
||||||
|
/** Name string */
|
||||||
|
name: z.string(),
|
||||||
|
|
||||||
|
/** User group */
|
||||||
|
group: z.instanceof(Group),
|
||||||
|
})
|
||||||
|
|
||||||
|
const UserSchemableObject = makeSchemableClass({ schema: UserSchema })
|
||||||
|
|
||||||
|
const UserJsonifiableSchemableObject = makeJsonifiableSchemableClass(UserSchemableObject, {
|
||||||
|
jsonifySchema: ({ schema, s }) => schema.extend({
|
||||||
|
id: jsonifyBigIntSchema(s.id),
|
||||||
|
group: jsonifySchemableSchema(Group, s.group),
|
||||||
|
}),
|
||||||
|
|
||||||
|
dejsonifySchema: ({ schema, s }) => schema.extend({
|
||||||
|
id: dejsonifyBigIntSchema(s.id),
|
||||||
|
group: dejsonifySchemableSchema(Group, s.group),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
class User extends UserJsonifiableSchemableObject {}
|
||||||
|
|
||||||
|
|
||||||
|
const group1 = new Group({ id: 1n, name: "Group 1" })
|
||||||
|
|
||||||
|
const user1 = new User({ id: 1n, name: "User 1", group: group1 })
|
||||||
|
const user2 = newSchemable(User, { id: 2n, name: "User 2", group: group1 })
|
||||||
|
|
||||||
|
const jsonifiedUser2 = user2.jsonify()
|
||||||
|
const dejsonifiedUser2 = dejsonifySchemable(User, jsonifiedUser2)
|
||||||
|
console.log(dejsonifiedUser2)
|
||||||
82
src/util.ts
Normal file
82
src/util.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { Effect, pipe } from "effect"
|
||||||
|
import { mapValues } from "lodash-es"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the static members of a class.
|
||||||
|
* @template C - The class type.
|
||||||
|
*/
|
||||||
|
export type StaticMembers<C> = {
|
||||||
|
[Key in keyof C as Key extends "prototype" ? never : Key]: C[Key]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes default values from a ZodObject schema and returns a new schema.
|
||||||
|
*
|
||||||
|
* @param schema - The ZodObject schema to process.
|
||||||
|
* @returns A new ZodObject schema with default values removed.
|
||||||
|
*/
|
||||||
|
export const zodObjectRemoveDefaults = <
|
||||||
|
T extends z.ZodRawShape,
|
||||||
|
UnknownKeys extends z.UnknownKeysParam,
|
||||||
|
Catchall extends z.ZodTypeAny,
|
||||||
|
Output extends {},
|
||||||
|
Input extends {},
|
||||||
|
>(
|
||||||
|
schema: z.ZodObject<
|
||||||
|
T,
|
||||||
|
UnknownKeys,
|
||||||
|
Catchall,
|
||||||
|
Output,
|
||||||
|
Input
|
||||||
|
>
|
||||||
|
) =>
|
||||||
|
schema.extend(zodShapeRemoveDefaults(schema.shape))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes default values from a ZodObject shape and returns a new shape.
|
||||||
|
*
|
||||||
|
* @param shape - The ZodObject shape to process.
|
||||||
|
* @returns A new shape with default values removed.
|
||||||
|
*/
|
||||||
|
export const zodShapeRemoveDefaults = <
|
||||||
|
Shape extends z.ZodRawShape
|
||||||
|
>(
|
||||||
|
shape: Shape
|
||||||
|
): {
|
||||||
|
[K in keyof Shape]:
|
||||||
|
Shape[K] extends z.ZodDefault<infer T>
|
||||||
|
? T
|
||||||
|
: Shape[K]
|
||||||
|
} =>
|
||||||
|
mapValues(shape, el =>
|
||||||
|
el instanceof z.ZodDefault
|
||||||
|
? el.removeDefault()
|
||||||
|
: el
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a value using a ZodType schema wrapped in an Effect monad.
|
||||||
|
*
|
||||||
|
* @param schema - The ZodType schema to use for parsing.
|
||||||
|
* @param args - The arguments to pass to the `safeParseAsync` method of the schema.
|
||||||
|
* @returns An Effect monad representing the parsing result.
|
||||||
|
*/
|
||||||
|
export const parseZodTypeEffect = <
|
||||||
|
Output,
|
||||||
|
Input,
|
||||||
|
>(
|
||||||
|
schema: z.ZodType<Output, z.ZodTypeDef, Input>,
|
||||||
|
...args: Parameters<typeof schema.safeParseAsync>
|
||||||
|
) => pipe(
|
||||||
|
Effect.promise(() => schema.safeParseAsync(...args)),
|
||||||
|
|
||||||
|
Effect.flatMap(response =>
|
||||||
|
response.success
|
||||||
|
? Effect.succeed(response.data)
|
||||||
|
: Effect.fail(response.error)
|
||||||
|
),
|
||||||
|
)
|
||||||
24
tsconfig.json
Normal file
24
tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
// "allowImportingTsExtensions": true,
|
||||||
|
// "noEmit": true,
|
||||||
|
"declaration": true,
|
||||||
|
"composite": true,
|
||||||
|
"strict": true,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"types": [
|
||||||
|
"bun-types"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user