UNPKG

@medusajs/index

Version:
516 lines • 23.5 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _PostgresProvider_isReady_; Object.defineProperty(exports, "__esModule", { value: true }); exports.PostgresProvider = void 0; const types_1 = require("@medusajs/framework/types"); const utils_1 = require("@medusajs/framework/utils"); const _models_1 = require("../models"); const utils_2 = require("../utils"); const flatten_object_keys_1 = require("../utils/flatten-object-keys"); const normalize_fields_selection_1 = require("../utils/normalize-fields-selection"); class PostgresProvider { constructor(container, options, moduleOptions) { _PostgresProvider_isReady_.set(this, void 0); this.eventActionToMethodMap_ = { created: "onCreate", updated: "onUpdate", deleted: "onDelete", attached: "onAttach", detached: "onDetach", }; this.manager_ = container.manager; this.query_ = container.query; this.moduleOptions_ = moduleOptions; this.baseRepository_ = container.baseRepository; this.schemaObjectRepresentation_ = options.schemaObjectRepresentation; this.schemaEntitiesMap_ = options.entityMap; } async onApplicationStart() { let initalizedOk = () => { }; let initalizedNok = () => { }; __classPrivateFieldSet(this, _PostgresProvider_isReady_, new Promise((resolve, reject) => { initalizedOk = resolve; initalizedNok = reject; }), "f"); await (0, utils_2.createPartitions)(this.schemaObjectRepresentation_, this.manager_.fork()) .then(initalizedOk) .catch(initalizedNok); } static parseData(data, schemaEntityObjectRepresentation) { const data_ = Array.isArray(data) ? data : [data]; // Always keep the id in the entity properties const entityProperties = ["id"]; const parentsProperties = {}; /** * Split fields into entity properties and parents properties */ schemaEntityObjectRepresentation.fields.forEach((field) => { if (field.includes(".")) { const parentAlias = field.split(".")[0]; const parentSchemaObjectRepresentation = schemaEntityObjectRepresentation.parents.find((parent) => parent.inverseSideProp === parentAlias); if (!parentSchemaObjectRepresentation) { throw new Error(`IndexModule error, unable to parse data for ${schemaEntityObjectRepresentation.entity}. The parent schema object representation could not be found for the alias ${parentAlias} for the entity ${schemaEntityObjectRepresentation.entity}.`); } parentsProperties[parentSchemaObjectRepresentation.ref.entity] ??= []; parentsProperties[parentSchemaObjectRepresentation.ref.entity].push(field); } else { entityProperties.push(field); } }); return { data: data_, entityProperties, parentsProperties, }; } static parseMessageData(message) { const isExpectedFormat = (0, utils_1.isDefined)(message?.data) && (0, utils_1.isDefined)(message?.metadata?.action); if (!isExpectedFormat) { return; } const result = { action: "", data: [], ids: [], }; result.action = message.metadata.action; result.data = message.data; result.data = Array.isArray(result.data) ? result.data : [result.data]; result.ids = result.data.flatMap((d) => Array.isArray(d.id) ? d.id : [d.id]); return result; } consumeEvent(schemaEntityObjectRepresentation) { return async (event) => { await __classPrivateFieldGet(this, _PostgresProvider_isReady_, "f"); const data_ = Array.isArray(event.data) ? event.data : [event.data]; let ids = data_.flatMap((d) => Array.isArray(d.id) ? d.id : [d.id]); let action = event.name.split(".").pop() || ""; const parsedMessage = PostgresProvider.parseMessageData(event); if (parsedMessage) { action = parsedMessage.action; ids = parsedMessage.ids; } const targetMethod = this.eventActionToMethodMap_[action]; if (!targetMethod) { return; } const { fields, alias } = schemaEntityObjectRepresentation; let withDeleted; if (action === utils_1.CommonEvents.DELETED || action === utils_1.CommonEvents.DETACHED) { withDeleted = true; } // Process ids in batches of 100 const batchSize = 100; const idsBatches = []; for (let i = 0; i < ids.length; i += batchSize) { idsBatches.push(ids.slice(i, i + batchSize)); } for (const idsBatch of idsBatches) { const graphConfig = { entity: alias, filters: { id: idsBatch, }, fields: [...new Set(["id", ...fields])], withDeleted, }; const { data: entityData } = await this.query_.graph(graphConfig); const argument = { entity: schemaEntityObjectRepresentation.entity, data: entityData, schemaEntityObjectRepresentation, }; await this[targetMethod](argument); } }; } async query(config, sharedContext = {}) { await __classPrivateFieldGet(this, _PostgresProvider_isReady_, "f"); const { fields = [], filters = {}, joinFilters = {}, idsOnly } = config; const { take, skip, order: inputOrderBy = {} } = config.pagination ?? {}; const select = (0, normalize_fields_selection_1.normalizeFieldsSelection)(fields); const where = (0, flatten_object_keys_1.flattenObjectKeys)((0, utils_1.unflattenObjectKeys)(filters)); const inputOrderByObj = (0, utils_1.unflattenObjectKeys)(inputOrderBy); const joinWhere = (0, flatten_object_keys_1.flattenObjectKeys)((0, utils_1.unflattenObjectKeys)(joinFilters)); const orderBy = (0, flatten_object_keys_1.flattenObjectKeys)(inputOrderByObj); const { manager } = sharedContext; let hasPagination = false; let hasCount = false; if ((0, utils_1.isDefined)(skip) || (0, utils_1.isDefined)(take)) { hasPagination = true; if ((0, utils_1.isDefined)(skip)) { hasCount = true; } } const requestedFields = (0, utils_1.deepMerge)((0, utils_1.deepMerge)(select, filters), inputOrderByObj); const connection = manager.getConnection(); const qb = new utils_2.QueryBuilder({ schema: this.schemaObjectRepresentation_, entityMap: this.schemaEntitiesMap_, knex: connection.getKnex(), selector: { select, where, joinWhere, }, options: { skip, take, orderBy, }, rawConfig: config, requestedFields, idsOnly, }); const { sql, sqlCount } = qb.buildQuery({ hasPagination, hasCount, }); const [resultSet, countResult] = await Promise.all([ manager.execute(sql), hasCount ? manager.execute(sqlCount) : null, ]); const resultMetadata = hasPagination ? { estimate_count: hasCount ? parseInt(countResult[0]?.estimate_count ?? 0) : undefined, skip, take, } : undefined; return { data: qb.buildObjectFromResultset(resultSet), metadata: resultMetadata, }; } /** * Create the index entry and the index relation entry when this event is emitted. * @param entity * @param data * @param schemaEntityObjectRepresentation * @param sharedContext * @protected */ async onCreate({ entity, data, schemaEntityObjectRepresentation, }, sharedContext = {}) { const { transactionManager: em } = sharedContext; const indexRepository = em.getRepository((0, utils_1.toMikroORMEntity)(_models_1.IndexData)); const indexRelationRepository = em.getRepository((0, utils_1.toMikroORMEntity)(_models_1.IndexRelation)); const { data: data_, entityProperties, parentsProperties, } = PostgresProvider.parseData(data, schemaEntityObjectRepresentation); /** * Clean the entity data to only keep the properties that are defined in the schema */ const cleanedData = data_.map((entityData) => { return entityProperties.reduce((acc, property) => { acc[property] = entityData[property]; return acc; }, {}); }); /** * Loop through the data and create index entries for each entity as well as the * index relation entries if the entity has parents */ const entitiesToUpsert = new Set(); const relationsToUpsert = new Set(); cleanedData.forEach((entityData, index) => { entitiesToUpsert.add(JSON.stringify({ id: entityData.id, name: entity, data: entityData, staled_at: null, })); /** * Retrieve the parents to attach it to the index entry. */ for (const [parentEntity, parentProperties] of Object.entries(parentsProperties)) { const parentAlias = parentProperties[0].split(".")[0]; const parentData = data_[index][parentAlias]; if (!parentData) { continue; } const parentDataCollection = Array.isArray(parentData) ? parentData : [parentData]; for (const parentData_ of parentDataCollection) { relationsToUpsert.add(JSON.stringify({ parent_id: parentData_.id, parent_name: parentEntity, child_id: entityData.id, child_name: entity, pivot: `${parentEntity}-${entity}`, staled_at: null, })); } } }); if (entitiesToUpsert.size) { await indexRepository.upsertMany(Array.from(entitiesToUpsert).map((entity) => JSON.parse(entity)), { onConflictAction: "merge", onConflictFields: ["id", "name"], onConflictMergeFields: ["data", "staled_at"], }); } if (relationsToUpsert.size) { await indexRelationRepository.upsertMany(Array.from(relationsToUpsert).map((relation) => JSON.parse(relation)), { onConflictAction: "merge", onConflictFields: [ "pivot", "parent_id", "child_id", "parent_name", "child_name", ], onConflictMergeFields: ["staled_at"], }); } } /** * Update the index entry when this event is emitted. * @param entity * @param data * @param schemaEntityObjectRepresentation * @param sharedContext * @protected */ async onUpdate({ entity, data, schemaEntityObjectRepresentation, }, sharedContext = {}) { const { transactionManager: em } = sharedContext; const indexRepository = em.getRepository((0, utils_1.toMikroORMEntity)(_models_1.IndexData)); const { data: data_, entityProperties } = PostgresProvider.parseData(data, schemaEntityObjectRepresentation); await indexRepository.upsertMany(data_.map((entityData) => { return { id: entityData.id, name: entity, data: entityProperties.reduce((acc, property) => { acc[property] = entityData[property]; return acc; }, {}), staled_at: null, }; }), { onConflictAction: "merge", onConflictFields: ["id", "name"], onConflictMergeFields: ["data", "staled_at"], }); } /** * Delete the index entry when this event is emitted. * @param entity * @param data * @param schemaEntityObjectRepresentation * @param sharedContext * @protected */ async onDelete({ entity, data, schemaEntityObjectRepresentation, }, sharedContext = {}) { const { transactionManager: em } = sharedContext; const indexRepository = em.getRepository((0, utils_1.toMikroORMEntity)(_models_1.IndexData)); const indexRelationRepository = em.getRepository((0, utils_1.toMikroORMEntity)(_models_1.IndexRelation)); const { data: data_ } = PostgresProvider.parseData(data, schemaEntityObjectRepresentation); const ids = data_.map((entityData) => entityData.id); await indexRepository.nativeDelete({ id: { $in: ids }, name: entity, }); await indexRelationRepository.nativeDelete({ $or: [ { parent_id: { $in: ids }, parent_name: entity, }, { child_id: { $in: ids }, child_name: entity, }, ], }); } /** * event emitted from the link modules to attach a link entity to its parent and child entities from the linked modules. * @param entity * @param data * @param schemaEntityObjectRepresentation * @protected */ async onAttach({ entity, data, schemaEntityObjectRepresentation, }, sharedContext = {}) { const { transactionManager: em } = sharedContext; const indexRepository = em.getRepository((0, utils_1.toMikroORMEntity)(_models_1.IndexData)); const indexRelationRepository = em.getRepository((0, utils_1.toMikroORMEntity)(_models_1.IndexRelation)); const { data: data_, entityProperties } = PostgresProvider.parseData(data, schemaEntityObjectRepresentation); /** * Retrieve the property that represent the foreign key related to the parent entity of the link entity. * Then from the service name of the parent entity, retrieve the entity name using the linkable keys. */ const parentPropertyId = schemaEntityObjectRepresentation.moduleConfig.relationships[0].foreignKey; const parentServiceName = schemaEntityObjectRepresentation.moduleConfig.relationships[0] .serviceName; const parentEntityName = this.schemaObjectRepresentation_._serviceNameModuleConfigMap[parentServiceName].linkableKeys?.[parentPropertyId]; if (!parentEntityName) { throw new Error(`IndexModule error, unable to handle attach event for ${entity}. The parent entity name could not be found using the linkable keys from the module ${parentServiceName}.`); } /** * Retrieve the property that represent the foreign key related to the child entity of the link entity. * Then from the service name of the child entity, retrieve the entity name using the linkable keys. */ const childPropertyId = schemaEntityObjectRepresentation.moduleConfig.relationships[1].foreignKey; const childServiceName = schemaEntityObjectRepresentation.moduleConfig.relationships[1] .serviceName; const childEntityName = this.schemaObjectRepresentation_._serviceNameModuleConfigMap[childServiceName].linkableKeys?.[childPropertyId]; if (!childEntityName) { throw new Error(`IndexModule error, unable to handle attach event for ${entity}. The child entity name could not be found using the linkable keys from the module ${childServiceName}.`); } /** * Clean the link entity data to only keep the properties that are defined in the schema */ const cleanedData = data_.map((entityData) => { return entityProperties.reduce((acc, property) => { acc[property] = entityData[property]; return acc; }, {}); }); let relationsToUpsert = []; const entitiesToUpsert = cleanedData.map((entityData) => { relationsToUpsert.push({ parent_id: entityData[parentPropertyId], parent_name: parentEntityName, child_id: entityData.id, child_name: entity, pivot: `${parentEntityName}-${entity}`, staled_at: null, }, { parent_id: entityData.id, parent_name: entity, child_id: entityData[childPropertyId], child_name: childEntityName, pivot: `${entity}-${childEntityName}`, staled_at: null, }); return { id: entityData.id, name: entity, data: entityData, staled_at: null, }; }); if (entitiesToUpsert.length) { await indexRepository.upsertMany(entitiesToUpsert, { onConflictAction: "merge", onConflictFields: ["id", "name"], onConflictMergeFields: ["data", "staled_at"], }); } if (relationsToUpsert.length) { await indexRelationRepository.upsertMany(relationsToUpsert, { onConflictAction: "merge", onConflictFields: [ "pivot", "parent_id", "child_id", "parent_name", "child_name", ], onConflictMergeFields: ["staled_at"], }); } } /** * Event emitted from the link modules to detach a link entity from its parent and child entities from the linked modules. * @param entity * @param data * @param schemaEntityObjectRepresentation * @param sharedContext * @protected */ async onDetach({ entity, data, schemaEntityObjectRepresentation, }, sharedContext = {}) { const { transactionManager: em } = sharedContext; const indexRepository = em.getRepository((0, utils_1.toMikroORMEntity)(_models_1.IndexData)); const indexRelationRepository = em.getRepository((0, utils_1.toMikroORMEntity)(_models_1.IndexRelation)); const { data: data_ } = PostgresProvider.parseData(data, schemaEntityObjectRepresentation); const ids = data_.map((entityData) => entityData.id); await indexRepository.nativeDelete({ id: { $in: ids }, name: entity, }); await indexRelationRepository.nativeDelete({ $or: [ { parent_id: { $in: ids }, parent_name: entity, }, { child_id: { $in: ids }, child_name: entity, }, ], }); } } exports.PostgresProvider = PostgresProvider; _PostgresProvider_isReady_ = new WeakMap(); __decorate([ (0, utils_1.InjectManager)(), __param(1, (0, utils_1.MedusaContext)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], PostgresProvider.prototype, "query", null); __decorate([ (0, utils_1.InjectTransactionManager)(), __param(1, (0, utils_1.MedusaContext)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], PostgresProvider.prototype, "onCreate", null); __decorate([ (0, utils_1.InjectTransactionManager)(), __param(1, (0, utils_1.MedusaContext)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], PostgresProvider.prototype, "onUpdate", null); __decorate([ (0, utils_1.InjectTransactionManager)(), __param(1, (0, utils_1.MedusaContext)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], PostgresProvider.prototype, "onDelete", null); __decorate([ (0, utils_1.InjectTransactionManager)(), __param(1, (0, utils_1.MedusaContext)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], PostgresProvider.prototype, "onAttach", null); __decorate([ (0, utils_1.InjectTransactionManager)(), __param(1, (0, utils_1.MedusaContext)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], PostgresProvider.prototype, "onDetach", null); //# sourceMappingURL=postgres-provider.js.map