Skip to content

Data Resolve

In this section, we will discuss how to resolve data from a record to another model’s record(s).

We’ll use this diagram for our examples:

┏━━━━━━━━━━┓ ┏━━━━━━━━━━━━┓ ┏━━━━━━━━━━━┓
┃ shops ┃ ┃ shop_items ┃ ┃ items ┃
┣━━━━━━━━━━┫ ┣━━━━━━━━━━━━┫ ┣━━━━━━━━━━━┫
│ id │<─────┐ │ id │ ┌─────>│ id │
├──────────┤ │ ├────────────┤ │ ├───────────┤
│ name │ └─────>│ shop_id │ │ │ name │
├──────────┤ ├────────────┤ │ ├───────────┤
│ location │ │ item_id │<─────┘ │ price │
└──────────┘ ├────────────┤ ├───────────┤
│ stock │ │ is_locked │
└────────────┘ └───────────┘
// `shops` model
shops = new NimbusDBModel("global", "shops", {
id: {
type: NIMBUSDB_DATA_TYPE.INTEGER,
const: NIMBUSDB_CONSTRAINT.PRIMARY_KEY
},
name: NIMBUSDB_DATA_TYPE.STRING,
location: NIMBUSDB_DATA_TYPE.STRING
}, [
{ id: 1, name: "Starter Shop", location: "Town 1" },
{ id: 2, name: "Adventurer's Guild", location: "Alpha Dungeon" },
{ id: 3, name: "Grand Capital Mart", location: "Capital City" }
]);
// `shop_items` model
shop_items = new NimbusDBModel("global", "shop_items", {
id: {
type: NIMBUSDB_DATA_TYPE.INTEGER,
const: NIMBUSDB_CONSTRAINT.PRIMARY_KEY
},
shop_id: NIMBUSDB_DATA_TYPE.INTEGER,
item_id: NIMBUSDB_DATA_TYPE.INTEGER,
stock: NIMBUSDB_DATA_TYPE.INTEGER
}, [
{ id: 1, shop_id: 1, item_id: 1, stock: 20 },
{ id: 2, shop_id: 1, item_id: 2, stock: 17 },
{ id: 3, shop_id: 1, item_id: 3, stock: 23 },
{ id: 4, shop_id: 2, item_id: 3, stock: 50 },
{ id: 5, shop_id: 3, item_id: 5, stock: 10 }
]);
// `items` model
items = new NimbusDBModel("global", "items", {
id: {
type: NIMBUSDB_DATA_TYPE.INTEGER,
const: NIMBUSDB_CONSTRAINT.PRIMARY_KEY
},
name: NIMBUSDB_DATA_TYPE.STRING,
price: {
type: NIMBUSDB_DATA_TYPE.NUMBER,
validator: function(data, value) {
return value >= 0;
},
default_value: 0
},
is_locked: {
type: NIMBUSDB_DATA_TYPE.BOOLEAN,
const: NIMBUSDB_CONSTRAINT.OPTIONAL,
default_value: false
}
}, [
{ id: 1, name: "Apple", price: 5 },
{ id: 2, name: "Banana", price: 7.2 },
{ id: 3, name: "Cherry", price: 15 },
{ id: 4, name: "Date", price: 12.5 },
{ id: 5, name: "Elderberry", price: 8 },
{ id: 6, name: "Fig", price: 10 },
{ id: 7, name: "Grape", price: 6 },
{ id: 8, name: "Honeydew", price: 9 },
{ id: 9, name: "Kiwi", price: 4 },
{ id: 10, name: "Lemon", price: 3 }
]);
// register all models to a catalog
ctg_items = new NimbusDBCatalog("items", {
model: [shops, shop_items, items]
});
// define relations
ctg_items.define_relation([
"shops.id <=> shop_items.shop_id",
"items.id <=> shop_items.item_id"
]);

Resolving data means we fetch and add the related record(s) into the current record(s) by using the provided foreign-key column. It’s a clean way to get combined records from current model with other model(s) without need to .join() the related model(s).

The simplest way to resolve data is to use the .resolve() method with a column name that acts as a foreign-key column.

