
Research
/Security News
Critical Vulnerability in NestJS Devtools: Localhost RCE via Sandbox Escape
A flawed sandbox in @nestjs/devtools-integration lets attackers run code on your machine via CSRF, leading to full Remote Code Execution (RCE).
[](https://travis-ci.org/autoric/scheming)
Define powerful object schemas in javascript. Includes default values, getters, setters, validators, type checking or coercion. Builds reactive objects which you can watch for changes. Written to be extensible and customizable. Works on the browser or the server.
So what does it look like?
Scheming = require('Scheming')
User = Scheming.create 'User',
name :
type : String
required : true
email :
type : String,
required : true
validate : (val) ->
if val.match('@')
return true
else
return 'An email address must have an @ symbol!'
birthday : Date
password :
type : String
setter : (val) ->
return md5(val)
Group = Scheming.create 'Group',
name : String
dateCreated : {type : Date, default : -> Date.now()}
users : [User]
jane = new User
email : 'jane.gmail.com'
birthday : '9/14/86'
password : 'p@$$w0rd!'
console.log User.validate jane
# {name : 'Field is required.', email: 'An email address must have an @ symbol!'}
jane.name = 'jane'
jane.email = 'jane@gmail.com'
console.log User.validate jane
# null
Defines throttling strategies for resolution of changes to scheming models. If the selected option is not supported in the current environment, a console.warn will be issued and no change is made. Options are:
TIMEOUT
sets a timeout of 0IMMEDIATE
uses setImmediateANIMATION_FRAME
uses requestAnimationFrameDefines the primitive types that can be assigned to a field in a schema definition. Each type defines a string name, an identifier, and a parser. A type may also optionally provide a constructor reference. For detailed reference, see the TYPE definitions in source. Note the Mixed type, which is effectively untyped, and will allow for any value to be assigned.
Below are the default types and the ways that you can reference them when defining a schema:
Scheming.TYPES.String
'string'
String
Scheming.TYPES.Number
'number'
Number
Scheming.TYPES.Integer
'integer'
Scheming.TYPES.Date
'date'
Date
Scheming.TYPES.Boolean
'boolean'
Boolean
Scheming.TYPES.Mixed
'*'
For example, the following are equivalent:
Scheming.create {name : Scheming.TYPES.String}
Scheming.create {name : 'string'}
Scheming.create {name : String}
The Mixed type is intended for arbitrary untyped data, including nested objects, arrays, etc. Note that when watching properties of the Mixed type, a watch will fire on reference change (re-assignment), but will not fire on mutations of the value. If you need watches to propagate, then you need to use nested schemas so that Scheming knows about nested properties and can manage changes.
You can extend the Scheming TYPES object to add support for custom types. For example:
Scheming.TYPES.Symbol =
constructor : Symbol
string : 'symbol'
identifier : (val) ->
typeof val == "symbol"
parser : (val) ->
Symbol(val)
# I can now declare Schemas with my new type
Scheming.create {name : Symbol}
In addition to declaring new types, you can modify the currently existing types. For example, say you don't like dealing with javascript Date objects, and would rather use with moment.js.
Scheming.TYPES.Date.identifier = moment.isMoment
Scheming.TYPES.Date.parser = moment
Person = Scheming.create birthday : Date
bill = new Person {birthday : '9/14/86'}
bill.birthday.format "YYYY MM DD"
# "1986 09 14"
# Bill's birthday is a momentjs object, and has the format method!
In addition to the 'primitive' types defined in Scheming.TYPES, Schemas also support arrays values and nested schemas. Anywhere you can provide a type declaration, you can use the following nested types. This section assumes some knowledge of property configuration syntax. Take a look at the Schema.defineProperty docs.
For any schema you can declare a property whose type is an array of values.
Simple arrays:
BlogPost = Scheming.create
comments : [String] # an array of strings
miscellaneous : ['*'] # an untyped array
post = new BlogPost()
post.comments = ['Hello', 'World']
post.miscellaneous = ['Stuff', 2, null, {}]x
Arrays with validation, defaults, etc. Here is a blog post which requires 2 or more comments to be valid. Note that the configuration is being applied to the array itself, not to the members of the array.
BlogPost = Scheming.create
comments :
type : [String]
default : []
required : true
validate : (comments) ->
return comments.length >= 2
Any Schema can have a property whose type is another nested schema. This can be used to create any depth of nesting, or to create circular type definitions. In all cases, a Schema constructor is a valid type definition. When a value is a assigned to a property whose type is Schema, if it is not already an instance of that Schema, it value will be run through the Schema constructor as part of parsing.
Simple nested schemas:
Car = Schema.create
make : String
model : String
Person = Schema.create
name : String
car : Car
mark = new Person {name : 'mark'}
# Explicit construction and assignment
# At the time of assignment, civic is already an instance of Car
# so the Car constructor will not be invoked a second time
civic = new Car {make : 'honda', model : 'civic'}
mark.car = civic
# Implicit construction
# At the time of assignment, the value is a plain object. Therefore
# the object is passed to the Car constructor (or in strict mode,
# an error is thrown)
mark.car = {make : 'toyota', model : 'corolla'}
mark.car instanceof Car # true
This is fine for one-way type references. However, it is easy to conceive of data models with circular type references. What do we do in this case? The simple solution is to create both schemas first, then define their properties afterwards, so that the Schema references are valid.
Simple circular type references:
Person = Schema.create()
Car = Schema.create()
Person.defineProperties
name : String
car : Car
Car.defineProperties
make : String
model : String
owner : Person
This model still presents a problem. What if my schemas are declared in different files? What if I don't want to juggle references, or be careful about the order in which schemas are declared? This is where lazy initialization comes in. When you create a Schema, you have the option to name it. See the docs on Scheming.create and Scheming.get to understand how to name and retrieve named schemas.
If you have registered a named schema, you can create a type reference to that schema using the syntax 'Schema:Name'
. Scheming will accept this as a valid type reference without evaluating immediately or throwing errors. The Schema reference will not be retrieved until the first time the identifier or parser is invoked. So as long as you have declared all of your schemas before you start creating instances, you don't have to worry about it. Note that lazy initialization will throw an error if the Schema reference does not exist at the time of initialization.
Lazy initialization:
# I am registering the Schema with the name 'Person'
Person = Schema.create 'Person',
name : String
car : 'Schema:Car'
# This would throw an error, because 'Schema:Car' does not resolve to a registered Schema
bill = new Person
name : 'Bill'
car : {make : 'honda', model : 'civic'}
# Now I am creating and registering the 'Car' Schema
Car = Schema.create 'Car',
make : String
model : String
# This reference is using the registered name of the Schema 'Person'
owner : 'Schema:Person'
# Success!
bill = new Person
name : 'Bill'
car : {make : 'honda', model : 'civic'}
What we have seen so far is the ability to explicitly create Schemas and reference them as types. While this is extremely powerful, sometimes you just want to declare nested objects on your Schema. When you do this, new anonymous Schemas are implicitly created and assigned as the type.
In the example below, we create a blog post that has some flat properties, and creates two implicit schemas. The first is the author property, the second is the comments property. Each of these cause an anonymous schema to be created, and any assignment to that value will run the assigned object through the corresponding Schema constructor
Blog = Scheming.create
title : String
content : String
posted : Date
author :
name : String
age : Number
comments : [{
text : String
posted : Date
}]
Note one subtlety: the syntax for Complex Configuration and implicit schemas is basically the same. In both cases you are using property names and nested objects. Scheming determines whether to treat a nested object as property configuration or a nested schema based on the presence of the type
key. This effectively makes type
a reserved word for implicit Schemas.
# Oops! In the example below, author is not a nested schema.
# It is a property with a primitive type of string.
Blog = Scheming.create
author :
name : String
age : Number
type : String
The default options used when Scheming.create is invoked. If you prefer for all schemas to be created with the seal or strict options set to true, you can modify the default options. See the options on Scheming.create for details.
Sets the throttling strategy for resolving changes. Valid options are defined by Scheming.THROTTLE.
Registers a callback for when the first change is queued. This callback is guaranteed to be called only once before changes are resolved. This is useful for testing.
Unregisters the callback registered with Scheming.registerQueueCallback
Registers a callback to be called when a change is resolved. This callback is guaranteed to be called only once after a queued change and won't be called again until new queued changes are resolved. This is useful for testing.
it 'should update my DOM', (done) ->
Scheming.registerResolveCallback ->
expect($('.bill')[0]).to.have.text 'bill'
bill.name = 'bill'
This can also be used for test runners like Protractor to hook into Angular:
Scheming.registerQueueCallback $browser.$$incOutstandingRequestCount
Scheming.registerResolveCallback -> $browser.$$completeOutstandingRequest(angular.noop)
Unregisters the callback registered with Scheming.registerResolveCallback
Retrieves a schema that has been built using Scheming.create.
Creates a new Schema constructor.
Person = Scheming.create {name : String}, {seal : true}
bill = new Person {name : 'bill', age : 19}
bill.home = 'Colorado'
bill.name # 'bill'
bill.age # undefined
bill.home # undefined
Person = Scheming.create {age : Number}, {strict : true}
bill = new Person()
bill.age = 9 # success
bill.age = '9' # Error : Error assigning '9' to age. Value is not of type number.
The constructor function returned by Scheming.create. Constructs objects based on the property definitions outlined in the schema. When you invoke the constructor, you can pass a model with initial values to be applied to the instance.
Person = Scheming.create
name : String
age : Number
lisa = new Person
name : 'lisa'
age : 8
Defines properties on your Schema. This is where you specify properties and their expected type, define default values, getters, setters, and validators.
If you do not need any of the other features, you can simply provide a type. You can reference the primitive types in any of the ways outlined in Schema.TYPES
Schema.create 'Person'
name : String
age : Number
birthday : Date
For more complex field configuration, pass a configuration object. The configuration object supports the following keys, outlined below:
Schema.create
age :
type : Number
default : 2
getter : (val) -> val * 2
setter : (val) -> val * 2
validate : (val) -> val % 2 == 0
required : true
this
context of the instance, and can be used to define virtual fields.this
context of the instance.true
or a string, validation will fail with a generic error message. See Schema.validate for details on how validation works. Validation functions are invoked with the this
context of the instance.A convenience method for defining Schema properties in bulk.
Validates an instance of the schema and all child schema instances. Checks for required fields and runs all validators. Validators are not run on fields which are not defined.
null
.If validation succeeds (including if no validators are defined), validate()
returns null
Person = Scheming.create {name : String}
bill = new Person {name : 'bill}
errors = Person.validate bill # null
A validator function should return true if it passes. Any other return value will be treated as validation failure. If a validator returns a string, that string will be treated as the failure message. If a validator throws an error at any point, the error.message
property will be treated as the failure message. Otherwise, the validation will fail with a generic error message.
If multiple validators are defined, all will be run against the value. The errors object will return error messages for all validators that failed.
Person = Scheming.create
name :
type : String
validate : [
-> return "Error number one"
-> throw new Error "Error number two"
-> return true
-> return false
]
bill = new Person()
errors = Person.validate bill
# returns null, because bill object does not have a name defined, and name is not required
bill.name = 'bill'
errors = Person.validate bill
# {name : ["Error number one", "Error number two", "Validation error occurred."]}
The required
configuration is a special validator that checks if the value is defined. If required validation fails, other validators will not be run. This means that validators are guaranteed to receive a value, and do not need to do null checking.
Person = Scheming.create
name :
type : String
required : true
validate : [
-> return "Error number one"
-> throw new Error "Error number two"
-> return true
-> return false
]
bill = new Person()
errors = Person.validate bill
# {name : ["Field is required."]}
The object instance returned by newing up a Schema constructor.
Watches a schema instance for changes. The registered listener function will fire asynchronously each time one or more of the specified properties change.
IMPORTANT Watchers and change detection depend on the set
functionality of Object.defineProperty
. This means that changes made by mutating a value will not necessarily be detected. The scheming library does overwrite the most common array mutators (splice, pop, push, etc.) for a seamless experience. But other less common mutations like Date setters will not be picked up. When in doubt, only manipulate schema instance data via assignment with the =
operator, and do not use mutators.
For all examples, we will use the following schema. For more examples, see the tests.
Person = Scheming.create 'Person',
name : String
age : Number
mother : 'Schema:Person'
friends : ['Schema:Person']
lisa = new Person()
Watching a single property
When a watch is set, it will always be called with the current value, even if no changes were made to the object.
lisa.watch 'name', (newVal, oldVal) ->
# will be called when the event queue clears
newVal == undefined
oldVal == undefined
Synchronous changes will be reflected when the watcher fires.
lisa.name = 'lisa'
lisa.watch 'name', (newVal, oldVal) ->
newVal == 'lisa'
oldVal == undefined
Multiple synchronous changes are rolled up
lisa.watch 'name', (newVal, oldVal) ->
# this listener is called once
newVal == 'lisa'
oldVal == undefined
lisa.name = 'a'
lisa.name = 'b'
lisa.name = 'lisa'
Watching multiple properties
lisa.watch ['name', 'age'], (newVal, oldVal) ->
# newVal == {name : 'lisa', age : 7}
# oldVal == {name : undefined, age : undefined}
lisa.name = 'lisa'
lisa.age = 7
Cleaning up a watch
unwatch = lisa.watch 'name', (newVal, oldVal) ->
newVal == 'lisa'
lisa.name = 'lisa'
# some time later...
unwatch()
lisa.name = 'a' # watch no longer fires
<script>
tag to your html to include lodash on your page.2.x || 3.x
to reduce bower version conflicts on the client. Because they suck.registerQueueCallback
, registerResolveCallback
, unregisterQueueCallback
, and unregisterResolveCallback
functions, exposing hooks into Scheming's change management events.setThrottle
method to allow for different throttling strategies for change resolution on scheming models.this
context of the calling instanceflush
to the public API, add deprecated warning for _flush
FAQs
[](https://travis-ci.org/autoric/scheming)
The npm package scheming receives a total of 2 weekly downloads. As such, scheming popularity was classified as not popular.
We found that scheming demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
/Security News
A flawed sandbox in @nestjs/devtools-integration lets attackers run code on your machine via CSRF, leading to full Remote Code Execution (RCE).
Product
Customize license detection with Socket’s new license overlays: gain control, reduce noise, and handle edge cases with precision.
Product
Socket now supports Rust and Cargo, offering package search for all users and experimental SBOM generation for enterprise projects.