model
📦 Installation
npm install @janiscommerce/model
Breaking changes ⚠️
6.0.0
- When use
changeKeys
param and cannot get any items, it will return an empty object (before returns an empty array)
Future versions 🔜
9.0.0
- ⚠️ Deprecated: Settings with
@janiscommerce/settings
will no longer be supported. (Replaced with AWS Parameter Store Since 8.8.0) - ⚠️ Deprecated: Usage of AWS Secrets Manager for credentials will no longer be supported. (Replaced with AWS Parameter Store Since 8.8.0)
🔧 Database connection settings
In order to use this package with a DB, you must to add the database connection settings, it can be set in service settings janiscommercerc.json
(Core model) or in session (Client Model).
Configure database connection with databaseKey
Regardless if you use a Core or Client model you may set the databaseKey
that your model will use to get the database connection settings. Default: 'default'
.
class MyModel extends Model {
get databaseKey() {
return 'myDatabaseKey';
}
}
👀 Either in Core or Client model the databaseKey
connection settings structure is the same:
:information_source: The type
property is the only one used by this package to fetch the correct DB Driver package to use.
:warning: The rest of the connection properties depends entirely by the DB Driver that you will use.
{
myDatabaseKey: {
write: {
"type": "mongodb",
"host": "http://write-host-name.org",
},
read: {
"type": "mongodb",
"host": "http://read-host-name.org",
}
}
}
:information_source: Model types
There are two different model types:
🛠 Core: Intended for internal databases that manages common data between clients.
:one: Set the databaseKey
in your Model extended class
class MyModel extends Model {
get databaseKey() {
return 'core';
}
}
:two: Model will try to find your databaseKey
in database service settings
Using Settings, with settings in file /path/to/root/.janiscommercerc.json
:
{
"database": {
"core": {
"write":{
"host": "http://my-host-name.org",
"type": "mysql",
}
}
}
}
👥 Client: Intended for only client databases that not shares data with other databases.
💉 Session injection
The session injection is useful when you have a dedicated database per client.
Using the public setter session
, the session will be stored in the controller
instance.
All the controllers and models getted using that controller will have the session injected.
:one: Set the databaseKey
in your Model extended class
class MyModel extends Model {
get databaseKey() {
return 'myDatabaseKey';
}
}
:two: Database connection configured with session injected
Your client should have the config for read (optional) and/or write (required) databases.
Example of received client:
{
"name": "Some Client",
"code": "some-client",
"databases": {
"default":{
"write": {
"type": "mongodb",
"host": "http://default-host-name.org",
}
},
"myDatabaseKey": {
"write": {
"type": "mongodb",
"host": "http://write-host-name.org",
},
"read": {
"type": "mongodb",
"host": "http://read-host-name.org",
}
}
}
}
:outbox_tray: Getters
-
shouldCreateLogs (static getter).
Returns if the model should log the write operations. Default: true
. For more details about logging, read the logging section.
-
excludeFieldsInLog (static getter).
Returns the fields that will be removed from the logs as an array of strings. For example: ['password', 'super-secret']
. For more details about logging, read the logging section.
-
statuses (class getter).
Returns an object
with the default statuses (active
/ inactive
)
-
executionTime (class getter).
Returns the time spent in ms on the las query.
:inbox_tray: Setters
- useReadDB
[Boolean]
(class setter)
Set if model should use the read DB in all read/write DB operations. Default: false
.
:gear: API
async getDb()
Get the configured/sessionated DBDriver instance to use methods not supported by model.
Example:
const dbDriver = await myModel.getDb();
await myModel.dbDriver.specialMethod(myModel);
async isCore()
Returns true when the model is core or false otherwise.
Since 8.8.0
Core Example:
const myCoreModel = new MyCoreModel();
const isCore = await myCoreModel.isCore();
Non-Core Example:
const myClientModel = new MyClientModel();
const isCore = await myClientModel.isCore();
async get(params)
Returns items from database.
Parameters
params
is an optional Object with filters, order, paginator.params.readonly
as true
if you want to use the Read Database.
Example
const items = await myModel.get({ filters: { status: 'active' } });
async getById(id, [params])
It's an alias of get(), passing and ID as filter and handling return value as an array if id is an array, or an object otherwise.
Parameters
id
is required. It can be one ID or an array of IDsparams
is an optional Object with filters, order, paginator, changeKeys.
Example
const items = await myModel.getById(123, { filters: { status: 'active' } });
async getBy(field, id, [params])
It's an alias of get(), passing and field and the values of that field as filter and handling return value as an array if value is an array, or an object otherwise.
Parameters
field
is required. A string as a fieldvalue
is required. It can be one value or an array of valuesparams
is an optional Object with filters, order, paginator.
Example
const items = await myModel.getBy(orderId, 123, { filters: { status: 'active' } });
async getPaged(params, callback)
Returns items from database using pages, the default limit is 500 items per page.
Parameters
params
See get() methodcallback
A function to be executed for each page. Receives three arguments: the items found, the current page and the page limit
Example
await myModel.getPaged({ filters: { status: 'active' } }, (items, page, limit) => {
});
Default order
The default order when no order was received is field id
using asc
as order direction. Since 6.8.3
async getTotals(filters)
This method returns an object with data of totals. If filters is not present it will default to last get() filters. If no get() was executed before and no filters param is present, it will use no filters
Parameters
filters
is an optional. Object with filters or array of filters. Since 7.1.0
Result object structure:
- pages: The total pages for the filters applied
- page: The current page
- limit: The limit applied in get
- total: The total number of items in DB for the applied filters
Example
await myModel.get({ filters: { status: 'active' } });
const totals = await myModel.getTotals();
const totals = await myModel.getTotals( { status: 'active' } );
async mapIdBy(field, fieldValues, [params])
Search all items filtering by field and fieldValues and return an Object with key: referenceIds and values: id, only those founds.
Parameters
field
Field to filter for (String
)fieldValues
List of values to filter for (Array<strings>
)params
See get() method
Example
await myModel.mapIdBy('code', ['code-123', 'code-456'], {
order: { code: 'desc' }
});
async mapIdByReferenceId(referencesIds, [params])
Search all References Ids and return an Object with key: referenceIds and values: id, only those founds.
Parameters
referencesIds
List of References Ids (Array<strings>
)params
See get() method
Example
await myModel.mapIdByReferenceId(['some-ref-id', 'other-ref-id', 'foo-ref-id'], {
order: { date: 'asc' },
filters: { foo: 'bar' }
});
async distinct(key, params)
Returns unique values of the key field from database.
Parameters
params
is an optional Object with filters.
Examples
const uniqueValues = await myModel.distinct('status');
const uniqueValues = await myModel.distinct('status', {
filters: {
type: 'some-type'
}
});
async insert(item)
Inserts an item in DB. This method is only for insert, will not perform an update.
Example
await myModel.insert({ foo: 'bar' });
const items = await myModel.get({ filters: { foo: 'bar' }});
async save(item, setOnInsert)
Inserts/updates an item in DB. This method will perfrom an upsert.
Parameters
setOnInsert
to add default values on Insert, optional
Example
await myModel.save({ foo: 'bar' }, { status: 'active' });
const items = await myModel.get({ filters: { foo: 'bar' }});
async update(values, filter, params)
Update items that match with the filter.
Parameters
params
optional parameters to define some behavior of the query
skipAutomaticSetModifiedData
: Boolean. When receive as true, the fields dateModified
and userModified
are not updated automatically.
Example
await myModel.update({ updated: 1 }, { status: 5 });
async remove(item)
Remove an item from DB.
Example
await myModel.remove({ foo: 'bar' });
const items = await myModel.get({ filters: { foo: 'bar' }});
async multiInsert(items)
Perform a bulk insert of items in DB. This action will insert elements, and will not update elements.
Example
await myModel.multiInsert([{ foo: 1 }, { foo: 2 }]);
const items = await myModel.get();
async multiSave(items, setOnInsert)
Perform a bulk save of items in DB. This action will insert/update (upsert) elements.
Parameters
setOnInsert
to add default values on Insert, optional
Example
await myModel.multiSave([{ foo: 1 }, { foo: 2, status: 'pending' }], { status: 'active' });
const items = await myModel.get();
async multiRemove(filter)
Perform a bulk remove of items in DB.
Example
await myModel.multiRemove({ status: 2 });
const items = await myModel.get({ filters: { status: 2 }});
async increment(filters, incrementData)
Increment/decrement values from an item in DB. This method will not perform an upsert.
Example
await myModel.increment({ uniqueIndex: 'bar' }, { increment: 1, decrement: -1 });
static changeKeys(items, newKey)
Creates an object list from the received array of items, using the specified field as keys.
Parameters
items
The items arraynewKey
The common field in items that will be used as key for each item
Example
const myItems = await myModel.get();
const myItemsByKey = MyModel.changeKeys(myItems, 'some');
:information_source: In get methods such as get
and getBy
you can add the changeKeys
param with the newKey
value.
const myItems = await myModel.get({ changeKeys: 'some' });
:information_source: Since 6.0.0: When no items were found it will return an empty object
const myItems = await myModel.get({ filters: { status: 'active' }, changeKeys: 'other' });
async dropDatabase()
Drop the Database.
Example
await myModel.dropDatabase();
async aggregate(stages)
Execute Aggregation operations to obtain computed results in Databases
:warning: Not supported by all database connectors
Parameters
stages
An array with the aggregation stages
Example
const results = await myModel.aggregate([
{ $match: { id: '0000000055f2255a1a8e0c54' } },
{ $unset: 'category' },
]);
async multiUpdate(operations)
Perform a bulk save of update operations in DB. This action will update elements according to received filters.
Example
await myModel.multiUpdate([
{ filter: { id: [1,2] }, data: { name: 'test 1' } },
{ filter: { otherId: 3 }, data: { name: 'test 2' } }
]);
const items = await myModel.get();
:bookmark_tabs: Indexes Manipulation
:warning: Only if DB Driver supports it
async getIndexes()
Get an array of indexes in Database table.
Example
await myModel.getIndexes();
async createIndex(index)
Create a single index in Database Table.
Example
await myModel.createIndex({ name: 'name', key: { name: 1}, unique: true });
async createIndexes(indexes)
Create a multiple indexes in Database Table.
Example
await myModel.createIndexes([{ name: 'name', key: { name: 1}, unique: true }, { name: 'code', key: { code: -1 }}]);
async dropIndex(name)
Drop a single in Database Table.
Example
await myModel.dropIndex('name');
async dropIndexes(names)
Drop multiple indexes in Database Table.
Example
await myModel.dropIndexes(['name', 'code']);
async getIdStruct()
Returns struct function to validate ID Type. This struct will vary depending on the implemented DB by the model
Example (for mongodb DB)
const idStruct = await myModel.getIdStruct();
:clipboard: Logging
This package automatically logs any write operation such as:
insert()
multiInsert()
update()
save()
multiSave()
increment()
remove()
multiRemove()
:information_source: The logs will be added using the package @janiscommerce/log.
Automatic data in log
The package will add automatic fields in the log
Object field.
executionTime
. Each log will have the time spent on the query. Since 6.6.0itemsBatch
. Exclusively for methods multiInsert()
and multiSave()
, will be added the quantity of items inserted or updated in the same query. Since 6.6.0
:no_mouth: Disabling automatic logging
This functionality can be disabled in 2 ways
- For all Model operation: by setting the
static getter shouldCreateLogs
to false
.
Example
class MyModel extends Model {
static get shouldCreateLogs() {
return false;
}
}
- For the "next" operation: by using the method
disableLogs()
. Since 8.3.0
:information_source: The logs are disabled only for the following operation
Example
await myModel.disableLogs().insert({
pet: 'roger',
animal: 'dog',
age: 8
});
await myModel.insert({
pet: 'ringo',
animal: 'dog',
age: 7
});
:no_entry_sign: Excluding fields from logs
You can exclude fields for logs in case you have sensitive information in your entries such as passwords, addresses, etc.
Specify the fields to exclude by setting them in the static getter
excludeFieldsInLog
:
class MyModel extends Model {
static get excludeFieldsInLog() {
return [
'password',
'address',
'secret'
]
}
}
By setting this when you do an operation with an item like:
await myModel.insert({
user: 'johndoe',
password: 'some-password',
country: 'AR',
address: 'Fake St 123'
});
It will be logged as:
{
id: '5ea30bcbe28c55870324d9f0',
user: 'johndoe',
country: 'AR'
}
:memo: Set custom log data
You can add custom message or object data to log
Adding a custom message:
await myModel
.setLogData('custom message!')
.insert({ foo: 'bar' });
Adding a custom object data:
await myModel
.setLogData({message:'custom message!', type:'some type'})
.insert({ foo: 'bar' });
Adding a custom object data with log property name:
await myModel
.setLogData({message:'custom message!', type:'some type', log: { isTest: true }})
.insert({ foo: 'bar' });
🔑 Secrets
⚠️ Deprecated: This configuration will no longer be supported starting from version 9.0.0. ⚠️
The package will get the secret using the JANIS_SERVICE_NAME
environment variable.
If the secret is found, the result will be merged with the settings found in the janiscommercerc.json
file or in the Client databases configuration. See Database connection settings.
The Secrets are stored in AWS Secrets Manager and obtained with the package @janiscommerce/aws-secrets-manager
Complete example in which the settings are obtained for settings file or Client and merged with the fetched credentials in AWS Secrets Manager.
- Settings in file or Client.
{
"core": {
"write": {
"type": "mongodb",
"database": "core",
"otherDBDriverConfig": 100
}
}
}
- Secret fetched.
{
"databases": {
"core": {
"write": {
"host": "mongodb+srv://some-host.mongodb.net",
"user": "secure-user",
"password": "secure-password",
}
}
}
}
- Config passed to the Database Driver.
{
"core": {
"write": {
"type": "mongodb",
"database": "core",
"otherDBDriverConfig": 100,
"host": "mongodb+srv://some-host.mongodb.net",
"user": "secure-user",
"password": "secure-password",
}
}
}
Skip Credential Fetching
To skip the fetch of the credentials, it can be used the setting skipFetchCredentials
set as true.
{
"core": {
"write": {
"skipFetchCredentials": true,
"type": "mongodb",
"protocol": "mongodb+srv://",
"host": "mongodb+srv://some-host.mongodb.net",
"user": "some-user",
"password": "insecure-password"
}
}
}