// (1) column intersection
var shop_item1_res = shop_items.find(1).resolve("item_id", items);
// this will resolve `shop_items.item_id` -> `items.item_id`, which is not what we want (there's no `items.item_id`)
// this form is useful if the both models use the same column name as foreign-primary key
// so, shop_item1_res will be `undefined`
// (2) different column names
// `on` option specify which column in `with` model that should be used as primary-key column
var shop_item1_res = shop_items.find(1).resolve("item_id", items, {
on: "id"
});
// so, this will resolve `shop_items.item_id` -> `items.id`
// which means this will resolve `shop_item` with `id = 1` to `items` with `id = 1`
// so, shop_item1_res has value: {
// id: 1,
// shop_id: 1,
// item_id: { id: 1, name: "Apple", price: 5 },
// stock: 20
// }
// (3) resolve to different column name
// `as` option to specify the column name used for storing resolved data
var shop_item1_res = shop_items.find(1).resolve("item_id", items, {
on: "id",
as: "item"
});
// so, this will resolve `shop_items.item_id` -> `items.id`
// which means this will resolve `shop_item` with `id = 1` to `items` with `id = 1`
// so, shop_item1_res has value: {
// id: 1,
// shop_id: 1,
// item_id: 1, // <- the original value
// stock: 20,
// item: { id: 1, name: "Apple", price: 5 }
// }
// (4) if the resolved records is more than 1
var shops1 = shops.find(1).resolve("id", shop_items, {
on: "shop_id",
as: "shop_items",
match_all: true // if not set, it will only resolve the first record
});
// so, this will resolve `shops.id` -> `shop_items.shop_id`
// which means this will resolve `shop` with `id = 1` to `shop_items` with `shop_id = 1`
// so, shops1 has value: {
// id: 1,
// name: "Starter Shop",
// location: "Town 1",
// shop_items: [
// { id: 1, shop_id: 1, item_id: 1, stock: 20 },
// { id: 2, shop_id: 1, item_id: 2, stock: 17 },
// { id: 3, shop_id: 1, item_id: 3, stock: 23 }
// ]
// }
// (5) resolve multiple times
var shop_item2_res = shop_items.find(2)
.resolve("item_id", items, { // first, resolve `shop_items.item_id` -> `items.id`
on: "id",
as: "item"
})
.resolve("shop_id", shops, { // then, resolve `shop_items.shop_id` -> `shops.id`
on: "id",
as: "shop"
})
.print(); // (for debugging purpose) show final resolved record
// so, this will resolve `shop_items.item_id` -> `items.id` and `shop_items.shop_id` -> `shops.id`
// which means this will resolve `shop_item` with `id = 2` to `items` with `id = 2` and `shops` with `id = 1`
// so, shop_item2_res has value: {
// id: 2,
// shop_id: 1,
// item_id: 2,
// stock: 17,
// item: { id: 2, name: "Banana", price: 7.2 },
// shop: { id: 1, name: "Starter Shop", location: "Town 1" }
// }

If you already defined relation(s) between current model and other model(s), you can use the relation id or relation object to resolve the related data.

// (1) using relation id
var rel_id = shop_items.get_relation("items", "item_id", "id");
var shop_item1_res = shop_items.find(1).resolve(rel_id);
// in this case, the `on` option is not needed since it's already defined in the relation
// so, this will resolve `shop_items.item_id` -> `items.id`
// which means this will resolve `shop_item` with `id = 1` to `items` with `id = 1`
// so, shop_item1_res has value: {
// id: 1,
// shop_id: 1,
// item_id: { id: 1, name: "Apple", price: 5 },
// stock: 20
// }
// (2) using relation object
var rel = shop_items.get_relation("items", "item_id", "id", { return_object: true });
var shop_item1_res = shop_items.find(1).resolve(rel);
// same as (1)
// (3) using relation object with options
var rel_id = shop_items.get_relation("items", "item_id", "id");
var shop_item1_res = shop_items.find(1).resolve(rel_id, {
as: "item"
});
// shop_item1_res has value: {
// id: 1,
// shop_id: 1,
// item_id: 1,
// stock: 20,
// item: { id: 1, name: "Apple", price: 5 }
// }

This form is great for when you have multiple columns to resolve at once.

