Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify #122

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

Unify #122

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* **BREAKING CHANGE**: `{"*": object(...)}` and `{"x": object(...)}` now behave the same when
deserializing `{"x": "str_not_object"}` (result: `{x: null}`). Previously the `"*"` schema would
have returned `{}`.
* **BREAKING CHANGE**: Removed deprecated `ref` and `child` functions. Use `reference` and `object`
instead.
* You can pass a `pattern` argument as `AdditionalPropArgs` instead of having to manually assign it
to a PropSchema: `@serializeAll("*": list(primitive(), { pattern: /^_.*/ }))`. Note this only make
sense together with the `"*"` property.
Expand Down
3 changes: 2 additions & 1 deletion src/api/createModelSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { invariant } from "../utils/utils"
import getDefaultModelSchema from "./getDefaultModelSchema"
import setDefaultModelSchema from "./setDefaultModelSchema"
import { ModelSchema, Clazz, Props, Factory, ClazzOrModelSchema } from "./types"
import object from "../types/object"

/**
* Creates a model schema that (de)serializes an object created by a constructor function (class).
Expand Down Expand Up @@ -49,6 +50,6 @@ export default function createModelSchema<T extends Object>(
if (s && s.targetClass !== clazz) model.extends = s
}
setDefaultModelSchema(clazz, model)
return model
return object(model)
}
const x: Clazz<any> = Object
5 changes: 3 additions & 2 deletions src/api/createSimpleSchema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Props, ModelSchema } from "./types"
import object from "../types/object"

/**
* Creates a model schema that (de)serializes from / to plain javascript objects.
Expand All @@ -17,10 +18,10 @@ import { Props, ModelSchema } from "./types"
* @returns model schema
*/
export default function createSimpleSchema<T extends Object>(props: Props): ModelSchema<T> {
return {
return object({
factory: function() {
return {} as any
},
props: props
}
})
}
26 changes: 13 additions & 13 deletions src/api/serializable.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { invariant, isPropSchema, isAliasedPropSchema } from "../utils/utils"
import { invariant, isSchema, isAliasedSchema } from "../utils/utils"
import { _defaultPrimitiveProp } from "../constants"
import primitive from "../types/primitive"
import getDefaultModelSchema from "../api/getDefaultModelSchema"
import createModelSchema from "../api/createModelSchema"
import { PropSchema, ModelSchema, PropDef } from "./types"
import { Schema, ModelSchema, PropDef } from "./types"
import Context from "../core/Context"

// Ugly way to get the parameter names since they aren't easily retrievable via reflection
Expand All @@ -16,7 +16,7 @@ function getParamNames(func: Function) {
}

