Security News
Fluent Assertions Faces Backlash After Abandoning Open Source Licensing
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
@next-model/core
Advanced tools
AWS Data API model layer package with zero dependencies to use in lambda
Write scoped models using TypeScript.
NextModel gives you the ability to:
See GitHub project for current progress/tasks
Fix typos
Add associations between models
Improve documentation
Implement order in Connector
createdAt
and updatedAt
timestamps
Add callbacks
Predefined validations
Improve schema with eg. default values, limits
Improve associations eg. cascading deletions
Add more packages for eg. versioning and soft deleting
Help to improve tests and the test coverage.
Add more connectors for eg. graphQL and dynamoDB
includes
prefetches relations with two db queries (fetch records => pluck ids => fetch related records by ids) instead of one query per related model.
User.includes({address: {}})
, Profile.includes({user: {address: {}}})
Add a solution to create Migrations
// import
import { Model } from '@next-model/core';
class User extends Model({
tableName: 'users',
init: (props: { firstName?: string; lastName?: string; gender?: string }) => props,
}) {
static get males() {
return this.filterBy({ gender: 'male' });
}
static get females() {
return this.filterBy({ gender: 'female' });
}
static withFirstName(firstName: string) {
return this.filterBy({ firstName });
}
get addresses() {
return Address.filterBy({ userId: this.attributes.id });
}
get name(): string {
return `${this.attributes.firstName} ${this.attributes.lastName}`;
}
}
class Address extends Model({
tableName: 'addresses',
init: (props: { street: string; userId: number }) => props,
}) {
get user() {
return User.filterBy({ id: this.attributes.userId }).first;
}
}
// Creating
user = User.build({
firstName: 'John',
lastName: 'Doe',
gender: 'male',
});
user.name === 'John Doe';
user = await user.save();
user = User.males.buildScoped({ firstName: 'John', lastName: 'Doe' });
user.gender === 'male';
user = await User.create({
firstName: 'John',
lastName: 'Doe',
gender: 'male',
});
address = await user.addresses.createScoped({
street: 'Bakerstr.',
});
address.userId === user.id;
// Searching
users = await User.males.all();
user = await User.withFirstName('John').first();
addresses = await user.addresses.all();
users = await User.filterBy({ lastName: 'Doe' }).all();
users = await User.males()
.order({ key: 'lastName' })
.all();
Initializes new record without saving it to the database.
user = User.build({ firstName: 'John', lastName: 'Doe' });
user.isNew === true;
user.name === 'John Doe';
Returns a Promise
which returns the created record on success or the initialized if sth. goes wrong.
user = await User.create({
firstName: 'John',
lastName: 'Doe',
});
An record can be buildScoped
or createScoped
from filters. These records are created with scope values as default.
address = user.addresses.buildScoped();
address.userId === user.id;
user = User.males.buildScoped();
user.gender === 'male';
user = User.withFirstName('John').buildScoped();
user.firstName === 'John';
user = User.withFirstName('John')
.filterBy({ lastName: 'Doe' })
.buildScoped();
user.name === 'John Doe';
user = User.filterBy({ gender: 'male' }).buildScoped();
user.gender === 'male';
Define the Model associations. Describe the relation between models to get predefined scopes and constructors.
A .belongsTo
association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model.
For example, if your application includes users and addresses, and each user can be assigned to exactly one address, you'd declare the user model this way:
class User extends NextModel<UserSchema>() {
get address() {
return this.belongsTo(Address);
}
}
user = await User.create({ addressId: id });
address = await user.address;
address.id === id;
user = User.build();
user.address = address;
user.addressId === address.id;
A .hasMany
association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a belongsTo association. This association indicates that each instance of the model has zero or more instances of another model.
For example, in an application containing users and addresses, the author model could be declared like this:
class Address extends NextModel<AddressSchema>() {
get users() {
return this.hasMany(User);
}
};
users = await address.users.all;
user = await address.users.create({ ... });
A .hasOne
association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model.
For example, if each address in your application has only one user, you'd declare the user model like this:
class User extends NextModel<UserSchema>() {
get address() {
return this.hasOne(Address);
}
}
class Address extends NextModel<AddressSchema>() {
get user() {
return this.belongsTo(User);
}
}
address = await user.address;
Special filter syntax is dependent on used connector. But all connectors and the cache supports basic attribute filtering and the special queries $and, $or and $now. All special queries start with an leading $. The filter can be completely cleared by calling .unfiltered
User.filterBy({ gender: 'male' });
User.filterBy({ age: 21 });
User.filterBy({ name: 'John', gender: 'male' });
User.filterBy({ $or: [{ firstName: 'John' }, { firstName: 'Foo' }] });
User.filterBy({ $and: [{ firstName: 'John' }, { lastName: 'Doe' }] });
User.filterBy({ $not: [{ gender: 'male' }, { gender: 'female' }] });
User.males.filterBy({ name: 'John' });
User.males.unfiltered().females;
The fetched data can be sorted before fetching then. The orderBy
function takes an object with property names as keys and the sort direction as value. Valid values are asc
and desc
. The order can be resetted by calling .unordered
.
User.orderBy({ key: 'name' });
User.orderBy({ key: 'name', dir: SortDirection.Desc });
User.orderBy([{ key: 'name' }, { key: 'age', dir: SortDirection.Desc }]);
User.males.orderBy({ key: 'name' dir: SortDirection.Asc });
User.orderBy({ key: 'name' }).unordered;
An defined amont of matching records can be skipped with .skipBy(amount)
and be resetted with .unskipped
. The current skipped amount of records can be fetched with .skip
.
Please note: .skipBy(amount)
and .unskipped
will return a scoped model and will not modify the existing one.
Default value is 0
.
User.count(); //=> 10
User.skipBy(3).count(); //=> 7
User.count(); //=> 10 - creates new instance and does not modify existing
User.skipBy(15).count(); //=> 10
User.skipBy(5).unskipped.count(); //=> 10
The resultset can be limited with .limitBy(amount)
and be resetted with .unlimited
. The current limit can be fetched with .limit
.
Please note: .limitBy(amount)
and .unlimited
will return a scoped model and will not modify the existing one.
Default value is Number.MAX_SAFE_INTEGER
.
User.count(); //=> 10
User.limitBy(3).count(); //=> 3
User.count(); //=> 10 - creates new instance and does not modify existing
User.limitBy(15).count(); //=> 10
User.limitBy(5).unlimited.count(); //=> 10
Scopes are predefined queries on a Model. For example filters, orders and limitations.
class User extends NextModel<UserSchema>() {
static get males() {
return this.filterBy({ gender: 'male' });
}
static get females() {
return this.filterBy({ gender: 'female' });
}
static withFirstName(firstName) {
return this.filterBy({ firstName });
}
}
Now you can use these scopes to search/filter records.
User.males;
User.withFirstName('John');
Scopes can be chained with other scopes or search queries.
User.males.witFirsthName('John');
User.withFirstName('John').filterBy({ gender: 'transgender' });
profile = User.males.buildScoped();
profile.gender === 'male';
User.males.young;
User.males.young.filterBy({ ... });
If you want to read the data of the samples of the previous section you can fetch if with the following functions. Each fetching function will return a Promise
to read the data.
Returns all data of the query. Results can be limited by skipBy and limitBy.
users = await User.all();
users = await User.males.all();
users = await User.filterBy({ firstName: 'John' }).all();
Returns the first record which matches the query. Use orderBy to sort matching records before fetching the first one.
user = await User.first();
user = await User.males.first();
user = await User.filterBy({ firstName: 'John' }).first();
user = await User.orderBy({ lastName: 'asc' }).first();
Returns the count of the matching records. Ignores orderBy, skip and limit and always returns complete count of matching records.
count = await User.count();
count = await User.males.count();
count = await User.filterBy({ name: 'John' }).count();
When there is a need to change/delete multiple records at once its recommended to use the following methods if possible. They provide a much better performance compared to do it record by record.
.updateAll(attrs)
updates all matching records with the passed attributes.
users = await User.filterBy({ firstName: 'John' }).updateAll({ gender: 'male' });
users = await User.updateAll({ encryptedPassword: undefined });
Deletes and returns all matching records..
deletedUsers = await User.deleteAll();
deletedUsers = await User.query({ firstName: 'John', lastName: 'Doe' }).deleteAll();
Class Properties are static getters which can be defined with the class. Some of them can be modified by Queries which creates a new Class.
A connector is the bridge between models and the database. NextModel comes with an DefaultConnector which reads and writes on an simpe js object.
Available connectors:
const Connector = require('next-model-knex-connector');
const connector = new Connector(options);
class User extends NextModel<UserSchema>() {
static get connector() {
return connector;
}
}
Define an base model with connector to prevent adding connector to all Models.
Please note: In this case its better to call the @Model
Decorator just on the final models and not on the base model, else you need to define the modelName on each model because its reflected from the base model.
class BaseModel<S extends Identifiable> extends NextModel<S>() {
static get connector() {
return connector;
}
};
class User extends BaseModel<UserSchema> {
...
};
class Address extends BaseModel<AddressSchema> {
...
};
The .keys
will return all possible attributes you can pass to build new model Instances. The keys depend on schema.
class Foo extends NextModel {
static get schema(): Schema {
return {
bar: { type: 'string' },
};
}
}
Foo.keys; //=> ['bar']
A default scope can be defined by adding a getter for .filter
. You need call unfiltered to search without this scope.
class User extends NextModel<UserSchema>() {
static get filter() {
return {
deletedAt: null,
};
}
};
user = await User.first;
User.unqueried.where( ... );
Adds an default Order to all queries unless its overwritten.
class User extends NextModel<UserSchema>() {
static get order() {
return [
{
name: 'asc',
},
];
}
}
Adds an default amount of skipped records on every query. This can be changed by skipBy and removed by .unskipped
.
class User extends NextModel<UserSchema>() {
static get limit(): number {
return 10;
}
}
Limits all queries made to this model to an specific amount. This can be changed by limitBy and removed by .unlimited
.
class User extends NextModel<UserSchema>() {
static get limit(): number {
return 100;
}
}
An record is new unless the record is saved to the database. NextModel checks if the identifier property is set for this attribute.
address = Address.build();
address.isNew === true;
address = await address.save();
address.isNew === false;
The opposite of isNew. Returns false unless the record is not saved to the database.
address = Address.build();
address.isPersistent === false;
address = await address.save();
address.isPersistent === true;
Returns an object which contains all properties defined by schema.
user = User.build({
firstName: 'John',
lastName: 'Doe',
gender: 'male',
});
user.attributes() ===
{
id: 1,
firstName: 'John',
lastName: 'Doe',
gender: 'male',
};
When you change a fresh build or created Class instance this property changes to true.
address = Address.build({
street: '1st street',
city: 'New York',
});
address.isChanged === false;
address.street = '2nd street';
address.isChanged === true;
This property does not change when the value is same after assignment.
address = Address.build({
street: '1st street',
city: 'New York',
});
address.isChanged === false;
address.street = '1st street';
address.isChanged === false;
The changes
property contains an object
of changes per property which has changed. Each entry contains an from
and to
property. Just the last value is saved at the to
property if the property is changed multiple times. The changes are cleared once its set again to its initial value, or if the record got saved.
address = Address.build({
street: '1st street',
city: 'New York',
});
address.changes === {};
address.street = '2nd street';
address.changes ===
{
street: { from: '1st street', to: '2nd street' },
};
address.street = '3rd street';
address.changes ===
{
street: { from: '1st street', to: '3nd street' },
};
address.street = '1st street';
address.changes === {};
address = Address.build({
street: '1st street',
city: 'New York',
});
address.changes === {};
address.street = '2nd street';
address.changes ===
{
street: { from: '1st street', to: '2nd street' },
};
address = await address.save();
address.changes === {};
Custom attributes can be defined as on every other js class.
class User extends NextModel<UserSchema>() {
static get schema() {
return {
firstname: { type: 'string' },
lastname: { type: 'string' },
};
}
get name() {
return `${this.firstName} ${this.lastName}`;
}
}
user = User.build({
firstname: 'Foo',
lastname: 'Bar',
});
user.name === 'Foo Bar';
You can assign a new value to an schema defined property. This does not automatically save the data to the database. All assigned attributes will be tracked by changes
address.assign({
street: '1st Street',
city: 'New York',
});
Saves the record to database. Returns a Promise
with the created record including its newly created id. An already existing record gets updated.
address = Address.build({ street: '1st street' });
address = await address.save();
address.isNew === false;
address.street = 'changed';
address = await address.save();
Removes the record from database. Returns a Promise
with the deleted record.
address.isNew === false;
address = await address.delete();
address.isNew === true;
Refetches the record from database. All temporary attributes and changes will be lost. Returns a Promise
with the reloaded record.
address.isNew === false;
address.street = 'changed';
address.notAnDatabaseColumn = 'foo';
address = address.reload();
address.name === '1st Street';
address.notAnDatabaseColumn === undefined;
Reverts an unsaved change with #revertChange(key)
or reverts all unsaved changed with #revertChanges()
.
address = Address.build({
street: '1st street',
city: 'New York',
});
address.changes === {};
address.street = '2nd street';
address.changes ===
{
street: { from: '1st street', to: '2nd street' },
};
address.revertChange('street');
address.changes === {};
address = Address.build({
street: '1st street',
city: 'New York',
});
address.changes === {};
address.street = '2nd street';
(address.city = 'San Francisco'),
address.changes ===
{
street: { from: '1st street', to: '2nd street' },
street: { from: 'New York', to: 'San Francisco' },
};
address.revertChanges();
address.changes === {};
Checks if the current instance is valid. Promises to return boolean value.
class User extends NextModel<UserSchema>() {
static get validators(): Validators {
return {
ageCheck: user => Promise.resolve(user.age > 0),
};
}
}
isValid = await new User({ age: 28 }).isValid; //=> true
isValid = await new User({ age: -1 }).isValid; //=> flase
UncheckedUser = User.skipValidator('ageCheck');
isValid = await new User({ age: -1 }).isValid; //=> true
class User extends NextModel<UserSchema>() {
static ageCheck(user): Promise<boolean> {
return Promise.resolve(user.age > 0);
}
static get validators(): Validators {
return {
ageCheck: this.ageCheck,
};
}
}
See history for more details.
1.0.0
2019-xx-xx Complete rewrite in typescript0.4.1
2017-04-05 Bugfix: before and after callback0.4.0
2017-02-28 Added platform specific callbacks0.3.0
2017-02-27 Tracked property changes0.2.0
2017-02-25 Improved browser compatibility0.1.0
2017-02-23 Added Browser compatibility0.0.4
2017-02-16 Added callbacks for build
, create
, save
and delete
0.0.3
2017-02-12 Added CI0.0.2
2017-02-05 Published knex connector0.0.1
2017-01-23 Initial commit with query and scoping functionsFAQs
AWS Data API model layer package with zero dependencies to use in lambda
The npm package @next-model/core receives a total of 7 weekly downloads. As such, @next-model/core popularity was classified as not popular.
We found that @next-model/core 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.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
Research
Security News
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.