npm install @janiscommerce/model- When use
changeKeysparam and cannot get any items, it will return an empty object (before returns an empty array)
⚠️ Deprecated: Settings with@janiscommerce/settingswill 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)
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).
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:
ℹ️ 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",
// ...
}
}
}There are two different model types:
🛠 Core: Intended for internal databases that manages common data between clients.
class MyModel extends Model {
get databaseKey() {
return 'core';
}
}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.
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.
class MyModel extends Model {
get databaseKey() {
return 'myDatabaseKey';
}
}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",
// ...
}
}
}
}-
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
objectwith the default statuses (active/inactive) -
executionTime (class getter). Returns the time spent in ms on the las query.
- useReadDB
[Boolean](class setter) Set if model should use the read DB in all read/write DB operations. Default:false.
Get the configured/sessionated DBDriver instance to use methods not supported by model.
const dbDriver = await myModel.getDb();
await myModel.dbDriver.specialMethod(myModel);Returns true when the model is core or false otherwise.
Since 8.8.0
const myCoreModel = new MyCoreModel();
const isCore = await myCoreModel.isCore(); // true expectedconst myClientModel = new MyClientModel();
const isCore = await myClientModel.isCore(); // false expectedReturns items from database.
paramsis an optional Object with filters, order, paginator.params.readonlyastrueif you want to use the Read Database.
const items = await myModel.get({ filters: { status: 'active' } });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.
idis required. It can be one ID or an array of IDsparamsis an optional Object with filters, order, paginator, changeKeys.
const items = await myModel.getById(123, { filters: { status: 'active' } });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.
fieldis required. A string as a fieldvalueis required. It can be one value or an array of valuesparamsis an optional Object with filters, order, paginator.
const items = await myModel.getBy(orderId, 123, { filters: { status: 'active' } });Returns items from database using pages, the default limit is 500 items per page.
paramsSee get() methodcallbackA function to be executed for each page. Receives three arguments: the items found, the current page and the page limit
await myModel.getPaged({ filters: { status: 'active' } }, (items, page, limit) => {
// items is an array with the result from DB
});The default order when no order was received is field id using asc as order direction. Since 6.8.3
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
filtersis an optional Object with filters or array of filters. Since 7.1.0paramsis an optional Object with params. Since 8.9.0limitThe max amount of items to count as total. This can be ignored by the DB Driver if it's not supported or a full count is not a performance issue (for example, full collection count in MongoDB).
- 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
await myModel.get({ filters: { status: 'active' } });
const totals = await myModel.getTotals();
/**
totals content:
{
pages: 15,
page: 1,
limit: 500,
total: 7450
}
*/const totals = await myModel.getTotals( { status: 'active' }, { limit: 6000 } );
/**
totals content:
{
pages: 12,
page: 1,
limit: 500,
total: 6000
}
*/Search all items filtering by field and fieldValues and return an Object with key: referenceIds and values: id, only those founds.
fieldField to filter for (String)fieldValuesList of values to filter for (Array<strings>)paramsSee get() method
await myModel.mapIdBy('code', ['code-123', 'code-456'], {
order: { code: 'desc' }
});
/**
{
code-456: 'the-code-456-id',
code-123: 'the-code-123-id'
}
*/Search all References Ids and return an Object with key: referenceIds and values: id, only those founds.
referencesIdsList of References Ids (Array<strings>)paramsSee get() method
await myModel.mapIdByReferenceId(['some-ref-id', 'other-ref-id', 'foo-ref-id'], {
order: { date: 'asc' },
filters: { foo: 'bar' }
});
/**
{
some-ref-id: 'some-id',
foo-ref-id: 'foo-id'
}
*/Returns unique values of the key field from database.
paramsis an optional Object with filters.
const uniqueValues = await myModel.distinct('status');const uniqueValues = await myModel.distinct('status', {
filters: {
type: 'some-type'
}
});Inserts an item in DB. This method is only for insert, will not perform an update.
await myModel.insert({ foo: 'bar' });
const items = await myModel.get({ filters: { foo: 'bar' }});
/**
itemInserted content:
[
{
foo: 'bar'
}
//...
]
*/Inserts/updates an item in DB. This method will perfrom an upsert.
setOnInsertto add default values on Insert, optional
await myModel.save({ foo: 'bar' }, { status: 'active' });
const items = await myModel.get({ filters: { foo: 'bar' }});
/**
items content:
[
{
foo: 'bar',
status: 'active'
}
//...
]
*/Update items that match with the filter.
paramsoptional parameters to define some behavior of the queryskipAutomaticSetModifiedData: Boolean. When receive as true, the fieldsdateModifiedanduserModifiedare not updated automatically.
await myModel.update({ updated: 1 }, { status: 5 });
// will set updated = 1 for the items that has status = 5Remove an item from DB.
await myModel.remove({ foo: 'bar' });
const items = await myModel.get({ filters: { foo: 'bar' }});
/**
items content:
[]
*/Perform a bulk insert of items in DB. This action will insert elements, and will not update elements.
await myModel.multiInsert([{ foo: 1 }, { foo: 2 }]);
const items = await myModel.get();
/**
items content:
[
{ foo: 1 },
{ foo: 2 }
]
*/Perform a bulk save of items in DB. This action will insert/update (upsert) elements.
setOnInsertto add default values on Insert, optional
await myModel.multiSave([{ foo: 1 }, { foo: 2, status: 'pending' }], { status: 'active' });
const items = await myModel.get();
/**
items content:
[
{ foo: 1, status: 'active' },
{ foo: 2, status: 'pending' }
]
*/Perform a bulk remove of items in DB.
await myModel.multiRemove({ status: 2 });
const items = await myModel.get({ filters: { status: 2 }});
/**
items content:
[]
*/Increment/decrement values from an item in DB. This method will not perform an upsert.
await myModel.increment({ uniqueIndex: 'bar' }, { increment: 1, decrement: -1 });
/**
before:
items content:
[
{
increment: 1,
decrement: 2
}
//...
]
after:
items content:
[
{
increment: 2,
decrement: 1
}
//...
]
*/Creates an object list from the received array of items, using the specified field as keys.
itemsThe items arraynewKeyThe common field in items that will be used as key for each item
const myItems = await myModel.get();
/*
[
{ some: 'item', otherProp: false },
{ some: 'otherItem', otherProp: true }
]
*/
const myItemsByKey = MyModel.changeKeys(myItems, 'some');
/*
{
item: { some: 'item', otherProp: false },
otherItem: { some: 'otherItem', otherProp: true }
}
*/ℹ️ 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' });
/*
{
item: { some: 'item', otherProp: false },
otherItem: { some: 'otherItem', otherProp: true }
}
*/ℹ️ 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' });
/*
{}
*/Execute Aggregation operations to obtain computed results in Databases
⚠️ Not supported by all database connectors
stagesAn array with the aggregation stagesoptionsAn object with additional options
const results = await myModel.aggregate([
{ $match: { id: '0000000055f2255a1a8e0c54' } }, // find the document with that id
{ $unset: 'category' }, // Removes the category field
]);
/**
[
{
id: '0000000055f2255a1a8e0c54',
name: 'Product 1',
description: 'Product 1 description'
}
]
*/await myModel.aggregate([
{ $group: { _id: '$status', count: { $sum: 1 } } },
], {
allowDiskUse: true,
hint: { status: 1 }
});
/* >
{
active: 2342,
inactive: 992
}
*/Perform a bulk save of update operations in DB. This action will update elements according to received filters.
await myModel.multiUpdate([
{ filter: { id: [1,2] }, data: { name: 'test 1' } },
{ filter: { otherId: 3 }, data: { name: 'test 2' } }
]);
const items = await myModel.get();
/**
items content:
[
{ id: 1, name: 'test 1' },
{ id: 4, otherId: 3, name: 'test 2' }
]
*/operations:Array<UpdateOperation>- A list of db operations to be executedoptions:Object(optional) - Additional options for the multiUpdate operation
Get an array of indexes in Database table.
await myModel.getIndexes();
/*
[
{ name: '_id_', key: { _id_: 1}, unique: true },
{ name: 'code', key: { code: 1} }
]
*/Create a single index in Database Table.
await myModel.createIndex({ name: 'name', key: { name: 1}, unique: true });Create a multiple indexes in Database Table.
await myModel.createIndexes([{ name: 'name', key: { name: 1}, unique: true }, { name: 'code', key: { code: -1 }}]);Returns struct function to validate ID Type. This struct will vary depending on the implemented DB by the model
const idStruct = await myModel.getIdStruct();
/*
struct('objectId')
*/This package automatically logs any write operation such as:
insert()multiInsert()update()save()multiSave()increment()remove()multiRemove()
ℹ️ The logs will be added using the package @janiscommerce/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 methodsmultiInsert()andmultiSave(), will be added the quantity of items inserted or updated in the same query. Since 6.6.0
This functionality can be disabled in 2 ways
- For all Model operation: by setting the
static getter shouldCreateLogstofalse.
class MyModel extends Model {
static get shouldCreateLogs() {
return false;
}
}- For the "next" operation: by using the method
disableLogs(). Since 8.3.0
ℹ️ The logs are disabled only for the following operation
// will not save logs
await myModel.disableLogs().insert({
pet: 'roger',
animal: 'dog',
age: 8
});
// will save logs normally
await myModel.insert({
pet: 'ringo',
animal: 'dog',
age: 7
});You can exclude fields for logs in case you have sensitive information in your entries such as passwords, secondFactor, etc.
Specify the fields (as field name or field path) to exclude by setting them in the static getter excludeFieldsInLog:
class MyModel extends Model {
static get excludeFieldsInLog() {
return [
'password', // Exclude the password field
'preferences.**.specialNotes', // Exclude the specialNotes field without knowing the intermediate field path between such field and the root path
'*.shipping.*.secondFactor', // Exclude the secondFactor field in any object in the shipping array without specifying the root object
'*.shipping.*.items.*.quantity' // Exclude the quantity field in any object in the items array of any object in the shipping array without specifying the root object
]
}
}By setting this when you do an operation with an item like:
await myModel.insert({
user: 'johndoe',
password: 'some-password',
preferences: {
shippingDetails: {
specialNotes: 'some shipping notes'
}
}
location: {
country: 'some-country',
address: 'some-address'
},
shipping: [
{
addressCommerceId: 'some-address-commerce-id',
isPickup: false,
type: 'delivery',
deliveryEstimateDate: '2025-04-24T22:35:56.742Z',
deliveryWindow: {
initialDate: '2025-04-24T22:25:56.742Z',
finalDate: '2025-04-24T22:35:56.742Z'
},
price: 10,
items: [
{
index: 0,
quantity: 1
}
],
secondFactor: {
method: 'numericPin',
value: '1234'
}
}
]
});It will be logged as:
{
user: 'johndoe',
preferences: {
shippingDetails: {}
}
location: {
country: 'some-country',
address: 'some-address'
},
shipping: [
{
addressCommerceId: 'some-address-commerce-id',
isPickup: false,
type: 'delivery',
deliveryEstimateDate: '2025-04-24T22:35:56.742Z',
deliveryWindow: {
initialDate: '2025-04-24T22:25:56.742Z',
finalDate: '2025-04-24T22:35:56.742Z'
},
price: 10,
items: [
{
index: 0
}
]
}
]
}ℹ️ Note:
- The wildcard
*in the field path of theexcludeFieldsInLogstatic getter, is used to access properties inside arrays or when the root field path is unknown. - The wildcard
**in the field path of theexcludeFieldsInLogstatic getter, can be used when the intermediate field path is unknown between the root and the field to exclude. - The
excludeFieldsInLogstatic getter can have both field names and field paths.
- When using the wildcard
*alone in the field path of theexcludeFieldsInLogstatic getter, it will exclude all the fields in the log. - In case the field path is incorrect, it will not exclude any field.
You can add custom message or object data to log
Adding a custom message:
await myModel
.setLogData('custom message!')
.insert({ foo: 'bar' });
/*
Log: {
...logData,
message: 'custom message!'
}
*/Adding a custom object data:
await myModel
.setLogData({message:'custom message!', type:'some type'})
.insert({ foo: 'bar' });
/*
Log: {
...logData,
message: 'custom message!',
type:'some type'
}
*/Adding a custom object data with log property name:
await myModel
.setLogData({message:'custom message!', type:'some type', log: { isTest: true }})
.insert({ foo: 'bar' });
/*
Log: {
...logData,
message: 'custom message!',
type:'some type',
log:{
...defaultModelLogData,
isTest: true
}
}
*/
⚠️ 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",
}
}
}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"
}
}
}