// (1) column intersection
var shop_item1_res = shop_items.find(1).resolve({
item_id: { with_model: items }
});
// resolve `shop_items.item_id` -> `items.item_id`
// shop_item1_res has `undefined` value, because there's no `item_id` column in `items` model
// (2) column mapping
var shop_item1_res = shop_items.find(1).resolve({
item_id: { with_model: items, on: "id" }
});
// resolve `shop_items.item_id` -> `items.id`
// shop_item1_res has value: {
// id: 1,
// shop_id: 1,
// item_id: { id: 1, name: "Apple", price: 5 },
// stock: 20
// }
// (3) resolve with different column name
var shop_item1_res = shop_items.find(1).resolve({
item_id: { with_model: items, on: "id", as: "item" }
});
// resolve `shop_items.item_id` -> `items.id`
// shop_item1_res has value: {
// id: 1,
// shop_id: 1,
// item_id: 1,
// stock: 20,
// item: { id: 1, name: "Apple", price: 5 }
// }
// (4) resolve multiple columns
var shop_item1_res = shop_items.find(1).resolve({
item_id: { with_model: items, on: "id", as: "item" },
shop_id: { with_model: shops, on: "id", as: "shop" }
});
// resolve `shop_items.item_id` -> `items.id` and `shop_items.shop_id` -> `shops.id`
// shop_item1_res has value: {
// id: 1,
// shop_id: 1,
// item_id: 1,
// stock: 20,
// item: { id: 1, name: "Apple", price: 5 },
// shop: { id: 1, name: "Starter Shop", location: "Town 1" }
// }

Let’s add another record to the shop_items model for demonstration.

shop_items.insert({
id: 6,
shop_id: 3,
item_id: [1, 2, 4, 6], // multiple foreign keys
stock: 3,
__force: true // force insert, only for example
});
var shop_item6_res = shop_items.find(6).resolve("item_id", items, {
on: "id",
as: "items"
});
// resolve `shop_items.item_id` -> `items.id`
// shop_item6_res has value: {
// id : 6,
// shop_id : 3,
// items : [
// { id : 1, price : 5, name : "Apple", is_locked : 0, color : "red" },
// { id : 2, price : 7.20, name : "Banana", is_locked : 0, color : "yellow" },
// { id : 4, price : 12.50, name : "Date", is_locked : 0, color : "green" },
// { id : 6, price : 10, name : "Fig", is_locked : 0, color : "purple" }
// ],
// item_id : [ 1,2,4,6 ],
// stock : 2
// }

By default, .resolve() will resolve the related data to the column name that acts as the foreign key (or column specified by as option). If you need to merge it with the source record, you can use the expand or populate option.

var shop_item1_res = shop_items.find(1).resolve("item_id", items, {
on: "id",
expand: true // or `populate: true`
});
// resolve `shop_items.item_id` -> `items.id`
// shop_item1_res has value: {
// id: 1,
// shop_id: 1,
// item_id: 1,
// stock: 20,
// name: "Apple", // from `items.name` column
// price: 5 // from `items.price` column
// }

Resolves a foreign-key column by looking up the related record in another model.

data.d.ts
class NimbusDBData {
// ... other methods and properties ...
static resolve(
_column: string,
_with: NimbusDBModel,
_options?: Partial<NimbusDBDataResolve>
): NimbusDBData;
}
  • Type: string
  • The column name in both this data and _with model.
  • Type: NimbusDBModel
  • The model to resolve against.
  • Type: Partial<NimbusDBDataResolve>
  • Default: undefined
  • Optional options for resolving the data.
  • Type: NimbusDBData
  • The modified NimbusDBData instance after resolution.

Object for resolving data by object form.

data.d.ts
type NimbusDBDataResolve = {
with_model: NimbusDBModel;
as?: string; // set new property instead of overwriting the original (default = undefined). no effect if `expand` is true
expand?: boolean; // set the result directly to the data
isolate?: boolean; // return an isolated copy of the data after the operation (default = false)
match_all?: boolean; // match all records instead of the first one (default = false)
on?: string; // column name to resolve (default = with.primary_key)
write_through?: boolean; // sync the change to the original data (default = false)
affect_original?: boolean; // alias for `write_through`
populate?: boolean; // alias for `expand`
};

Optional configurations for .resolve() method.

data.d.ts
export type NimbusDBDataResolveOptions = Partial<{
isolate: boolean; // return an isolated copy of the data after the operation (default = false)
}>;