-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fixed multilevel objectsa and added back tests and example
- Loading branch information
1 parent
ad1b1c2
commit 5296750
Showing
3 changed files
with
177 additions
and
177 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,174 +1,174 @@ | ||
/* eslint-disable */ | ||
import { z, ZodType } from 'zod' | ||
import { proxy as vproxy, useSnapshot as vsnap } from 'valtio' | ||
import _ from 'lodash' | ||
import { z, ZodType } from 'zod'; | ||
import { proxy as vproxy, useSnapshot as vsnap } from 'valtio'; | ||
import _ from 'lodash'; | ||
|
||
type ValtioProxy<T> = { | ||
[P in keyof T]: T[P] | ||
} | ||
[P in keyof T]: T[P]; | ||
}; | ||
|
||
type SchemaConfig = { | ||
parseAsync?: boolean | ||
safeParse?: boolean | ||
errorHandler?: (error: unknown) => void | ||
} | ||
parseAsync?: boolean; | ||
safeParse?: boolean; | ||
errorHandler?: (error: unknown) => void; | ||
}; | ||
|
||
const defaultConfig = { | ||
parseAsync: false, | ||
safeParse: false, | ||
errorHandler: (error: unknown) => console.error(error) | ||
} | ||
errorHandler: (error: unknown) => console.error(error), | ||
}; | ||
|
||
export const vzGlobalConfig = { | ||
safeParse: false, | ||
errorHandler: (error: unknown) => console.error(error) | ||
} | ||
errorHandler: (error: unknown) => console.error(error), | ||
}; | ||
|
||
const isObject = (x: unknown): x is object => | ||
typeof x === 'object' && x !== null | ||
typeof x === 'object' && x !== null; | ||
|
||
type MergedConfig = Required<SchemaConfig> | ||
type MergedConfig = Required<SchemaConfig>; | ||
|
||
type SchemaMeta = SchemaConfig & { | ||
initialState: unknown | ||
} | ||
initialState: unknown; | ||
}; | ||
|
||
type PropType = string | number | symbol | ||
const schemaMeta = new WeakMap<ZodType<any>, SchemaMeta>() | ||
const pathList = new WeakMap<{}, PropType[]>() | ||
type PropType = string | number | symbol; | ||
const schemaMeta = new WeakMap<ZodType<any>, SchemaMeta>(); | ||
const pathList = new WeakMap<{}, PropType[]>(); | ||
|
||
type SchemaReturn<T extends ZodType<any>> = { | ||
proxy: { | ||
(initialState: any, config?: SchemaConfig): ValtioProxy<z.infer<T>> | ||
} | ||
} | ||
(initialState: any, config?: SchemaConfig): ValtioProxy<z.infer<T>>; | ||
}; | ||
}; | ||
|
||
const valtioStoreSymbol = Symbol('valtioStore') | ||
const valtioStoreSymbol = Symbol('valtioStore'); | ||
|
||
export const useSnapshot = (store: any) => { | ||
return vsnap(store[valtioStoreSymbol]) | ||
} | ||
return vsnap(store[valtioStoreSymbol]); | ||
}; | ||
|
||
export const schema = <T extends ZodType<any>>( | ||
zodSchema: T | ||
zodSchema: T, | ||
): SchemaReturn<T> => { | ||
const proxy = ( | ||
initialState: z.infer<T>, | ||
config: SchemaConfig = {} | ||
config: SchemaConfig = {}, | ||
): ValtioProxy<z.infer<T>> => { | ||
if (!isObject(initialState)) { | ||
throw new Error('object required') | ||
throw new Error('object required'); | ||
} | ||
|
||
const mergedConfig: MergedConfig = { ...defaultConfig, ...config } | ||
const mergedConfig: MergedConfig = { ...defaultConfig, ...config }; | ||
|
||
const parseAsync = mergedConfig.parseAsync | ||
const safeParse = mergedConfig.safeParse | ||
const errorHandler = mergedConfig.errorHandler | ||
const parseAsync = mergedConfig.parseAsync; | ||
const safeParse = mergedConfig.safeParse; | ||
const errorHandler = mergedConfig.errorHandler; | ||
|
||
// before proxying, validate the initial state | ||
if (parseAsync) { | ||
zodSchema.parseAsync(initialState).catch((e) => { | ||
throw e | ||
}) | ||
throw e; | ||
}); | ||
} else { | ||
zodSchema.parse(initialState) | ||
zodSchema.parse(initialState); | ||
} | ||
|
||
const valtioProxy = vproxy(initialState) | ||
const valtioProxy = vproxy(initialState); | ||
|
||
const createProxy = (target: any, parentPath: PropType[] = []): any => { | ||
if (!schemaMeta.has(zodSchema)) { | ||
schemaMeta.set(zodSchema, { | ||
safeParse, | ||
parseAsync, | ||
errorHandler, | ||
initialState | ||
}) | ||
initialState, | ||
}); | ||
} | ||
|
||
return new Proxy(target, { | ||
get(target, prop, receiver) { | ||
const value = Reflect.get(target, prop, receiver) | ||
const value = Reflect.get(target, prop, receiver); | ||
if (isObject(value)) { | ||
const newPath = parentPath.concat(prop) | ||
pathList.set(value, newPath) | ||
return createProxy(value, newPath) | ||
const newPath = parentPath.concat(prop); | ||
pathList.set(value, newPath); | ||
return createProxy(value, newPath); | ||
} else { | ||
const pathToSet = [...(pathList.get(target) || []), prop] | ||
return _.get(valtioProxy, pathToSet, value) | ||
const pathToSet = [...(pathList.get(target) || []), prop]; | ||
return _.get(valtioProxy, pathToSet, value); | ||
} | ||
}, | ||
set(target, prop, value, receiver) { | ||
const originalObject = schemaMeta.get(zodSchema)! | ||
.initialState as z.infer<T> | ||
.initialState as z.infer<T>; | ||
|
||
const objectToValidate = _.cloneDeep(originalObject) | ||
const path = (pathList.get(target) || []).concat(prop) | ||
const objectToValidate = _.cloneDeep(originalObject); | ||
const path = (pathList.get(target) || []).concat(prop); | ||
|
||
_.set(objectToValidate, path, value) | ||
_.set(objectToValidate, path, value); | ||
|
||
const handleAsyncParse = async () => { | ||
try { | ||
const parsedValue = await zodSchema.parseAsync(objectToValidate) | ||
_.set(valtioProxy, value, path) | ||
Reflect.set(target, prop, value, receiver) | ||
return true | ||
const parsedValue = await zodSchema.parseAsync(objectToValidate); | ||
_.set(valtioProxy, value, path); | ||
Reflect.set(target, prop, value, receiver); | ||
return true; | ||
} catch (error) { | ||
errorHandler(error) | ||
errorHandler(error); | ||
if (!safeParse) { | ||
throw error | ||
throw error; | ||
} | ||
return false | ||
return false; | ||
} | ||
} | ||
}; | ||
|
||
const handleSyncParse = () => { | ||
try { | ||
if (safeParse) { | ||
const result = zodSchema.safeParse(objectToValidate) | ||
const result = zodSchema.safeParse(objectToValidate); | ||
if (result.success) { | ||
valtioProxy[prop] = value | ||
Reflect.set(target, prop, value, receiver) | ||
return true | ||
valtioProxy[prop] = value; | ||
Reflect.set(target, prop, value, receiver); | ||
return true; | ||
} else { | ||
errorHandler(result.error) | ||
return false | ||
errorHandler(result.error); | ||
return false; | ||
} | ||
} else { | ||
const parsedValue = zodSchema.parse(objectToValidate) | ||
Reflect.set(target, prop, value, receiver) | ||
valtioProxy[prop] = value | ||
return true | ||
const parsedValue = zodSchema.parse(objectToValidate); | ||
Reflect.set(target, prop, value, receiver); | ||
valtioProxy[prop] = value; | ||
return true; | ||
} | ||
} catch (error) { | ||
errorHandler(error) | ||
errorHandler(error); | ||
if (!safeParse) { | ||
throw error | ||
throw error; | ||
} | ||
return false | ||
return false; | ||
} | ||
} | ||
}; | ||
|
||
if (parseAsync) { | ||
handleAsyncParse().catch((error) => { | ||
errorHandler(error) | ||
errorHandler(error); | ||
if (!safeParse) { | ||
throw error | ||
throw error; | ||
} | ||
}) | ||
return true | ||
}); | ||
return true; | ||
} else { | ||
return handleSyncParse() | ||
return handleSyncParse(); | ||
} | ||
} | ||
}) | ||
} | ||
}, | ||
}); | ||
}; | ||
|
||
const store = createProxy(valtioProxy) | ||
store[valtioStoreSymbol] = valtioProxy | ||
const store = createProxy(valtioProxy); | ||
store[valtioStoreSymbol] = valtioProxy; | ||
|
||
return store | ||
} | ||
return { proxy } | ||
} | ||
return store; | ||
}; | ||
return { proxy }; | ||
}; |
Oops, something went wrong.