function serializableDecorator(
propSchema: PropSchema,
propSchema: Schema,
target: any,
propName: string,
descriptor: PropertyDescriptor | undefined
Expand All @@ -34,8 +34,8 @@ function serializableDecorator(
descriptor !== undefined &&
typeof descriptor === "number"
) {
invariant(isPropSchema(propSchema), "Constructor params must use alias(name)")
invariant(isAliasedPropSchema(propSchema), "Constructor params must use alias(name)")
invariant(isSchema(propSchema), "Constructor params must use alias(name)")
invariant(isAliasedSchema(propSchema), "Constructor params must use alias(name)")
const paramNames = getParamNames(target)
if (paramNames.length >= descriptor) {
propName = paramNames[descriptor]
Expand All @@ -46,12 +46,12 @@ function serializableDecorator(
factory = function(context: Context) {
const params: any = []
for (let i = 0; i < target.constructor.length; i++) {
Object.keys(context.modelSchema.props).forEach(function(key) {
for (const key of Object.keys(context.modelSchema.props)) {
const prop = context.modelSchema.props[key]
if ((prop as PropSchema).paramNumber === i) {
params[i] = context.json[(prop as PropSchema).jsonname!]
if ((prop as Schema).paramNumber === i) {
params[i] = context.json[(prop as Schema).jsonname!]
}
})
}
}

return target.constructor.bind(undefined, ...params)
Expand Down Expand Up @@ -105,15 +105,15 @@ export default function serializable(
baseDescriptor?: PropertyDescriptor
): void
export default function serializable(
targetOrPropSchema: any | PropDef,
targetOrSchema: any | PropDef,
key?: string,
baseDescriptor?: PropertyDescriptor
) {
if (!key) {
// decorated with propSchema
const propSchema =
targetOrPropSchema === true ? _defaultPrimitiveProp : (targetOrPropSchema as PropSchema)
invariant(isPropSchema(propSchema), "@serializable expects prop schema")
targetOrSchema === true ? _defaultPrimitiveProp : (targetOrSchema as Schema)
invariant(isSchema(propSchema), "@serializable expects prop schema")
const result: (
target: Object,
key: string,
Expand All @@ -122,6 +122,6 @@ export default function serializable(
return result
} else {
// decorated without arguments, treat as primitive
serializableDecorator(primitive(), targetOrPropSchema, key, baseDescriptor!)
serializableDecorator(primitive(), targetOrSchema, key, baseDescriptor!)
}
}
47 changes: 23 additions & 24 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,47 @@ export interface AdditionalPropArgs {
}
export type PropSerializer = (
sourcePropertyValue: any,
key: string | number | symbol,
key: string | number | symbol | undefined,
sourceObject: any
) => any | SKIP
export type PropDeserializer = (
jsonValue: any,
callback: (err?: any, targetPropertyValue?: any | SKIP) => void,
callback: (err?: any, newValue?: any | SKIP) => void,
context: Context,
currentPropertyValue?: any
currentPropertyValue?: any,
customArg?: any
) => void
export interface PropSchema {
serializer: PropSerializer
deserializer: PropDeserializer
beforeDeserialize?: BeforeDeserializeFunc
afterDeserialize?: AfterDeserializeFunc
/**
* Filter properties to which this schema applies. Used with `ModelSchema.props["*"]`.
*/
pattern?: { test: (propName: string) => boolean }
jsonname?: string
identifier?: true
paramNumber?: number
}

export type AfterDeserializeFunc = (
callback: (err: any, value: any) => void,
err: any,
newValue: any,
jsonValue: any,
jsonParentValue: any,
propNameOrIndex: string | number | symbol,
jsonPropNameOrIndex: string | number | undefined,
context: Context,
propDef: PropSchema
propDef: Schema
) => void

export type BeforeDeserializeFunc = (
callback: (err: any, value: any) => void,
jsonValue: any,
jsonParentValue: any,
propNameOrIndex: string | number,
propNameOrIndex: string | number | undefined,
context: Context,
propDef: PropSchema
propDef: Schema
) => void
export interface Schema {
serializer: PropSerializer
deserializer: PropDeserializer
beforeDeserialize?: BeforeDeserializeFunc
afterDeserialize?: AfterDeserializeFunc
/**
* Filter properties to which this schema applies. Used with `ModelSchema.props["*"]`.
*/
pattern?: { test: (propName: string) => boolean }
jsonname?: string
identifier?: true
paramNumber?: number
}

export type Factory<T> = (context: Context) => T

Expand All @@ -60,9 +59,9 @@ export type Factory<T> = (context: Context) => T
export type Props<T = any> = {
[propName in keyof T]: PropDef
}
export type PropDef = PropSchema | boolean | undefined
export type PropDef = Schema | boolean | undefined

export interface ModelSchema<T> {
export interface ModelSchema<T> extends Schema {
targetClass?: Clazz<any>
factory: Factory<T>
props: Props<T>
Expand Down
26 changes: 5 additions & 21 deletions src/core/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ import { ModelSchema } from "../api/types"
const rootContextCache = new WeakMap()

export default class Context<T = any> {
private isRoot: boolean
private pendingCallbacks: number
private pendingRefsCount: number
public target: any
private hasError: boolean
public rootContext: Context<any>
private args: any
private pendingRefs!: {
[uuid: string]: {
modelSchema: ModelSchema<any>
Expand All @@ -26,29 +23,19 @@ export default class Context<T = any> {
}

constructor(
readonly parentContext: Context<any> | undefined,
readonly modelSchema: ModelSchema<T>,
readonly json: any,
private readonly onReadyCb: (err?: any, value?: T) => void,
customArgs?: any[]
private readonly args?: any
) {
this.isRoot = !parentContext
this.pendingCallbacks = 0
this.pendingRefsCount = 0
this.target = undefined // always set this property using setTarget
this.hasError = false
if (!parentContext) {
this.rootContext = this
this.args = customArgs
this.pendingRefs = {}
this.resolvedRefs = {}
} else {
this.rootContext = parentContext.rootContext
this.args = parentContext.args
}
this.pendingRefs = {}
this.resolvedRefs = {}
}

createCallback(fn: (value: T) => void) {
createCallback(fn: (value: any) => void) {
this.pendingCallbacks++
// once: defend against user-land calling 'done' twice
return once((err?: any, value?: T) => {
Expand Down Expand Up @@ -85,7 +72,6 @@ export default class Context<T = any> {
// given an object with uuid, modelSchema, callback, awaits until the given uuid is available
// resolve immediately if possible
await(modelSchema: ModelSchema<any>, uuid: string, callback: (err?: any, value?: any) => void) {
invariant(this.isRoot, "await can only be called on the root context")
if (uuid in this.resolvedRefs) {
const match = this.resolvedRefs[uuid].filter(function(resolved) {
return isAssignableTo(resolved.modelSchema, modelSchema)
Expand All @@ -103,7 +89,6 @@ export default class Context<T = any> {

// given a model schema, uuid and value, resolve all references that were looking for this object
resolve(modelSchema: ModelSchema<any>, uuid: string, value: any) {
invariant(this.isRoot, "resolve can only called on the root context")
if (!this.resolvedRefs[uuid]) this.resolvedRefs[uuid] = []
this.resolvedRefs[uuid].push({
modelSchema: modelSchema,
Expand All @@ -123,7 +108,7 @@ export default class Context<T = any> {

// set target and update root context cache
setTarget(target: T) {
if (this.isRoot && this.target) {
if (this.target) {
rootContextCache.delete(this.target)
}
this.target = target
Expand All @@ -132,7 +117,6 @@ export default class Context<T = any> {

// call all remaining reference lookup callbacks indicating an error during ref resolution
cancelAwaits() {
invariant(this.isRoot, "cancelAwaits can only be called on the root context")
const self = this
Object.keys(this.pendingRefs).forEach(function(uuid) {
self.pendingRefs[uuid].forEach(function(refOpts) {
Expand Down
Loading