Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Tiny ODM for MongoDB. The Ne comes from NeDB, and ko refers to the Japanese diminuitive "ko" (小) or little.
NekoDB comes with NeDB built in, so you can access a Mongo-like database, without actually installing or running a database at all. NeDB is suitable for datasets in the range of tens of thousands of documents. For larger datasets, it is recommended you upgrade to MongoDB.
const ko = require('nekodb')
ko.connect({
client: 'nedb',
filepath: __dirname + '/db'
})
ko.models({
Celebrity: {
name: ko.String[50],
age: ko.Number.integer(),
birthday: ko.Date,
instagram: ko.String[30],
followers: ko.Number
},
Family: {
name: ko.String,
members: [ko.models.Celebrity],
city: ko.String
}
})
Connect to a backend, and then register your models by calling ko.models
and passing an
object containing the schemas for each model.
const celebrity = ko.models.Celebrity.create({
name: 'Kim Kardashian West',
age: 37,
birthday: new Date('1980-10-21'),
instagram: '@kimkardashian',
followers: 1080000
})
celebrity.save().then(instance => {
console.log(instance)
// instance will have been assigned an _id
})
Create a model instance with the create
method. To save the instance to the database, call
save
.
ko.models.Celebrity.find({}).then(instances => {
console.log(instances)
})
ko.models.Celebrity.findOne({name: 'Kanye West'}).then(instance => {
console.log(instance)
})
Retrieve models from the database with find
, findOne
, and findById
methods.
ko.models.Celebrity.findOne({name: 'Kim Kardashian'}).then(instance => {
instance.name = 'Kim Kardashian West'
return instance.save()
})
Retrieving a model or models returns model instances, so to update the model, update the fields
on the model and then call save
.
ko.models.Celebrity.deleteOne({name: 'Johnny Depp'}).then(deletedCount => {
console.log(deletedCount)
})
Delete models with the deleteOne
, deleteMany
, or deleteById
methods.
ko.models.Family.findOne({name: 'West'}).join().then(instances => {
console.log(instances)
})
With each reference in the array replaced with the full instance of the referenced document, the result might look something like this:
Instance {
name: 'West',
members: [ Instance {
_id: 'ps30L4dHbv9rLTln',
name: 'Kim Kardashian West',
age: 37,
birthday: 1980-10-21T00:00:00.000Z,
instagram: '@kimkardashian',
followers: 1080000 },
Instance {_id: 'ps30L4dHbv9rLTlo',
name: 'Kanye West',
age: 40,
birthday: 1977-06-08T00:00:00.000Z,
instagram: '@privatekanye',
followers: 406000 } ],
city: 'Los Angeles, CA' }
Before you can do anything else, you must connect to a backend. There are two options available:
NeDB and MongoDB. To connect, call ko.connect
and supply a configuration.
Supply a file path where NeDB will store the database files. Alternatively, set 'inMemory' to true to use the in-memory client, without persisting the database.
ko.connect({
client: 'nedb',
filepath: __dirname + '/path/to/db'
})
or
ko.connect({
client: 'nedb',
inMemory: true
})
Supply the information necessary to connect to MongoDB, as well as the name of the database to use.
You can omit username and password if they're not needed to connect.
Note that to use the MongoDB client, you must also install the optional npm package mongodb
.
ko.connect({
client: 'mongodb',
username: 'root',
password: 'mongopassword',
address: 'localhost:27017',
database: 'nekodb'
})
or
ko.connect({
client: 'mongodb',
url: 'mongodb://username:password@localhost:27017/nekodb'
})
You don't need to supply a username or password if they are not required to connect.
Note that you don't have to wait for a callback after connecting. You can just start issuing commands, which will be queued up and executed once the database connection has been established.
Though they are executed in order, there's no way to guarantee that they finish in order. If you need the results of one call in a subsequent call you should still use a Promise chain to order your calls.
To create a schema you can call ko.models
as in the Quick Intro to create all your schemas at
once, or individually with ko.Model
. Since typing ko.models
every time you want to access a
model can quickly become tiresome, you can create them one at a time and assign them each to a
variable.
const User = ko.Model('User', {
username: ko.String,
password: ko.String,
email: ko.Email
})
The first argument to ko.Model
is the name of the model, which is the name of the collection
and the property name with which it will be attached to ko.models
.
In this example, we can access the newly created model either by its variable name User
, or
at ko.models.User
.
The second argument is the schema to use, which is supplied as an object. The object's keys are the field names, and its values are Ko typeclasses. Typeclasses will check to ensure instance values are of the correct type, and can also perform additional validation and set values, as we will see shortly.
The _id
field is automatically added to every model, expecting a type of ObjectID to be created
and saved by the database. If you intend to use a different type of _id that you will supply
yourself, you must specify so explicitly.
const Model = ko.Model('Model', {
_id: ko.String,
name: ko.String
})
In this example, we're going to set our own _id fields and they are expected to be strings.
Fields specified in a schema are considered required by default. See optional fields for how to make a field optional.
The supported JavaScript types are:
String
Number
Boolean
Date
Array
Object
null
We refer to them by their Ko Typeclasses. The available typeclasses are:
ko.String
ko.Number
ko.Boolean
ko.Date
ko.Array
ko.Option
ko.Document
ko.null
const AllTypesModel = ko.Model('AllTypesModel', {
string: ko.String,
number: ko.Number,
boolean: ko.Boolean,
date: ko.Date,
array: ko.Array(ko.Number) // an array of numbers
option: ko.Option([ko.Number, ko.String]) // either a string or a number
embedded: ko.Document({
subfield: ko.String
}),
null: ko.null // expected to be null or undefined
})
Rather than using ko.Array
, ko.Option
, ko.Document
, or ko.null
, directly, there are
shorthands to create them.
To specify that a field should contain an array of a certain type, pass an array containing one typeclass. To specify a field that can contain varying types, pass an array containing more than one typeclass. These can be nested. To create an array whose elements can be of differing types, supply an array containing the array of options.
const Model = ko.Model('Model', {
array: [ko.Number], // field must be an array of numbers
option: [ko.Number, ko.String] // field must be a number or a string
optArr: [[ko.Number, ko.String]] // field must be an array of strings or numbers
})
To specify that a field should be null, you can just use null
in lieu of a typeclass. To make
a field optional, you can either pass an option array containing the desired type and null, or
using the typeclass method optional
const Optional = ko.Model('Optional', {
optionalString: [ko.String, null],
altOptionalStr: ko.String.optional()
// these accomplish the same thing
})
To specify that a field should contain an embedded document, supply an object: this object will look a lot like a schema, with specified field names and typeclasses. This is the type against which values will be checked.
const Embedded = ko.Model('Embedded', {
document: {
embeddedField: ko.String,
embeddedNum: ko.Number
}
})
In this example, any instance of the Embedded model must contain an object in the document
field and that object must contain the fields embeddedField
and embeddedNum
.
To specify that a field should contain a constant (and to assign it that value) you can supply a number, string, boolean, or Date.
const Constants = ko.Model('Constants', {
string: 'always this string',
number: 0,
boolean: true,
date: new Date('2018-02-25')
})
To specify that a string should have a maximum length, you can use square brackets on ko.String passing in the desired maximum length.
const MaxLength = ko.Model('MaxLength', {
string: ko.String[10]
})
In addition to checking the type, each built-in typeclass contains a number of validator methods that can perform more specific validation. Length limiting strings is one example. These are called as methods on the typeclass, and return a new typeclass.
Since the returned result is a typeclass, you can continue to call methods on it in a chain.
You may find it more readable to define your extended typeclasses above your model rather than putting the whole complicated thing into the schema.
// contains only our desired characters
const Username = ko.String.match(/^[a-zA-Z0-9_\-\.~[\]@!$'()\*+;,= ]{2,30}$/)
// contains at least 1 lowercase, 1 uppercase, and one number
const Password = ko.String.minlength(8)
.match(/[a-z]/)
.match(/[A-Z]/)
.match(/\d/)
const User = ko.Model('User', {
username: Username,
password: Password,
email: ko.Email
})
// only integers from 1 through 10
const Rating = ko.Number.integer().range(1, 10)
const MovieReview = ko.Model('MovieReview', {
author: User,
movie: ko.String[100],
rating: Rating
body: ko.String,
})
The full list of validators available is found below in the API reference.
A method can also cause the typeclass to set a value when the model is created. Any typeclass can
have a default value, set with the default
method. You can set the value to the current time
and date with ko.Date.now()
.
const Member = ko.Model('Member', {
name: ko.String,
bio: ko.String.default('This user has not set a bio')
joined: ko.Date.now()
})
To specify that a field should contain a reference to a model, you can simply supply that Model.
const Author = ko.Model('Author', {
name: ko.String,
email: ko.Email
})
const BlogPost = ko.Model('BlogPost', {
title: ko.String,
body: ko.String,
author: Author,
postDate: ko.Date.today()
})
Author.create({
name: 'Darwood',
email: 'dmarvin@nekodb.net'
}).save().then(author => {
BlogPost.create({
title: 'Why good documentation matters',
body: 'If you forget to document certain features, users will have a hard time...',
author: author._id
})
})
When creating an instance of a Model containing a reference, pass in the _id of the referenced
model. When you perform a find, the reference can be resolved using join
. (See
joining references)
To reference a Model that has not been created yet, or to reference it without access to
the variable it was saved to (for instance, if requiring it would cause a circular dependency)
you can refer to the Model by its name on ko.models
const Blog = ko.Model('Blog', {
owner: ko.models.Author,
posts: [ko.models.BlogPost],
})
In addition to referencing a model by its _id, you can also embed a copy of it in another
document to avoid having to perform a join when you often or always need the data of the
reference. To do so, call the embed
method on a Model.
const Comment = ko.Model('Comment', {
author: ko.models.Author,
body: ko.String
date: ko.Date.now()
})
const BlogPost2 = ko.Model('BlogPost2', {
title: ko.String,
body: ko.String,
author: Author,
postDate: ko.Date.today()
comments: [Comment.embed()]
})
By default, embedding a document saves a copy of it in its collection. To avoid this, use
embedOnly
. Currently, updating and saving an embedded model that is embedded will update it
in its collection, but saving an embedded model directly will not update the models it is
referenced in. This will change in a future version. For now, you can consider embedded models
you find on their own read-only.
There some built-in utility typeclasses, which perform common validation. They are:
ko.Email
a String containing a valid emailko.URL
a String containing a valid URLko.URL.Relative
a String containing a valid URI to a relative path on the same serverOpen an issue if you can think of other utility typeclasses that should be built-in.
To create a new model, first create a model instance with <Model>.create()
. Calling create
without an argument creates a blank model, with all its fields set to undefined. If you have all
the data handy when you're creating it, you can pass an object to create
whose values will
be copied over.
Once created and populated, your instance is ready to be saved to the database. You do so by
calling <instance>.save()
which returns a Promise. If all the fields were valid and the save
completed successfully, the promise will be fulfilled with the same instance you created before
but with an _id assigned by the database. If an _id is provided explicitly, it will use that
instead of one assigned by the database.
If the save was not successful, the promise will be rejected. If it simply failed validation, the promise will be rejected with an object containing all the offending fields and their values. If some other error occurred, the promise will be rejected with that error.
const user1 = ko.models.User.create()
user1.username = 'nekodb'
user1.password = 'Password123'
user1.email = 'neko@nekodb.net'
// or
const user2 = ko.models.User.create({
username: 'nekodb',
password: undefined,
email: 'neko@nekodb.net'
})
user1.save().then(user => {
console.log(user._id)
// generated _id from database
})
user2.save().catch(errors => {
console.log(errors)
// errors will contain the password field,
// since it doesn't contain a String as was required
})
Since every instance uses getters/setters to determine which fields have been updated, console.log-ing them
directly does not yield very interesting results. Every instance has a method called slice
which converts
it into a basic object that you can log and JSON.stringify.
To retrieve models from the database use the Model find
, findOne
, or findById
methods.
Each of these arguments takes a MongoDB query object as its first argument. This is a paper
thin wrapper over the client's own find
methods, so the syntax is literally identical to that
of the client you are using.
find
returns an array of models, findOne
and findById
return a single model. Every find
method returns a Promise that resolves to Instances like those returned by create()
.
const Celebrity = ko.models.Celebrity
Celebrity.find({age: 37}).then(celebs => {
celebs.forEach(celeb => {
console.log(celeb.slice())
})
}).catch(err => {
console.log(err)
})
// returns all celebrity models with age = 37
Celebrity.findOne({name: 'Kanye West'}).then(kanye => {
console.log(kanye.slice())
}).catch(err => {
console.log(err)
})
// finds one model whose name is 'Kanye West'
Celebrity.findById('ps30L4dHbv9rLTln').then(celeb => {
console.log(celeb.slice())
}).catch(err => {
console.log(err)
})
// finds the model with the given _id
The basic structure of a query is an object with the fields to compare and either an expected
value or comparison operators ($lt
, $lte
, $gt
, $gte
, $in
, $nin
, $ne
). These can
be combined with logical operators $or
, $and
, $not
and $nor
.
Regex matches can be performed using the $regex
operator.
Basic queries look for documents whose fields match the values you specify. An example would be
{firstname: 'John', lastname: 'Smith'}
which returns all models named "John Smith".
Using {}
as a query returns all the documents.
ko.models.Celebrity.find({age: {$gte: 40}}).then(celebs => {
celebs.forEach(celeb => console.log(celeb.slice()))
}).catch(err => {
console.log(err)
})
// logs all celebrities at least 40 years old
ko.models.BlogPost.find({
$and: [
{date: {$gte: '2017-01-01', $lt: '2018-01-01'}},
{$or: [
{title: {$regex: /JavaScript/i}},
{title: {$regex: /MongoDB/i}}
]}
]
}).then(posts => {
posts.forEach(post => console.log(post.slice()))
}).catch(err => {
console.log(err)
})
// we specified two fields in this query: title and postDate
// logs all blog posts whose titles contain "JavaScript" or "MongoDB" posted in 2017
For a more in depth explanation of the syntax, please refer to the documentation of the client you are using.
Actually, if you're completely unfamiliar with MongoDB query syntax, I recommend looking at NeDB's documentation first, even if you're using MongoDB, as it provides a gentler introduction and makes notes of where the syntax differs from MongoDB's so you won't get mixed up.
The second, optional argument to the find
family of methods is the projections you want to
perform. It uses MongoDB's syntax for projections, that is, an object containing fields, and
values 0 or 1 to either exclude or include fields. So for instance {a: 1, b: 1}
would include
only fields a
and b
, and {c: 0}
would include all fields except for c
. You can't mix
the two, except for _id which is by default always returned but can be omitted, even when using 1s.
Since this results in an incomplete object that would no longer pass validation, projections are a one way trip, and do not return a model instance, rather just returning a plain object.
ko.models.BlogPost.find({}, {title: 1, _id: 0}).then(titles => {
console.log(titles)
// objects that only contain the title field
}).catch(err => {
console.log(err)
})
Sorting and paginating is done the same way as it's done in MongoDB and NeDB, using a Cursor
object returned by find
. The cursor has methods to alter the results it will return.
Once you've modified the query as needed, execute it by calling then
on it like it's a Promise,
or forEach
like it's an array.
Sort the results you get back by calling the sort
method on a cursor. Sort takes an object
as an argument which specifies the fields and which direction to sort them in. An example would be
{name: 1}
which sorts by name in ascending order or {name: 1, age: -1}
which would sort first
by name in ascending order, then by age in descending order.
By combining the skip
and limit
cursor methods, you can accomplish pagination.
ko.models.Comment.find({author: '3Pipc0uSq1cXqlgE'}).sort({postDate: 1}).skip(0).limit(5).then(comments => {
console.log(comments)
// first 5 comments
return ko.models.Comment.find({author: '3Pipc0uSq1cXqlgE'}).sort({postDate: 1}).skip(5).limit(5)
}).then(comments => {
console.log(comments)
// next 5 comments
})
// etc.
The cursor also has a method not found in MongoDB or NeDB called join
. By calling join
with an array of field names, the references contained in those field names will be replaced
with the full models that they reference. Calling it without an argument joins all references
on the model.
Calls to findOne
and findById
can also be joined this way, calling join
before then
.
Model instances also have a join
method which will perform the join on the instance, returning
a Promise that resolves to the instance with its references replaced.
const Blog = ko.Model('Blog', {
owner: ko.models.Author,
posts: [ko.models.BlogPost],
})
Blog.find({}).join().then(blogs => {
console.log(blogs)
// every blog in the array contains a full Author model in the owner field,
// and an array of full BlogPost models in the posts field
}).catch(err => {
console.log(err)
})
join
can also be called with an object whose fields are the fields on the model to be joined,
and whose values are projection queries specifying which fields of the reference to keep or
omit.
Blog.find({}).join({posts: {_id: 0, title: 1}}).then(blogs => {
console.log(blogs)
// the posts field will contain an array of partial posts, with only the title field
// the owner field will not be joined, only containing a reference
}).catch(err => {
console.log(err)
})
In this case, the model will not be able to be saved again because we omitted the _id field. If we did not omit the _id field, it could be coerced back into a reference and could be saved after modifications.
You cannot call saveRefs
after performing a partial join; in fact, an error will be thrown if
you attempt to do so.
Updating is performed by first finding the model(s) to modify and then, since the find
methods
return Instances, editing the model(s) and then calling save()
.
Only fields that have been updated will be revalidated and saved. Modifications to array and document fields are also considered.
BlogPost.findOne({title: 'Asynchronous JavaSrcipt'}).then(blogpost => {
blogpost.title = 'Asynchronous JavaScript'
return blogpost.save()
}).then(() => {
// continue processing
})
const Player = ko.Model('Player', {
username: ko.String,
gold: ko.Number.default(0)
})
Player.find({}).then(allPlayers => {
return Promise.all(allPlayers.map(player => {
player.gold += 100
return player.save()
}))
}).then(() => {
// continue processing
})
Using Array prototype methods (like .push
, .splice
, etc.) causes the whole array to be replaced
when saving to the database. Same goes for setting the field to a new array. To work more
efficiently with arrays, you can use MongoDB's array manipulation update operators $push
,
$pop
, $addToSet
, and $pull
, which are exposed as special methods on any array field.
$push
and $addToSet
can be called multiple times; $pop
and $pull
can be called only once.
MongoDB only allows one atomic operation per field for one update, so you can't mix more than one at once.
Adds element(s) to the array. You can pass in either a single element or an array of elements
as the first argument to $push
.
You can use the modifiers $slice
and $position
to affect the outcome of the push. They are
supplied as properties on an object as the second argument to $push
.
$slice
limits the length of the array to a certain number, cutting off the end of the array
if its length exceeds the amount in $slice
$position
specifies the position at which to insert the elements. 0 inserts at the beginning
of the array.Removes the first or last element of an array. The argument passed is 1
or -1
which removes
the last or first element respectively.
Adds element(s) to the array, but does not add duplicate values. You can pass in either a single element or an array of elements. For objects, performs a deep-equal comparison when considering whether an element is a duplicate.
Pulls value(s) from the array. You can pass in either a value to pull or an array of values to pull.
MongoDB (and NeDB) allow you to pass a query to $pull
rather than a value. While this will
also work here, NekoDB does not yet contain a full-blown query processor, and as such your
instance will go out of sync, with those values apparently not pulled from the array, though
they will be when the instance is saved. If you specified that the array should not be empty,
using a query may result in the array being empty after all but validation will not catch it.
It's also not tested. Be careful when passing a query to $pull
, and avoid it if possible.
const Student = ko.Model('Person', {
name: ko.String,
classes: [ko.String]
})
Student.create({
name: 'Joanne',
classes: ['Calculus', 'Poetry', 'Databases']
}).save().then(student => {
student.classes.$push('Biology')
return student.save()
}).then(student => {
student.classes.$push(['Greek Drama', 'Greek Comedy'], {
$position: 0,
$slice: 4,
})
// classes will now contain ['Greek Drama', 'Greek Comedy', 'Calculus', 'Poetry']
return student.save()
}).then(student => {
// 'Poetry' will not be added to the array as it already exists
student.classes.$addToSet(['Poetry', 'Nuclear Physics'])
return student.save()
}).then(student => {
// removes the last element of the array. $pop(-1) removes the first.
student.classes.$pop(1)
return student.save()
}).then(student => {
student.classes.$pull('Calculus')
student.save()
}).catch(err => {
console.log(err)
})
If you perform a join on a model, you may note that the model should no longer pass validation: where it expects a reference, it now contains an object. However, you can still edit your model and save it without any issues. References that have been joined will be coerced back into references prior to saving.
You can also make changes to a reference while it is joined and have those changes be saved
using the saveRefs()
method. To perform both saveRefs()
and save()
in one method call, you
can use saveAll()
As saveRefs
just calls save()
on the reference instances, saveAll()
can also be used to
create an entirely new model to be saved to the database. This way you can create your models
and references together in one action.
ko.models.Blog.create({
owner: {
name: 'Niko D.B.',
email: 'niko@nekodb.net'
},
posts: []
}).saveAll().then(blog => {
console.log(blog.owner)
// this will be the _id of the newly created Author model
blog.posts.$push({
title: 'My first blog',
body: 'The content of the blog post',
author: blog.owner
})
return blog.saveAll()
}).then(blog => {
console.log(blog.posts)
// now contains the _id of the newly created BlogPost
}).catch(err => {
console.log(err)
})
Currently you can't use either client's native update
methods as they would bypass validation.
This will be supported in a later version.
To count the number of documents matching a query, use the Model count
method, which takes
the same kind of query as the find
methods. count
returns a Promise that resolves to the
number of models that matched the query.
ko.models.Author.count({}).then(count => {
console.log(count)
// logs the total number of Author models
}).catch(err => {
console.log(err)
})
You can delete a model two ways: using the Model methods deleteOne
, deleteMany
, or
deleteById
, or by calling delete()
on a model instance.
These all return a Promise that resolves to the number of documents deleted by the operation.
ko.models.User.deleteOne({username: 'nekodb'}).then(deletedCount => {
console.log(deletedCount)
// this will be 1 if we succeeded
})
ko.models.User.findOne({username: 'milkperil'}).then(user => {
return user.delete()
}).then(deletedCount => {
console.log(deletedCount)
// this will be 1 if we succeeded
}).catch(err => {
console.log(err)
})
If predelete
or postdelete
hooks are specified, they will be run for each instance using
any of the methods to delete the model.
You can inject code to be run before and after certain steps of saving a model to the database.
Your hook will be called with two arguments: the instance and a next()
function you call when
you're finished with your hook. You can also return a Promise rather than call next()
.
You can add hooks two ways. First is by including a $$hooks property on your schema when you define your model, where the keys are the names of the hooks to be added and the values are the hooks. Or, you can set a value on the Model object whose property name is one of the names of the hooks.
To determine whether a hook should modify a certain field you can use the instance method
isUpdated
which takes a field name.
Runs immediately after a model is created. Used to asynchronously add default values to a model
as Typeclass.default()
is limited to synchronous code only.
Runs before validation. Can be used to populate fields that are based on other fields.
Runs after validation.
Runs before saving. If you need to modify a field before storing it in the database, you should do it here.
Runs after a model is saved.
Runs before a model is deleted.
Runs after a model is deleted.
const bcrypt = require('bcrypt')
const saltRounds = process.env.SALTROUNDS
// contains at least 1 lowercase, 1 uppercase, and one number
const Password = ko.String.minlength(8)
.match(/[a-z]/)
.match(/[A-Z]/)
.match(/\d/)
const User = ko.Model('User', {
username: ko.String.range(2, 30),
password: Password,
$$hooks: {
presave: (user, next) => {
if (user.isUpdated('password')) {
bcrypt.hash(user.password, saltRounds, function (err, hash) {
if (err) {
return next(err)
}
user.password = hash
next()
})
} else {
next()
}
}
}
})
User.create({
username: 'amy',
password: 'Here is a G00D password'
}).save().then(user => {
console.log(user.password)
// this will be encrypted now
user.username = 'Amy'
return user.save()
}).then(user => {
// the save will succeed even though the password is encrypted and might otherwise fail validation
}).catch(err => {
console.log(err)
})
Here we supply a named presave
hook to be run any time the password is updated. Because
validation only occurs when a field is updated, even though the new password may or may not
pass validation, you can still modify the model and save it as needed.
We make sure not to double encrypt the password by specifying that it should only be updated if
the password
field is updated.
To update the password, you need only set a new password on the model and the new password will be validated and encrypted.
You can specify indexes on fields by adding an $$index
field to a schema or by calling
createIndex
on a Model. Indexes help speed up performance by reducing the amount of
elements the database must check, and can also be used to specify uniqueness constraints.
You should essentially have an index on any field you intend to search by or sort by, to avoid having to scan all the elements in a collection.
See Indexes in MongoDB for more information on what indexes are and why you might want to use them.
Creating an index directly calls the client's own index creation mechanism, so the full power of MongoDB or NeDB indexes are at your disposal.
const User = ko.Model('User', {
username: ko.String,
password: ko.String,
$$indexes: {
username: {
unique: true
}
}
})
Now an error will be thrown if we attempt to create two users with the same username.
See client index sections for what indexes are avaialable to each client.
When setting values on an instance or making a query, type coersion will be performed to make
the values match those expected by the fields. The string literals 'true'
and 'false'
will
be coerced to true
and false
if the field expects a Boolean. Otherwise, native JavaScript
type coercion is performed on the value. For dates, the Date
constructor is called, allowing
you to pass a wide variety of date formats in.
For fields where multiple types were declared, the first type declared is what will be used for
coercion. For optional fields, type coercion will be performed unless the value is null
in
which case it will remain null
rather than be coerced. String 'null'
is coerced to null
.
const CoerceModel = ko.Model('CoerceModel', {
number: ko.Number,
optional: ko.Number.optional(),
multitype: [ko.Number, ko.String],
date: ko.Date
})
const model = CoerceModel.create({
number: '100',
optional: '25',
multitype: '40',
date: '2018-04-07'
})
console.log(model.slice())
// number: 100,
// optional: 25,
// multitype: 40,
// date: 2018-04-07T00:00:00.000Z
model.save().then(() => {
return CoerceModel.findById(model._id.toString())
}).then(found => {
console.log(found.slice())
found.optional = null
return found.save()
}).then(() => {
return CoerceModel.findOne({optional: 'null'})
}).then(found => {
console.log(found.slice())
})
connect(Object config)
Connects to a backend (either NeDB or MongoDB) using supplied config.close()
Closes a MongoDB connection, and sets own properties to null
.Model(Object schema)
Registers a Model and creates a collection to store it in. Returns a Model
. schema has form {fieldname: typeclass, ...}models(Object schemas)
Registers multiple Models at once. schemas has form {modelname: {schema}, ...}models
The Models registry. The properties of this object are a way to refer to each Model that has been registered.client
The currently used backend. Either an instance of MongoClient
(not MongoDB's MongoClient) or NeDBClient
These methods are available on all Typeclasses.
default(value)
Sets a default value when a model is createdoptional()
Makes a field accept null and undefined as well as the current type. This method
should be called last if calling multiple methods.constant(value)
Sets a default value and ensures that the field actually contains that value.validate(Function validator(value))
Adds a validator, which is a function that takes a value and returns true or falseextend(Function/Object extend)
If argument is a function, calls the function with the this
context set to the Typeclass. In the function,
set value
, validator
, or for multiple validators, validators
. If it is an object, those same
fields are copied over.Instance of Typeclass
Specifies that a field must be a Number.
min(Number min)
Specifies a minimum value.max(Number max)
Specifies a maximum value.minx(Number min)
Specifies a minimum value, from but not including the minimum.maxx(Number max)
Specifies a maximum value, up to but not including the maximum.range(Number min, Number max)
Specifies a range of valid values from min to max.naturalRange(Number min, Number max)
Specifies a range of values starting from min up to but not including max.integer()
Specifies that a field should contain only integer values.Instance of Typeclass
Specifies that a field must be a String.
[Number maxlength]
Shorthand for specifying a maximum length.minlength(Number length)
Specifies a minimum length for the value.maxlength(Number length)
Specifies a maximum length for the value.range(Number minlength, Number maxlength)
Specifies a range of valid lengths.match(Regex pattern)
Tests the value against the supplied regular expression.Instance of Typeclass
Specifies that a field must be a Boolean.
Instance of Typeclass
Specifies that a field must be a JavaScript Date object.
after(Date minDate)
Specifies that the value must occur after minDatebefore(Date maxDate)
Specifies that the value must occur before maxDatepast()
Specifies that the value should be before Date.now()future()
Specifies that the value should be after Date.now()range(Date minDate, Date maxDate)
Specifies a range of valid dates, from minDate to maxDatenow()
Sets the value to the value of Date.now() at the time of model creationtoday()
Sets the value to midnight of the current day when the model is createdInstance of Typeclass
Specifies that a field should contain null
or undefined
.
Instance of Typeclass
Specifies that a field should contain an Array of the supplied type.
notEmpty()
Specifies that the value array must contain at least one element.Instance of Typeclass
Specifies that a field should contain one of the supplied types.
Instance of Typeclass
Specifies that a field should contain an object matching the supplied
schema.
Created by ko.Model()
or ko.models()
. Represents a Model based on a supplied schema.
create([Object document])
Returns a model instance, optionally copying the values of the supplied documentcount(Object query)
Returns a Promise that resolves to the number of models matching the
supplied MongoDB query.deleteById(_id)
Deletes one model with the specified _id. Returns a Promise that resolves to
the number of models deleted.deleteMany(Object query)
Deletes all models matching the supplied MongoDB query. Returns
a Promise that resolves to the number of models deleted.deleteOne(Object query)
Deletes the first model matching the supplied MongoDB query. Returns
a Promise that resolves to the number of models deleted.find(Object query)
Finds all models matching the supplied MongoDB query. Returns a Cursor
object that can be used to sort, paginate, and join as an extension to the query.findById(Object query)
Finds one model with the specified _id. Returns a deferred Promise that
can be used to join as an extension to the query. Otherwise it behaves like a Promise.findOne(Object query)
Finds the first model matching the supplied MongoDB query. Returns a deferred Promise that
can be used to join as an extension to the query. Otherwise it behaves like a Promise.reference()
Returns a ModelReference object for use in other Models. Simple reference based
on _id field. Typically called implicitly when passing a Model to a schema.ref()
Shorthand for reference()
embed()
Returns a ModelReference object for use in other Models. A reference that includes
the reference as an embedded document, and also saves the embedded model to its own collection.embedOnly()
Returns a ModelReference object for use in other Models. Same as embed()
but
does not save a copy.oncreate
The oncreate hook. Assign this to run a custom hook when a model is created.prevalidate
The prevalidate hook. Assign this to run a custom hook before validation.postvalidate
The postvalidate hook. Assign this to run a custom hook after validation.presave
The presave hook. Assign this to run a custom hook before saving.postsave
The postsave hook. Assign this to run a custom hook after saving.predelete
The predelete hook. Assign this to run a custom hook.postdelete
The postdelete hook. Assign this to run a custom hook.createIndex(String field, Object options)
Creates an index on the collection that contains the
Model. See clients for available options.A model instance. An object whose keys are field names and whose values are the field values.
save()
Saves the model to the database, creating an _id if it does not yet exist.saveRefs()
Saves references that have been joined or embedded into Instances to the database.saveAll()
Saves the model and its Instance members to the database.delete()
Deletes the model from the database.join([Array fields]|[Object fields])
Populates references on the specified fields to their full models. If
fields is not supplied, joins all references on the model. If fields is an object, performs
the join(s) with the specified projections, returning only partially joined references.slice()
Returns a simple object (without prototype methods) that contains the data of the model.A cursor object returned by Model.find()
.
sort(Object params)
Sorts the results of the query by the supplied params. The object
contains field names, and a sort direction for each field name: 1 for ascending order, -1 for
descending.skip(Number count)
Skips over the first count models when performing the query.limit(Number count)
Limits the number of models returned by the query. When used together with
skip
you can perform pagination.join([Array fields])
Populates each model returned by the query, replacing references with the
full models they reference. Joins on specified fields. If fields is not supplied, joins all
references. If fields is an object, performs the join(s) with the specified projections,
returning only partially joined references.then(Function callback)
Executes the query and returns a Promise that resolves to the found
models.toArray(Function callback)
Alias for then()
forEach(Function callback)
Runs the callback for each model returned by the query, in order.The NeDBClient config looks like
{
client: 'nedb',
[filepath: '/path/to/db/directory'],
[inMemory: true],
[autocompactionInterval: Number interval]
}
where one of filepath or inMemory are supplied. Auto-compaction refers to NeDB's option to periodically compact the database file to reduce size. This is measured in ms, and should be at least 5000.
close()
NeDB does not support close, so this method does nothing but issue a warning.createIndex(String collection, String fieldName, Object options)
Creates an index on the
supplied collection and fieldName. Typically called implicitly by Model.createIndex()
. The
available options are:
unique
(optional, defaults to false) enforces uniqueness on the field.sparse
(optional, defaults to false) does not index documents on which the field is not
defined.expireAfterSeconds(Number seconds)
Removes documents from the database when the system time
exceeds the value of the field + seconds. Documents where the field is not defined or not a date
are ignored.The MongoClient config looks like
{
client: 'mongodb',
[username: 'username'],
[password: 'password'],
[address: 'localhost:27017'],
[database: 'the_db]',
[url: 'mongodb://localhost:27017/the_db']
}
You can supply either the pieces of the connection string as properties (username, password, address, and database) or a connection string (url)
close()
Closes the connection to the MongoDB server.createIndex(String collection, String fieldName, Object options)
Creates an index on the
supplied collection and fieldName. Typically called implicitly by Model.createIndex()
As it calls createIndex directly on the underlying MongoClient, it supports
the full gamut of MongoDB index options.client
The underlying MongoClient powering the backend. Could be useful for testing.db
The underlying MongoDB database object. Could be useful for testing.$push
, $pop
, $addToSet
, and $pull
methods to array fieldsisUpdated
instance methodAlthough the project is still in its early stages, the code is reasonably well tested.
To run tests, run the command npm test
. To test the MongoDB client, in the config.js
file in the test directory, change testMongo
to true, and supply your MongoDB information.
Bug reports, feature requests, and questions are all welcome: just open a Github issue and I'll get back to you.
FAQs
Tiny ODM for MongoDB/NeDB
The npm package nekodb receives a total of 9 weekly downloads. As such, nekodb popularity was classified as not popular.
We found that nekodb demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer 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
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.