@decaf-ts/core
Version:
Core persistence module for the decaf framework
744 lines • 31.6 kB
JavaScript
import { Model, ValidationKeys, } from "@decaf-ts/decorator-validation";
import { Repository } from "./../repository/Repository.js";
import { InternalError, NotFoundError } from "@decaf-ts/db-decorators";
import { PersistenceKeys } from "./../persistence/constants.js";
import { Cascade } from "./../repository/constants.js";
import { Metadata } from "@decaf-ts/decoration";
import { isClass } from "@decaf-ts/logging";
/**
* @description Creates or updates a model instance
* @summary Determines whether to create a new model or update an existing one based on the presence of a primary key
* @template M - The model type extending Model
* @template F - The repository flags type
* @param {M} model - The model instance to create or update
* @param {Context<F>} context - The context for the operation
* @param {Repo<M, F, Context<F>>} [repository] - Optional repository to use for the operation
* @return {Promise<M>} A promise that resolves to the created or updated model
* @function createOrUpdate
* @memberOf module:core
* @mermaid
* sequenceDiagram
* participant Caller
* participant createOrUpdate
* participant Repository
* participant Model
*
* Caller->>createOrUpdate: model, context, repository?
* alt repository not provided
* createOrUpdate->>Model: get(model.constructor.name)
* Model-->>createOrUpdate: constructor
* createOrUpdate->>Repository: forModel(constructor)
* Repository-->>createOrUpdate: repository
* end
*
* alt primary key undefined
* createOrUpdate->>Repository: create(model, context)
* Repository-->>createOrUpdate: created model
* else primary key defined
* createOrUpdate->>Repository: update(model, context)
* alt update successful
* Repository-->>createOrUpdate: updated model
* else NotFoundError
* createOrUpdate->>Repository: create(model, context)
* Repository-->>createOrUpdate: created model
* end
* end
*
* createOrUpdate-->>Caller: model
*/
export async function createOrUpdate(model, context, alias, repository) {
const log = context.logger.for(createOrUpdate);
if (!repository) {
const constructor = Model.get(model.constructor.name);
if (!constructor)
throw new InternalError(`Could not find model ${model.constructor.name}`);
repository = Repository.forModel(constructor, alias);
log.info(`Retrieved ${repository.toString()}`);
}
let result;
if (typeof model[Model.pk(repository.class)] === "undefined") {
log.info(`No pk found in ${Model.tableName(repository.class)} - creating`);
result = await repository.create(model, context);
}
else {
log.info(`pk found in ${Model.tableName(repository.class)} - attempting update`);
try {
result = await repository.update(model, context);
log.info(`Updated ${Model.tableName(repository.class)}`);
}
catch (e) {
if (!(e instanceof NotFoundError)) {
throw e;
}
log.info(`update Failed - creating new ${Model.tableName(repository.class)}`);
result = await repository.create(model, context);
}
log.info(`After create update: ${result}`);
}
return result;
}
//
// export async function createOrUpdateBulk<
// M extends Model,
// F extends AdapterFlags,
// >(
// models: M[],
// context: Context<F>,
// alias: string,
// repository?: Repo<M>
// ): Promise<M> {
// const log = context.logger.for(createOrUpdateBulk);
// if (!repository) {
// const constructor = Model.get(models[0].constructor.name);
// if (!constructor)
// throw new InternalError(
// `Could not find model ${models[0].constructor.name}`
// );
// repository = Repository.forModel<M, Repo<M>>(
// constructor as unknown as ModelConstructor<M>,
// alias
// );
// log.info(`Retrieved ${repository.toString()}`);
// }
// const pks = models.map((m) => m[Model.pk(m)]);
//
// const existing = await Promise.allSettled(pks.map((pk) => repository.read(pk as string, context)));
//
// existing.forEach((ex, i) => {
// if (ex.status === "fulfilled") {
//
// }
// })
//
// for (let ex of existing){
// if (ex.)
// }
// let result: M;
//
// if (typeof model[Model.pk(repository.class)] === "undefined") {
// log.info(`No pk found in ${Model.tableName(repository.class)} - creating`);
// result = await repository.create(model, context);
// } else {
// log.info(
// `pk found in ${Model.tableName(repository.class)} - attempting update`
// );
// try {
// result = await repository.update(model, context);
// log.info(`Updated ${Model.tableName(repository.class)}`);
// } catch (e: any) {
// if (!(e instanceof NotFoundError)) {
// throw e;
// }
// log.info(
// `update Failed - creating new ${Model.tableName(repository.class)}`
// );
// result = await repository.create(model, context);
// }
//
// log.info(`After create update: ${result}`);
// }
// return result;
// }
/**
* @description Handles one-to-one relationship creation
* @summary Processes a one-to-one relationship when creating a model, either by referencing an existing model or creating a new one
* @template M - The model type extending Model
* @template R - The repository type extending Repo<M, F, C>
* @template V - The relations metadata type extending RelationsMetadata
* @template F - The repository flags type
* @template C - The context type extending Context<F>
* @param {R} this - The repository instance
* @param {Context<F>} context - The context for the operation
* @param {V} data - The relations metadata
* @param {string} key - The property key of the relationship
* @param {M} model - The model instance
* @return {Promise<void>} A promise that resolves when the operation is complete
* @function oneToOneOnCreate
* @memberOf module:core
* @mermaid
* sequenceDiagram
* participant Caller
* participant oneToOneOnCreate
* participant repositoryFromTypeMetadata
* participant Model
* participant Repository
* participant cacheModelForPopulate
*
* Caller->>oneToOneOnCreate: this, context, data, key, model
* oneToOneOnCreate->>oneToOneOnCreate: check if propertyValue exists
*
* alt propertyValue is not an object
* oneToOneOnCreate->>repositoryFromTypeMetadata: model, key
* repositoryFromTypeMetadata-->>oneToOneOnCreate: innerRepo
* oneToOneOnCreate->>innerRepo: read(propertyValue)
* innerRepo-->>oneToOneOnCreate: read
* oneToOneOnCreate->>cacheModelForPopulate: context, model, key, propertyValue, read
* oneToOneOnCreate->>oneToOneOnCreate: set model[key] = propertyValue
* else propertyValue is an object
* oneToOneOnCreate->>Model: get(data.class)
* Model-->>oneToOneOnCreate: constructor
* oneToOneOnCreate->>Repository: forModel(constructor)
* Repository-->>oneToOneOnCreate: repo
* oneToOneOnCreate->>repo: create(propertyValue)
* repo-->>oneToOneOnCreate: created
* oneToOneOnCreate->>findPrimaryKey: created
* findPrimaryKey-->>oneToOneOnCreate: pk
* oneToOneOnCreate->>cacheModelForPopulate: context, model, key, created[pk], created
* oneToOneOnCreate->>oneToOneOnCreate: set model[key] = created[pk]
* end
*
* oneToOneOnCreate-->>Caller: void
*/
export async function oneToOneOnCreate(context, data, key, model) {
const propertyValue = model[key];
if (!propertyValue)
return;
if (typeof propertyValue !== "object") {
const innerRepo = repositoryFromTypeMetadata(model, key, this.adapter.alias);
const read = await innerRepo.read(propertyValue);
await cacheModelForPopulate(context, model, key, propertyValue, read);
model[key] = propertyValue;
return;
}
const constructor = isClass(data.class) ? data.class : data.class();
if (!constructor)
throw new InternalError(`Could not find model ${data.class}`);
const repo = Repository.forModel(constructor, this.adapter.alias);
const created = await repo.create(propertyValue, context);
const pk = Model.pk(created);
await cacheModelForPopulate(context, model, key, created[pk], created);
model[key] = created[pk];
}
/**
* @description Handles one-to-one relationship updates
* @summary Processes a one-to-one relationship when updating a model, either by referencing an existing model or updating the related model
* @template M - The model type extending Model
* @template R - The repository type extending Repo<M, F, C>
* @template V - The relations metadata type extending RelationsMetadata
* @template F - The repository flags type
* @template C - The context type extending Context<F>
* @param {R} this - The repository instance
* @param {Context<F>} context - The context for the operation
* @param {V} data - The relations metadata
* @param key - The property key of the relationship
* @param {M} model - The model instance
* @return {Promise<void>} A promise that resolves when the operation is complete
* @function oneToOneOnUpdate
* @memberOf module:core
* @mermaid
* sequenceDiagram
* participant Caller
* participant oneToOneOnUpdate
* participant repositoryFromTypeMetadata
* participant createOrUpdate
* participant findPrimaryKey
* participant cacheModelForPopulate
*
* Caller->>oneToOneOnUpdate: this, context, data, key, model
* oneToOneOnUpdate->>oneToOneOnUpdate: check if propertyValue exists
* oneToOneOnUpdate->>oneToOneOnUpdate: check if cascade.update is CASCADE
*
* alt propertyValue is not an object
* oneToOneOnUpdate->>repositoryFromTypeMetadata: model, key
* repositoryFromTypeMetadata-->>oneToOneOnUpdate: innerRepo
* oneToOneOnUpdate->>innerRepo: read(propertyValue)
* innerRepo-->>oneToOneOnUpdate: read
* oneToOneOnUpdate->>cacheModelForPopulate: context, model, key, propertyValue, read
* oneToOneOnUpdate->>oneToOneOnUpdate: set model[key] = propertyValue
* else propertyValue is an object
* oneToOneOnUpdate->>createOrUpdate: model[key], context
* createOrUpdate-->>oneToOneOnUpdate: updated
* oneToOneOnUpdate->>findPrimaryKey: updated
* findPrimaryKey-->>oneToOneOnUpdate: pk
* oneToOneOnUpdate->>cacheModelForPopulate: context, model, key, updated[pk], updated
* oneToOneOnUpdate->>oneToOneOnUpdate: set model[key] = updated[pk]
* end
*
* oneToOneOnUpdate-->>Caller: void
*/
export async function oneToOneOnUpdate(context, data, key, model) {
const propertyValue = model[key];
if (!propertyValue)
return;
if (data.cascade.update !== Cascade.CASCADE)
return;
if (typeof propertyValue !== "object") {
const innerRepo = repositoryFromTypeMetadata(model, key, this.adapter.alias);
const read = await innerRepo.read(propertyValue, context);
await cacheModelForPopulate(context, model, key, propertyValue, read);
model[key] = propertyValue;
return;
}
const updated = await createOrUpdate(model[key], context, this.adapter.alias);
const pk = Model.pk(updated);
await cacheModelForPopulate(context, model, key, updated[pk], updated);
model[key] = updated[pk];
}
/**
* @description Handles one-to-one relationship deletion
* @summary Processes a one-to-one relationship when deleting a model, deleting the related model if cascade is enabled
* @template M - The model type extending Model
* @template R - The repository type extending Repo<M, F, C>
* @template V - The relations metadata type extending RelationsMetadata
* @template F - The repository flags type
* @template C - The context type extending Context<F>
* @param {R} this - The repository instance
* @param {Context<F>} context - The context for the operation
* @param {V} data - The relations metadata
* @param key - The property key of the relationship
* @param {M} model - The model instance
* @return {Promise<void>} A promise that resolves when the operation is complete
* @function oneToOneOnDelete
* @memberOf module:core
* @mermaid
* sequenceDiagram
* participant Caller
* participant oneToOneOnDelete
* participant repositoryFromTypeMetadata
* participant cacheModelForPopulate
*
* Caller->>oneToOneOnDelete: this, context, data, key, model
* oneToOneOnDelete->>oneToOneOnDelete: check if propertyValue exists
* oneToOneOnDelete->>oneToOneOnDelete: check if cascade.update is CASCADE
*
* oneToOneOnDelete->>repositoryFromTypeMetadata: model, key
* repositoryFromTypeMetadata-->>oneToOneOnDelete: innerRepo
*
* alt propertyValue is not a Model instance
* oneToOneOnDelete->>innerRepo: delete(model[key], context)
* innerRepo-->>oneToOneOnDelete: deleted
* else propertyValue is a Model instance
* oneToOneOnDelete->>innerRepo: delete(model[key][innerRepo.pk], context)
* innerRepo-->>oneToOneOnDelete: deleted
* end
*
* oneToOneOnDelete->>cacheModelForPopulate: context, model, key, deleted[innerRepo.pk], deleted
* oneToOneOnDelete-->>Caller: void
*/
export async function oneToOneOnDelete(context, data, key, model) {
const propertyValue = model[key];
if (!propertyValue)
return;
if (data.cascade.update !== Cascade.CASCADE)
return;
const innerRepo = repositoryFromTypeMetadata(model, key, this.adapter.alias);
let deleted;
if (!(propertyValue instanceof Model))
deleted = await innerRepo.delete(model[key], context);
else
deleted = await innerRepo.delete(model[key][innerRepo.pk], context);
await cacheModelForPopulate(context, model, key, deleted[innerRepo.pk], deleted);
}
/**
* @description Handles one-to-many relationship creation
* @summary Processes a one-to-many relationship when creating a model, either by referencing existing models or creating new ones
* @template M - The model type extending Model
* @template R - The repository type extending Repo<M, F, C>
* @template V - The relations metadata type extending RelationsMetadata
* @template F - The repository flags type
* @template C - The context type extending Context<F>
* @param {R} this - The repository instance
* @param {Context<F>} context - The context for the operation
* @param {V} data - The relations metadata
* @param key - The property key of the relationship
* @param {M} model - The model instance
* @return {Promise<void>} A promise that resolves when the operation is complete
* @function oneToManyOnCreate
* @memberOf module:core
* @mermaid
* sequenceDiagram
* participant Caller
* participant oneToManyOnCreate
* participant repositoryFromTypeMetadata
* participant createOrUpdate
* participant findPrimaryKey
* participant cacheModelForPopulate
*
* Caller->>oneToManyOnCreate: this, context, data, key, model
* oneToManyOnCreate->>oneToManyOnCreate: check if propertyValues exists and has length
* oneToManyOnCreate->>oneToManyOnCreate: check if all elements have same type
* oneToManyOnCreate->>oneToManyOnCreate: create uniqueValues set
*
* alt arrayType is not "object"
* oneToManyOnCreate->>repositoryFromTypeMetadata: model, key
* repositoryFromTypeMetadata-->>oneToManyOnCreate: repo
* loop for each id in uniqueValues
* oneToManyOnCreate->>repo: read(id)
* repo-->>oneToManyOnCreate: read
* oneToManyOnCreate->>cacheModelForPopulate: context, model, key, id, read
* end
* oneToManyOnCreate->>oneToManyOnCreate: set model[key] = [...uniqueValues]
* else arrayType is "object"
* oneToManyOnCreate->>findPrimaryKey: propertyValues[0]
* findPrimaryKey-->>oneToManyOnCreate: pkName
* oneToManyOnCreate->>oneToManyOnCreate: create result set
* loop for each m in propertyValues
* oneToManyOnCreate->>createOrUpdate: m, context
* createOrUpdate-->>oneToManyOnCreate: record
* oneToManyOnCreate->>cacheModelForPopulate: context, model, key, record[pkName], record
* oneToManyOnCreate->>oneToManyOnCreate: add record[pkName] to result
* end
* oneToManyOnCreate->>oneToManyOnCreate: set model[key] = [...result]
* end
*
* oneToManyOnCreate-->>Caller: void
*/
export async function oneToManyOnCreate(context, data, key, model) {
const propertyValues = model[key];
if (!propertyValues || !propertyValues.length)
return;
const arrayType = typeof propertyValues[0];
if (!propertyValues.every((item) => typeof item === arrayType))
throw new InternalError(`Invalid operation. All elements of property ${key} must match the same type.`);
const log = context.logger.for(oneToManyOnCreate);
const uniqueValues = new Set([...propertyValues]);
if (arrayType !== "object") {
const repo = repositoryFromTypeMetadata(model, key, this.adapter.alias);
const read = await repo.readAll([...uniqueValues.values()], context);
for (let i = 0; i < read.length; i++) {
const model = read[i];
log.warn(`FOUND ONE TO MANY VALUE: ${JSON.stringify(model)}`);
await cacheModelForPopulate(context, model, key, [...uniqueValues.values()][i], read);
}
// for (const model of read) {
// // const read = await repo.read(id, context);
//
// }
model[key] = [...uniqueValues];
log.warn(`SET ONE TO MANY IDS: ${model[key]}`);
return;
}
const pkName = Model.pk(propertyValues[0].constructor);
const result = new Set();
for (const m of propertyValues) {
log.info(`Creating or updating one-to-many model: ${JSON.stringify(m)}`);
const record = await createOrUpdate(m, context, this.adapter.alias);
log.info(`caching: ${JSON.stringify(record)} under ${record[pkName]}`);
await cacheModelForPopulate(context, model, key, record[pkName], record);
log.info(`Creating or updating one-to-many model: ${JSON.stringify(m)}`);
result.add(record[pkName]);
}
model[key] = [...result];
}
/**
* @description Handles one-to-many relationship updates
* @summary Processes a one-to-many relationship when updating a model, delegating to oneToManyOnCreate if cascade update is enabled
* @template M - The model type extending Model
* @template R - The repository type extending Repo<M, F, C>
* @template V - The relations metadata type extending RelationsMetadata
* @template F - The repository flags type
* @template C - The context type extending Context<F>
* @param {R} this - The repository instance
* @param {Context<F>} context - The context for the operation
* @param {V} data - The relations metadata
* @param key - The property key of the relationship
* @param {M} model - The model instance
* @return {Promise<void>} A promise that resolves when the operation is complete
* @function oneToManyOnUpdate
* @memberOf module:core
* @mermaid
* sequenceDiagram
* participant Caller
* participant oneToManyOnUpdate
* participant oneToManyOnCreate
*
* Caller->>oneToManyOnUpdate: this, context, data, key, model
* oneToManyOnUpdate->>oneToManyOnUpdate: check if cascade.update is CASCADE
*
* alt cascade.update is CASCADE
* oneToManyOnUpdate->>oneToManyOnCreate: apply(this, [context, data, key, model])
* oneToManyOnCreate-->>oneToManyOnUpdate: void
* end
*
* oneToManyOnUpdate-->>Caller: void
*/
export async function oneToManyOnUpdate(context, data, key, model) {
const { cascade } = data;
if (cascade.update !== Cascade.CASCADE)
return;
return oneToManyOnCreate.apply(this, [
context,
data,
key,
model,
]);
}
/**
* @description Handles one-to-many relationship deletion
* @summary Processes a one-to-many relationship when deleting a model, deleting all related models if cascade delete is enabled
* @template M - The model type extending Model
* @template R - The repository type extending Repo<M, F, C>
* @template V - The relations metadata type extending RelationsMetadata
* @template F - The repository flags type
* @template C - The context type extending Context<F>
* @param {R} this - The repository instance
* @param {Context<F>} context - The context for the operation
* @param {V} data - The relations metadata
* @param key - The property key of the relationship
* @param {M} model - The model instance
* @return {Promise<void>} A promise that resolves when the operation is complete
* @function oneToManyOnDelete
* @memberOf module:core
* @mermaid
* sequenceDiagram
* participant Caller
* participant oneToManyOnDelete
* participant Repository
* participant repositoryFromTypeMetadata
* participant cacheModelForPopulate
*
* Caller->>oneToManyOnDelete: this, context, data, key, model
* oneToManyOnDelete->>oneToManyOnDelete: check if cascade.delete is CASCADE
* oneToManyOnDelete->>oneToManyOnDelete: check if values exists and has length
* oneToManyOnDelete->>oneToManyOnDelete: check if all elements have same type
*
* alt isInstantiated (arrayType is "object")
* oneToManyOnDelete->>Repository: forModel(values[0])
* Repository-->>oneToManyOnDelete: repo
* else not instantiated
* oneToManyOnDelete->>repositoryFromTypeMetadata: model, key
* repositoryFromTypeMetadata-->>oneToManyOnDelete: repo
* end
*
* oneToManyOnDelete->>oneToManyOnDelete: create uniqueValues set
*
* loop for each id in uniqueValues
* oneToManyOnDelete->>repo: delete(id, context)
* repo-->>oneToManyOnDelete: deleted
* oneToManyOnDelete->>cacheModelForPopulate: context, model, key, id, deleted
* end
*
* oneToManyOnDelete->>oneToManyOnDelete: set model[key] = [...uniqueValues]
* oneToManyOnDelete-->>Caller: void
*/
export async function oneToManyOnDelete(context, data, key, model) {
if (data.cascade.delete !== Cascade.CASCADE)
return;
const values = model[key];
if (!values || !values.length)
return;
const arrayType = typeof values[0];
const areAllSameType = values.every((item) => typeof item === arrayType);
if (!areAllSameType)
throw new InternalError(`Invalid operation. All elements of property ${key} must match the same type.`);
const clazz = typeof data.class === "function" && !data.class.name
? data.class()
: data.class;
const isInstantiated = arrayType === "object";
const repo = isInstantiated
? Repository.forModel(clazz, this.adapter.alias)
: repositoryFromTypeMetadata(model, key, this.adapter.alias);
const uniqueValues = new Set([
...(isInstantiated
? values.map((v) => v[repo["pk"]])
: values),
]);
const ids = [...uniqueValues.values()];
let deleted;
try {
deleted = await repo.deleteAll(ids, context);
}
catch (e) {
context.logger.error(`Failed to delete all records`, e);
throw e;
}
let del;
for (let i = 0; i < deleted.length; i++) {
del = deleted[i];
try {
await cacheModelForPopulate(context, model, key, ids[i], del);
}
catch (e) {
context.logger.error(`Failed to cache record ${ids[i]} with key ${key} and model ${JSON.stringify(model, undefined, 2)} `, e);
throw e;
}
}
model[key] = ids;
}
/**
* @description Generates a key for caching populated model relationships
* @summary Creates a unique key for storing and retrieving populated model relationships in the cache
* @param {string} tableName - The name of the table or model
* @param {string} fieldName - The name of the field or property
* @param {string|number} id - The identifier of the related model
* @return {string} A dot-separated string that uniquely identifies the relationship
* @function getPopulateKey
* @memberOf module:core
*/
export function getPopulateKey(tableName, fieldName, id) {
return [PersistenceKeys.POPULATE, tableName, fieldName, id].join(".");
}
/**
* @description Caches a model for later population
* @summary Stores a model in the context cache for efficient retrieval during relationship population
* @template M - The model type extending Model
* @template F - The repository flags type
* @param {Context<F>} context - The context for the operation
* @param {M} parentModel - The parent model that contains the relationship
* @param propertyKey - The property key of the relationship
* @param {string | number} pkValue - The primary key value of the related model
* @param {any} cacheValue - The model instance to cache
* @return {Promise<any>} A promise that resolves with the result of the cache operation
* @function cacheModelForPopulate
* @memberOf module:core
*/
export async function cacheModelForPopulate(context, parentModel, propertyKey, pkValue, cacheValue) {
const cacheKey = getPopulateKey(parentModel.constructor.name, propertyKey, pkValue);
const cache = context.get("cacheForPopulate") || {};
cache[cacheKey] = cacheValue;
return context.accumulate({ cacheForPopulate: cache });
}
/**
* @description Populates a model's relationship
* @summary Retrieves and attaches related models to a model's relationship property
* @template M - The model type extending Model
* @template R - The repository type extending Repo<M, F, C>
* @template V - The relations metadata type extending RelationsMetadata
* @template F - The repository flags type
* @template C - The context type extending Context<F>
* @param {R} this - The repository instance
* @param {Context<F>} context - The context for the operation
* @param {V} data - The relations metadata
* @param key - The property key of the relationship
* @param {M} model - The model instance
* @return {Promise<void>} A promise that resolves when the operation is complete
* @function populate
* @memberOf module:core
* @mermaid
* sequenceDiagram
* participant Caller
* participant populate
* participant fetchPopulateValues
* participant getPopulateKey
* participant Context
* participant repositoryFromTypeMetadata
*
* Caller->>populate: this, context, data, key, model
* populate->>populate: check if data.populate is true
* populate->>populate: get nested value and check if it exists
*
* populate->>fetchPopulateValues: context, model, key, isArr ? nested : [nested]
*
* fetchPopulateValues->>fetchPopulateValues: initialize variables
*
* loop for each proKeyValue in propKeyValues
* fetchPopulateValues->>getPopulateKey: model.constructor.name, propName, proKeyValue
* getPopulateKey-->>fetchPopulateValues: cacheKey
*
* alt try to get from cache
* fetchPopulateValues->>Context: get(cacheKey)
* Context-->>fetchPopulateValues: val
* else catch error
* fetchPopulateValues->>repositoryFromTypeMetadata: model, propName
* repositoryFromTypeMetadata-->>fetchPopulateValues: repo
* fetchPopulateValues->>repo: read(proKeyValue)
* repo-->>fetchPopulateValues: val
* end
*
* fetchPopulateValues->>fetchPopulateValues: add val to results
* end
*
* fetchPopulateValues-->>populate: results
* populate->>populate: set model[key] = isArr ? res : res[0]
* populate-->>Caller: void
*/
export async function populate(context, data, key, model) {
if (!data.populate)
return;
const nested = model[key];
const isArr = Array.isArray(nested);
if (typeof nested === "undefined" || (isArr && nested.length === 0))
return;
async function fetchPopulateValues(c, model, propName, propKeyValues, alias) {
let cacheKey;
let val;
const results = [];
const cache = c.get("cacheForPopulate") || {};
for (const proKeyValue of propKeyValues) {
cacheKey = getPopulateKey(model.constructor.name, propName, proKeyValue);
try {
val = cache[cacheKey];
if (!val)
throw new Error("Not found in cache");
// eslint-disable-next-line @typescript-eslint/no-unused-vars
}
catch (e) {
const repo = repositoryFromTypeMetadata(model, propName, alias);
if (!repo)
throw new InternalError("Could not find repo");
val = await repo.read(proKeyValue, context);
}
results.push(val);
}
return results;
}
const res = await fetchPopulateValues(context, model, key, isArr ? nested : [nested], this.adapter.alias);
model[key] = isArr ? res : res[0];
}
/**
* @description List of common JavaScript types
* @summary An array of strings representing common JavaScript types that are not custom model types
* @const commomTypes
* @memberOf module:core
*/
const commomTypes = [
"array",
"string",
"number",
"boolean",
"symbol",
"function",
"object",
"undefined",
"null",
"bigint",
];
/**
* @description Retrieves a repository for a model property based on its type metadata
* @summary Examines a model property's type metadata to determine the appropriate repository for related models
* @template M - The model type extending Model
* @param {any} model - The model instance containing the property
* @param propertyKey - The property key to examine
* @return {Repo<M>} A repository for the model type associated with the property
* @function repositoryFromTypeMetadata
* @memberOf module:core
* @mermaid
* sequenceDiagram
* participant Caller
* participant repositoryFromTypeMetadata
* participant Reflect
* participant Validation
* participant Model
* participant Repository
*
* Caller->>repositoryFromTypeMetadata: model, propertyKey
*
* repositoryFromTypeMetadata->>repositoryFromTypeMetadata: Get allowedTypes array
* repositoryFromTypeMetadata->>repositoryFromTypeMetadata: find constructorName not in commomTypes
* repositoryFromTypeMetadata->>repositoryFromTypeMetadata: check if constructorName exists
*
* repositoryFromTypeMetadata->>Model: get(constructorName)
* Model-->>repositoryFromTypeMetadata: constructor
* repositoryFromTypeMetadata->>repositoryFromTypeMetadata: check if constructor exists
*
* repositoryFromTypeMetadata->>Repository: forModel(constructor)
* Repository-->>repositoryFromTypeMetadata: repo
*
* repositoryFromTypeMetadata-->>Caller: repo
*/
export function repositoryFromTypeMetadata(model, propertyKey, alias) {
if (!model)
throw new Error("No model was provided to get repository");
let allowedTypes;
if (Array.isArray(model[propertyKey]) || model[propertyKey] instanceof Set) {
const customTypes = Metadata.get(model instanceof Model ? model.constructor : model, Metadata.key(ValidationKeys.REFLECT, propertyKey, ValidationKeys.LIST))?.clazz;
if (!customTypes)
throw new InternalError(`Failed to find types decorators for property ${propertyKey}`);
allowedTypes = (Array.isArray(customTypes) ? [...customTypes] : [customTypes]).map((t) => (typeof t === "function" && !t.name ? t() : t));
}
else
allowedTypes = Metadata.getPropDesignTypes(model instanceof Model ? model.constructor : model, propertyKey)?.designTypes;
const constructor = allowedTypes?.find((t) => !commomTypes.includes(`${t.name}`.toLowerCase()));
return Repository.forModel(constructor, alias);
}
//# sourceMappingURL=construction.js.map