@autofleet/sheilta 2.8.24 → 2.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.cjs +1 -1
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/package.json +3 -3
package/lib/index.cjs
CHANGED
|
@@ -125,5 +125,5 @@ if processed >= total then
|
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
return processed
|
|
128
|
-
`,{keys:[i],arguments:[String(n),String(r.length),JSON.stringify(r),String(this.defaults.errorLogLimit),Date.now().toString()]});let a=await this.getJob(t);if(a?.status===`completed`){let e=a.duration.durationMs||0;this.eventEmitter?.emitEvent(`job:completed`,{jobId:t,action:a.action,processed:a.processed,failed:a.failed,duration:e})}}async addUserJob(t,n){let r=e.userJobsKey(t),i=this.redis.multi();i.lPush(r,n),this.defaults.maxJobsPerUser>0&&i.lTrim(r,0,this.defaults.maxJobsPerUser-1),i.expire(r,3600),await i.exec()}async removeUserJob(t,n){let r=e.userJobsKey(t);await this.redis.lRem(r,0,n)}async getUserJobs(t,n=20){let r=e.userJobsKey(t),i=await this.redis.lRange(r,0,n-1);return await Promise.all(i.map(e=>this.getJob(e)))}};const We={emitEvent:()=>{}};var Ge=class extends _.EventEmitter{constructor(e){if(super(),this.bulkRouters=[],this.sequelize=e.sequelize,this.logger=e.logger,this.rabbit=e.rabbit,this.eventsEnabled=e.emitEvents??!1,e.redis&&typeof e.redis.connect==`function`)this.redis=e.redis,this.redis.isOpen||this.redis.connect().catch(e=>{this.logger.error(`Error connecting to Redis:`,e)});else{let t=e.redis;this.redis=(0,m.createClient)({socket:{host:t.host,port:t.port},commandsQueueMaxLength:5e3,disableOfflineQueue:!1,password:t.password,database:t.db}),this.redis.connect().catch(e=>{this.logger.error(`Error connecting to Redis:`,e)})}this.getUserId=e.getUserId??(()=>null),this.defaults={maxJobsPerUser:e.defaults?.maxJobsPerUser??-1,pageSize:e.defaults?.pageSize??1e3,idField:e.defaults?.idField??`id`,consumerBatchSize:e.defaults?.consumerBatchSize??1,workerConcurrency:e.defaults?.workerConcurrency??16,jobTtlSeconds:e.defaults?.jobTtlSeconds??168*3600,errorLogLimit:e.defaults?.errorLogLimit??10},this.jobManager=new $(this.redis,{maxJobsPerUser:this.defaults.maxJobsPerUser,jobTtlSeconds:this.defaults.jobTtlSeconds,errorLogLimit:this.defaults.errorLogLimit}),this.jobManager.setEventEmitter(this.eventsEnabled?{emitEvent:this.emitEvent.bind(this)}:We)}pingRedis(){return this.redis.ping()}createBulkRouter(e,t){let n=new He(this,e,t);return this.bulkRouters.push(n),n}emitEvent(e,...t){this.eventsEnabled&&this.emit(e,...t)}};exports.Bulker=Ge,exports.BulkerError=Z,exports.BulkerJobManager=$,exports.formatOperators=x,exports.generateFilterReplacements=L,exports.queryFormatMiddleware=Fe,exports.queryHandler=Ie,exports.queryValidationMiddleware=Pe,exports.validatePayload=W,Object.defineProperty(exports,`z`,{enumerable:!0,get:function(){return g.z}});
|
|
128
|
+
`,{keys:[i],arguments:[String(n),String(r.length),JSON.stringify(r),String(this.defaults.errorLogLimit),Date.now().toString()]});let a=await this.getJob(t);if(a?.status===`completed`){let e=a.duration.durationMs||0;this.eventEmitter?.emitEvent(`job:completed`,{jobId:t,action:a.action,processed:a.processed,failed:a.failed,duration:e})}}async addUserJob(t,n){let r=e.userJobsKey(t),i=this.redis.multi();i.lPush(r,n),this.defaults.maxJobsPerUser>0&&i.lTrim(r,0,this.defaults.maxJobsPerUser-1),i.expire(r,3600),await i.exec()}async removeUserJob(t,n){let r=e.userJobsKey(t);await this.redis.lRem(r,0,n)}async getUserJobs(t,n=20){let r=e.userJobsKey(t),i=await this.redis.lRange(r,0,n-1);return await Promise.all(i.map(e=>this.getJob(e)))}};const We={emitEvent:()=>{}};var Ge=class extends _.EventEmitter{constructor(e){if(super(),this.bulkRouters=[],this.sequelize=e.sequelize,this.logger=e.logger,this.rabbit=e.rabbit,this.eventsEnabled=e.emitEvents??!1,e.redis&&typeof e.redis.connect==`function`)this.redis=e.redis,this.redis.isOpen||this.redis.connect().catch(e=>{this.logger.error(`Error connecting to Redis:`,e)});else{let t=e.redis;this.redis=(0,m.createClient)({socket:{connectTimeout:6e4,host:t.host,port:t.port,reconnectStrategy(t,n){return e.logger.warn(`[BULKER Redis] Reconnecting to Redis (attempt ${t}). Cause: ${n?.message}`),Math.min(t*50,2e3)}},commandsQueueMaxLength:5e3,disableOfflineQueue:!1,password:t.password,database:t.db}),this.redis.connect().catch(e=>{this.logger.error(`Error connecting to Redis:`,e)})}this.getUserId=e.getUserId??(()=>null),this.defaults={maxJobsPerUser:e.defaults?.maxJobsPerUser??-1,pageSize:e.defaults?.pageSize??1e3,idField:e.defaults?.idField??`id`,consumerBatchSize:e.defaults?.consumerBatchSize??1,workerConcurrency:e.defaults?.workerConcurrency??16,jobTtlSeconds:e.defaults?.jobTtlSeconds??168*3600,errorLogLimit:e.defaults?.errorLogLimit??10},this.jobManager=new $(this.redis,{maxJobsPerUser:this.defaults.maxJobsPerUser,jobTtlSeconds:this.defaults.jobTtlSeconds,errorLogLimit:this.defaults.errorLogLimit}),this.jobManager.setEventEmitter(this.eventsEnabled?{emitEvent:this.emitEvent.bind(this)}:We)}pingRedis(){return this.redis.ping()}createBulkRouter(e,t){let n=new He(this,e,t);return this.bulkRouters.push(n),n}emitEvent(e,...t){this.eventsEnabled&&this.emit(e,...t)}};exports.Bulker=Ge,exports.BulkerError=Z,exports.BulkerJobManager=$,exports.formatOperators=x,exports.generateFilterReplacements=L,exports.queryFormatMiddleware=Fe,exports.queryHandler=Ie,exports.queryValidationMiddleware=Pe,exports.validatePayload=W,Object.defineProperty(exports,`z`,{enumerable:!0,get:function(){return g.z}});
|
|
129
129
|
//# sourceMappingURL=index.cjs.map
|
package/lib/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["DefaultOp","sequelize","Op","BadRequest","customFields","order","replacements: Record<string, string>","replacementMap: Record<string, string>","formattedOrders: SequelizeOrder[]","formattedQuery: Record<string, unknown>","externalQueryValues: Record<string, unknown>","Joi","BadRequest","formatPayload","UnexpectedError","z","DEFAULT_IDENTITY_SCOPES: readonly string[]","bulker: Bulker","opts: BulkRouteOptions<T>","validatedPayload: T | undefined","z","additionalIds: Id[]","orClauses: WhereOptions[]","finalWhere: WhereOptions","batches: Id[][]","lastId: Id | null","Op","rows: Record<string, any>[]","id: Id | undefined","err: any","redis","failedResults: { id: any; error?: string; }[]","NOOP_EMITTER: EmitsBulkEvents","EventEmitter"],"sources":["../src/operators/index.ts","../src/utils.ts","../src/formatter/jsonAttributesFormater.ts","../src/formatter/index.ts","../src/validations/index.ts","../src/middleware/index.ts","../src/handler/index.ts","../src/bulker/src/Errors.ts","../src/bulker/src/utils/identityScope.ts","../src/bulker/src/types.ts","../src/bulker/src/BulkRoute.ts","../src/bulker/src/BulkRouter.ts","../src/bulker/src/redis-scripts.ts","../src/bulker/src/JobManager.ts","../src/bulker/src/index.ts"],"sourcesContent":["import { Op as DefaultOp } from 'sequelize';\n\nexport const OPERATORS = [\n 'eq',\n 'ne',\n 'gte',\n 'gt',\n 'lte',\n 'lt',\n 'not',\n 'in',\n 'notIn',\n 'is',\n 'like',\n 'iLike',\n 'notLike',\n 'between',\n 'and',\n 'or',\n 'overlap',\n 'contains',\n] as const;\n\nexport const OPERATOR_PREFIX = '$';\n\nexport const OPERATORS_TO_SQL = {\n $eq: '=',\n $ne: '!=',\n $gte: '>=',\n $gt: '>',\n $lte: '<=',\n $lt: '<',\n $not: 'NOT',\n $in: 'IN',\n $notIn: 'NOT IN',\n $is: 'IS',\n $like: 'LIKE',\n $iLike: 'ILIKE',\n $notLike: 'NOT LIKE',\n $and: 'AND',\n $or: 'OR',\n};\n\nexport const formatOperators = (sequelize: { Op: typeof DefaultOp; } = { Op: DefaultOp }): Record<string, symbol> => {\n const { Op } = sequelize;\n return Object.fromEntries(OPERATORS.map(o => [`${OPERATOR_PREFIX + o}`, Op[o]]));\n};\n","import { randomInt } from 'node:crypto';\nimport { BadRequest } from '@autofleet/errors';\nimport { OPERATOR_PREFIX } from './operators';\n\nexport const ORDER_PREFIX = '-';\nexport const ASSOCIATION_PREFIX = '.';\nexport const ASSOCIATION_PATH_WRAPPER = '$';\nexport const PER_PAGE_DEFAULT = 20;\nexport const PAGE_DEFAULT = 1;\nexport const PER_PAGE_MAX_LIMIT = 100;\nexport const PER_PAGE_MIN_LIMIT = 1;\nexport const PAGE_MIN = 1;\n\nexport const wrapAttributeWithOperator = (attribute: string) => `${OPERATOR_PREFIX}${attribute}${OPERATOR_PREFIX}`;\nexport const isAttributeByAssociation = (attributeName: string, associatedModels: string[]): boolean => attributeName.includes(ASSOCIATION_PREFIX)\n && associatedModels.includes(attributeName.split(ASSOCIATION_PREFIX, 1)[0]);\n\nexport const extractAttributeNameFromOrder = (order: string, associationModels: string[]): string => {\n let formattedOrder = order;\n if (order.includes(ORDER_PREFIX)) {\n [, formattedOrder] = formattedOrder.split(ORDER_PREFIX, 2);\n }\n if (isAttributeByAssociation(formattedOrder, associationModels)) {\n [formattedOrder] = formattedOrder.split(ASSOCIATION_PREFIX, 1);\n }\n return formattedOrder;\n};\n\nexport const isOrderDesc = (order: string): boolean => order.includes(ORDER_PREFIX);\n\nexport const throwBadRequestError = (message: string): never => {\n throw new BadRequest([new Error(message)]);\n};\n\nexport const extractAssociatedAttributeNameFromOrder = (order: string): string => order.split(ASSOCIATION_PREFIX, 2)[1];\n\nexport const generateRandomString = (length = 5): string => {\n const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n return Array.from({ length }, () => characters.charAt(randomInt(characters.length))).join('');\n};\n\nexport const pick = <T extends object, K extends keyof T = keyof T>(\n obj: T,\n keys: K[],\n): Pick<T, K> => Object.fromEntries(keys.map(key => [key, obj[key]])) as Pick<T, K>;\n","import { literal } from 'sequelize';\n\nexport const ComputedActions = {\n length: 'length',\n} as const;\n\ninterface JsonSelectAttribute {\n columnName: string;\n keys: string[];\n alias?: string;\n}\n\ninterface JsonComputedAttribute {\n columnName: string;\n action: keyof typeof ComputedActions;\n alias: string;\n}\n\nexport interface JsonAttributes {\n select?: JsonSelectAttribute[];\n computed?: JsonComputedAttribute[];\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/(?!^)[A-Z]/g, letter => `_${letter.toLowerCase()}`);\n}\n\n/**\n * Builds a computed attribute based on the action specified.\n * Currently, supports 'length' action which calculates the length of a JSON array.\n *\n * @param {JsonComputedAttribute} computedAttribute - The computed attribute definition.\n * @returns {Array} An array containing the SQL literal and the alias for the computed attribute.\n */\nfunction buildComputedAttribute(computedAttribute: JsonComputedAttribute) {\n switch (computedAttribute.action) {\n case ComputedActions.length: {\n // Generates: jsonb_array_length(column)\n // Returns the length of the JSON array (e.g., [\"a\", \"b\"] -> 2)\n // Note: Expects the column to be a JSON array\n const sql = `jsonb_array_length(${toSnakeCase(computedAttribute.columnName)})`;\n return [literal(sql), computedAttribute.alias];\n }\n default:\n computedAttribute.action satisfies never;\n return [];\n }\n}\n\nfunction buildComputedAttributes(computed: JsonComputedAttribute[]) {\n return computed.map(attr => buildComputedAttribute(attr));\n}\n\n/**\n * Builds a list of SQL select attributes using json_build_object\n * to extract specific keys from a JSONB column.\n *\n * Example output:\n * SELECT json_build_object('a', column -> 'a', 'b', column -> 'b') AS alias\n */\nfunction buildSelectAttributes(select: JsonSelectAttribute[]) {\n return select.map((attr) => {\n const columnNameSnake = toSnakeCase(attr.columnName);\n const sql = `json_build_object(${attr.keys\n .map(k => `'${k}', ${columnNameSnake} -> '${k}'`)\n .join(', ')})`;\n const alias = attr.alias || attr.columnName;\n return [literal(sql), alias];\n });\n}\n\n/**\n * Return a single array combining selected and computed attributes.\n * Format: [[literal, alias], [literal, alias], ...]\n */\nexport default function buildJsonAttributes({ select = [], computed = [] }: JsonAttributes = {}): (string | ReturnType<typeof literal>)[][] {\n const selectAttributes = buildSelectAttributes(select);\n const computedAttributes = buildComputedAttributes(computed);\n\n return [...selectAttributes, ...computedAttributes];\n}\n","import type { literal } from 'sequelize';\nimport { customFields } from '@autofleet/common-types';\nimport {\n extractAssociatedAttributeNameFromOrder,\n extractAttributeNameFromOrder, generateRandomString,\n isAttributeByAssociation,\n isOrderDesc, ORDER_PREFIX,\n PAGE_DEFAULT,\n PER_PAGE_DEFAULT, wrapAttributeWithOperator,\n} from '../utils';\nimport type { LiteralAttribute } from '../middleware';\nimport { OPERATORS_TO_SQL } from '../operators';\nimport buildJsonAttributes, { type JsonAttributes } from './jsonAttributesFormater';\n\nconst DEFAULT_ORDER = 'id';\nconst DESCENDING_KEY = 'DESC';\nconst ASCENDING_KEY = 'ASC';\nconst CUSTOM_FIELDS_QUERY_PREFIX = 'customFields.';\nconst { CUSTOM_FIELDS_FILTER_SCOPE, CUSTOM_FIELDS_SORT_SCOPE } = customFields;\ntype OrderItem = string | [string, string];\ntype SequelizeOrder = string | OrderItem[];\nexport interface FormatPayloadOptions {\n includeRawPayload?: boolean;\n literalAttributes?: LiteralAttribute[];\n DBFormatter?: typeof literal;\n skipSearchTermFormat?: boolean;\n additionalAllowedAttributes?: string[];\n}\n\ninterface ConditionWithOperator {\n operator: string;\n value: string;\n}\nexport type ConditionValue = ConditionWithOperator | ConditionWithOperator[] | string | string[];\n\nconst parseCustomFieldScopeQueryValue = (value: unknown) => {\n if (['string', 'number'].includes(typeof value) || Array.isArray(value)) {\n return value;\n }\n return Object.entries(value as object).map(([operator, conditionValue]) => ({\n operator: OPERATORS_TO_SQL[operator as keyof typeof OPERATORS_TO_SQL],\n value: conditionValue,\n }));\n};\n\nconst getAttributeFromOrder = (order: SequelizeOrder[], options: FormatPayloadOptions = {}): [(SequelizeOrder | Literal)[], SequelizeOrder[]] => {\n const { literalAttributes = [], DBFormatter = undefined } = options;\n const [formattedOrder, attributes] = order.reduce<[(SequelizeOrder | Literal)[], SequelizeOrder[]]>((acc, o) => {\n const [item, orderStyle = 'ASC'] = Array.isArray(o) ? o : [o];\n const found = literalAttributes?.find(obj => obj.attribute === item);\n if (found) {\n const order = DBFormatter ? DBFormatter(`\"${found.attribute}\" ${orderStyle as string}`) : `${found.attribute} ${orderStyle as string}`;\n acc[1].push(found.literal);\n acc[0].push([order as string]);\n } else {\n acc[0].push(o);\n }\n return acc;\n }, [[], []]);\n\n return [formattedOrder, attributes];\n};\n\n/**\n * Generates replacements for the given conditions.\n *\n * @param conditions - The conditions to generate replacements for.\n * @returns The replacements object.\n */\nexport const generateFilterReplacements = (conditions: Record<string, ConditionValue>): Record<string, string> => {\n const replacements: Record<string, string> = {};\n\n Object.entries(conditions).forEach(([key, condition]) => {\n const replacementKey = generateRandomString();\n replacements[replacementKey] = key.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n\n if (Array.isArray(condition)) {\n condition.forEach((value) => {\n const valueKey = generateRandomString();\n replacements[valueKey] = typeof value === 'string' ? value : value.value;\n });\n } else if (typeof condition === 'string' || typeof condition === 'number') {\n const conditionKey = generateRandomString();\n replacements[conditionKey] = condition;\n } else if (condition?.operator) {\n const operatorKey = generateRandomString();\n replacements[operatorKey] = condition.value;\n }\n });\n\n return replacements;\n};\n\n/**\n * Generates replacements for the given order array.\n *\n * @param order - The order array to generate replacements for.\n * @returns The replacements object.\n */\nexport const generateOrderReplacements = (order: string[]): Record<string, string> => {\n const replacementMap: Record<string, string> = {};\n order.forEach((o) => {\n if (o.startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n const rand = generateRandomString();\n replacementMap[rand] = o.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n } else if (o.substring(1).startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n const rand = generateRandomString();\n replacementMap[rand] = o.substring(1).split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n }\n });\n return replacementMap;\n};\n\n/**\n * Creates a combined replacement map from order and query.\n *\n * @param order - The order array.\n * @param query - The query object.\n * @returns The combined replacements object.\n */\nconst createReplacementMap = (order: string[], query: Record<string, ConditionValue>): Record<string, string> => ({\n ...generateOrderReplacements(order),\n ...generateFilterReplacements(query),\n});\n\nconst formatOrder = ({\n order,\n associationModels = [],\n replacementsMap = {},\n}: { order: string[]; associationModels?: string[]; replacementsMap?: Record<string, string>; }): {\n formattedOrders: SequelizeOrder[];\n orderScopes: (string | { method: [string, { replacementsMap: Record<string, string>; scopeValue: Record<string, 'DESC' | 'ASC'>; }]; })[];\n replacementsMap: Record<string, string>;\n} => {\n const formattedOrders: SequelizeOrder[] = [];\n const orderScopesMap = new Map<string, Record<string, 'DESC' | 'ASC'>>();\n order.forEach((o) => {\n if ([o, o.substring(1)].some(t => t.startsWith(CUSTOM_FIELDS_QUERY_PREFIX))) {\n if (!orderScopesMap.has(CUSTOM_FIELDS_SORT_SCOPE)) {\n orderScopesMap.set(CUSTOM_FIELDS_SORT_SCOPE, {});\n }\n const scopeKey = o.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n orderScopesMap.get(CUSTOM_FIELDS_SORT_SCOPE)![scopeKey] = (isOrderDesc(o) ? DESCENDING_KEY : ASCENDING_KEY);\n return;\n }\n const formattedOrder = [extractAttributeNameFromOrder(o, associationModels)];\n const isOrderDescOrder = isOrderDesc(o);\n const isOrderAssociation = isAttributeByAssociation(isOrderDescOrder\n ? o.split(ORDER_PREFIX, 2)[1]\n : o, associationModels);\n if (isOrderAssociation) {\n formattedOrder.push(extractAssociatedAttributeNameFromOrder(o));\n }\n if (isOrderDescOrder) {\n formattedOrder.push(DESCENDING_KEY);\n }\n formattedOrders.push(formattedOrder);\n });\n return {\n formattedOrders,\n replacementsMap,\n orderScopes: Array.from(orderScopesMap.entries()).map(([scopeName, scopeValue]) => {\n /* v8 ignore next 3 */\n if (!scopeValue) {\n return scopeName;\n }\n return {\n method: [scopeName, {\n replacementsMap,\n scopeValue,\n }],\n };\n }),\n };\n};\n\nconst formatPage = (page?: number) => page || PAGE_DEFAULT;\n\nconst formatPerPage = (perPage?: number) => perPage || PER_PAGE_DEFAULT;\n\ninterface Include {\n association?: string;\n model?: string;\n required?: boolean;\n include?: Include[];\n}\n\nconst formatInclude = (include: (string | Include)[], associationsMap: Record<string, any> = {}) => {\n let formattedInclude = include.map((i): any => {\n const includedAssociation = associationsMap[typeof i === 'string' ? i : (i.association || i.model!)];\n return {\n ...(typeof i !== 'string' && i),\n association: includedAssociation,\n required: typeof i === 'string' || i.required !== false,\n ...(typeof i !== 'string' && i.include && {\n include: formatInclude(i.include, includedAssociation?.target?.associations),\n }),\n };\n });\n formattedInclude = formattedInclude.map(({ model: _model, ...i }) => i);\n return formattedInclude;\n};\nconst formatQuery = (query: Record<string, unknown>, associationModels: string[], replacementsMap: Record<string, string>, additionalAllowedAttributes: string[] = []) => {\n const formattedQuery: Record<string, unknown> = {};\n const externalQueryValues: Record<string, unknown> = {};\n const formattedScopeMap = new Map<string, any>();\n\n Object.entries(query).forEach(([queryItemKey, queryItemValue]) => {\n if (queryItemKey.startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n if (!formattedScopeMap.has(CUSTOM_FIELDS_FILTER_SCOPE)) {\n formattedScopeMap.set(CUSTOM_FIELDS_FILTER_SCOPE, {});\n }\n const scopeKey = queryItemKey.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n formattedScopeMap.get(CUSTOM_FIELDS_FILTER_SCOPE)[scopeKey] = parseCustomFieldScopeQueryValue(queryItemValue);\n return;\n }\n if (additionalAllowedAttributes.includes(queryItemKey)) {\n externalQueryValues[queryItemKey] = queryItemValue;\n return;\n }\n const key = isAttributeByAssociation(queryItemKey, associationModels)\n ? wrapAttributeWithOperator(queryItemKey)\n : queryItemKey;\n formattedQuery[key] = queryItemValue;\n });\n\n const formattedScopes = Array.from(formattedScopeMap.entries()).map(([scopeName, scopeValue]) => {\n /* v8 ignore next 3 */\n if (!scopeValue) {\n return scopeName;\n }\n return {\n method: [scopeName, {\n replacementsMap,\n scopeValue,\n }],\n };\n });\n\n return {\n formattedQuery,\n externalQueryValues,\n formattedScopes,\n };\n};\n\nconst formatSearchTerm = (searchTerm: string, attributesToSend: string[], rawAttributes: Record<string, any>) => ({\n $and: searchTerm.split(' ').map(term => ({\n $or: attributesToSend.filter(attrKey => rawAttributes[attrKey].type.key === 'STRING').map(attr => ({\n [attr]: {\n $iLike: `%${term}%`,\n },\n })),\n })),\n});\n\ninterface FormatPayloadData {\n order?: string[];\n page?: number;\n perPage?: number;\n include?: (string | Include)[];\n query?: Record<string, unknown>;\n attributes?: string[] | null;\n searchTerm?: string | null;\n jsonAttributes?: JsonAttributes;\n}\ntype Literal = ReturnType<typeof literal>;\n\ninterface FormattedPayload {\n query: Record<string, unknown>;\n\n externalQueryValues?: Record<string, unknown>;\n attributes: (SequelizeOrder | (string | Literal)[])[] | {\n include: (SequelizeOrder | (string | Literal)[])[];\n };\n order: (SequelizeOrder | Literal)[];\n page: number;\n perPage: number;\n include: any;\n scopes: (string | {\n method: (string | {\n replacementsMap: Record<string, string>;\n scopeValue: any;\n })[];\n })[];\n}\n\nconst formatPayload = ({\n order = [],\n page = PAGE_DEFAULT,\n perPage = PER_PAGE_DEFAULT,\n include = [],\n query = {},\n attributes = null,\n searchTerm = null,\n jsonAttributes = {},\n}: FormatPayloadData, model?: any, options?: FormatPayloadOptions): FormattedPayload => {\n const replacementsMap = createReplacementMap(order, query as Record<string, ConditionValue>);\n const associationModels = Object.keys(model?.associations || {});\n const { formattedOrders, orderScopes } = formatOrder({\n order: [...order, DEFAULT_ORDER],\n associationModels,\n replacementsMap,\n });\n const [filteredFormattedOrder, filteredLiteralAttributes] = getAttributeFromOrder(\n formattedOrders,\n options,\n );\n\n const formattedJsonAttributes = buildJsonAttributes(jsonAttributes);\n const allAttributes = [...filteredLiteralAttributes, ...(attributes ?? []), ...formattedJsonAttributes];\n const formattedAttribute = attributes?.length ? allAttributes : { include: allAttributes };\n\n const formattedInclude = formatInclude(include, model?.associations);\n const formattedPage = formatPage(page);\n const formattedPerPage = formatPerPage(perPage);\n const result = formatQuery(query, associationModels, replacementsMap, options?.additionalAllowedAttributes);\n const { formattedScopes: queryScopes, externalQueryValues } = result;\n let { formattedQuery } = result;\n if (searchTerm && !options?.skipSearchTermFormat) {\n const attributesToSend = attributes?.length ? attributes : Object.keys(model.rawAttributes || {});\n const queryWithSearchTerm = formatSearchTerm(searchTerm, attributesToSend, model.rawAttributes);\n formattedQuery = !formattedQuery || Object.keys(formattedQuery).length === 0 ? queryWithSearchTerm : {\n $and: [\n formattedQuery,\n queryWithSearchTerm,\n ],\n };\n }\n return {\n query: formattedQuery,\n order: filteredFormattedOrder,\n page: formattedPage,\n perPage: formattedPerPage,\n include: formattedInclude,\n scopes: [...queryScopes, ...orderScopes],\n ...(formattedAttribute && { attributes: formattedAttribute }),\n ...(Object.keys(externalQueryValues).length > 0 && { externalQueryValues }),\n };\n};\n\nexport default formatPayload;\n","import {\n ORDER_PREFIX,\n extractAttributeNameFromOrder,\n isOrderDesc,\n throwBadRequestError,\n PAGE_DEFAULT,\n PER_PAGE_DEFAULT,\n PAGE_MIN,\n PER_PAGE_MAX_LIMIT,\n PER_PAGE_MIN_LIMIT,\n isAttributeByAssociation,\n ASSOCIATION_PREFIX,\n ASSOCIATION_PATH_WRAPPER,\n} from '../utils';\nimport { OPERATORS, OPERATOR_PREFIX } from '../operators';\nimport type { MiddlewareValidationOption } from '../middleware';\nimport type { JsonAttributes } from '../formatter/jsonAttributesFormater';\n\nconst validateOperator = (operator: string): boolean => OPERATORS.includes(operator.split(OPERATOR_PREFIX, 2)[1] as typeof OPERATORS[number]);\n\nconst validateQueryAttribute = (\n rawAttribute: string,\n modelAttributes: string[] = [],\n associationModels: string[] = [],\n additionalAllowedAttributes: string[] = [],\n): boolean => {\n const attribute = (rawAttribute.startsWith(\n ASSOCIATION_PATH_WRAPPER,\n ) && rawAttribute.endsWith(\n ASSOCIATION_PATH_WRAPPER,\n )) ? rawAttribute.slice(1, -1) : rawAttribute;\n return [...modelAttributes, ...associationModels].includes(attribute.includes(ASSOCIATION_PREFIX)\n ? attribute.split(ASSOCIATION_PREFIX, 1)[0] : attribute)\n || additionalAllowedAttributes.includes(attribute);\n};\n\nconst validateSingleOrder = (\n currentOrder: string,\n rawAttributes: string[],\n associationModels: string[],\n options: MiddlewareValidationOption = {},\n): void => {\n const isOrderDescOrder = isOrderDesc(currentOrder);\n if (isOrderDescOrder && !currentOrder.startsWith(ORDER_PREFIX)) {\n throwBadRequestError(`${ORDER_PREFIX} must be only at the beginning of the word`);\n }\n const orderStringWithoutDesc = isOrderDescOrder ? currentOrder.split(ORDER_PREFIX, 2)[1] : currentOrder;\n const isOrderAssociation = isAttributeByAssociation(orderStringWithoutDesc, associationModels);\n let formattedOrderString = extractAttributeNameFromOrder(currentOrder, associationModels);\n const isLiteralAttribute = options?.literalAttributes?.map(la => la.attribute)?.includes(orderStringWithoutDesc);\n\n if (!isOrderAssociation && formattedOrderString.includes(ASSOCIATION_PREFIX)) {\n [formattedOrderString] = formattedOrderString.split(ASSOCIATION_PREFIX, 1);\n }\n\n if (!(rawAttributes.includes(formattedOrderString) || isOrderAssociation || isLiteralAttribute)) {\n throwBadRequestError(`${currentOrder} is invalid. isLiteralAttribute: ${isLiteralAttribute}`);\n }\n};\n\nconst validateSingleAttribute = (currentAttribute: string, rawAttributes: string[]): void => {\n if (!rawAttributes.includes(currentAttribute)) {\n throwBadRequestError(`${currentAttribute} is invalid`);\n }\n};\n\nconst validateOrderAttributes = (\n order: string[],\n rawAttributes: string[],\n associationModels: string[] = [],\n options: MiddlewareValidationOption = {},\n): void => {\n order.forEach(o => validateSingleOrder(o, rawAttributes, associationModels, options));\n};\n\nconst validateAttributes = (attributes: string[], rawAttributes: string[]): void => {\n attributes.forEach(a => validateSingleAttribute(a, rawAttributes));\n};\n\nconst validateJsonAttributes = (jsonAttributes: JsonAttributes, rawAttributes: string[]): void => {\n const allAttributes = [\n ...(jsonAttributes.select?.map(s => s.columnName) ?? []),\n ...(jsonAttributes.computed?.map(c => c.columnName) ?? []),\n ];\n\n validateAttributes(allAttributes, rawAttributes);\n};\n\n// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style\nconst validateEnrichments = (enrichments: string[] | { [enrichmentName: string]: { exclude?: string[]; }; }, options?: MiddlewareValidationOption): void => {\n const enrichmentKeys = Array.isArray(enrichments) ? enrichments : Object.keys(enrichments);\n if (!enrichmentKeys?.length) {\n return;\n }\n const invalidEnrichment = enrichmentKeys.find(enrichment => !options?.enrichmentAttributes?.includes(enrichment));\n if (invalidEnrichment) {\n throwBadRequestError(`enrichment attribute ${invalidEnrichment} is invalid`);\n }\n};\n\nconst validateQueryPayload = (query: object, rawAttributes: string[], associationModels: string[] = [], additionalAllowedAttributes: string[] = []): void => {\n Object.entries(query).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n if (value[0] && typeof value[0] === 'object') {\n value.map(v => validateQueryPayload(v, rawAttributes, associationModels, additionalAllowedAttributes));\n }\n } else if (validateOperator(key) || validateQueryAttribute(key, rawAttributes, associationModels, additionalAllowedAttributes)) {\n if (value && typeof value === 'object') {\n validateQueryPayload(value, rawAttributes, [], additionalAllowedAttributes);\n }\n } else {\n throwBadRequestError(`invalid key: ${key}`);\n }\n });\n};\n\nconst validatePagination = ({\n page,\n perPage,\n}: { page: number; perPage: number; }) => {\n if (page < PAGE_MIN) {\n throwBadRequestError('Page must be greater than 0');\n }\n\n if (perPage > PER_PAGE_MAX_LIMIT || perPage < PER_PAGE_MIN_LIMIT) {\n throwBadRequestError(`PerPage must be between ${PER_PAGE_MIN_LIMIT} to ${PER_PAGE_MAX_LIMIT}`);\n }\n};\n\nconst validateIncludePayload = (include: any[], associations: Record<string, any>): void => {\n const associationsKeys = Object.keys(associations);\n include.forEach((i) => {\n validateQueryAttribute(i.model, associationsKeys);\n const target = associations[i.model]?.target;\n if (!target) {\n throwBadRequestError('model not found in associations');\n }\n\n const { rawAttributes } = target;\n const attributeKeys = Object.keys(rawAttributes);\n if (i.where) {\n validateQueryPayload(i.where, attributeKeys);\n }\n if (i.order) {\n validateOrderAttributes(i.order, attributeKeys);\n }\n if (i.attributes) {\n validateAttributes(i.attributes, attributeKeys);\n }\n if (![null, undefined, true, false].includes(i.required)) {\n throwBadRequestError('include.required must be a boolean');\n }\n });\n};\n\ninterface PayloadValidationData {\n query?: object;\n order?: string[];\n attributes?: string[];\n include?: any[];\n page?: number;\n perPage?: number;\n // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style\n enrichments?: string[] | { [enrichmentName: string]: { exclude?: string[]; }; };\n group?: string[];\n jsonAttributes?: {\n select?: { columnName: string; keys: string[]; alias?: string; }[];\n computed?: { columnName: string; action: 'length'; alias: string; }[];\n };\n}\n\nexport const validatePayload = (\n {\n query = {},\n order = [],\n attributes = [],\n include = [],\n page = PAGE_DEFAULT,\n perPage = PER_PAGE_DEFAULT,\n enrichments = [],\n group = [],\n jsonAttributes = {},\n }: PayloadValidationData,\n model?: any,\n options: MiddlewareValidationOption = {},\n): boolean => {\n const rawAttributes = Object.keys(model.rawAttributes);\n const associationModels = Object.keys(model?.associations || {});\n if (!attributes || attributes.length === 0) {\n attributes = rawAttributes;\n } else {\n validateAttributes(attributes, rawAttributes);\n }\n\n validateOrderAttributes(order, rawAttributes, associationModels, options);\n validateQueryPayload(query, rawAttributes, associationModels, options.additionalAllowedAttributes);\n validateEnrichments(enrichments, options);\n validateJsonAttributes(jsonAttributes, rawAttributes);\n\n if (!Array.isArray(group)) {\n throwBadRequestError('group must be an array');\n }\n if (include.length && typeof include === 'object') {\n validateIncludePayload(include, model?.associations);\n } else if (include && typeof include !== 'object') {\n throwBadRequestError('include must be an array');\n }\n\n validatePagination({\n page,\n perPage,\n });\n return true;\n};\n","import type { Handler } from 'express';\nimport Logger, { type LoggerInstanceManager } from '@autofleet/logger';\nimport { BadRequest, handleError } from '@autofleet/errors';\nimport Joi from 'joi';\nimport formatPayload, { type FormatPayloadOptions } from '../formatter';\nimport { validatePayload } from '../validations';\nimport { ComputedActions } from '../formatter/jsonAttributesFormater';\n\nconst {\n object, string, number, any, array, alternatives,\n} = Joi.types();\nconst fallbackLogger = Logger();\n\nconst querySchema = object.keys({\n query: object,\n attributes: array.items(string),\n order: array.items(string),\n page: number,\n perPage: number,\n include: array.items(any),\n searchTerm: string,\n group: array.items(string),\n enrichments: alternatives.try(array.items(string), object.pattern(string, { exclude: array.items(string) })),\n jsonAttributes: Joi.object({\n select: Joi.array().items(\n Joi.object({\n columnName: Joi.string().required(),\n keys: Joi.array().items(Joi.string().required()).required(),\n alias: Joi.string().optional(),\n }),\n ).default([]),\n computed: Joi.array().items(\n Joi.object({\n columnName: Joi.string().required(),\n action: Joi.string().valid(...Object.values(ComputedActions)).required(),\n alias: Joi.string().required(),\n }),\n ).default([]),\n }).default({}),\n});\n\ntype literal = any;\ntype LiteralQuery = (literal | string)[] | literal;\nexport interface LiteralAttribute { attribute: string; literal: LiteralQuery; }\nexport interface MiddlewareValidationOption {\n literalAttributes?: LiteralAttribute[];\n enrichmentAttributes?: string[];\n additionalAllowedAttributes?: string[];\n logger?: LoggerInstanceManager;\n}\n\ntype ReqKeys = 'body' | 'query';\n\nexport const queryValidation = (model: any, data: any, options: MiddlewareValidationOption = {}): void => {\n const {\n query,\n attributes,\n order,\n page,\n perPage,\n include,\n group,\n enrichments,\n jsonAttributes,\n } = data;\n\n const result = querySchema.validate(data);\n if (result.error) {\n throw new BadRequest([result.error]);\n }\n validatePayload({\n query,\n attributes,\n order,\n page,\n perPage,\n include,\n enrichments,\n group,\n jsonAttributes,\n }, model, options);\n};\n\n/** consider using @see {@link queryHandler} directly */\nexport const queryValidationMiddleware = (model: any, options: MiddlewareValidationOption = {}, inner: ReqKeys = 'body'): Handler => (req, res, next): void => {\n try {\n queryValidation(model, req[inner], options);\n next();\n } catch (error) {\n const { query, attributes, order } = req[inner];\n handleError(error as Error, res, {\n logger: options.logger ?? fallbackLogger,\n message: 'error in query middleware',\n payload: {\n error,\n query,\n attributes,\n order,\n },\n });\n }\n};\n\nexport const queryFormat = (model: any, data: any, options: FormatPayloadOptions = {}): void => {\n const {\n order, page, perPage, include, query, attributes, searchTerm, jsonAttributes,\n } = data;\n\n const {\n query: formattedQuery,\n externalQueryValues,\n order: formattedOrder,\n page: formattedPage,\n perPage: formattedPerPage,\n include: formattedInclude,\n scopes: formattedScopes,\n attributes: formattedAttribute,\n } = formatPayload({\n query, order, page, perPage, include, attributes, searchTerm, jsonAttributes,\n }, model, options);\n\n data.query = formattedQuery;\n data.externalQueryValues = externalQueryValues;\n data.order = formattedOrder;\n data.attributes = formattedAttribute;\n data.page = formattedPage;\n data.perPage = formattedPerPage;\n data.include = formattedInclude;\n data.scopes = formattedScopes;\n if (options.includeRawPayload) {\n data.rawPayload = {\n order,\n page,\n perPage,\n include,\n query,\n attributes,\n searchTerm,\n };\n }\n};\n\n/** consider using @see {@link queryHandler} directly */\nexport const queryFormatMiddleware = (model: any, options: FormatPayloadOptions = {}, inner: ReqKeys = 'body'): Handler => (req, _res, next): void => {\n queryFormat(model, req[inner], options);\n next();\n};\n","import type { Handler } from 'express';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport { UnexpectedError, handleError } from '@autofleet/errors';\nimport type formatPayload from '../formatter';\nimport type { FormatPayloadOptions } from '../formatter';\nimport { pick } from '../utils';\nimport { type MiddlewareValidationOption, queryFormat, queryValidation } from '../middleware';\n\ninterface QueryValues extends ReturnType<typeof formatPayload> {\n enrichments?: string[] | Record<string, { exclude: string[]; }>;\n distinct: boolean;\n searchTerm?: string;\n}\n\ninterface QueryHandlerOptions {\n /** The sequelize model too which querying abilities are added. */\n model: any;\n /** Optional settings for validation. */\n validationOptions?: Omit<MiddlewareValidationOption, 'logger'>;\n /** Optional settings for payload formatting */\n formatOptions?: FormatPayloadOptions;\n logger: LoggerInstanceManager;\n /** The name of model to be printed in logs. defaults to `model`s constructor name. */\n modelName?: string;\n /** Sequelize scopes of the model to be used within the query. @example ['userScope'] */\n additionalScopes?: string[];\n /** Callback to allow modifying the query values prior to querying the DB */\n modifyQueryValues?: (queryValues: QueryValues) => QueryValues;\n /** Optional callback to modify endpoint's response based on the DBs response. */\n onRowsRetrieved?: (data: { rows: any[]; count: number; }, queryValues: QueryValues) => any;\n}\n\ntype Asyncify<T extends (...a: any[]) => any> = (...a: Parameters<T>) => Promise<Awaited<ReturnType<T>>>;\n\nexport const queryHandler = ({\n model, logger, validationOptions, formatOptions, modelName = model.constructor?.name, additionalScopes = [], modifyQueryValues, onRowsRetrieved,\n}: QueryHandlerOptions): Asyncify<Handler> => async (req, res) => {\n try {\n queryValidation(model, req.body, { ...validationOptions, logger });\n } catch (error) {\n const payload = pick(req.body as QueryValues, ['query', 'order', 'attributes'] as const);\n handleError(error as Error, res, { logger, message: 'error in query endpoint', payload });\n return;\n }\n try {\n queryFormat(model, req.body, formatOptions);\n\n const queryValues = Object.assign(\n pick(req.body as QueryValues, ['query', 'externalQueryValues', 'order', 'attributes', 'page', 'perPage', 'include', 'scopes', 'enrichments'] as const),\n { distinct: true },\n );\n\n logger.info(`querying ${modelName}`, { queryValues });\n\n const modifiedQuery = modifyQueryValues?.(queryValues) ?? queryValues;\n const {\n scopes = [],\n query: where,\n perPage: limit,\n page,\n enrichments: _enrichments,\n externalQueryValues: _externalQueryValues,\n ...rest\n } = modifiedQuery;\n\n const result = await model.scope([...additionalScopes, ...scopes]).findAndCountAll({\n where,\n limit,\n offset: (page - 1) * limit,\n ...rest,\n });\n\n if (!result.rows.length || !onRowsRetrieved) {\n res.json(result);\n return;\n }\n\n const enrichmentResult = await onRowsRetrieved(result, modifiedQuery);\n\n res.json(enrichmentResult);\n } catch (error) {\n handleError(new UnexpectedError(error as Error), res, { logger, message: `Error while querying ${modelName}`, payload: { query: req.body } });\n }\n};\n","export class BulkerError extends Error {\n public readonly retryable: boolean;\n\n constructor(message: string, retryable = false) {\n super(message);\n this.name = 'BulkerError';\n this.retryable = retryable;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static isBulkerError(err: unknown): err is BulkerError {\n return err instanceof BulkerError;\n }\n\n static wrap(err: unknown, message?: string, retryable = false): BulkerError {\n if (err instanceof BulkerError) {\n return new BulkerError(message ?? err.message, retryable || err.retryable);\n }\n\n const msg = message ? `${message}: ${err instanceof Error ? err.message : String(err)}` : String(err);\n return new BulkerError(msg, retryable);\n }\n\n static retryable(message: string): BulkerError {\n return new BulkerError(message, true);\n }\n\n static nonRetryable(message: string): BulkerError {\n return new BulkerError(message, false);\n }\n}\n\nexport class ValidationError extends BulkerError {\n constructor(message: string, retryable = false) {\n super(message, retryable);\n this.name = 'ValidationError';\n }\n}\n","import { z } from 'zod';\n\nconst getErrorMessage = (keys: string[]) => {\n if (keys.length === 0) return 'No keys provided';\n if (keys.length === 1) return `Key \"${keys[0]}\" is required`;\n return `Exactly one of [${keys.join(', ')}] must be provided`;\n};\n\nexport function oneOfKeys<T extends z.ZodRawShape>(\n shape: T,\n keys: (keyof T)[],\n): z.ZodType<any> {\n return z.object(shape).refine(\n (data: any) => {\n const presentKeys = keys.filter(\n k => data[k as string] !== undefined && data[k as string] !== null,\n );\n return presentKeys.length === 1;\n },\n {\n message: getErrorMessage(keys as string[]),\n },\n );\n}\n\nconst uuidValidation = z.string().uuid();\n\nexport function buildIdentityScopeSchema(keys: readonly string[]): ReturnType<typeof oneOfKeys> {\n const shape = Object.fromEntries(\n keys.map(key => [key, z.union([uuidValidation, z.array(uuidValidation)]).optional()]),\n );\n return oneOfKeys(shape, keys as (keyof typeof shape)[]);\n}\n","import type { Model, ModelStatic } from 'sequelize';\nimport type RabbitMq from '@autofleet/rabbit';\nimport type { z } from 'zod';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\n\nexport type Id = string | number;\n\nexport interface JobMetadata {\n status: string;\n total: number;\n action: string;\n}\n\nexport interface ConsumeOptions {\n enableRabbitTrace?: boolean;\n [key: string]: any;\n}\n\nexport interface JobStatus {\n jobId: string;\n status: string;\n action: string;\n total: number;\n queued: number;\n processed: number;\n succeeded: number;\n failed: number;\n errors: any[];\n createdAt?: string;\n updatedAt?: string;\n duration: {\n startTime: string | null;\n endTime: string | null;\n durationMs: number | null;\n };\n}\n\nexport interface PartialAckResult<Id = string | number> {\n succeeded?: Id[];\n failed?: { id: Id; error?: string; }[];\n}\n\ntype MessageAck = () => Promise<void>;\ntype MessageNack = (err?: Error, requeue?: boolean) => Promise<void>;\ntype MessagePartialAck = (result: PartialAckResult) => Promise<void>;\n\nexport type BulkPerIdHandler<T = any> = (args: {\n jobId?: string; // optional, in case needed for logging\n ids: Id[]; // array of IDs - can be single ID [id] or multiple [id1, id2, ...]\n /* id will be available to use only if consumerBatchSize is 1 */\n id?: Id; // single ID being processed\n payload: T;\n}, ack: MessageAck, nack: MessageNack, partialAck: MessagePartialAck) => Promise<void>;\nexport interface AdditionalIdsHookData<T = any> {\n rawPayload: {\n query: Record<string, any>;\n include?: any[];\n searchTerm: string;\n };\n payload: T;\n}\n\nexport interface BulkRouteOptions<T = any> {\n /** e.g. \"change-state\" - the action identifier sent in request body */\n action: string;\n /** Sequelize model from which to scan IDs */\n model: Model<any, any> | ModelStatic<any> | any;\n modelScopes: string[]; // optional, Sequelize scopes to apply when scanning\n /** Consumer that processes IDs (can handle single ID [id] or multiple [id1, id2, ...]) */\n consumer: BulkPerIdHandler<T>; // required, worker function to process IDs\n /** Consumer batch size - how many IDs to send to consumer at once (default 1) */\n consumerBatchSize?: number; // optional, default 1 (single ID per call)\n consumerOptions?: ConsumeOptions; // optional, RabbitMQ consumer options\n rabbitQueueName?: string; // optional, if using RabbitMQ, the name of the queue to publish to and consume from\n\n payloadSchema?: z.ZodType<T>; // optional Zod schema to validate and type payload structure\n /** Identity Scopes to be provided and are used and validated from the query.query object */\n identityScopes?: string[]; // identity scope fields to validate in payload\n\n // Optional per-route overrides:\n idField?: string; // default: inherited from bulker.defaults.idField\n pageSize?: number; // default: inherited from bulker.defaults.pageSize\n workerConcurrency?: number; // default: inherited\n jobAttempts?: number; // default: inherited\n jobBackoffMs?: number; // default: inherited\n removeOnComplete?: boolean | number; // default: inherited\n removeOnFail?: boolean | number; // default: inherited\n\n /**\n * Hook to provide additional IDs that will be OR-ed with the main query.\n * This is useful when you need to manually fetch additional IDs based on the raw request body.\n * The returned IDs will be included in the where clause using an OR statement.\n *\n * @example\n * additionalIdsHook: async (data) => {\n * const { rawPayload, payload } = reqBody;\n * if (!rawPayload.searchTerm) return [];\n * const filters = rawPayload?.query?.fleetId ? { fleetId: query.query.fleetId } : {};\n * const labelIds = await searchDriversByLabelsValue(rawPayload.searchTerm, filters);\n * const vendorIds = await searchDriversByVendorName(rawPayload.searchTerm, filters);\n * return [...labelIds, ...vendorIds];\n * }\n */\n additionalIdsHook?: (data: AdditionalIdsHookData) => Promise<Id[]>;\n}\n\n// ============================================================================\n// Bulker Initialization Types\n// ============================================================================\n\nexport interface BulkerInit {\n sequelize: any; // Sequelize\n logger: LoggerInstanceManager;\n rabbit: RabbitMq;\n redis: any; // RedisClientType or config object\n getUserId?: () => string | null; // function that returns the user ID for the current request context, or null if not available\n emitEvents?: boolean; // opt-in to enable event emission (default: false)\n defaults?: {\n pageSize?: number; // default 1000\n maxJobsPerUser?: number; // default unlimited (no per-user rate limiting)\n idField?: string; // default \"id\"\n consumerBatchSize?: number; // default 1 (batch size for consumer)\n workerConcurrency?: number; // default 16\n jobTtlSeconds?: number; // default 7 days (for Redis status hash expiry)\n errorLogLimit?: number; // default 10 (max number of error logs to keep per job)\n };\n}\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\nexport interface BulkRouterEvents {\n // Job lifecycle events\n 'job:created': (data: { jobId: string; action: string; total: number; }) => void;\n 'job:started': (data: { jobId: string; action: string; }) => void;\n 'job:queued': (data: { jobId: string; action: string; queued: number; total: number; }) => void;\n 'job:completed': (data: { jobId: string; action: string; processed: number; failed: number; duration: number; }) => void;\n 'job:canceled': (data: { jobId: string; action: string; }) => void;\n 'job:failed': (data: { jobId: string; action: string; error: string; }) => void;\n\n // Item processing events\n 'item:processing': (data: { jobId: string; action: string; id: string | number; }) => void;\n 'item:processed': (data: { jobId: string; action: string; id: string | number; }) => void;\n 'item:failed': (data: { jobId: string; action: string; id: string | number; error: string; }) => void;\n 'item:retrying': (data: { jobId: string; action: string; id: string | number; }) => void;\n\n // Worker events\n 'worker:started': (data: { action: string; queueName: string; }) => void;\n 'worker:error': (data: { action: string; error: string; }) => void;\n\n // Scan events\n 'scan:page': (data: { jobId: string; action: string; pageNumber: number; itemsInPage: number; }) => void;\n}\n\n/**\n * Interface for event emitters with the emitEvent helper method\n */\nexport interface IBulkEventEmitter {\n emitEvent<K extends keyof BulkRouterEvents>(\n event: K,\n ...args: Parameters<BulkRouterEvents[K]>\n ): void;\n}\n\n// ============================================================================\n// Identity Scope Types\n// ============================================================================\n\nexport const DEFAULT_IDENTITY_SCOPES: readonly string[] = [\n 'businessModelId',\n 'fleetId',\n 'demandSourceId',\n 'contextId',\n 'userId',\n 'businessAccountId',\n 'activeBusinessModelId',\n];\n\nexport type DefaultIdentityScope = typeof DEFAULT_IDENTITY_SCOPES[number];\n","import type { Handler } from 'express';\nimport { randomUUID } from 'node:crypto';\nimport type { Model, ModelStatic, WhereOptions } from 'sequelize';\nimport { Op } from 'sequelize';\nimport type RabbitMq from '@autofleet/rabbit';\nimport { z } from 'zod';\nimport type { Bulker } from '.';\nimport { BulkerError } from './Errors';\nimport { queryValidation, queryFormat } from '../../middleware';\nimport { pick } from '../../utils';\nimport { buildIdentityScopeSchema } from './utils/identityScope';\nimport type {\n BulkPerIdHandler,\n BulkRouteOptions,\n Id,\n JobStatus,\n ConsumeOptions,\n PartialAckResult,\n} from './types';\nimport { DEFAULT_IDENTITY_SCOPES } from './types';\n\ntype Asyncify<T extends (...a: any[]) => any> = (...a: Parameters<T>) => Promise<Awaited<ReturnType<T>>>;\n\nexport class BulkRoute<T = any> {\n public readonly action: string;\n\n // TODO: understand why theres an issue with the model type\n private readonly model: ModelStatic<any> | Model<any, any> | any;\n\n private readonly consumer: BulkPerIdHandler<T>;\n\n private readonly idField: string;\n\n private readonly pageSize: number;\n\n private readonly consumerBatchSize: number;\n\n private readonly rabbitQueueName: string | null = null;\n\n private readonly rabbit: RabbitMq | null = null;\n\n private readonly consumerOptions: ConsumeOptions;\n\n private readonly payloadSchema?: z.ZodType<T>;\n\n private readonly identityScopeSchema: z.ZodType<any>;\n\n private readonly modelScopes: string[];\n\n constructor(\n private readonly bulker: Bulker,\n private readonly opts: BulkRouteOptions<T>,\n ) {\n this.action = opts.action;\n this.model = opts.model;\n this.modelScopes = opts.modelScopes ?? [];\n this.consumer = opts.consumer;\n this.rabbit = bulker.rabbit;\n this.rabbitQueueName = opts.rabbitQueueName ?? `bulk-${this.action}-queue`;\n this.consumerOptions = {\n enableRabbitTrace: true, // always enable user tracing to be able to do getUser()\n ...this.opts.consumerOptions,\n };\n this.payloadSchema = opts.payloadSchema;\n\n if (!/^[a-zA-Z0-9-_]+$/.test(this.action)) throw new Error('BulkRoute action must be alphanumeric');\n if (!this.model || typeof this.model.findAll !== 'function' || typeof this.model.count !== 'function') throw new Error('BulkRoute model must be a valid Sequelize model');\n if (typeof this.consumer !== 'function') throw new Error('BulkRoute consumer must be a function');\n if (this.payloadSchema && !(this.payloadSchema instanceof z.ZodType)) throw new Error('BulkRoute payloadSchema must be a Zod schema');\n if (this.modelScopes && !Array.isArray(this.modelScopes)) throw new Error('BulkRoute scopes must be an array of strings');\n if (this.opts.additionalIdsHook && typeof this.opts.additionalIdsHook !== 'function') throw new Error('BulkRoute additionalIdsHook must be a function');\n\n // resolve defaults\n this.idField = opts.idField ?? bulker.defaults.idField;\n this.pageSize = opts.pageSize ?? bulker.defaults.pageSize;\n this.consumerBatchSize = opts.consumerBatchSize ?? bulker.defaults.consumerBatchSize;\n\n const modelAttributes = this.model.rawAttributes || {};\n\n const identityScopes = opts.identityScopes && opts.identityScopes.length > 0 ? opts.identityScopes : DEFAULT_IDENTITY_SCOPES;\n const filteredIdentityScopes = identityScopes.filter(s => !!modelAttributes[s]);\n\n if (filteredIdentityScopes.length === 0) {\n this.bulker.logger.warn(`BulkRoute for action ${this.action} has no valid identityScopes configured - all records will be accessible`);\n }\n\n this.bulker.logger.info(`BulkRoute for action ${this.action} using idField ${this.idField}, pageSize ${this.pageSize},\n consumerBatchSize ${this.consumerBatchSize}, identityScopes: ${filteredIdentityScopes.join(', ')}`);\n\n this.identityScopeSchema = buildIdentityScopeSchema(filteredIdentityScopes);\n\n // Start worker immediately\n this.startRabbitWorker().catch((err) => {\n this.bulker.logger.error(`Failed to start RabbitMQ worker for queue ${this.rabbitQueueName}: ${err.message || err}`);\n });\n }\n\n public bulkHandler: Asyncify<Handler> = async (req, res, next): Promise<any> => {\n try {\n const { query, payload, preview } = req.body ?? {};\n let validatedPayload: T | undefined = payload;\n\n if (payload && this.payloadSchema) {\n try {\n validatedPayload = this.payloadSchema.parse(payload);\n } catch (err) {\n if (err instanceof z.ZodError) {\n return res.status(400).json({\n error: 'invalid_payload',\n details: err.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', '),\n });\n }\n throw err;\n }\n }\n\n try {\n queryValidation(this.model, query, { logger: this.bulker.logger as any });\n } catch (err) {\n return res.status(400).json({ error: 'invalid_query', details: (err as Error).message || err });\n }\n\n const identityScopeResult = this.identityScopeSchema.safeParse(query?.query || {});\n if (!identityScopeResult.success) {\n return res.status(400).json({\n error: 'invalid_identity_scope',\n details: identityScopeResult.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join(', '),\n });\n }\n\n queryFormat(this.model, query, { includeRawPayload: true });\n\n const queryValues = Object.assign(\n pick(query, ['query', 'externalQueryValues', 'order', 'include', 'scopes', 'enrichments'] as const),\n { distinct: true },\n );\n\n const {\n query: where,\n ...rest\n } = queryValues;\n\n let additionalIds: Id[] = [];\n if (this.opts.additionalIdsHook) {\n const { rawPayload } = query;\n try {\n additionalIds = await this.opts.additionalIdsHook({ rawPayload, payload: validatedPayload! });\n this.bulker.logger.info(`additionalIdsHook returned ${additionalIds.length} IDs for action ${this.action}`);\n } catch (err) {\n this.bulker.logger.error(`Error in additionalIdsHook for action ${this.action}: ${(err as Error).message || err}`, { err });\n return res.status(500).json({ error: 'additional_ids_hook_error', details: (err as Error).message || err });\n }\n }\n\n const orClauses: WhereOptions[] = [];\n\n if (where && typeof where === 'object' && Object.keys(where).length > 0) {\n orClauses.push(where as WhereOptions);\n }\n\n if (Array.isArray(additionalIds) && additionalIds.length > 0) {\n orClauses.push({ id: { $in: additionalIds } });\n }\n\n let finalWhere: WhereOptions;\n if (orClauses.length === 0) {\n return res.status(400).json({ error: 'no_query', details: 'No valid query provided to select records' });\n } else if (orClauses.length === 1) {\n const [onlyWhere] = orClauses;\n finalWhere = onlyWhere;\n } else {\n finalWhere = { $or: orClauses };\n }\n this.bulker.logger.info(`Constructed final where clause for action ${this.action}`, { where: finalWhere });\n\n const findData = {\n where: finalWhere,\n ...rest,\n };\n\n // Preview count\n const total = await this.model.scope(this.modelScopes).count({\n ...findData,\n col: this.idField,\n });\n if (preview) {\n return res.json({ estimatedCount: total });\n }\n\n // Create job status in Redis\n const jobId = randomUUID();\n await this.bulker.jobManager.initJob(jobId, {\n status: 'queued',\n total,\n action: this.action,\n });\n\n // Emit job:created event\n this.bulker.emitEvent('job:created', { jobId, action: this.action, total });\n\n // Kick off scanning + enqueuing (async)\n setImmediate(() => {\n this.rabbitScanAndEnqueue(jobId, findData, validatedPayload).catch(err => this.bulker.logger.error(err));\n });\n\n return res.status(202).json({ jobId, estimatedCount: total });\n } catch (e) {\n this.bulker.logger.error(`Error in bulkHandler for action ${this.action}: ${(e as Error).message || e}`, { err: e });\n return next(e);\n }\n };\n\n /** Get user jobs - public method for BulkRouter to use */\n public async getUserJobs(userId: string, limit = 20): Promise<(JobStatus | null)[]> {\n return this.bulker.jobManager.getUserJobs(userId, limit);\n }\n\n /** Helper method to batch IDs into chunks of consumerBatchSize */\n private batchIds(ids: Id[]): Id[][] {\n const batches: Id[][] = [];\n for (let i = 0; i < ids.length; i += this.consumerBatchSize) {\n batches.push(ids.slice(i, i + this.consumerBatchSize));\n }\n return batches;\n }\n\n private async rabbitScanAndEnqueue(jobId: string, findData: any, payload: T | undefined): Promise<void> {\n if (!this.rabbit) throw new Error('RabbitMQ not configured in Bulker');\n const startTime = Date.now().toString();\n if (this.bulker.getUserId) {\n const userId = this.bulker.getUserId();\n if (userId) await this.bulker.jobManager.addUserJob(userId, jobId);\n }\n\n // Batch the job start status and time update\n await this.bulker.jobManager.setJobFields(jobId, {\n status: 'running',\n startTime,\n });\n\n // Emit job:started event\n this.bulker.emitEvent('job:started', { jobId, action: this.action });\n\n let lastId: Id | null = null;\n let queued = 0;\n let pageNumber = 0;\n\n while (true) {\n // check for cancel before fetching the next page\n const st = await this.bulker.jobManager.getJobField(jobId, 'status');\n if (!st || st === 'canceled') {\n this.bulker.logger.info(`Job ${jobId} was canceled, stopping scan and enqueue`);\n break;\n }\n\n const pageWhere = lastId\n ? { [Op.and]: [findData.where, { [this.idField]: { [Op.gt]: lastId } }] }\n : findData.where;\n // add getRows function to overwrite\n const rows: Record<string, any>[] = await this.model.scope(this.modelScopes).findAll({\n where: pageWhere,\n ...pick(findData, ['include', 'order', 'scopes'] as const),\n attributes: [this.idField],\n order: [[this.idField, 'ASC']],\n limit: this.pageSize,\n raw: true,\n subQuery: false,\n });\n if (rows.length === 0) break;\n\n // Batch IDs and enqueue\n let id: Id | undefined;\n const allIds = rows.map(r => r[this.idField]);\n if (this.consumerBatchSize === 1 && allIds.length === 1) {\n id = allIds[0];\n }\n const batches = this.batchIds(allIds);\n const rabbitJobs = batches.map(ids => ({\n jobId, ids, payload, id,\n }));\n this.bulker.logger.info(`Enqueuing ${rabbitJobs.length} batched messages (${allIds.length} total IDs) to RabbitMQ queue ${this.rabbitQueueName}`);\n const publishToRabbitRes = await Promise.allSettled(rabbitJobs.map(j => this.rabbit!.sendToQueue(this.rabbitQueueName!, j)));\n const rejected = publishToRabbitRes.filter(r => r.status === 'rejected');\n if (rejected.length > 0) {\n this.bulker.logger.error(`Failed to enqueue ${rejected.length} messages to RabbitMQ`, { rejected });\n throw new Error(`Failed to enqueue ${rejected.length} messages to RabbitMQ`);\n }\n\n queued += rows.length;\n await this.bulker.jobManager.incrJobField(jobId, 'queued', rows.length);\n lastId = rows[rows.length - 1][this.idField];\n\n // Emit scan:page event\n pageNumber += 1;\n this.bulker.emitEvent('scan:page', {\n jobId,\n action: this.action,\n pageNumber,\n itemsInPage: rows.length,\n });\n\n // Emit job:queued event with progress\n const job = await this.bulker.jobManager.getJob(jobId);\n if (job) {\n this.bulker.emitEvent('job:queued', {\n jobId,\n action: this.action,\n queued,\n total: job.total,\n });\n }\n }\n\n // If no items were queued, mark job as completed immediately\n if (queued === 0) {\n await this.bulker.jobManager.completeEmptyJob(jobId);\n }\n }\n\n public async startRabbitWorker(): Promise<void> {\n this.bulker.logger.info(`Starting RabbitMQ consumer for queue ${this.rabbitQueueName}`);\n\n // Emit worker:started event\n this.bulker.emitEvent('worker:started', {\n action: this.action,\n queueName: this.rabbitQueueName!,\n });\n\n return this.rabbit?.consume(this.rabbitQueueName!, async (msg, ack, nack) => {\n if (!msg) return;\n const { jobId, ids, payload } = msg.content as { jobId: string; ids: Id[]; payload: T; };\n let messageAcked = false;\n const wrappedAck = async () => {\n if (!messageAcked) {\n messageAcked = true;\n await Promise.all([\n this.bulker.jobManager.ack(jobId, ids.length),\n ack(),\n ]);\n // Emit item:processed event for each ID (keep existing behavior)\n for (const id of ids) {\n this.bulker.emitEvent('item:processed', { jobId, action: this.action, id });\n }\n }\n };\n\n const wrappedNack = async (err?: Error, requeue?: boolean) => {\n if (messageAcked) return;\n messageAcked = true;\n\n let requeueFinal = requeue;\n if (requeueFinal === undefined) {\n if (BulkerError.isBulkerError(err)) {\n requeueFinal = err.retryable;\n } else {\n requeueFinal = false; // non-BulkerErrors are considered non-retryable by default\n }\n }\n\n await Promise.all([\n this.bulker.jobManager.nack(jobId, err ? (err.message || String(err)) : 'nacked', { ids }, ids.length),\n nack(null, { skipRetry: !requeueFinal }),\n ]);\n\n // Emit item events for each ID (keep existing behavior)\n for (const id of ids) {\n if (requeueFinal) {\n this.bulker.emitEvent('item:retrying', {\n jobId,\n action: this.action,\n id,\n });\n } else {\n this.bulker.emitEvent('item:failed', {\n jobId,\n action: this.action,\n id,\n error: err ? (err.message || String(err)) : 'nacked',\n });\n }\n }\n };\n\n const wrappedPartialAck = async (result: PartialAckResult) => {\n if (messageAcked) return;\n messageAcked = true;\n\n const succeededIds = result.succeeded ?? [];\n const failedResults = result.failed ?? [];\n\n await Promise.all([\n this.bulker.jobManager.partialAck(jobId, succeededIds.length, failedResults),\n ack(), // Always ack the RabbitMQ message since we've fully processed the batch\n ]);\n\n // Emit item:processed event for succeeded IDs\n for (const id of succeededIds) {\n this.bulker.emitEvent('item:processed', { jobId, action: this.action, id });\n }\n\n // Emit item:failed event for failed IDs\n for (const failedItem of failedResults) {\n this.bulker.emitEvent('item:failed', {\n jobId,\n action: this.action,\n id: failedItem.id,\n error: failedItem.error || 'Item failed',\n });\n }\n };\n\n // If canceled, count as processed but do nothing\n const st = await this.bulker.jobManager.getJobField(jobId, 'status');\n if (!st || st === 'canceled') {\n await this.bulker.jobManager.incrJobField(jobId, 'processed', ids.length);\n return;\n }\n\n try {\n this.bulker.logger.info(`Started processing job ${jobId} action ${this.action} ids amount: ${ids.length}`);\n\n // Emit item:processing event for each ID\n for (const id of ids) {\n this.bulker.emitEvent('item:processing', { jobId, action: this.action, id });\n }\n\n await this.consumer(\n {\n jobId,\n ids,\n id: this.consumerBatchSize === 1 ? ids[0] : undefined,\n payload,\n },\n wrappedAck,\n wrappedNack,\n wrappedPartialAck,\n );\n // If consumer did not ack/nack, we will ack here\n await wrappedAck();\n } catch (err: any) {\n this.bulker.logger.error(`Error processing job ${jobId} action ${this.action}: ${err.message || err}`, { err });\n\n // Emit worker:error event\n this.bulker.emitEvent('worker:error', {\n action: this.action,\n error: err.message || String(err),\n });\n\n // If consumer did not ack/nack, we will nack here\n await wrappedNack(err, false);\n }\n }, this.consumerOptions);\n }\n}\n","import { Router } from 'express';\nimport type { Bulker } from '.';\nimport { BulkRoute } from './BulkRoute';\nimport type { BulkRouteOptions } from './types';\n\nexport default class BulkRouter {\n private readonly bulker: Bulker;\n\n private readonly router: Router;\n\n private routes: BulkRoute<any>[] = [];\n\n private actionsToRouteMap = new Map<string, BulkRoute<any>>();\n\n private staticRoute: string;\n\n constructor(bulker: Bulker, router?: Router, staticRoute = '/bulk-actions') {\n this.bulker = bulker;\n this.router = router ?? Router();\n this.staticRoute = staticRoute;\n this.registerStaticRoutes();\n }\n\n private registerStaticRoutes() {\n // POST /bulk-actions - action-based routing\n this.router.post(this.staticRoute, this.bulkHandler);\n\n // GET /jobs/:id - get job status\n this.router.get('/jobs/:id', this.getJobHandler);\n\n // GET /jobs - get user's recent jobs\n this.router.get('/jobs', this.getMyJobsHandler);\n\n // POST /jobs/:id/cancel - cancel a job\n this.router.post('/jobs/:id/cancel', this.cancelJobHandler);\n }\n\n addAction<T = any>(actionName: string, opts: Omit<BulkRouteOptions<T>, 'action'>): BulkRoute<T> {\n const route = new BulkRoute<T>(this.bulker, {\n action: actionName,\n ...opts,\n });\n this.bulker.logger.info(`Registering action handler: ${actionName}`);\n\n // Map action to route for action-based routing\n if (this.actionsToRouteMap.has(actionName)) {\n throw new Error(`Action \"${actionName}\" is already registered`);\n }\n this.actionsToRouteMap.set(actionName, route);\n this.routes.push(route);\n return route;\n }\n\n private bulkHandler = async (req: any, res: any, next: any) => {\n try {\n const { action } = req.body ?? {};\n\n if (!action) {\n return res.status(400).json({\n error: 'missing_action',\n message: 'Request body must include an \"action\" field',\n });\n }\n\n const route = this.actionsToRouteMap.get(action);\n if (!route) {\n return res.status(404).json({\n error: 'unknown_action',\n message: `Action \"${action}\" is not registered`,\n availableActions: Array.from(this.actionsToRouteMap.keys()),\n });\n }\n\n // Delegate to the specific route handler\n return route.bulkHandler(req, res, next);\n } catch (e) {\n this.bulker.logger.error(`Error in BulkRouter bulkHandler: ${(e as Error).message || e}`, { err: e });\n next(e);\n return null;\n }\n };\n\n private getJobHandler = async (req: any, res: any, next: any) => {\n try {\n if (!req.params.id) return res.status(400).json({ error: 'missing_id' });\n\n const status = await this.bulker.jobManager.getJob(req.params.id);\n if (!status) {\n return res.status(404).json({ error: 'not_found' });\n }\n\n return res.json(status);\n } catch (e) {\n return next(e);\n }\n };\n\n private cancelJobHandler = async (req: any, res: any, next: any) => {\n try {\n if (!req.params.id) return res.status(400).json({ error: 'missing_id' });\n\n const ok = await this.bulker.jobManager.cancelJob(req.params.id);\n if (!ok) {\n return res.status(404).json({ error: 'not_found' });\n }\n\n this.bulker.logger.info(`Job ${req.params.id} cancel requested`);\n return res.json({ ok: true });\n } catch (e) {\n return next(e);\n }\n };\n\n private getMyJobsHandler = async (req: any, res: any, next: any) => {\n try {\n if (!this.bulker.getUserId) {\n return res.status(400).json({\n error: 'user_id_function_not_configured',\n message: 'Bulker instance does not have a getUserId function configured',\n });\n }\n\n const userId = this.bulker.getUserId();\n\n if (!userId) {\n return res.json([]);\n }\n\n const jobs = await this.bulker.jobManager.getUserJobs(userId);\n const filteredJobs = jobs.filter(j => j !== null);\n\n // Sort by creation date (most recent first)\n filteredJobs.sort((a, b) => {\n const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return dateB - dateA;\n });\n\n return res.json(filteredJobs);\n } catch (e) {\n return next(e);\n }\n };\n\n /** Get the configured router instance */\n getRouter(): Router {\n return this.router;\n }\n}\n","// Unified Lua script for atomically updating job status in Redis\n// Handles ack (all succeeded), nack (all failed), and partial ack (mixed results)\n// Takes the job key as KEYS[1]\n// Arguments: succeededCount, failedCount, failedResults (JSON), errorLogLimit, updatedAt\n// Returns the updated processed count\nexport const localPartialAckScript = `\nlocal jobKey = KEYS[1]\nlocal succeededCount = tonumber(ARGV[1]) or 0\nlocal failedCount = tonumber(ARGV[2]) or 0\nlocal failedResults = ARGV[3]\nlocal errorLogLimit = tonumber(ARGV[4])\nlocal updatedAt = ARGV[5]\n\nlocal total = tonumber(redis.call('HGET', jobKey, 'total'))\nif not total then\n return 0\nend\n\n-- Update counters\nlocal totalCount = succeededCount + failedCount\nredis.call('HINCRBY', jobKey, 'succeeded', succeededCount)\nredis.call('HINCRBY', jobKey, 'failed', failedCount)\nlocal processed = redis.call('HINCRBY', jobKey, 'processed', totalCount)\nredis.call('HSET', jobKey, 'updatedAt', updatedAt)\n\n-- Process failed results and add errors\nif failedCount > 0 then\n local errorsJson = redis.call('HGET', jobKey, 'errors') or '[]'\n local errors = cjson.decode(errorsJson)\n local failedData = cjson.decode(failedResults)\n\n for i = 1, #failedData do\n if #errors >= errorLogLimit then\n -- Keep only the last (errorLogLimit - 1) entries\n local newErrors = {}\n for j = #errors - errorLogLimit + 2, #errors do\n table.insert(newErrors, errors[j])\n end\n errors = newErrors\n end\n\n local failedItem = failedData[i]\n local errorData\n -- If id is a table (object), store it as-is (for backwards compat with nack)\n -- Otherwise, wrap simple values in { id: value } structure (for partialAck)\n if type(failedItem.id) == \"table\" then\n errorData = failedItem.id\n else\n errorData = { id = failedItem.id }\n end\n\n local errorEntry = {\n message = failedItem.error or 'Item failed',\n data = errorData\n }\n table.insert(errors, errorEntry)\n end\n\n redis.call('HSET', jobKey, 'errors', cjson.encode(errors))\nend\n\n-- Check if job is complete\nif processed >= total then\n redis.call('HSET', jobKey, 'status', 'completed')\n redis.call('HSET', jobKey, 'endTime', updatedAt)\nend\n\nreturn processed\n`;\n","import type { RedisClientType } from 'redis';\nimport { localPartialAckScript } from './redis-scripts';\nimport type { EmitsBulkEvents } from './events';\nimport type { Id, JobMetadata, JobStatus } from './types';\n\n/**\n * Manages all job-related operations in Redis\n * Centralizes job creation, status updates, cancellation, and user job tracking\n */\nexport class JobManager {\n private eventEmitter?: EmitsBulkEvents;\n\n private readonly redis: RedisClientType;\n\n private readonly defaults: {\n maxJobsPerUser: number;\n jobTtlSeconds: number;\n errorLogLimit: number;\n };\n\n constructor(\n redis: RedisClientType,\n defaults?: {\n maxJobsPerUser?: number;\n jobTtlSeconds?: number;\n errorLogLimit?: number;\n },\n ) {\n this.redis = redis;\n this.defaults = {\n maxJobsPerUser: defaults?.maxJobsPerUser ?? -1, // -1 means unlimited\n jobTtlSeconds: defaults?.jobTtlSeconds ?? 7 * 24 * 3600, // default 7 days\n errorLogLimit: defaults?.errorLogLimit ?? 10, // default 10 errors\n };\n }\n\n /** Set event emitter for emitting job events */\n setEventEmitter(emitter: EmitsBulkEvents): void {\n this.eventEmitter = emitter;\n }\n\n /** Generate Redis key for job */\n static jobKey(jobId: string): string {\n return `bulker:job:${jobId}`;\n }\n\n /** Generate Redis key for user jobs list */\n static userJobsKey(userId: string): string {\n return `bulker:user:${userId}:jobs`;\n }\n\n /**\n * Initialize a new job in Redis\n */\n async initJob(jobId: string, meta: JobMetadata): Promise<void> {\n const now = Date.now().toString();\n const jobKey = JobManager.jobKey(jobId);\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, {\n status: meta.status,\n total: String(meta.total),\n queued: '0',\n processed: '0',\n succeeded: '0',\n failed: '0',\n action: meta.action,\n errors: JSON.stringify([]),\n createdAt: now,\n updatedAt: now,\n startTime: now,\n endTime: '',\n });\n multi.expire(jobKey, this.defaults.jobTtlSeconds);\n\n await multi.exec();\n }\n\n /**\n * Get job status from Redis\n */\n async getJob(jobId: string): Promise<JobStatus | null> {\n const jobKey = JobManager.jobKey(jobId);\n const obj = await this.redis.hGetAll(jobKey);\n\n if (!obj || Object.keys(obj).length === 0) {\n return null;\n }\n\n // Calculate duration\n const startTime = obj.startTime ? Number(obj.startTime) : null;\n const endTime = obj.endTime && obj.endTime !== '' ? Number(obj.endTime) : null;\n const currentTime = Date.now();\n\n const duration = {\n startTime: startTime ? new Date(startTime).toISOString() : null,\n endTime: endTime ? new Date(endTime).toISOString() : null,\n durationMs: startTime ? (endTime || currentTime) - startTime : null,\n };\n\n return {\n jobId,\n status: obj.status,\n action: obj.action,\n total: Number(obj.total ?? 0),\n queued: Number(obj.queued ?? 0),\n processed: Number(obj.processed ?? 0),\n succeeded: Number(obj.succeeded ?? 0),\n failed: Number(obj.failed ?? 0),\n errors: obj.errors ? JSON.parse(obj.errors) : [],\n createdAt: obj.createdAt ? new Date(Number(obj.createdAt)).toISOString() : undefined,\n updatedAt: obj.updatedAt ? new Date(Number(obj.updatedAt)).toISOString() : undefined,\n duration,\n };\n }\n\n /**\n * Set a single field on a job\n */\n async setJobField(jobId: string, field: string, value: string): Promise<boolean> {\n const jobKey = JobManager.jobKey(jobId);\n const exists = await this.redis.exists(jobKey);\n if (!exists) return false;\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, field, value);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n await multi.exec();\n return true;\n }\n\n /**\n * Set multiple fields on a job\n */\n async setJobFields(jobId: string, fields: Record<string, string>): Promise<boolean> {\n const jobKey = JobManager.jobKey(jobId);\n const exists = await this.redis.exists(jobKey);\n if (!exists) return false;\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, fields);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n await multi.exec();\n return true;\n }\n\n /**\n * Increment a counter field on a job\n */\n async incrJobField(jobId: string, field: 'queued' | 'processed' | 'succeeded' | 'failed', by = 1): Promise<number> {\n const jobKey = JobManager.jobKey(jobId);\n\n const multi = this.redis.multi();\n multi.hIncrBy(jobKey, field, by);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n const results = await multi.exec() as [number | null, number][];\n if (results?.[0]?.[0] === null) {\n return results[0][1];\n }\n return 0;\n }\n\n /**\n * Get a single field from a job\n */\n async getJobField(jobId: string, field: string): Promise<string | undefined> {\n const jobKey = JobManager.jobKey(jobId);\n return this.redis.hGet(jobKey, field);\n }\n\n /**\n * Cancel a job\n */\n async cancelJob(jobId: string): Promise<boolean> {\n return this.setJobField(jobId, 'status', 'canceled');\n }\n\n /**\n * Mark job as completed (for jobs with no items to process)\n */\n async completeEmptyJob(jobId: string): Promise<void> {\n const endTime = Date.now().toString();\n await this.setJobFields(jobId, {\n status: 'completed',\n endTime,\n });\n }\n\n /**\n * Handle successful message processing (ack)\n * Internally calls partialAck with all items succeeded\n */\n async ack(jobId: string, count = 1): Promise<void> {\n // All succeeded, no failures\n await this.partialAck(jobId, count, []);\n }\n\n /**\n * Handle failed message processing (nack)\n * Internally calls partialAck with all items failed\n */\n async nack(jobId: string, errorMsg: string, data: { ids: Id[]; }, count = 1): Promise<void> {\n // All failed, no successes\n // Create failed results array with one entry per failed item\n const failedResults: { id: any; error?: string; }[] = [];\n\n const getId = (index: number): any => {\n if (data.ids && Array.isArray(data.ids)) {\n return data.ids[index] ?? `unknown-${index}`;\n }\n return `unknown-${index}`;\n };\n\n for (let i = 0; i < count; i++) {\n failedResults.push({\n id: getId(i),\n error: errorMsg,\n });\n }\n\n await this.partialAck(jobId, 0, failedResults);\n }\n\n /**\n * Handle partial success/failure for a batch of items\n * Uses Redis Lua script for atomic operation\n */\n async partialAck(\n jobId: string,\n succeededCount: number,\n failedResults: { id: any; error?: string; }[],\n ): Promise<void> {\n const jobKey = JobManager.jobKey(jobId);\n await this.redis.eval(localPartialAckScript, {\n keys: [jobKey],\n arguments: [\n String(succeededCount),\n String(failedResults.length),\n JSON.stringify(failedResults),\n String(this.defaults.errorLogLimit),\n Date.now().toString(),\n ],\n });\n\n // Check if job completed and emit event\n const job = await this.getJob(jobId);\n if (job?.status === 'completed') {\n const duration = job.duration.durationMs || 0;\n this.eventEmitter?.emitEvent('job:completed', {\n jobId,\n action: job.action,\n processed: job.processed,\n failed: job.failed,\n duration,\n });\n }\n }\n\n /**\n * Add a job to user's job list\n */\n async addUserJob(userId: string, jobId: string): Promise<void> {\n const userJobsKey = JobManager.userJobsKey(userId);\n const USER_JOB_TTL_SECONDS = 3600; // 1 hour\n\n const multi = this.redis.multi();\n multi.lPush(userJobsKey, jobId);\n if (this.defaults.maxJobsPerUser > 0) {\n multi.lTrim(userJobsKey, 0, this.defaults.maxJobsPerUser - 1);\n }\n multi.expire(userJobsKey, USER_JOB_TTL_SECONDS);\n\n await multi.exec();\n }\n\n /**\n * Remove a job from user's job list\n */\n async removeUserJob(userId: string, jobId: string): Promise<void> {\n const userJobsKey = JobManager.userJobsKey(userId);\n await this.redis.lRem(userJobsKey, 0, jobId);\n }\n\n /**\n * Get all jobs for a user\n */\n async getUserJobs(userId: string, limit = 20): Promise<(JobStatus | null)[]> {\n const userJobsKey = JobManager.userJobsKey(userId);\n const jobIds = await this.redis.lRange(userJobsKey, 0, limit - 1);\n\n const jobs = await Promise.all(\n jobIds.map(jobId => this.getJob(jobId)),\n );\n\n return jobs;\n }\n}\n","import type { Sequelize } from 'sequelize';\nimport { createClient, type RedisClientType } from 'redis';\nimport type { Router } from 'express';\nimport type rabbit from '@autofleet/rabbit';\nimport BulkRouter from './BulkRouter';\nimport { JobManager } from './JobManager';\nimport type { BulkEventEmitter, BulkRouterEvents, EmitsBulkEvents } from './events';\nimport { EventEmitter } from 'node:events';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\n\nconst NOOP_EMITTER: EmitsBulkEvents = {\n emitEvent: () => {\n // no-op\n },\n};\n\nexport interface BulkerInit {\n sequelize: Sequelize;\n logger: LoggerInstanceManager;\n rabbit: rabbit;\n redis: RedisClientType | ReturnType<typeof createClient> | { host: string; port?: number; password?: string; db?: number; };\n getUserId?: () => string | null; // function that returns the user ID for the current request context, or null if not available\n emitEvents?: boolean; // opt-in to enable event emission (default: false)\n defaults?: {\n pageSize?: number; // default 1000\n maxJobsPerUser?: number; // default unlimited (no per-user rate limiting)\n idField?: string; // default \"id\"\n consumerBatchSize?: number; // default 1 (batch size for consumer)\n workerConcurrency?: number; // default 16\n jobTtlSeconds?: number; // default 7 days (for Redis status hash expiry)\n errorLogLimit?: number; // default 10 (max number of error logs to keep per job)\n };\n}\n\nexport class Bulker extends EventEmitter implements BulkEventEmitter {\n public readonly sequelize: Sequelize;\n\n public readonly logger: BulkerInit['logger'];\n\n public readonly redis: RedisClientType;\n\n public readonly rabbit: rabbit;\n\n public readonly defaults: Required<NonNullable<BulkerInit['defaults']>>;\n\n public readonly jobManager: JobManager;\n\n private readonly bulkRouters: BulkRouter[] = [];\n\n public getUserId: BulkerInit['getUserId'];\n\n public readonly eventsEnabled: boolean;\n\n constructor(init: BulkerInit) {\n super();\n this.sequelize = init.sequelize;\n this.logger = init.logger;\n this.rabbit = init.rabbit;\n this.eventsEnabled = init.emitEvents ?? false;\n if (init.redis && typeof (init.redis as any).connect === 'function') {\n this.redis = init.redis as RedisClientType;\n if (!(this.redis.isOpen)) {\n this.redis.connect().catch((err) => {\n this.logger.error('Error connecting to Redis:', err);\n });\n }\n } else {\n const config = init.redis as { host: string; port?: number; password?: string; db?: number; };\n this.redis = createClient({\n socket: {\n host: config.host,\n port: config.port,\n },\n commandsQueueMaxLength: 5000,\n disableOfflineQueue: false,\n password: config.password,\n database: config.db,\n });\n this.redis.connect().catch((err) => {\n this.logger.error('Error connecting to Redis:', err);\n });\n }\n this.getUserId = init.getUserId ?? (() => null);\n\n this.defaults = {\n maxJobsPerUser: init.defaults?.maxJobsPerUser ?? -1,\n pageSize: init.defaults?.pageSize ?? 1000,\n idField: init.defaults?.idField ?? 'id',\n consumerBatchSize: init.defaults?.consumerBatchSize ?? 1,\n workerConcurrency: init.defaults?.workerConcurrency ?? 16,\n jobTtlSeconds: init.defaults?.jobTtlSeconds ?? 7 * 24 * 3600,\n errorLogLimit: init.defaults?.errorLogLimit ?? 10,\n };\n\n // Initialize JobManager\n this.jobManager = new JobManager(this.redis, {\n maxJobsPerUser: this.defaults.maxJobsPerUser,\n jobTtlSeconds: this.defaults.jobTtlSeconds,\n errorLogLimit: this.defaults.errorLogLimit,\n });\n\n // Set event emitter reference in JobManager\n this.jobManager.setEventEmitter(this.eventsEnabled ? {\n emitEvent: this.emitEvent.bind(this),\n } : NOOP_EMITTER);\n }\n\n pingRedis(): Promise<string> {\n return this.redis.ping();\n }\n\n createBulkRouter(router?: Router, staticRoute?: string): BulkRouter {\n const bulkRouter = new BulkRouter(this, router, staticRoute);\n this.bulkRouters.push(bulkRouter);\n return bulkRouter;\n }\n\n /**\n * Emit event only if events are enabled.\n * Centralizes the eventsEnabled check to avoid repetition.\n */\n emitEvent<K extends keyof BulkRouterEvents>(\n event: K,\n ...args: Parameters<BulkRouterEvents[K]>\n ): void {\n if (this.eventsEnabled) {\n this.emit(event, ...args);\n }\n }\n}\n\nexport { BulkerError } from './Errors';\nexport { BulkRoute } from './BulkRoute';\nexport { JobManager } from './JobManager';\nexport { type BulkEventEmitter } from './events';\nexport { z } from 'zod';\n\nexport {\n type BulkRouteOptions,\n type JobMetadata,\n type JobStatus,\n type BulkRouterEvents,\n type IBulkEventEmitter,\n type AdditionalIdsHookData,\n type BulkPerIdHandler,\n} from './types';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["DefaultOp","sequelize","Op","BadRequest","customFields","order","replacements: Record<string, string>","replacementMap: Record<string, string>","formattedOrders: SequelizeOrder[]","formattedQuery: Record<string, unknown>","externalQueryValues: Record<string, unknown>","Joi","BadRequest","formatPayload","UnexpectedError","z","DEFAULT_IDENTITY_SCOPES: readonly string[]","bulker: Bulker","opts: BulkRouteOptions<T>","validatedPayload: T | undefined","z","additionalIds: Id[]","orClauses: WhereOptions[]","finalWhere: WhereOptions","batches: Id[][]","lastId: Id | null","Op","rows: Record<string, any>[]","id: Id | undefined","err: any","redis","failedResults: { id: any; error?: string; }[]","NOOP_EMITTER: EmitsBulkEvents","EventEmitter"],"sources":["../src/operators/index.ts","../src/utils.ts","../src/formatter/jsonAttributesFormater.ts","../src/formatter/index.ts","../src/validations/index.ts","../src/middleware/index.ts","../src/handler/index.ts","../src/bulker/src/Errors.ts","../src/bulker/src/utils/identityScope.ts","../src/bulker/src/types.ts","../src/bulker/src/BulkRoute.ts","../src/bulker/src/BulkRouter.ts","../src/bulker/src/redis-scripts.ts","../src/bulker/src/JobManager.ts","../src/bulker/src/index.ts"],"sourcesContent":["import { Op as DefaultOp } from 'sequelize';\n\nexport const OPERATORS = [\n 'eq',\n 'ne',\n 'gte',\n 'gt',\n 'lte',\n 'lt',\n 'not',\n 'in',\n 'notIn',\n 'is',\n 'like',\n 'iLike',\n 'notLike',\n 'between',\n 'and',\n 'or',\n 'overlap',\n 'contains',\n] as const;\n\nexport const OPERATOR_PREFIX = '$';\n\nexport const OPERATORS_TO_SQL = {\n $eq: '=',\n $ne: '!=',\n $gte: '>=',\n $gt: '>',\n $lte: '<=',\n $lt: '<',\n $not: 'NOT',\n $in: 'IN',\n $notIn: 'NOT IN',\n $is: 'IS',\n $like: 'LIKE',\n $iLike: 'ILIKE',\n $notLike: 'NOT LIKE',\n $and: 'AND',\n $or: 'OR',\n};\n\nexport const formatOperators = (sequelize: { Op: typeof DefaultOp; } = { Op: DefaultOp }): Record<string, symbol> => {\n const { Op } = sequelize;\n return Object.fromEntries(OPERATORS.map(o => [`${OPERATOR_PREFIX + o}`, Op[o]]));\n};\n","import { randomInt } from 'node:crypto';\nimport { BadRequest } from '@autofleet/errors';\nimport { OPERATOR_PREFIX } from './operators';\n\nexport const ORDER_PREFIX = '-';\nexport const ASSOCIATION_PREFIX = '.';\nexport const ASSOCIATION_PATH_WRAPPER = '$';\nexport const PER_PAGE_DEFAULT = 20;\nexport const PAGE_DEFAULT = 1;\nexport const PER_PAGE_MAX_LIMIT = 100;\nexport const PER_PAGE_MIN_LIMIT = 1;\nexport const PAGE_MIN = 1;\n\nexport const wrapAttributeWithOperator = (attribute: string) => `${OPERATOR_PREFIX}${attribute}${OPERATOR_PREFIX}`;\nexport const isAttributeByAssociation = (attributeName: string, associatedModels: string[]): boolean => attributeName.includes(ASSOCIATION_PREFIX)\n && associatedModels.includes(attributeName.split(ASSOCIATION_PREFIX, 1)[0]);\n\nexport const extractAttributeNameFromOrder = (order: string, associationModels: string[]): string => {\n let formattedOrder = order;\n if (order.includes(ORDER_PREFIX)) {\n [, formattedOrder] = formattedOrder.split(ORDER_PREFIX, 2);\n }\n if (isAttributeByAssociation(formattedOrder, associationModels)) {\n [formattedOrder] = formattedOrder.split(ASSOCIATION_PREFIX, 1);\n }\n return formattedOrder;\n};\n\nexport const isOrderDesc = (order: string): boolean => order.includes(ORDER_PREFIX);\n\nexport const throwBadRequestError = (message: string): never => {\n throw new BadRequest([new Error(message)]);\n};\n\nexport const extractAssociatedAttributeNameFromOrder = (order: string): string => order.split(ASSOCIATION_PREFIX, 2)[1];\n\nexport const generateRandomString = (length = 5): string => {\n const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n return Array.from({ length }, () => characters.charAt(randomInt(characters.length))).join('');\n};\n\nexport const pick = <T extends object, K extends keyof T = keyof T>(\n obj: T,\n keys: K[],\n): Pick<T, K> => Object.fromEntries(keys.map(key => [key, obj[key]])) as Pick<T, K>;\n","import { literal } from 'sequelize';\n\nexport const ComputedActions = {\n length: 'length',\n} as const;\n\ninterface JsonSelectAttribute {\n columnName: string;\n keys: string[];\n alias?: string;\n}\n\ninterface JsonComputedAttribute {\n columnName: string;\n action: keyof typeof ComputedActions;\n alias: string;\n}\n\nexport interface JsonAttributes {\n select?: JsonSelectAttribute[];\n computed?: JsonComputedAttribute[];\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/(?!^)[A-Z]/g, letter => `_${letter.toLowerCase()}`);\n}\n\n/**\n * Builds a computed attribute based on the action specified.\n * Currently, supports 'length' action which calculates the length of a JSON array.\n *\n * @param {JsonComputedAttribute} computedAttribute - The computed attribute definition.\n * @returns {Array} An array containing the SQL literal and the alias for the computed attribute.\n */\nfunction buildComputedAttribute(computedAttribute: JsonComputedAttribute) {\n switch (computedAttribute.action) {\n case ComputedActions.length: {\n // Generates: jsonb_array_length(column)\n // Returns the length of the JSON array (e.g., [\"a\", \"b\"] -> 2)\n // Note: Expects the column to be a JSON array\n const sql = `jsonb_array_length(${toSnakeCase(computedAttribute.columnName)})`;\n return [literal(sql), computedAttribute.alias];\n }\n default:\n computedAttribute.action satisfies never;\n return [];\n }\n}\n\nfunction buildComputedAttributes(computed: JsonComputedAttribute[]) {\n return computed.map(attr => buildComputedAttribute(attr));\n}\n\n/**\n * Builds a list of SQL select attributes using json_build_object\n * to extract specific keys from a JSONB column.\n *\n * Example output:\n * SELECT json_build_object('a', column -> 'a', 'b', column -> 'b') AS alias\n */\nfunction buildSelectAttributes(select: JsonSelectAttribute[]) {\n return select.map((attr) => {\n const columnNameSnake = toSnakeCase(attr.columnName);\n const sql = `json_build_object(${attr.keys\n .map(k => `'${k}', ${columnNameSnake} -> '${k}'`)\n .join(', ')})`;\n const alias = attr.alias || attr.columnName;\n return [literal(sql), alias];\n });\n}\n\n/**\n * Return a single array combining selected and computed attributes.\n * Format: [[literal, alias], [literal, alias], ...]\n */\nexport default function buildJsonAttributes({ select = [], computed = [] }: JsonAttributes = {}): (string | ReturnType<typeof literal>)[][] {\n const selectAttributes = buildSelectAttributes(select);\n const computedAttributes = buildComputedAttributes(computed);\n\n return [...selectAttributes, ...computedAttributes];\n}\n","import type { literal } from 'sequelize';\nimport { customFields } from '@autofleet/common-types';\nimport {\n extractAssociatedAttributeNameFromOrder,\n extractAttributeNameFromOrder, generateRandomString,\n isAttributeByAssociation,\n isOrderDesc, ORDER_PREFIX,\n PAGE_DEFAULT,\n PER_PAGE_DEFAULT, wrapAttributeWithOperator,\n} from '../utils';\nimport type { LiteralAttribute } from '../middleware';\nimport { OPERATORS_TO_SQL } from '../operators';\nimport buildJsonAttributes, { type JsonAttributes } from './jsonAttributesFormater';\n\nconst DEFAULT_ORDER = 'id';\nconst DESCENDING_KEY = 'DESC';\nconst ASCENDING_KEY = 'ASC';\nconst CUSTOM_FIELDS_QUERY_PREFIX = 'customFields.';\nconst { CUSTOM_FIELDS_FILTER_SCOPE, CUSTOM_FIELDS_SORT_SCOPE } = customFields;\ntype OrderItem = string | [string, string];\ntype SequelizeOrder = string | OrderItem[];\nexport interface FormatPayloadOptions {\n includeRawPayload?: boolean;\n literalAttributes?: LiteralAttribute[];\n DBFormatter?: typeof literal;\n skipSearchTermFormat?: boolean;\n additionalAllowedAttributes?: string[];\n}\n\ninterface ConditionWithOperator {\n operator: string;\n value: string;\n}\nexport type ConditionValue = ConditionWithOperator | ConditionWithOperator[] | string | string[];\n\nconst parseCustomFieldScopeQueryValue = (value: unknown) => {\n if (['string', 'number'].includes(typeof value) || Array.isArray(value)) {\n return value;\n }\n return Object.entries(value as object).map(([operator, conditionValue]) => ({\n operator: OPERATORS_TO_SQL[operator as keyof typeof OPERATORS_TO_SQL],\n value: conditionValue,\n }));\n};\n\nconst getAttributeFromOrder = (order: SequelizeOrder[], options: FormatPayloadOptions = {}): [(SequelizeOrder | Literal)[], SequelizeOrder[]] => {\n const { literalAttributes = [], DBFormatter = undefined } = options;\n const [formattedOrder, attributes] = order.reduce<[(SequelizeOrder | Literal)[], SequelizeOrder[]]>((acc, o) => {\n const [item, orderStyle = 'ASC'] = Array.isArray(o) ? o : [o];\n const found = literalAttributes?.find(obj => obj.attribute === item);\n if (found) {\n const order = DBFormatter ? DBFormatter(`\"${found.attribute}\" ${orderStyle as string}`) : `${found.attribute} ${orderStyle as string}`;\n acc[1].push(found.literal);\n acc[0].push([order as string]);\n } else {\n acc[0].push(o);\n }\n return acc;\n }, [[], []]);\n\n return [formattedOrder, attributes];\n};\n\n/**\n * Generates replacements for the given conditions.\n *\n * @param conditions - The conditions to generate replacements for.\n * @returns The replacements object.\n */\nexport const generateFilterReplacements = (conditions: Record<string, ConditionValue>): Record<string, string> => {\n const replacements: Record<string, string> = {};\n\n Object.entries(conditions).forEach(([key, condition]) => {\n const replacementKey = generateRandomString();\n replacements[replacementKey] = key.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n\n if (Array.isArray(condition)) {\n condition.forEach((value) => {\n const valueKey = generateRandomString();\n replacements[valueKey] = typeof value === 'string' ? value : value.value;\n });\n } else if (typeof condition === 'string' || typeof condition === 'number') {\n const conditionKey = generateRandomString();\n replacements[conditionKey] = condition;\n } else if (condition?.operator) {\n const operatorKey = generateRandomString();\n replacements[operatorKey] = condition.value;\n }\n });\n\n return replacements;\n};\n\n/**\n * Generates replacements for the given order array.\n *\n * @param order - The order array to generate replacements for.\n * @returns The replacements object.\n */\nexport const generateOrderReplacements = (order: string[]): Record<string, string> => {\n const replacementMap: Record<string, string> = {};\n order.forEach((o) => {\n if (o.startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n const rand = generateRandomString();\n replacementMap[rand] = o.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n } else if (o.substring(1).startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n const rand = generateRandomString();\n replacementMap[rand] = o.substring(1).split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n }\n });\n return replacementMap;\n};\n\n/**\n * Creates a combined replacement map from order and query.\n *\n * @param order - The order array.\n * @param query - The query object.\n * @returns The combined replacements object.\n */\nconst createReplacementMap = (order: string[], query: Record<string, ConditionValue>): Record<string, string> => ({\n ...generateOrderReplacements(order),\n ...generateFilterReplacements(query),\n});\n\nconst formatOrder = ({\n order,\n associationModels = [],\n replacementsMap = {},\n}: { order: string[]; associationModels?: string[]; replacementsMap?: Record<string, string>; }): {\n formattedOrders: SequelizeOrder[];\n orderScopes: (string | { method: [string, { replacementsMap: Record<string, string>; scopeValue: Record<string, 'DESC' | 'ASC'>; }]; })[];\n replacementsMap: Record<string, string>;\n} => {\n const formattedOrders: SequelizeOrder[] = [];\n const orderScopesMap = new Map<string, Record<string, 'DESC' | 'ASC'>>();\n order.forEach((o) => {\n if ([o, o.substring(1)].some(t => t.startsWith(CUSTOM_FIELDS_QUERY_PREFIX))) {\n if (!orderScopesMap.has(CUSTOM_FIELDS_SORT_SCOPE)) {\n orderScopesMap.set(CUSTOM_FIELDS_SORT_SCOPE, {});\n }\n const scopeKey = o.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n orderScopesMap.get(CUSTOM_FIELDS_SORT_SCOPE)![scopeKey] = (isOrderDesc(o) ? DESCENDING_KEY : ASCENDING_KEY);\n return;\n }\n const formattedOrder = [extractAttributeNameFromOrder(o, associationModels)];\n const isOrderDescOrder = isOrderDesc(o);\n const isOrderAssociation = isAttributeByAssociation(isOrderDescOrder\n ? o.split(ORDER_PREFIX, 2)[1]\n : o, associationModels);\n if (isOrderAssociation) {\n formattedOrder.push(extractAssociatedAttributeNameFromOrder(o));\n }\n if (isOrderDescOrder) {\n formattedOrder.push(DESCENDING_KEY);\n }\n formattedOrders.push(formattedOrder);\n });\n return {\n formattedOrders,\n replacementsMap,\n orderScopes: Array.from(orderScopesMap.entries()).map(([scopeName, scopeValue]) => {\n /* v8 ignore next 3 */\n if (!scopeValue) {\n return scopeName;\n }\n return {\n method: [scopeName, {\n replacementsMap,\n scopeValue,\n }],\n };\n }),\n };\n};\n\nconst formatPage = (page?: number) => page || PAGE_DEFAULT;\n\nconst formatPerPage = (perPage?: number) => perPage || PER_PAGE_DEFAULT;\n\ninterface Include {\n association?: string;\n model?: string;\n required?: boolean;\n include?: Include[];\n}\n\nconst formatInclude = (include: (string | Include)[], associationsMap: Record<string, any> = {}) => {\n let formattedInclude = include.map((i): any => {\n const includedAssociation = associationsMap[typeof i === 'string' ? i : (i.association || i.model!)];\n return {\n ...(typeof i !== 'string' && i),\n association: includedAssociation,\n required: typeof i === 'string' || i.required !== false,\n ...(typeof i !== 'string' && i.include && {\n include: formatInclude(i.include, includedAssociation?.target?.associations),\n }),\n };\n });\n formattedInclude = formattedInclude.map(({ model: _model, ...i }) => i);\n return formattedInclude;\n};\nconst formatQuery = (query: Record<string, unknown>, associationModels: string[], replacementsMap: Record<string, string>, additionalAllowedAttributes: string[] = []) => {\n const formattedQuery: Record<string, unknown> = {};\n const externalQueryValues: Record<string, unknown> = {};\n const formattedScopeMap = new Map<string, any>();\n\n Object.entries(query).forEach(([queryItemKey, queryItemValue]) => {\n if (queryItemKey.startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n if (!formattedScopeMap.has(CUSTOM_FIELDS_FILTER_SCOPE)) {\n formattedScopeMap.set(CUSTOM_FIELDS_FILTER_SCOPE, {});\n }\n const scopeKey = queryItemKey.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n formattedScopeMap.get(CUSTOM_FIELDS_FILTER_SCOPE)[scopeKey] = parseCustomFieldScopeQueryValue(queryItemValue);\n return;\n }\n if (additionalAllowedAttributes.includes(queryItemKey)) {\n externalQueryValues[queryItemKey] = queryItemValue;\n return;\n }\n const key = isAttributeByAssociation(queryItemKey, associationModels)\n ? wrapAttributeWithOperator(queryItemKey)\n : queryItemKey;\n formattedQuery[key] = queryItemValue;\n });\n\n const formattedScopes = Array.from(formattedScopeMap.entries()).map(([scopeName, scopeValue]) => {\n /* v8 ignore next 3 */\n if (!scopeValue) {\n return scopeName;\n }\n return {\n method: [scopeName, {\n replacementsMap,\n scopeValue,\n }],\n };\n });\n\n return {\n formattedQuery,\n externalQueryValues,\n formattedScopes,\n };\n};\n\nconst formatSearchTerm = (searchTerm: string, attributesToSend: string[], rawAttributes: Record<string, any>) => ({\n $and: searchTerm.split(' ').map(term => ({\n $or: attributesToSend.filter(attrKey => rawAttributes[attrKey].type.key === 'STRING').map(attr => ({\n [attr]: {\n $iLike: `%${term}%`,\n },\n })),\n })),\n});\n\ninterface FormatPayloadData {\n order?: string[];\n page?: number;\n perPage?: number;\n include?: (string | Include)[];\n query?: Record<string, unknown>;\n attributes?: string[] | null;\n searchTerm?: string | null;\n jsonAttributes?: JsonAttributes;\n}\ntype Literal = ReturnType<typeof literal>;\n\ninterface FormattedPayload {\n query: Record<string, unknown>;\n\n externalQueryValues?: Record<string, unknown>;\n attributes: (SequelizeOrder | (string | Literal)[])[] | {\n include: (SequelizeOrder | (string | Literal)[])[];\n };\n order: (SequelizeOrder | Literal)[];\n page: number;\n perPage: number;\n include: any;\n scopes: (string | {\n method: (string | {\n replacementsMap: Record<string, string>;\n scopeValue: any;\n })[];\n })[];\n}\n\nconst formatPayload = ({\n order = [],\n page = PAGE_DEFAULT,\n perPage = PER_PAGE_DEFAULT,\n include = [],\n query = {},\n attributes = null,\n searchTerm = null,\n jsonAttributes = {},\n}: FormatPayloadData, model?: any, options?: FormatPayloadOptions): FormattedPayload => {\n const replacementsMap = createReplacementMap(order, query as Record<string, ConditionValue>);\n const associationModels = Object.keys(model?.associations || {});\n const { formattedOrders, orderScopes } = formatOrder({\n order: [...order, DEFAULT_ORDER],\n associationModels,\n replacementsMap,\n });\n const [filteredFormattedOrder, filteredLiteralAttributes] = getAttributeFromOrder(\n formattedOrders,\n options,\n );\n\n const formattedJsonAttributes = buildJsonAttributes(jsonAttributes);\n const allAttributes = [...filteredLiteralAttributes, ...(attributes ?? []), ...formattedJsonAttributes];\n const formattedAttribute = attributes?.length ? allAttributes : { include: allAttributes };\n\n const formattedInclude = formatInclude(include, model?.associations);\n const formattedPage = formatPage(page);\n const formattedPerPage = formatPerPage(perPage);\n const result = formatQuery(query, associationModels, replacementsMap, options?.additionalAllowedAttributes);\n const { formattedScopes: queryScopes, externalQueryValues } = result;\n let { formattedQuery } = result;\n if (searchTerm && !options?.skipSearchTermFormat) {\n const attributesToSend = attributes?.length ? attributes : Object.keys(model.rawAttributes || {});\n const queryWithSearchTerm = formatSearchTerm(searchTerm, attributesToSend, model.rawAttributes);\n formattedQuery = !formattedQuery || Object.keys(formattedQuery).length === 0 ? queryWithSearchTerm : {\n $and: [\n formattedQuery,\n queryWithSearchTerm,\n ],\n };\n }\n return {\n query: formattedQuery,\n order: filteredFormattedOrder,\n page: formattedPage,\n perPage: formattedPerPage,\n include: formattedInclude,\n scopes: [...queryScopes, ...orderScopes],\n ...(formattedAttribute && { attributes: formattedAttribute }),\n ...(Object.keys(externalQueryValues).length > 0 && { externalQueryValues }),\n };\n};\n\nexport default formatPayload;\n","import {\n ORDER_PREFIX,\n extractAttributeNameFromOrder,\n isOrderDesc,\n throwBadRequestError,\n PAGE_DEFAULT,\n PER_PAGE_DEFAULT,\n PAGE_MIN,\n PER_PAGE_MAX_LIMIT,\n PER_PAGE_MIN_LIMIT,\n isAttributeByAssociation,\n ASSOCIATION_PREFIX,\n ASSOCIATION_PATH_WRAPPER,\n} from '../utils';\nimport { OPERATORS, OPERATOR_PREFIX } from '../operators';\nimport type { MiddlewareValidationOption } from '../middleware';\nimport type { JsonAttributes } from '../formatter/jsonAttributesFormater';\n\nconst validateOperator = (operator: string): boolean => OPERATORS.includes(operator.split(OPERATOR_PREFIX, 2)[1] as typeof OPERATORS[number]);\n\nconst validateQueryAttribute = (\n rawAttribute: string,\n modelAttributes: string[] = [],\n associationModels: string[] = [],\n additionalAllowedAttributes: string[] = [],\n): boolean => {\n const attribute = (rawAttribute.startsWith(\n ASSOCIATION_PATH_WRAPPER,\n ) && rawAttribute.endsWith(\n ASSOCIATION_PATH_WRAPPER,\n )) ? rawAttribute.slice(1, -1) : rawAttribute;\n return [...modelAttributes, ...associationModels].includes(attribute.includes(ASSOCIATION_PREFIX)\n ? attribute.split(ASSOCIATION_PREFIX, 1)[0] : attribute)\n || additionalAllowedAttributes.includes(attribute);\n};\n\nconst validateSingleOrder = (\n currentOrder: string,\n rawAttributes: string[],\n associationModels: string[],\n options: MiddlewareValidationOption = {},\n): void => {\n const isOrderDescOrder = isOrderDesc(currentOrder);\n if (isOrderDescOrder && !currentOrder.startsWith(ORDER_PREFIX)) {\n throwBadRequestError(`${ORDER_PREFIX} must be only at the beginning of the word`);\n }\n const orderStringWithoutDesc = isOrderDescOrder ? currentOrder.split(ORDER_PREFIX, 2)[1] : currentOrder;\n const isOrderAssociation = isAttributeByAssociation(orderStringWithoutDesc, associationModels);\n let formattedOrderString = extractAttributeNameFromOrder(currentOrder, associationModels);\n const isLiteralAttribute = options?.literalAttributes?.map(la => la.attribute)?.includes(orderStringWithoutDesc);\n\n if (!isOrderAssociation && formattedOrderString.includes(ASSOCIATION_PREFIX)) {\n [formattedOrderString] = formattedOrderString.split(ASSOCIATION_PREFIX, 1);\n }\n\n if (!(rawAttributes.includes(formattedOrderString) || isOrderAssociation || isLiteralAttribute)) {\n throwBadRequestError(`${currentOrder} is invalid. isLiteralAttribute: ${isLiteralAttribute}`);\n }\n};\n\nconst validateSingleAttribute = (currentAttribute: string, rawAttributes: string[]): void => {\n if (!rawAttributes.includes(currentAttribute)) {\n throwBadRequestError(`${currentAttribute} is invalid`);\n }\n};\n\nconst validateOrderAttributes = (\n order: string[],\n rawAttributes: string[],\n associationModels: string[] = [],\n options: MiddlewareValidationOption = {},\n): void => {\n order.forEach(o => validateSingleOrder(o, rawAttributes, associationModels, options));\n};\n\nconst validateAttributes = (attributes: string[], rawAttributes: string[]): void => {\n attributes.forEach(a => validateSingleAttribute(a, rawAttributes));\n};\n\nconst validateJsonAttributes = (jsonAttributes: JsonAttributes, rawAttributes: string[]): void => {\n const allAttributes = [\n ...(jsonAttributes.select?.map(s => s.columnName) ?? []),\n ...(jsonAttributes.computed?.map(c => c.columnName) ?? []),\n ];\n\n validateAttributes(allAttributes, rawAttributes);\n};\n\n// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style\nconst validateEnrichments = (enrichments: string[] | { [enrichmentName: string]: { exclude?: string[]; }; }, options?: MiddlewareValidationOption): void => {\n const enrichmentKeys = Array.isArray(enrichments) ? enrichments : Object.keys(enrichments);\n if (!enrichmentKeys?.length) {\n return;\n }\n const invalidEnrichment = enrichmentKeys.find(enrichment => !options?.enrichmentAttributes?.includes(enrichment));\n if (invalidEnrichment) {\n throwBadRequestError(`enrichment attribute ${invalidEnrichment} is invalid`);\n }\n};\n\nconst validateQueryPayload = (query: object, rawAttributes: string[], associationModels: string[] = [], additionalAllowedAttributes: string[] = []): void => {\n Object.entries(query).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n if (value[0] && typeof value[0] === 'object') {\n value.map(v => validateQueryPayload(v, rawAttributes, associationModels, additionalAllowedAttributes));\n }\n } else if (validateOperator(key) || validateQueryAttribute(key, rawAttributes, associationModels, additionalAllowedAttributes)) {\n if (value && typeof value === 'object') {\n validateQueryPayload(value, rawAttributes, [], additionalAllowedAttributes);\n }\n } else {\n throwBadRequestError(`invalid key: ${key}`);\n }\n });\n};\n\nconst validatePagination = ({\n page,\n perPage,\n}: { page: number; perPage: number; }) => {\n if (page < PAGE_MIN) {\n throwBadRequestError('Page must be greater than 0');\n }\n\n if (perPage > PER_PAGE_MAX_LIMIT || perPage < PER_PAGE_MIN_LIMIT) {\n throwBadRequestError(`PerPage must be between ${PER_PAGE_MIN_LIMIT} to ${PER_PAGE_MAX_LIMIT}`);\n }\n};\n\nconst validateIncludePayload = (include: any[], associations: Record<string, any>): void => {\n const associationsKeys = Object.keys(associations);\n include.forEach((i) => {\n validateQueryAttribute(i.model, associationsKeys);\n const target = associations[i.model]?.target;\n if (!target) {\n throwBadRequestError('model not found in associations');\n }\n\n const { rawAttributes } = target;\n const attributeKeys = Object.keys(rawAttributes);\n if (i.where) {\n validateQueryPayload(i.where, attributeKeys);\n }\n if (i.order) {\n validateOrderAttributes(i.order, attributeKeys);\n }\n if (i.attributes) {\n validateAttributes(i.attributes, attributeKeys);\n }\n if (![null, undefined, true, false].includes(i.required)) {\n throwBadRequestError('include.required must be a boolean');\n }\n });\n};\n\ninterface PayloadValidationData {\n query?: object;\n order?: string[];\n attributes?: string[];\n include?: any[];\n page?: number;\n perPage?: number;\n // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style\n enrichments?: string[] | { [enrichmentName: string]: { exclude?: string[]; }; };\n group?: string[];\n jsonAttributes?: {\n select?: { columnName: string; keys: string[]; alias?: string; }[];\n computed?: { columnName: string; action: 'length'; alias: string; }[];\n };\n}\n\nexport const validatePayload = (\n {\n query = {},\n order = [],\n attributes = [],\n include = [],\n page = PAGE_DEFAULT,\n perPage = PER_PAGE_DEFAULT,\n enrichments = [],\n group = [],\n jsonAttributes = {},\n }: PayloadValidationData,\n model?: any,\n options: MiddlewareValidationOption = {},\n): boolean => {\n const rawAttributes = Object.keys(model.rawAttributes);\n const associationModels = Object.keys(model?.associations || {});\n if (!attributes || attributes.length === 0) {\n attributes = rawAttributes;\n } else {\n validateAttributes(attributes, rawAttributes);\n }\n\n validateOrderAttributes(order, rawAttributes, associationModels, options);\n validateQueryPayload(query, rawAttributes, associationModels, options.additionalAllowedAttributes);\n validateEnrichments(enrichments, options);\n validateJsonAttributes(jsonAttributes, rawAttributes);\n\n if (!Array.isArray(group)) {\n throwBadRequestError('group must be an array');\n }\n if (include.length && typeof include === 'object') {\n validateIncludePayload(include, model?.associations);\n } else if (include && typeof include !== 'object') {\n throwBadRequestError('include must be an array');\n }\n\n validatePagination({\n page,\n perPage,\n });\n return true;\n};\n","import type { Handler } from 'express';\nimport Logger, { type LoggerInstanceManager } from '@autofleet/logger';\nimport { BadRequest, handleError } from '@autofleet/errors';\nimport Joi from 'joi';\nimport formatPayload, { type FormatPayloadOptions } from '../formatter';\nimport { validatePayload } from '../validations';\nimport { ComputedActions } from '../formatter/jsonAttributesFormater';\n\nconst {\n object, string, number, any, array, alternatives,\n} = Joi.types();\nconst fallbackLogger = Logger();\n\nconst querySchema = object.keys({\n query: object,\n attributes: array.items(string),\n order: array.items(string),\n page: number,\n perPage: number,\n include: array.items(any),\n searchTerm: string,\n group: array.items(string),\n enrichments: alternatives.try(array.items(string), object.pattern(string, { exclude: array.items(string) })),\n jsonAttributes: Joi.object({\n select: Joi.array().items(\n Joi.object({\n columnName: Joi.string().required(),\n keys: Joi.array().items(Joi.string().required()).required(),\n alias: Joi.string().optional(),\n }),\n ).default([]),\n computed: Joi.array().items(\n Joi.object({\n columnName: Joi.string().required(),\n action: Joi.string().valid(...Object.values(ComputedActions)).required(),\n alias: Joi.string().required(),\n }),\n ).default([]),\n }).default({}),\n});\n\ntype literal = any;\ntype LiteralQuery = (literal | string)[] | literal;\nexport interface LiteralAttribute { attribute: string; literal: LiteralQuery; }\nexport interface MiddlewareValidationOption {\n literalAttributes?: LiteralAttribute[];\n enrichmentAttributes?: string[];\n additionalAllowedAttributes?: string[];\n logger?: LoggerInstanceManager;\n}\n\ntype ReqKeys = 'body' | 'query';\n\nexport const queryValidation = (model: any, data: any, options: MiddlewareValidationOption = {}): void => {\n const {\n query,\n attributes,\n order,\n page,\n perPage,\n include,\n group,\n enrichments,\n jsonAttributes,\n } = data;\n\n const result = querySchema.validate(data);\n if (result.error) {\n throw new BadRequest([result.error]);\n }\n validatePayload({\n query,\n attributes,\n order,\n page,\n perPage,\n include,\n enrichments,\n group,\n jsonAttributes,\n }, model, options);\n};\n\n/** consider using @see {@link queryHandler} directly */\nexport const queryValidationMiddleware = (model: any, options: MiddlewareValidationOption = {}, inner: ReqKeys = 'body'): Handler => (req, res, next): void => {\n try {\n queryValidation(model, req[inner], options);\n next();\n } catch (error) {\n const { query, attributes, order } = req[inner];\n handleError(error as Error, res, {\n logger: options.logger ?? fallbackLogger,\n message: 'error in query middleware',\n payload: {\n error,\n query,\n attributes,\n order,\n },\n });\n }\n};\n\nexport const queryFormat = (model: any, data: any, options: FormatPayloadOptions = {}): void => {\n const {\n order, page, perPage, include, query, attributes, searchTerm, jsonAttributes,\n } = data;\n\n const {\n query: formattedQuery,\n externalQueryValues,\n order: formattedOrder,\n page: formattedPage,\n perPage: formattedPerPage,\n include: formattedInclude,\n scopes: formattedScopes,\n attributes: formattedAttribute,\n } = formatPayload({\n query, order, page, perPage, include, attributes, searchTerm, jsonAttributes,\n }, model, options);\n\n data.query = formattedQuery;\n data.externalQueryValues = externalQueryValues;\n data.order = formattedOrder;\n data.attributes = formattedAttribute;\n data.page = formattedPage;\n data.perPage = formattedPerPage;\n data.include = formattedInclude;\n data.scopes = formattedScopes;\n if (options.includeRawPayload) {\n data.rawPayload = {\n order,\n page,\n perPage,\n include,\n query,\n attributes,\n searchTerm,\n };\n }\n};\n\n/** consider using @see {@link queryHandler} directly */\nexport const queryFormatMiddleware = (model: any, options: FormatPayloadOptions = {}, inner: ReqKeys = 'body'): Handler => (req, _res, next): void => {\n queryFormat(model, req[inner], options);\n next();\n};\n","import type { Handler } from 'express';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport { UnexpectedError, handleError } from '@autofleet/errors';\nimport type formatPayload from '../formatter';\nimport type { FormatPayloadOptions } from '../formatter';\nimport { pick } from '../utils';\nimport { type MiddlewareValidationOption, queryFormat, queryValidation } from '../middleware';\n\ninterface QueryValues extends ReturnType<typeof formatPayload> {\n enrichments?: string[] | Record<string, { exclude: string[]; }>;\n distinct: boolean;\n searchTerm?: string;\n}\n\ninterface QueryHandlerOptions {\n /** The sequelize model too which querying abilities are added. */\n model: any;\n /** Optional settings for validation. */\n validationOptions?: Omit<MiddlewareValidationOption, 'logger'>;\n /** Optional settings for payload formatting */\n formatOptions?: FormatPayloadOptions;\n logger: LoggerInstanceManager;\n /** The name of model to be printed in logs. defaults to `model`s constructor name. */\n modelName?: string;\n /** Sequelize scopes of the model to be used within the query. @example ['userScope'] */\n additionalScopes?: string[];\n /** Callback to allow modifying the query values prior to querying the DB */\n modifyQueryValues?: (queryValues: QueryValues) => QueryValues;\n /** Optional callback to modify endpoint's response based on the DBs response. */\n onRowsRetrieved?: (data: { rows: any[]; count: number; }, queryValues: QueryValues) => any;\n}\n\ntype Asyncify<T extends (...a: any[]) => any> = (...a: Parameters<T>) => Promise<Awaited<ReturnType<T>>>;\n\nexport const queryHandler = ({\n model, logger, validationOptions, formatOptions, modelName = model.constructor?.name, additionalScopes = [], modifyQueryValues, onRowsRetrieved,\n}: QueryHandlerOptions): Asyncify<Handler> => async (req, res) => {\n try {\n queryValidation(model, req.body, { ...validationOptions, logger });\n } catch (error) {\n const payload = pick(req.body as QueryValues, ['query', 'order', 'attributes'] as const);\n handleError(error as Error, res, { logger, message: 'error in query endpoint', payload });\n return;\n }\n try {\n queryFormat(model, req.body, formatOptions);\n\n const queryValues = Object.assign(\n pick(req.body as QueryValues, ['query', 'externalQueryValues', 'order', 'attributes', 'page', 'perPage', 'include', 'scopes', 'enrichments'] as const),\n { distinct: true },\n );\n\n logger.info(`querying ${modelName}`, { queryValues });\n\n const modifiedQuery = modifyQueryValues?.(queryValues) ?? queryValues;\n const {\n scopes = [],\n query: where,\n perPage: limit,\n page,\n enrichments: _enrichments,\n externalQueryValues: _externalQueryValues,\n ...rest\n } = modifiedQuery;\n\n const result = await model.scope([...additionalScopes, ...scopes]).findAndCountAll({\n where,\n limit,\n offset: (page - 1) * limit,\n ...rest,\n });\n\n if (!result.rows.length || !onRowsRetrieved) {\n res.json(result);\n return;\n }\n\n const enrichmentResult = await onRowsRetrieved(result, modifiedQuery);\n\n res.json(enrichmentResult);\n } catch (error) {\n handleError(new UnexpectedError(error as Error), res, { logger, message: `Error while querying ${modelName}`, payload: { query: req.body } });\n }\n};\n","export class BulkerError extends Error {\n public readonly retryable: boolean;\n\n constructor(message: string, retryable = false) {\n super(message);\n this.name = 'BulkerError';\n this.retryable = retryable;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static isBulkerError(err: unknown): err is BulkerError {\n return err instanceof BulkerError;\n }\n\n static wrap(err: unknown, message?: string, retryable = false): BulkerError {\n if (err instanceof BulkerError) {\n return new BulkerError(message ?? err.message, retryable || err.retryable);\n }\n\n const msg = message ? `${message}: ${err instanceof Error ? err.message : String(err)}` : String(err);\n return new BulkerError(msg, retryable);\n }\n\n static retryable(message: string): BulkerError {\n return new BulkerError(message, true);\n }\n\n static nonRetryable(message: string): BulkerError {\n return new BulkerError(message, false);\n }\n}\n\nexport class ValidationError extends BulkerError {\n constructor(message: string, retryable = false) {\n super(message, retryable);\n this.name = 'ValidationError';\n }\n}\n","import { z } from 'zod';\n\nconst getErrorMessage = (keys: string[]) => {\n if (keys.length === 0) return 'No keys provided';\n if (keys.length === 1) return `Key \"${keys[0]}\" is required`;\n return `Exactly one of [${keys.join(', ')}] must be provided`;\n};\n\nexport function oneOfKeys<T extends z.ZodRawShape>(\n shape: T,\n keys: (keyof T)[],\n): z.ZodType<any> {\n return z.object(shape).refine(\n (data: any) => {\n const presentKeys = keys.filter(\n k => data[k as string] !== undefined && data[k as string] !== null,\n );\n return presentKeys.length === 1;\n },\n {\n message: getErrorMessage(keys as string[]),\n },\n );\n}\n\nconst uuidValidation = z.string().uuid();\n\nexport function buildIdentityScopeSchema(keys: readonly string[]): ReturnType<typeof oneOfKeys> {\n const shape = Object.fromEntries(\n keys.map(key => [key, z.union([uuidValidation, z.array(uuidValidation)]).optional()]),\n );\n return oneOfKeys(shape, keys as (keyof typeof shape)[]);\n}\n","import type { Model, ModelStatic } from 'sequelize';\nimport type RabbitMq from '@autofleet/rabbit';\nimport type { z } from 'zod';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\n\nexport type Id = string | number;\n\nexport interface JobMetadata {\n status: string;\n total: number;\n action: string;\n}\n\nexport interface ConsumeOptions {\n enableRabbitTrace?: boolean;\n [key: string]: any;\n}\n\nexport interface JobStatus {\n jobId: string;\n status: string;\n action: string;\n total: number;\n queued: number;\n processed: number;\n succeeded: number;\n failed: number;\n errors: any[];\n createdAt?: string;\n updatedAt?: string;\n duration: {\n startTime: string | null;\n endTime: string | null;\n durationMs: number | null;\n };\n}\n\nexport interface PartialAckResult<Id = string | number> {\n succeeded?: Id[];\n failed?: { id: Id; error?: string; }[];\n}\n\ntype MessageAck = () => Promise<void>;\ntype MessageNack = (err?: Error, requeue?: boolean) => Promise<void>;\ntype MessagePartialAck = (result: PartialAckResult) => Promise<void>;\n\nexport type BulkPerIdHandler<T = any> = (args: {\n jobId?: string; // optional, in case needed for logging\n ids: Id[]; // array of IDs - can be single ID [id] or multiple [id1, id2, ...]\n /* id will be available to use only if consumerBatchSize is 1 */\n id?: Id; // single ID being processed\n payload: T;\n}, ack: MessageAck, nack: MessageNack, partialAck: MessagePartialAck) => Promise<void>;\nexport interface AdditionalIdsHookData<T = any> {\n rawPayload: {\n query: Record<string, any>;\n include?: any[];\n searchTerm: string;\n };\n payload: T;\n}\n\nexport interface BulkRouteOptions<T = any> {\n /** e.g. \"change-state\" - the action identifier sent in request body */\n action: string;\n /** Sequelize model from which to scan IDs */\n model: Model<any, any> | ModelStatic<any> | any;\n modelScopes: string[]; // optional, Sequelize scopes to apply when scanning\n /** Consumer that processes IDs (can handle single ID [id] or multiple [id1, id2, ...]) */\n consumer: BulkPerIdHandler<T>; // required, worker function to process IDs\n /** Consumer batch size - how many IDs to send to consumer at once (default 1) */\n consumerBatchSize?: number; // optional, default 1 (single ID per call)\n consumerOptions?: ConsumeOptions; // optional, RabbitMQ consumer options\n rabbitQueueName?: string; // optional, if using RabbitMQ, the name of the queue to publish to and consume from\n\n payloadSchema?: z.ZodType<T>; // optional Zod schema to validate and type payload structure\n /** Identity Scopes to be provided and are used and validated from the query.query object */\n identityScopes?: string[]; // identity scope fields to validate in payload\n\n // Optional per-route overrides:\n idField?: string; // default: inherited from bulker.defaults.idField\n pageSize?: number; // default: inherited from bulker.defaults.pageSize\n workerConcurrency?: number; // default: inherited\n jobAttempts?: number; // default: inherited\n jobBackoffMs?: number; // default: inherited\n removeOnComplete?: boolean | number; // default: inherited\n removeOnFail?: boolean | number; // default: inherited\n\n /**\n * Hook to provide additional IDs that will be OR-ed with the main query.\n * This is useful when you need to manually fetch additional IDs based on the raw request body.\n * The returned IDs will be included in the where clause using an OR statement.\n *\n * @example\n * additionalIdsHook: async (data) => {\n * const { rawPayload, payload } = reqBody;\n * if (!rawPayload.searchTerm) return [];\n * const filters = rawPayload?.query?.fleetId ? { fleetId: query.query.fleetId } : {};\n * const labelIds = await searchDriversByLabelsValue(rawPayload.searchTerm, filters);\n * const vendorIds = await searchDriversByVendorName(rawPayload.searchTerm, filters);\n * return [...labelIds, ...vendorIds];\n * }\n */\n additionalIdsHook?: (data: AdditionalIdsHookData) => Promise<Id[]>;\n}\n\n// ============================================================================\n// Bulker Initialization Types\n// ============================================================================\n\nexport interface BulkerInit {\n sequelize: any; // Sequelize\n logger: LoggerInstanceManager;\n rabbit: RabbitMq;\n redis: any; // RedisClientType or config object\n getUserId?: () => string | null; // function that returns the user ID for the current request context, or null if not available\n emitEvents?: boolean; // opt-in to enable event emission (default: false)\n defaults?: {\n pageSize?: number; // default 1000\n maxJobsPerUser?: number; // default unlimited (no per-user rate limiting)\n idField?: string; // default \"id\"\n consumerBatchSize?: number; // default 1 (batch size for consumer)\n workerConcurrency?: number; // default 16\n jobTtlSeconds?: number; // default 7 days (for Redis status hash expiry)\n errorLogLimit?: number; // default 10 (max number of error logs to keep per job)\n };\n}\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\nexport interface BulkRouterEvents {\n // Job lifecycle events\n 'job:created': (data: { jobId: string; action: string; total: number; }) => void;\n 'job:started': (data: { jobId: string; action: string; }) => void;\n 'job:queued': (data: { jobId: string; action: string; queued: number; total: number; }) => void;\n 'job:completed': (data: { jobId: string; action: string; processed: number; failed: number; duration: number; }) => void;\n 'job:canceled': (data: { jobId: string; action: string; }) => void;\n 'job:failed': (data: { jobId: string; action: string; error: string; }) => void;\n\n // Item processing events\n 'item:processing': (data: { jobId: string; action: string; id: string | number; }) => void;\n 'item:processed': (data: { jobId: string; action: string; id: string | number; }) => void;\n 'item:failed': (data: { jobId: string; action: string; id: string | number; error: string; }) => void;\n 'item:retrying': (data: { jobId: string; action: string; id: string | number; }) => void;\n\n // Worker events\n 'worker:started': (data: { action: string; queueName: string; }) => void;\n 'worker:error': (data: { action: string; error: string; }) => void;\n\n // Scan events\n 'scan:page': (data: { jobId: string; action: string; pageNumber: number; itemsInPage: number; }) => void;\n}\n\n/**\n * Interface for event emitters with the emitEvent helper method\n */\nexport interface IBulkEventEmitter {\n emitEvent<K extends keyof BulkRouterEvents>(\n event: K,\n ...args: Parameters<BulkRouterEvents[K]>\n ): void;\n}\n\n// ============================================================================\n// Identity Scope Types\n// ============================================================================\n\nexport const DEFAULT_IDENTITY_SCOPES: readonly string[] = [\n 'businessModelId',\n 'fleetId',\n 'demandSourceId',\n 'contextId',\n 'userId',\n 'businessAccountId',\n 'activeBusinessModelId',\n];\n\nexport type DefaultIdentityScope = typeof DEFAULT_IDENTITY_SCOPES[number];\n","import type { Handler } from 'express';\nimport { randomUUID } from 'node:crypto';\nimport type { Model, ModelStatic, WhereOptions } from 'sequelize';\nimport { Op } from 'sequelize';\nimport type RabbitMq from '@autofleet/rabbit';\nimport { z } from 'zod';\nimport type { Bulker } from '.';\nimport { BulkerError } from './Errors';\nimport { queryValidation, queryFormat } from '../../middleware';\nimport { pick } from '../../utils';\nimport { buildIdentityScopeSchema } from './utils/identityScope';\nimport type {\n BulkPerIdHandler,\n BulkRouteOptions,\n Id,\n JobStatus,\n ConsumeOptions,\n PartialAckResult,\n} from './types';\nimport { DEFAULT_IDENTITY_SCOPES } from './types';\n\ntype Asyncify<T extends (...a: any[]) => any> = (...a: Parameters<T>) => Promise<Awaited<ReturnType<T>>>;\n\nexport class BulkRoute<T = any> {\n public readonly action: string;\n\n // TODO: understand why theres an issue with the model type\n private readonly model: ModelStatic<any> | Model<any, any> | any;\n\n private readonly consumer: BulkPerIdHandler<T>;\n\n private readonly idField: string;\n\n private readonly pageSize: number;\n\n private readonly consumerBatchSize: number;\n\n private readonly rabbitQueueName: string | null = null;\n\n private readonly rabbit: RabbitMq | null = null;\n\n private readonly consumerOptions: ConsumeOptions;\n\n private readonly payloadSchema?: z.ZodType<T>;\n\n private readonly identityScopeSchema: z.ZodType<any>;\n\n private readonly modelScopes: string[];\n\n constructor(\n private readonly bulker: Bulker,\n private readonly opts: BulkRouteOptions<T>,\n ) {\n this.action = opts.action;\n this.model = opts.model;\n this.modelScopes = opts.modelScopes ?? [];\n this.consumer = opts.consumer;\n this.rabbit = bulker.rabbit;\n this.rabbitQueueName = opts.rabbitQueueName ?? `bulk-${this.action}-queue`;\n this.consumerOptions = {\n enableRabbitTrace: true, // always enable user tracing to be able to do getUser()\n ...this.opts.consumerOptions,\n };\n this.payloadSchema = opts.payloadSchema;\n\n if (!/^[a-zA-Z0-9-_]+$/.test(this.action)) throw new Error('BulkRoute action must be alphanumeric');\n if (!this.model || typeof this.model.findAll !== 'function' || typeof this.model.count !== 'function') throw new Error('BulkRoute model must be a valid Sequelize model');\n if (typeof this.consumer !== 'function') throw new Error('BulkRoute consumer must be a function');\n if (this.payloadSchema && !(this.payloadSchema instanceof z.ZodType)) throw new Error('BulkRoute payloadSchema must be a Zod schema');\n if (this.modelScopes && !Array.isArray(this.modelScopes)) throw new Error('BulkRoute scopes must be an array of strings');\n if (this.opts.additionalIdsHook && typeof this.opts.additionalIdsHook !== 'function') throw new Error('BulkRoute additionalIdsHook must be a function');\n\n // resolve defaults\n this.idField = opts.idField ?? bulker.defaults.idField;\n this.pageSize = opts.pageSize ?? bulker.defaults.pageSize;\n this.consumerBatchSize = opts.consumerBatchSize ?? bulker.defaults.consumerBatchSize;\n\n const modelAttributes = this.model.rawAttributes || {};\n\n const identityScopes = opts.identityScopes && opts.identityScopes.length > 0 ? opts.identityScopes : DEFAULT_IDENTITY_SCOPES;\n const filteredIdentityScopes = identityScopes.filter(s => !!modelAttributes[s]);\n\n if (filteredIdentityScopes.length === 0) {\n this.bulker.logger.warn(`BulkRoute for action ${this.action} has no valid identityScopes configured - all records will be accessible`);\n }\n\n this.bulker.logger.info(`BulkRoute for action ${this.action} using idField ${this.idField}, pageSize ${this.pageSize},\n consumerBatchSize ${this.consumerBatchSize}, identityScopes: ${filteredIdentityScopes.join(', ')}`);\n\n this.identityScopeSchema = buildIdentityScopeSchema(filteredIdentityScopes);\n\n // Start worker immediately\n this.startRabbitWorker().catch((err) => {\n this.bulker.logger.error(`Failed to start RabbitMQ worker for queue ${this.rabbitQueueName}: ${err.message || err}`);\n });\n }\n\n public bulkHandler: Asyncify<Handler> = async (req, res, next): Promise<any> => {\n try {\n const { query, payload, preview } = req.body ?? {};\n let validatedPayload: T | undefined = payload;\n\n if (payload && this.payloadSchema) {\n try {\n validatedPayload = this.payloadSchema.parse(payload);\n } catch (err) {\n if (err instanceof z.ZodError) {\n return res.status(400).json({\n error: 'invalid_payload',\n details: err.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', '),\n });\n }\n throw err;\n }\n }\n\n try {\n queryValidation(this.model, query, { logger: this.bulker.logger as any });\n } catch (err) {\n return res.status(400).json({ error: 'invalid_query', details: (err as Error).message || err });\n }\n\n const identityScopeResult = this.identityScopeSchema.safeParse(query?.query || {});\n if (!identityScopeResult.success) {\n return res.status(400).json({\n error: 'invalid_identity_scope',\n details: identityScopeResult.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join(', '),\n });\n }\n\n queryFormat(this.model, query, { includeRawPayload: true });\n\n const queryValues = Object.assign(\n pick(query, ['query', 'externalQueryValues', 'order', 'include', 'scopes', 'enrichments'] as const),\n { distinct: true },\n );\n\n const {\n query: where,\n ...rest\n } = queryValues;\n\n let additionalIds: Id[] = [];\n if (this.opts.additionalIdsHook) {\n const { rawPayload } = query;\n try {\n additionalIds = await this.opts.additionalIdsHook({ rawPayload, payload: validatedPayload! });\n this.bulker.logger.info(`additionalIdsHook returned ${additionalIds.length} IDs for action ${this.action}`);\n } catch (err) {\n this.bulker.logger.error(`Error in additionalIdsHook for action ${this.action}: ${(err as Error).message || err}`, { err });\n return res.status(500).json({ error: 'additional_ids_hook_error', details: (err as Error).message || err });\n }\n }\n\n const orClauses: WhereOptions[] = [];\n\n if (where && typeof where === 'object' && Object.keys(where).length > 0) {\n orClauses.push(where as WhereOptions);\n }\n\n if (Array.isArray(additionalIds) && additionalIds.length > 0) {\n orClauses.push({ id: { $in: additionalIds } });\n }\n\n let finalWhere: WhereOptions;\n if (orClauses.length === 0) {\n return res.status(400).json({ error: 'no_query', details: 'No valid query provided to select records' });\n } else if (orClauses.length === 1) {\n const [onlyWhere] = orClauses;\n finalWhere = onlyWhere;\n } else {\n finalWhere = { $or: orClauses };\n }\n this.bulker.logger.info(`Constructed final where clause for action ${this.action}`, { where: finalWhere });\n\n const findData = {\n where: finalWhere,\n ...rest,\n };\n\n // Preview count\n const total = await this.model.scope(this.modelScopes).count({\n ...findData,\n col: this.idField,\n });\n if (preview) {\n return res.json({ estimatedCount: total });\n }\n\n // Create job status in Redis\n const jobId = randomUUID();\n await this.bulker.jobManager.initJob(jobId, {\n status: 'queued',\n total,\n action: this.action,\n });\n\n // Emit job:created event\n this.bulker.emitEvent('job:created', { jobId, action: this.action, total });\n\n // Kick off scanning + enqueuing (async)\n setImmediate(() => {\n this.rabbitScanAndEnqueue(jobId, findData, validatedPayload).catch(err => this.bulker.logger.error(err));\n });\n\n return res.status(202).json({ jobId, estimatedCount: total });\n } catch (e) {\n this.bulker.logger.error(`Error in bulkHandler for action ${this.action}: ${(e as Error).message || e}`, { err: e });\n return next(e);\n }\n };\n\n /** Get user jobs - public method for BulkRouter to use */\n public async getUserJobs(userId: string, limit = 20): Promise<(JobStatus | null)[]> {\n return this.bulker.jobManager.getUserJobs(userId, limit);\n }\n\n /** Helper method to batch IDs into chunks of consumerBatchSize */\n private batchIds(ids: Id[]): Id[][] {\n const batches: Id[][] = [];\n for (let i = 0; i < ids.length; i += this.consumerBatchSize) {\n batches.push(ids.slice(i, i + this.consumerBatchSize));\n }\n return batches;\n }\n\n private async rabbitScanAndEnqueue(jobId: string, findData: any, payload: T | undefined): Promise<void> {\n if (!this.rabbit) throw new Error('RabbitMQ not configured in Bulker');\n const startTime = Date.now().toString();\n if (this.bulker.getUserId) {\n const userId = this.bulker.getUserId();\n if (userId) await this.bulker.jobManager.addUserJob(userId, jobId);\n }\n\n // Batch the job start status and time update\n await this.bulker.jobManager.setJobFields(jobId, {\n status: 'running',\n startTime,\n });\n\n // Emit job:started event\n this.bulker.emitEvent('job:started', { jobId, action: this.action });\n\n let lastId: Id | null = null;\n let queued = 0;\n let pageNumber = 0;\n\n while (true) {\n // check for cancel before fetching the next page\n const st = await this.bulker.jobManager.getJobField(jobId, 'status');\n if (!st || st === 'canceled') {\n this.bulker.logger.info(`Job ${jobId} was canceled, stopping scan and enqueue`);\n break;\n }\n\n const pageWhere = lastId\n ? { [Op.and]: [findData.where, { [this.idField]: { [Op.gt]: lastId } }] }\n : findData.where;\n // add getRows function to overwrite\n const rows: Record<string, any>[] = await this.model.scope(this.modelScopes).findAll({\n where: pageWhere,\n ...pick(findData, ['include', 'order', 'scopes'] as const),\n attributes: [this.idField],\n order: [[this.idField, 'ASC']],\n limit: this.pageSize,\n raw: true,\n subQuery: false,\n });\n if (rows.length === 0) break;\n\n // Batch IDs and enqueue\n let id: Id | undefined;\n const allIds = rows.map(r => r[this.idField]);\n if (this.consumerBatchSize === 1 && allIds.length === 1) {\n id = allIds[0];\n }\n const batches = this.batchIds(allIds);\n const rabbitJobs = batches.map(ids => ({\n jobId, ids, payload, id,\n }));\n this.bulker.logger.info(`Enqueuing ${rabbitJobs.length} batched messages (${allIds.length} total IDs) to RabbitMQ queue ${this.rabbitQueueName}`);\n const publishToRabbitRes = await Promise.allSettled(rabbitJobs.map(j => this.rabbit!.sendToQueue(this.rabbitQueueName!, j)));\n const rejected = publishToRabbitRes.filter(r => r.status === 'rejected');\n if (rejected.length > 0) {\n this.bulker.logger.error(`Failed to enqueue ${rejected.length} messages to RabbitMQ`, { rejected });\n throw new Error(`Failed to enqueue ${rejected.length} messages to RabbitMQ`);\n }\n\n queued += rows.length;\n await this.bulker.jobManager.incrJobField(jobId, 'queued', rows.length);\n lastId = rows[rows.length - 1][this.idField];\n\n // Emit scan:page event\n pageNumber += 1;\n this.bulker.emitEvent('scan:page', {\n jobId,\n action: this.action,\n pageNumber,\n itemsInPage: rows.length,\n });\n\n // Emit job:queued event with progress\n const job = await this.bulker.jobManager.getJob(jobId);\n if (job) {\n this.bulker.emitEvent('job:queued', {\n jobId,\n action: this.action,\n queued,\n total: job.total,\n });\n }\n }\n\n // If no items were queued, mark job as completed immediately\n if (queued === 0) {\n await this.bulker.jobManager.completeEmptyJob(jobId);\n }\n }\n\n public async startRabbitWorker(): Promise<void> {\n this.bulker.logger.info(`Starting RabbitMQ consumer for queue ${this.rabbitQueueName}`);\n\n // Emit worker:started event\n this.bulker.emitEvent('worker:started', {\n action: this.action,\n queueName: this.rabbitQueueName!,\n });\n\n return this.rabbit?.consume(this.rabbitQueueName!, async (msg, ack, nack) => {\n if (!msg) return;\n const { jobId, ids, payload } = msg.content as { jobId: string; ids: Id[]; payload: T; };\n let messageAcked = false;\n const wrappedAck = async () => {\n if (!messageAcked) {\n messageAcked = true;\n await Promise.all([\n this.bulker.jobManager.ack(jobId, ids.length),\n ack(),\n ]);\n // Emit item:processed event for each ID (keep existing behavior)\n for (const id of ids) {\n this.bulker.emitEvent('item:processed', { jobId, action: this.action, id });\n }\n }\n };\n\n const wrappedNack = async (err?: Error, requeue?: boolean) => {\n if (messageAcked) return;\n messageAcked = true;\n\n let requeueFinal = requeue;\n if (requeueFinal === undefined) {\n if (BulkerError.isBulkerError(err)) {\n requeueFinal = err.retryable;\n } else {\n requeueFinal = false; // non-BulkerErrors are considered non-retryable by default\n }\n }\n\n await Promise.all([\n this.bulker.jobManager.nack(jobId, err ? (err.message || String(err)) : 'nacked', { ids }, ids.length),\n nack(null, { skipRetry: !requeueFinal }),\n ]);\n\n // Emit item events for each ID (keep existing behavior)\n for (const id of ids) {\n if (requeueFinal) {\n this.bulker.emitEvent('item:retrying', {\n jobId,\n action: this.action,\n id,\n });\n } else {\n this.bulker.emitEvent('item:failed', {\n jobId,\n action: this.action,\n id,\n error: err ? (err.message || String(err)) : 'nacked',\n });\n }\n }\n };\n\n const wrappedPartialAck = async (result: PartialAckResult) => {\n if (messageAcked) return;\n messageAcked = true;\n\n const succeededIds = result.succeeded ?? [];\n const failedResults = result.failed ?? [];\n\n await Promise.all([\n this.bulker.jobManager.partialAck(jobId, succeededIds.length, failedResults),\n ack(), // Always ack the RabbitMQ message since we've fully processed the batch\n ]);\n\n // Emit item:processed event for succeeded IDs\n for (const id of succeededIds) {\n this.bulker.emitEvent('item:processed', { jobId, action: this.action, id });\n }\n\n // Emit item:failed event for failed IDs\n for (const failedItem of failedResults) {\n this.bulker.emitEvent('item:failed', {\n jobId,\n action: this.action,\n id: failedItem.id,\n error: failedItem.error || 'Item failed',\n });\n }\n };\n\n // If canceled, count as processed but do nothing\n const st = await this.bulker.jobManager.getJobField(jobId, 'status');\n if (!st || st === 'canceled') {\n await this.bulker.jobManager.incrJobField(jobId, 'processed', ids.length);\n return;\n }\n\n try {\n this.bulker.logger.info(`Started processing job ${jobId} action ${this.action} ids amount: ${ids.length}`);\n\n // Emit item:processing event for each ID\n for (const id of ids) {\n this.bulker.emitEvent('item:processing', { jobId, action: this.action, id });\n }\n\n await this.consumer(\n {\n jobId,\n ids,\n id: this.consumerBatchSize === 1 ? ids[0] : undefined,\n payload,\n },\n wrappedAck,\n wrappedNack,\n wrappedPartialAck,\n );\n // If consumer did not ack/nack, we will ack here\n await wrappedAck();\n } catch (err: any) {\n this.bulker.logger.error(`Error processing job ${jobId} action ${this.action}: ${err.message || err}`, { err });\n\n // Emit worker:error event\n this.bulker.emitEvent('worker:error', {\n action: this.action,\n error: err.message || String(err),\n });\n\n // If consumer did not ack/nack, we will nack here\n await wrappedNack(err, false);\n }\n }, this.consumerOptions);\n }\n}\n","import { Router } from 'express';\nimport type { Bulker } from '.';\nimport { BulkRoute } from './BulkRoute';\nimport type { BulkRouteOptions } from './types';\n\nexport default class BulkRouter {\n private readonly bulker: Bulker;\n\n private readonly router: Router;\n\n private routes: BulkRoute<any>[] = [];\n\n private actionsToRouteMap = new Map<string, BulkRoute<any>>();\n\n private staticRoute: string;\n\n constructor(bulker: Bulker, router?: Router, staticRoute = '/bulk-actions') {\n this.bulker = bulker;\n this.router = router ?? Router();\n this.staticRoute = staticRoute;\n this.registerStaticRoutes();\n }\n\n private registerStaticRoutes() {\n // POST /bulk-actions - action-based routing\n this.router.post(this.staticRoute, this.bulkHandler);\n\n // GET /jobs/:id - get job status\n this.router.get('/jobs/:id', this.getJobHandler);\n\n // GET /jobs - get user's recent jobs\n this.router.get('/jobs', this.getMyJobsHandler);\n\n // POST /jobs/:id/cancel - cancel a job\n this.router.post('/jobs/:id/cancel', this.cancelJobHandler);\n }\n\n addAction<T = any>(actionName: string, opts: Omit<BulkRouteOptions<T>, 'action'>): BulkRoute<T> {\n const route = new BulkRoute<T>(this.bulker, {\n action: actionName,\n ...opts,\n });\n this.bulker.logger.info(`Registering action handler: ${actionName}`);\n\n // Map action to route for action-based routing\n if (this.actionsToRouteMap.has(actionName)) {\n throw new Error(`Action \"${actionName}\" is already registered`);\n }\n this.actionsToRouteMap.set(actionName, route);\n this.routes.push(route);\n return route;\n }\n\n private bulkHandler = async (req: any, res: any, next: any) => {\n try {\n const { action } = req.body ?? {};\n\n if (!action) {\n return res.status(400).json({\n error: 'missing_action',\n message: 'Request body must include an \"action\" field',\n });\n }\n\n const route = this.actionsToRouteMap.get(action);\n if (!route) {\n return res.status(404).json({\n error: 'unknown_action',\n message: `Action \"${action}\" is not registered`,\n availableActions: Array.from(this.actionsToRouteMap.keys()),\n });\n }\n\n // Delegate to the specific route handler\n return route.bulkHandler(req, res, next);\n } catch (e) {\n this.bulker.logger.error(`Error in BulkRouter bulkHandler: ${(e as Error).message || e}`, { err: e });\n next(e);\n return null;\n }\n };\n\n private getJobHandler = async (req: any, res: any, next: any) => {\n try {\n if (!req.params.id) return res.status(400).json({ error: 'missing_id' });\n\n const status = await this.bulker.jobManager.getJob(req.params.id);\n if (!status) {\n return res.status(404).json({ error: 'not_found' });\n }\n\n return res.json(status);\n } catch (e) {\n return next(e);\n }\n };\n\n private cancelJobHandler = async (req: any, res: any, next: any) => {\n try {\n if (!req.params.id) return res.status(400).json({ error: 'missing_id' });\n\n const ok = await this.bulker.jobManager.cancelJob(req.params.id);\n if (!ok) {\n return res.status(404).json({ error: 'not_found' });\n }\n\n this.bulker.logger.info(`Job ${req.params.id} cancel requested`);\n return res.json({ ok: true });\n } catch (e) {\n return next(e);\n }\n };\n\n private getMyJobsHandler = async (req: any, res: any, next: any) => {\n try {\n if (!this.bulker.getUserId) {\n return res.status(400).json({\n error: 'user_id_function_not_configured',\n message: 'Bulker instance does not have a getUserId function configured',\n });\n }\n\n const userId = this.bulker.getUserId();\n\n if (!userId) {\n return res.json([]);\n }\n\n const jobs = await this.bulker.jobManager.getUserJobs(userId);\n const filteredJobs = jobs.filter(j => j !== null);\n\n // Sort by creation date (most recent first)\n filteredJobs.sort((a, b) => {\n const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return dateB - dateA;\n });\n\n return res.json(filteredJobs);\n } catch (e) {\n return next(e);\n }\n };\n\n /** Get the configured router instance */\n getRouter(): Router {\n return this.router;\n }\n}\n","// Unified Lua script for atomically updating job status in Redis\n// Handles ack (all succeeded), nack (all failed), and partial ack (mixed results)\n// Takes the job key as KEYS[1]\n// Arguments: succeededCount, failedCount, failedResults (JSON), errorLogLimit, updatedAt\n// Returns the updated processed count\nexport const localPartialAckScript = `\nlocal jobKey = KEYS[1]\nlocal succeededCount = tonumber(ARGV[1]) or 0\nlocal failedCount = tonumber(ARGV[2]) or 0\nlocal failedResults = ARGV[3]\nlocal errorLogLimit = tonumber(ARGV[4])\nlocal updatedAt = ARGV[5]\n\nlocal total = tonumber(redis.call('HGET', jobKey, 'total'))\nif not total then\n return 0\nend\n\n-- Update counters\nlocal totalCount = succeededCount + failedCount\nredis.call('HINCRBY', jobKey, 'succeeded', succeededCount)\nredis.call('HINCRBY', jobKey, 'failed', failedCount)\nlocal processed = redis.call('HINCRBY', jobKey, 'processed', totalCount)\nredis.call('HSET', jobKey, 'updatedAt', updatedAt)\n\n-- Process failed results and add errors\nif failedCount > 0 then\n local errorsJson = redis.call('HGET', jobKey, 'errors') or '[]'\n local errors = cjson.decode(errorsJson)\n local failedData = cjson.decode(failedResults)\n\n for i = 1, #failedData do\n if #errors >= errorLogLimit then\n -- Keep only the last (errorLogLimit - 1) entries\n local newErrors = {}\n for j = #errors - errorLogLimit + 2, #errors do\n table.insert(newErrors, errors[j])\n end\n errors = newErrors\n end\n\n local failedItem = failedData[i]\n local errorData\n -- If id is a table (object), store it as-is (for backwards compat with nack)\n -- Otherwise, wrap simple values in { id: value } structure (for partialAck)\n if type(failedItem.id) == \"table\" then\n errorData = failedItem.id\n else\n errorData = { id = failedItem.id }\n end\n\n local errorEntry = {\n message = failedItem.error or 'Item failed',\n data = errorData\n }\n table.insert(errors, errorEntry)\n end\n\n redis.call('HSET', jobKey, 'errors', cjson.encode(errors))\nend\n\n-- Check if job is complete\nif processed >= total then\n redis.call('HSET', jobKey, 'status', 'completed')\n redis.call('HSET', jobKey, 'endTime', updatedAt)\nend\n\nreturn processed\n`;\n","import type { RedisClientType } from 'redis';\nimport { localPartialAckScript } from './redis-scripts';\nimport type { EmitsBulkEvents } from './events';\nimport type { Id, JobMetadata, JobStatus } from './types';\n\n/**\n * Manages all job-related operations in Redis\n * Centralizes job creation, status updates, cancellation, and user job tracking\n */\nexport class JobManager {\n private eventEmitter?: EmitsBulkEvents;\n\n private readonly redis: RedisClientType;\n\n private readonly defaults: {\n maxJobsPerUser: number;\n jobTtlSeconds: number;\n errorLogLimit: number;\n };\n\n constructor(\n redis: RedisClientType,\n defaults?: {\n maxJobsPerUser?: number;\n jobTtlSeconds?: number;\n errorLogLimit?: number;\n },\n ) {\n this.redis = redis;\n this.defaults = {\n maxJobsPerUser: defaults?.maxJobsPerUser ?? -1, // -1 means unlimited\n jobTtlSeconds: defaults?.jobTtlSeconds ?? 7 * 24 * 3600, // default 7 days\n errorLogLimit: defaults?.errorLogLimit ?? 10, // default 10 errors\n };\n }\n\n /** Set event emitter for emitting job events */\n setEventEmitter(emitter: EmitsBulkEvents): void {\n this.eventEmitter = emitter;\n }\n\n /** Generate Redis key for job */\n static jobKey(jobId: string): string {\n return `bulker:job:${jobId}`;\n }\n\n /** Generate Redis key for user jobs list */\n static userJobsKey(userId: string): string {\n return `bulker:user:${userId}:jobs`;\n }\n\n /**\n * Initialize a new job in Redis\n */\n async initJob(jobId: string, meta: JobMetadata): Promise<void> {\n const now = Date.now().toString();\n const jobKey = JobManager.jobKey(jobId);\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, {\n status: meta.status,\n total: String(meta.total),\n queued: '0',\n processed: '0',\n succeeded: '0',\n failed: '0',\n action: meta.action,\n errors: JSON.stringify([]),\n createdAt: now,\n updatedAt: now,\n startTime: now,\n endTime: '',\n });\n multi.expire(jobKey, this.defaults.jobTtlSeconds);\n\n await multi.exec();\n }\n\n /**\n * Get job status from Redis\n */\n async getJob(jobId: string): Promise<JobStatus | null> {\n const jobKey = JobManager.jobKey(jobId);\n const obj = await this.redis.hGetAll(jobKey);\n\n if (!obj || Object.keys(obj).length === 0) {\n return null;\n }\n\n // Calculate duration\n const startTime = obj.startTime ? Number(obj.startTime) : null;\n const endTime = obj.endTime && obj.endTime !== '' ? Number(obj.endTime) : null;\n const currentTime = Date.now();\n\n const duration = {\n startTime: startTime ? new Date(startTime).toISOString() : null,\n endTime: endTime ? new Date(endTime).toISOString() : null,\n durationMs: startTime ? (endTime || currentTime) - startTime : null,\n };\n\n return {\n jobId,\n status: obj.status,\n action: obj.action,\n total: Number(obj.total ?? 0),\n queued: Number(obj.queued ?? 0),\n processed: Number(obj.processed ?? 0),\n succeeded: Number(obj.succeeded ?? 0),\n failed: Number(obj.failed ?? 0),\n errors: obj.errors ? JSON.parse(obj.errors) : [],\n createdAt: obj.createdAt ? new Date(Number(obj.createdAt)).toISOString() : undefined,\n updatedAt: obj.updatedAt ? new Date(Number(obj.updatedAt)).toISOString() : undefined,\n duration,\n };\n }\n\n /**\n * Set a single field on a job\n */\n async setJobField(jobId: string, field: string, value: string): Promise<boolean> {\n const jobKey = JobManager.jobKey(jobId);\n const exists = await this.redis.exists(jobKey);\n if (!exists) return false;\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, field, value);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n await multi.exec();\n return true;\n }\n\n /**\n * Set multiple fields on a job\n */\n async setJobFields(jobId: string, fields: Record<string, string>): Promise<boolean> {\n const jobKey = JobManager.jobKey(jobId);\n const exists = await this.redis.exists(jobKey);\n if (!exists) return false;\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, fields);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n await multi.exec();\n return true;\n }\n\n /**\n * Increment a counter field on a job\n */\n async incrJobField(jobId: string, field: 'queued' | 'processed' | 'succeeded' | 'failed', by = 1): Promise<number> {\n const jobKey = JobManager.jobKey(jobId);\n\n const multi = this.redis.multi();\n multi.hIncrBy(jobKey, field, by);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n const results = await multi.exec() as [number | null, number][];\n if (results?.[0]?.[0] === null) {\n return results[0][1];\n }\n return 0;\n }\n\n /**\n * Get a single field from a job\n */\n async getJobField(jobId: string, field: string): Promise<string | undefined> {\n const jobKey = JobManager.jobKey(jobId);\n return this.redis.hGet(jobKey, field);\n }\n\n /**\n * Cancel a job\n */\n async cancelJob(jobId: string): Promise<boolean> {\n return this.setJobField(jobId, 'status', 'canceled');\n }\n\n /**\n * Mark job as completed (for jobs with no items to process)\n */\n async completeEmptyJob(jobId: string): Promise<void> {\n const endTime = Date.now().toString();\n await this.setJobFields(jobId, {\n status: 'completed',\n endTime,\n });\n }\n\n /**\n * Handle successful message processing (ack)\n * Internally calls partialAck with all items succeeded\n */\n async ack(jobId: string, count = 1): Promise<void> {\n // All succeeded, no failures\n await this.partialAck(jobId, count, []);\n }\n\n /**\n * Handle failed message processing (nack)\n * Internally calls partialAck with all items failed\n */\n async nack(jobId: string, errorMsg: string, data: { ids: Id[]; }, count = 1): Promise<void> {\n // All failed, no successes\n // Create failed results array with one entry per failed item\n const failedResults: { id: any; error?: string; }[] = [];\n\n const getId = (index: number): any => {\n if (data.ids && Array.isArray(data.ids)) {\n return data.ids[index] ?? `unknown-${index}`;\n }\n return `unknown-${index}`;\n };\n\n for (let i = 0; i < count; i++) {\n failedResults.push({\n id: getId(i),\n error: errorMsg,\n });\n }\n\n await this.partialAck(jobId, 0, failedResults);\n }\n\n /**\n * Handle partial success/failure for a batch of items\n * Uses Redis Lua script for atomic operation\n */\n async partialAck(\n jobId: string,\n succeededCount: number,\n failedResults: { id: any; error?: string; }[],\n ): Promise<void> {\n const jobKey = JobManager.jobKey(jobId);\n await this.redis.eval(localPartialAckScript, {\n keys: [jobKey],\n arguments: [\n String(succeededCount),\n String(failedResults.length),\n JSON.stringify(failedResults),\n String(this.defaults.errorLogLimit),\n Date.now().toString(),\n ],\n });\n\n // Check if job completed and emit event\n const job = await this.getJob(jobId);\n if (job?.status === 'completed') {\n const duration = job.duration.durationMs || 0;\n this.eventEmitter?.emitEvent('job:completed', {\n jobId,\n action: job.action,\n processed: job.processed,\n failed: job.failed,\n duration,\n });\n }\n }\n\n /**\n * Add a job to user's job list\n */\n async addUserJob(userId: string, jobId: string): Promise<void> {\n const userJobsKey = JobManager.userJobsKey(userId);\n const USER_JOB_TTL_SECONDS = 3600; // 1 hour\n\n const multi = this.redis.multi();\n multi.lPush(userJobsKey, jobId);\n if (this.defaults.maxJobsPerUser > 0) {\n multi.lTrim(userJobsKey, 0, this.defaults.maxJobsPerUser - 1);\n }\n multi.expire(userJobsKey, USER_JOB_TTL_SECONDS);\n\n await multi.exec();\n }\n\n /**\n * Remove a job from user's job list\n */\n async removeUserJob(userId: string, jobId: string): Promise<void> {\n const userJobsKey = JobManager.userJobsKey(userId);\n await this.redis.lRem(userJobsKey, 0, jobId);\n }\n\n /**\n * Get all jobs for a user\n */\n async getUserJobs(userId: string, limit = 20): Promise<(JobStatus | null)[]> {\n const userJobsKey = JobManager.userJobsKey(userId);\n const jobIds = await this.redis.lRange(userJobsKey, 0, limit - 1);\n\n const jobs = await Promise.all(\n jobIds.map(jobId => this.getJob(jobId)),\n );\n\n return jobs;\n }\n}\n","import type { Sequelize } from 'sequelize';\nimport { createClient, type RedisClientType } from 'redis';\nimport type { Router } from 'express';\nimport type rabbit from '@autofleet/rabbit';\nimport BulkRouter from './BulkRouter';\nimport { JobManager } from './JobManager';\nimport type { BulkEventEmitter, BulkRouterEvents, EmitsBulkEvents } from './events';\nimport { EventEmitter } from 'node:events';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\n\nconst NOOP_EMITTER: EmitsBulkEvents = {\n emitEvent: () => {\n // no-op\n },\n};\n\nexport interface BulkerInit {\n sequelize: Sequelize;\n logger: LoggerInstanceManager;\n rabbit: rabbit;\n redis: RedisClientType | ReturnType<typeof createClient> | { host: string; port?: number; password?: string; db?: number; };\n getUserId?: () => string | null; // function that returns the user ID for the current request context, or null if not available\n emitEvents?: boolean; // opt-in to enable event emission (default: false)\n defaults?: {\n pageSize?: number; // default 1000\n maxJobsPerUser?: number; // default unlimited (no per-user rate limiting)\n idField?: string; // default \"id\"\n consumerBatchSize?: number; // default 1 (batch size for consumer)\n workerConcurrency?: number; // default 16\n jobTtlSeconds?: number; // default 7 days (for Redis status hash expiry)\n errorLogLimit?: number; // default 10 (max number of error logs to keep per job)\n };\n}\n\nexport class Bulker extends EventEmitter implements BulkEventEmitter {\n public readonly sequelize: Sequelize;\n\n public readonly logger: BulkerInit['logger'];\n\n public readonly redis: RedisClientType;\n\n public readonly rabbit: rabbit;\n\n public readonly defaults: Required<NonNullable<BulkerInit['defaults']>>;\n\n public readonly jobManager: JobManager;\n\n private readonly bulkRouters: BulkRouter[] = [];\n\n public getUserId: BulkerInit['getUserId'];\n\n public readonly eventsEnabled: boolean;\n\n constructor(init: BulkerInit) {\n super();\n this.sequelize = init.sequelize;\n this.logger = init.logger;\n this.rabbit = init.rabbit;\n this.eventsEnabled = init.emitEvents ?? false;\n if (init.redis && typeof (init.redis as any).connect === 'function') {\n this.redis = init.redis as RedisClientType;\n if (!(this.redis.isOpen)) {\n this.redis.connect().catch((err) => {\n this.logger.error('Error connecting to Redis:', err);\n });\n }\n } else {\n const config = init.redis as { host: string; port?: number; password?: string; db?: number; };\n this.redis = createClient({\n socket: {\n connectTimeout: 60_000, // 60 seconds\n host: config.host,\n port: config.port,\n reconnectStrategy(retries, cause) {\n init.logger.warn(`[BULKER Redis] Reconnecting to Redis (attempt ${retries}). Cause: ${cause?.message}`);\n const delay = Math.min(retries * 50, 2000); // Exponential backoff up to 2 seconds\n return delay;\n },\n },\n commandsQueueMaxLength: 5000,\n disableOfflineQueue: false,\n password: config.password,\n database: config.db,\n });\n this.redis.connect().catch((err) => {\n this.logger.error('Error connecting to Redis:', err);\n });\n }\n this.getUserId = init.getUserId ?? (() => null);\n\n this.defaults = {\n maxJobsPerUser: init.defaults?.maxJobsPerUser ?? -1,\n pageSize: init.defaults?.pageSize ?? 1000,\n idField: init.defaults?.idField ?? 'id',\n consumerBatchSize: init.defaults?.consumerBatchSize ?? 1,\n workerConcurrency: init.defaults?.workerConcurrency ?? 16,\n jobTtlSeconds: init.defaults?.jobTtlSeconds ?? 7 * 24 * 3600,\n errorLogLimit: init.defaults?.errorLogLimit ?? 10,\n };\n\n // Initialize JobManager\n this.jobManager = new JobManager(this.redis, {\n maxJobsPerUser: this.defaults.maxJobsPerUser,\n jobTtlSeconds: this.defaults.jobTtlSeconds,\n errorLogLimit: this.defaults.errorLogLimit,\n });\n\n // Set event emitter reference in JobManager\n this.jobManager.setEventEmitter(this.eventsEnabled ? {\n emitEvent: this.emitEvent.bind(this),\n } : NOOP_EMITTER);\n }\n\n pingRedis(): Promise<string> {\n return this.redis.ping();\n }\n\n createBulkRouter(router?: Router, staticRoute?: string): BulkRouter {\n const bulkRouter = new BulkRouter(this, router, staticRoute);\n this.bulkRouters.push(bulkRouter);\n return bulkRouter;\n }\n\n /**\n * Emit event only if events are enabled.\n * Centralizes the eventsEnabled check to avoid repetition.\n */\n emitEvent<K extends keyof BulkRouterEvents>(\n event: K,\n ...args: Parameters<BulkRouterEvents[K]>\n ): void {\n if (this.eventsEnabled) {\n this.emit(event, ...args);\n }\n }\n}\n\nexport { BulkerError } from './Errors';\nexport { BulkRoute } from './BulkRoute';\nexport { JobManager } from './JobManager';\nexport { type BulkEventEmitter } from './events';\nexport { z } from 'zod';\n\nexport {\n type BulkRouteOptions,\n type JobMetadata,\n type JobStatus,\n type BulkRouterEvents,\n type IBulkEventEmitter,\n type AdditionalIdsHookData,\n type BulkPerIdHandler,\n} from './types';\n"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -62,5 +62,5 @@ if processed >= total then
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
return processed
|
|
65
|
-
`,{keys:[i],arguments:[String(n),String(r.length),JSON.stringify(r),String(this.defaults.errorLogLimit),Date.now().toString()]});let a=await this.getJob(t);if(a?.status===`completed`){let e=a.duration.durationMs||0;this.eventEmitter?.emitEvent(`job:completed`,{jobId:t,action:a.action,processed:a.processed,failed:a.failed,duration:e})}}async addUserJob(t,n){let r=e.userJobsKey(t),i=this.redis.multi();i.lPush(r,n),this.defaults.maxJobsPerUser>0&&i.lTrim(r,0,this.defaults.maxJobsPerUser-1),i.expire(r,3600),await i.exec()}async removeUserJob(t,n){let r=e.userJobsKey(t);await this.redis.lRem(r,0,n)}async getUserJobs(t,n=20){let r=e.userJobsKey(t),i=await this.redis.lRange(r,0,n-1);return await Promise.all(i.map(e=>this.getJob(e)))}};const Oe={emitEvent:()=>{}};var ke=class extends m{constructor(e){if(super(),this.bulkRouters=[],this.sequelize=e.sequelize,this.logger=e.logger,this.rabbit=e.rabbit,this.eventsEnabled=e.emitEvents??!1,e.redis&&typeof e.redis.connect==`function`)this.redis=e.redis,this.redis.isOpen||this.redis.connect().catch(e=>{this.logger.error(`Error connecting to Redis:`,e)});else{let t=e.redis;this.redis=u({socket:{host:t.host,port:t.port},commandsQueueMaxLength:5e3,disableOfflineQueue:!1,password:t.password,database:t.db}),this.redis.connect().catch(e=>{this.logger.error(`Error connecting to Redis:`,e)})}this.getUserId=e.getUserId??(()=>null),this.defaults={maxJobsPerUser:e.defaults?.maxJobsPerUser??-1,pageSize:e.defaults?.pageSize??1e3,idField:e.defaults?.idField??`id`,consumerBatchSize:e.defaults?.consumerBatchSize??1,workerConcurrency:e.defaults?.workerConcurrency??16,jobTtlSeconds:e.defaults?.jobTtlSeconds??168*3600,errorLogLimit:e.defaults?.errorLogLimit??10},this.jobManager=new $(this.redis,{maxJobsPerUser:this.defaults.maxJobsPerUser,jobTtlSeconds:this.defaults.jobTtlSeconds,errorLogLimit:this.defaults.errorLogLimit}),this.jobManager.setEventEmitter(this.eventsEnabled?{emitEvent:this.emitEvent.bind(this)}:Oe)}pingRedis(){return this.redis.ping()}createBulkRouter(e,t){let n=new De(this,e,t);return this.bulkRouters.push(n),n}emitEvent(e,...t){this.eventsEnabled&&this.emit(e,...t)}};export{ke as Bulker,X as BulkerError,$ as BulkerJobManager,_ as formatOperators,P as generateFilterReplacements,xe as queryFormatMiddleware,Se as queryHandler,be as queryValidationMiddleware,U as validatePayload,f as z};
|
|
65
|
+
`,{keys:[i],arguments:[String(n),String(r.length),JSON.stringify(r),String(this.defaults.errorLogLimit),Date.now().toString()]});let a=await this.getJob(t);if(a?.status===`completed`){let e=a.duration.durationMs||0;this.eventEmitter?.emitEvent(`job:completed`,{jobId:t,action:a.action,processed:a.processed,failed:a.failed,duration:e})}}async addUserJob(t,n){let r=e.userJobsKey(t),i=this.redis.multi();i.lPush(r,n),this.defaults.maxJobsPerUser>0&&i.lTrim(r,0,this.defaults.maxJobsPerUser-1),i.expire(r,3600),await i.exec()}async removeUserJob(t,n){let r=e.userJobsKey(t);await this.redis.lRem(r,0,n)}async getUserJobs(t,n=20){let r=e.userJobsKey(t),i=await this.redis.lRange(r,0,n-1);return await Promise.all(i.map(e=>this.getJob(e)))}};const Oe={emitEvent:()=>{}};var ke=class extends m{constructor(e){if(super(),this.bulkRouters=[],this.sequelize=e.sequelize,this.logger=e.logger,this.rabbit=e.rabbit,this.eventsEnabled=e.emitEvents??!1,e.redis&&typeof e.redis.connect==`function`)this.redis=e.redis,this.redis.isOpen||this.redis.connect().catch(e=>{this.logger.error(`Error connecting to Redis:`,e)});else{let t=e.redis;this.redis=u({socket:{connectTimeout:6e4,host:t.host,port:t.port,reconnectStrategy(t,n){return e.logger.warn(`[BULKER Redis] Reconnecting to Redis (attempt ${t}). Cause: ${n?.message}`),Math.min(t*50,2e3)}},commandsQueueMaxLength:5e3,disableOfflineQueue:!1,password:t.password,database:t.db}),this.redis.connect().catch(e=>{this.logger.error(`Error connecting to Redis:`,e)})}this.getUserId=e.getUserId??(()=>null),this.defaults={maxJobsPerUser:e.defaults?.maxJobsPerUser??-1,pageSize:e.defaults?.pageSize??1e3,idField:e.defaults?.idField??`id`,consumerBatchSize:e.defaults?.consumerBatchSize??1,workerConcurrency:e.defaults?.workerConcurrency??16,jobTtlSeconds:e.defaults?.jobTtlSeconds??168*3600,errorLogLimit:e.defaults?.errorLogLimit??10},this.jobManager=new $(this.redis,{maxJobsPerUser:this.defaults.maxJobsPerUser,jobTtlSeconds:this.defaults.jobTtlSeconds,errorLogLimit:this.defaults.errorLogLimit}),this.jobManager.setEventEmitter(this.eventsEnabled?{emitEvent:this.emitEvent.bind(this)}:Oe)}pingRedis(){return this.redis.ping()}createBulkRouter(e,t){let n=new De(this,e,t);return this.bulkRouters.push(n),n}emitEvent(e,...t){this.eventsEnabled&&this.emit(e,...t)}};export{ke as Bulker,X as BulkerError,$ as BulkerJobManager,_ as formatOperators,P as generateFilterReplacements,xe as queryFormatMiddleware,Se as queryHandler,be as queryValidationMiddleware,U as validatePayload,f as z};
|
|
66
66
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["DefaultOp","Op","order","replacements: Record<string, string>","replacementMap: Record<string, string>","formattedOrders: SequelizeOrder[]","formattedQuery: Record<string, unknown>","externalQueryValues: Record<string, unknown>","formatPayload","z","DEFAULT_IDENTITY_SCOPES: readonly string[]","bulker: Bulker","opts: BulkRouteOptions<T>","validatedPayload: T | undefined","z","additionalIds: Id[]","orClauses: WhereOptions[]","finalWhere: WhereOptions","batches: Id[][]","lastId: Id | null","rows: Record<string, any>[]","id: Id | undefined","err: any","failedResults: { id: any; error?: string; }[]","NOOP_EMITTER: EmitsBulkEvents"],"sources":["../src/operators/index.ts","../src/utils.ts","../src/formatter/jsonAttributesFormater.ts","../src/formatter/index.ts","../src/validations/index.ts","../src/middleware/index.ts","../src/handler/index.ts","../src/bulker/src/Errors.ts","../src/bulker/src/utils/identityScope.ts","../src/bulker/src/types.ts","../src/bulker/src/BulkRoute.ts","../src/bulker/src/BulkRouter.ts","../src/bulker/src/redis-scripts.ts","../src/bulker/src/JobManager.ts","../src/bulker/src/index.ts"],"sourcesContent":["import { Op as DefaultOp } from 'sequelize';\n\nexport const OPERATORS = [\n 'eq',\n 'ne',\n 'gte',\n 'gt',\n 'lte',\n 'lt',\n 'not',\n 'in',\n 'notIn',\n 'is',\n 'like',\n 'iLike',\n 'notLike',\n 'between',\n 'and',\n 'or',\n 'overlap',\n 'contains',\n] as const;\n\nexport const OPERATOR_PREFIX = '$';\n\nexport const OPERATORS_TO_SQL = {\n $eq: '=',\n $ne: '!=',\n $gte: '>=',\n $gt: '>',\n $lte: '<=',\n $lt: '<',\n $not: 'NOT',\n $in: 'IN',\n $notIn: 'NOT IN',\n $is: 'IS',\n $like: 'LIKE',\n $iLike: 'ILIKE',\n $notLike: 'NOT LIKE',\n $and: 'AND',\n $or: 'OR',\n};\n\nexport const formatOperators = (sequelize: { Op: typeof DefaultOp; } = { Op: DefaultOp }): Record<string, symbol> => {\n const { Op } = sequelize;\n return Object.fromEntries(OPERATORS.map(o => [`${OPERATOR_PREFIX + o}`, Op[o]]));\n};\n","import { randomInt } from 'node:crypto';\nimport { BadRequest } from '@autofleet/errors';\nimport { OPERATOR_PREFIX } from './operators';\n\nexport const ORDER_PREFIX = '-';\nexport const ASSOCIATION_PREFIX = '.';\nexport const ASSOCIATION_PATH_WRAPPER = '$';\nexport const PER_PAGE_DEFAULT = 20;\nexport const PAGE_DEFAULT = 1;\nexport const PER_PAGE_MAX_LIMIT = 100;\nexport const PER_PAGE_MIN_LIMIT = 1;\nexport const PAGE_MIN = 1;\n\nexport const wrapAttributeWithOperator = (attribute: string) => `${OPERATOR_PREFIX}${attribute}${OPERATOR_PREFIX}`;\nexport const isAttributeByAssociation = (attributeName: string, associatedModels: string[]): boolean => attributeName.includes(ASSOCIATION_PREFIX)\n && associatedModels.includes(attributeName.split(ASSOCIATION_PREFIX, 1)[0]);\n\nexport const extractAttributeNameFromOrder = (order: string, associationModels: string[]): string => {\n let formattedOrder = order;\n if (order.includes(ORDER_PREFIX)) {\n [, formattedOrder] = formattedOrder.split(ORDER_PREFIX, 2);\n }\n if (isAttributeByAssociation(formattedOrder, associationModels)) {\n [formattedOrder] = formattedOrder.split(ASSOCIATION_PREFIX, 1);\n }\n return formattedOrder;\n};\n\nexport const isOrderDesc = (order: string): boolean => order.includes(ORDER_PREFIX);\n\nexport const throwBadRequestError = (message: string): never => {\n throw new BadRequest([new Error(message)]);\n};\n\nexport const extractAssociatedAttributeNameFromOrder = (order: string): string => order.split(ASSOCIATION_PREFIX, 2)[1];\n\nexport const generateRandomString = (length = 5): string => {\n const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n return Array.from({ length }, () => characters.charAt(randomInt(characters.length))).join('');\n};\n\nexport const pick = <T extends object, K extends keyof T = keyof T>(\n obj: T,\n keys: K[],\n): Pick<T, K> => Object.fromEntries(keys.map(key => [key, obj[key]])) as Pick<T, K>;\n","import { literal } from 'sequelize';\n\nexport const ComputedActions = {\n length: 'length',\n} as const;\n\ninterface JsonSelectAttribute {\n columnName: string;\n keys: string[];\n alias?: string;\n}\n\ninterface JsonComputedAttribute {\n columnName: string;\n action: keyof typeof ComputedActions;\n alias: string;\n}\n\nexport interface JsonAttributes {\n select?: JsonSelectAttribute[];\n computed?: JsonComputedAttribute[];\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/(?!^)[A-Z]/g, letter => `_${letter.toLowerCase()}`);\n}\n\n/**\n * Builds a computed attribute based on the action specified.\n * Currently, supports 'length' action which calculates the length of a JSON array.\n *\n * @param {JsonComputedAttribute} computedAttribute - The computed attribute definition.\n * @returns {Array} An array containing the SQL literal and the alias for the computed attribute.\n */\nfunction buildComputedAttribute(computedAttribute: JsonComputedAttribute) {\n switch (computedAttribute.action) {\n case ComputedActions.length: {\n // Generates: jsonb_array_length(column)\n // Returns the length of the JSON array (e.g., [\"a\", \"b\"] -> 2)\n // Note: Expects the column to be a JSON array\n const sql = `jsonb_array_length(${toSnakeCase(computedAttribute.columnName)})`;\n return [literal(sql), computedAttribute.alias];\n }\n default:\n computedAttribute.action satisfies never;\n return [];\n }\n}\n\nfunction buildComputedAttributes(computed: JsonComputedAttribute[]) {\n return computed.map(attr => buildComputedAttribute(attr));\n}\n\n/**\n * Builds a list of SQL select attributes using json_build_object\n * to extract specific keys from a JSONB column.\n *\n * Example output:\n * SELECT json_build_object('a', column -> 'a', 'b', column -> 'b') AS alias\n */\nfunction buildSelectAttributes(select: JsonSelectAttribute[]) {\n return select.map((attr) => {\n const columnNameSnake = toSnakeCase(attr.columnName);\n const sql = `json_build_object(${attr.keys\n .map(k => `'${k}', ${columnNameSnake} -> '${k}'`)\n .join(', ')})`;\n const alias = attr.alias || attr.columnName;\n return [literal(sql), alias];\n });\n}\n\n/**\n * Return a single array combining selected and computed attributes.\n * Format: [[literal, alias], [literal, alias], ...]\n */\nexport default function buildJsonAttributes({ select = [], computed = [] }: JsonAttributes = {}): (string | ReturnType<typeof literal>)[][] {\n const selectAttributes = buildSelectAttributes(select);\n const computedAttributes = buildComputedAttributes(computed);\n\n return [...selectAttributes, ...computedAttributes];\n}\n","import type { literal } from 'sequelize';\nimport { customFields } from '@autofleet/common-types';\nimport {\n extractAssociatedAttributeNameFromOrder,\n extractAttributeNameFromOrder, generateRandomString,\n isAttributeByAssociation,\n isOrderDesc, ORDER_PREFIX,\n PAGE_DEFAULT,\n PER_PAGE_DEFAULT, wrapAttributeWithOperator,\n} from '../utils';\nimport type { LiteralAttribute } from '../middleware';\nimport { OPERATORS_TO_SQL } from '../operators';\nimport buildJsonAttributes, { type JsonAttributes } from './jsonAttributesFormater';\n\nconst DEFAULT_ORDER = 'id';\nconst DESCENDING_KEY = 'DESC';\nconst ASCENDING_KEY = 'ASC';\nconst CUSTOM_FIELDS_QUERY_PREFIX = 'customFields.';\nconst { CUSTOM_FIELDS_FILTER_SCOPE, CUSTOM_FIELDS_SORT_SCOPE } = customFields;\ntype OrderItem = string | [string, string];\ntype SequelizeOrder = string | OrderItem[];\nexport interface FormatPayloadOptions {\n includeRawPayload?: boolean;\n literalAttributes?: LiteralAttribute[];\n DBFormatter?: typeof literal;\n skipSearchTermFormat?: boolean;\n additionalAllowedAttributes?: string[];\n}\n\ninterface ConditionWithOperator {\n operator: string;\n value: string;\n}\nexport type ConditionValue = ConditionWithOperator | ConditionWithOperator[] | string | string[];\n\nconst parseCustomFieldScopeQueryValue = (value: unknown) => {\n if (['string', 'number'].includes(typeof value) || Array.isArray(value)) {\n return value;\n }\n return Object.entries(value as object).map(([operator, conditionValue]) => ({\n operator: OPERATORS_TO_SQL[operator as keyof typeof OPERATORS_TO_SQL],\n value: conditionValue,\n }));\n};\n\nconst getAttributeFromOrder = (order: SequelizeOrder[], options: FormatPayloadOptions = {}): [(SequelizeOrder | Literal)[], SequelizeOrder[]] => {\n const { literalAttributes = [], DBFormatter = undefined } = options;\n const [formattedOrder, attributes] = order.reduce<[(SequelizeOrder | Literal)[], SequelizeOrder[]]>((acc, o) => {\n const [item, orderStyle = 'ASC'] = Array.isArray(o) ? o : [o];\n const found = literalAttributes?.find(obj => obj.attribute === item);\n if (found) {\n const order = DBFormatter ? DBFormatter(`\"${found.attribute}\" ${orderStyle as string}`) : `${found.attribute} ${orderStyle as string}`;\n acc[1].push(found.literal);\n acc[0].push([order as string]);\n } else {\n acc[0].push(o);\n }\n return acc;\n }, [[], []]);\n\n return [formattedOrder, attributes];\n};\n\n/**\n * Generates replacements for the given conditions.\n *\n * @param conditions - The conditions to generate replacements for.\n * @returns The replacements object.\n */\nexport const generateFilterReplacements = (conditions: Record<string, ConditionValue>): Record<string, string> => {\n const replacements: Record<string, string> = {};\n\n Object.entries(conditions).forEach(([key, condition]) => {\n const replacementKey = generateRandomString();\n replacements[replacementKey] = key.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n\n if (Array.isArray(condition)) {\n condition.forEach((value) => {\n const valueKey = generateRandomString();\n replacements[valueKey] = typeof value === 'string' ? value : value.value;\n });\n } else if (typeof condition === 'string' || typeof condition === 'number') {\n const conditionKey = generateRandomString();\n replacements[conditionKey] = condition;\n } else if (condition?.operator) {\n const operatorKey = generateRandomString();\n replacements[operatorKey] = condition.value;\n }\n });\n\n return replacements;\n};\n\n/**\n * Generates replacements for the given order array.\n *\n * @param order - The order array to generate replacements for.\n * @returns The replacements object.\n */\nexport const generateOrderReplacements = (order: string[]): Record<string, string> => {\n const replacementMap: Record<string, string> = {};\n order.forEach((o) => {\n if (o.startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n const rand = generateRandomString();\n replacementMap[rand] = o.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n } else if (o.substring(1).startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n const rand = generateRandomString();\n replacementMap[rand] = o.substring(1).split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n }\n });\n return replacementMap;\n};\n\n/**\n * Creates a combined replacement map from order and query.\n *\n * @param order - The order array.\n * @param query - The query object.\n * @returns The combined replacements object.\n */\nconst createReplacementMap = (order: string[], query: Record<string, ConditionValue>): Record<string, string> => ({\n ...generateOrderReplacements(order),\n ...generateFilterReplacements(query),\n});\n\nconst formatOrder = ({\n order,\n associationModels = [],\n replacementsMap = {},\n}: { order: string[]; associationModels?: string[]; replacementsMap?: Record<string, string>; }): {\n formattedOrders: SequelizeOrder[];\n orderScopes: (string | { method: [string, { replacementsMap: Record<string, string>; scopeValue: Record<string, 'DESC' | 'ASC'>; }]; })[];\n replacementsMap: Record<string, string>;\n} => {\n const formattedOrders: SequelizeOrder[] = [];\n const orderScopesMap = new Map<string, Record<string, 'DESC' | 'ASC'>>();\n order.forEach((o) => {\n if ([o, o.substring(1)].some(t => t.startsWith(CUSTOM_FIELDS_QUERY_PREFIX))) {\n if (!orderScopesMap.has(CUSTOM_FIELDS_SORT_SCOPE)) {\n orderScopesMap.set(CUSTOM_FIELDS_SORT_SCOPE, {});\n }\n const scopeKey = o.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n orderScopesMap.get(CUSTOM_FIELDS_SORT_SCOPE)![scopeKey] = (isOrderDesc(o) ? DESCENDING_KEY : ASCENDING_KEY);\n return;\n }\n const formattedOrder = [extractAttributeNameFromOrder(o, associationModels)];\n const isOrderDescOrder = isOrderDesc(o);\n const isOrderAssociation = isAttributeByAssociation(isOrderDescOrder\n ? o.split(ORDER_PREFIX, 2)[1]\n : o, associationModels);\n if (isOrderAssociation) {\n formattedOrder.push(extractAssociatedAttributeNameFromOrder(o));\n }\n if (isOrderDescOrder) {\n formattedOrder.push(DESCENDING_KEY);\n }\n formattedOrders.push(formattedOrder);\n });\n return {\n formattedOrders,\n replacementsMap,\n orderScopes: Array.from(orderScopesMap.entries()).map(([scopeName, scopeValue]) => {\n /* v8 ignore next 3 */\n if (!scopeValue) {\n return scopeName;\n }\n return {\n method: [scopeName, {\n replacementsMap,\n scopeValue,\n }],\n };\n }),\n };\n};\n\nconst formatPage = (page?: number) => page || PAGE_DEFAULT;\n\nconst formatPerPage = (perPage?: number) => perPage || PER_PAGE_DEFAULT;\n\ninterface Include {\n association?: string;\n model?: string;\n required?: boolean;\n include?: Include[];\n}\n\nconst formatInclude = (include: (string | Include)[], associationsMap: Record<string, any> = {}) => {\n let formattedInclude = include.map((i): any => {\n const includedAssociation = associationsMap[typeof i === 'string' ? i : (i.association || i.model!)];\n return {\n ...(typeof i !== 'string' && i),\n association: includedAssociation,\n required: typeof i === 'string' || i.required !== false,\n ...(typeof i !== 'string' && i.include && {\n include: formatInclude(i.include, includedAssociation?.target?.associations),\n }),\n };\n });\n formattedInclude = formattedInclude.map(({ model: _model, ...i }) => i);\n return formattedInclude;\n};\nconst formatQuery = (query: Record<string, unknown>, associationModels: string[], replacementsMap: Record<string, string>, additionalAllowedAttributes: string[] = []) => {\n const formattedQuery: Record<string, unknown> = {};\n const externalQueryValues: Record<string, unknown> = {};\n const formattedScopeMap = new Map<string, any>();\n\n Object.entries(query).forEach(([queryItemKey, queryItemValue]) => {\n if (queryItemKey.startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n if (!formattedScopeMap.has(CUSTOM_FIELDS_FILTER_SCOPE)) {\n formattedScopeMap.set(CUSTOM_FIELDS_FILTER_SCOPE, {});\n }\n const scopeKey = queryItemKey.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n formattedScopeMap.get(CUSTOM_FIELDS_FILTER_SCOPE)[scopeKey] = parseCustomFieldScopeQueryValue(queryItemValue);\n return;\n }\n if (additionalAllowedAttributes.includes(queryItemKey)) {\n externalQueryValues[queryItemKey] = queryItemValue;\n return;\n }\n const key = isAttributeByAssociation(queryItemKey, associationModels)\n ? wrapAttributeWithOperator(queryItemKey)\n : queryItemKey;\n formattedQuery[key] = queryItemValue;\n });\n\n const formattedScopes = Array.from(formattedScopeMap.entries()).map(([scopeName, scopeValue]) => {\n /* v8 ignore next 3 */\n if (!scopeValue) {\n return scopeName;\n }\n return {\n method: [scopeName, {\n replacementsMap,\n scopeValue,\n }],\n };\n });\n\n return {\n formattedQuery,\n externalQueryValues,\n formattedScopes,\n };\n};\n\nconst formatSearchTerm = (searchTerm: string, attributesToSend: string[], rawAttributes: Record<string, any>) => ({\n $and: searchTerm.split(' ').map(term => ({\n $or: attributesToSend.filter(attrKey => rawAttributes[attrKey].type.key === 'STRING').map(attr => ({\n [attr]: {\n $iLike: `%${term}%`,\n },\n })),\n })),\n});\n\ninterface FormatPayloadData {\n order?: string[];\n page?: number;\n perPage?: number;\n include?: (string | Include)[];\n query?: Record<string, unknown>;\n attributes?: string[] | null;\n searchTerm?: string | null;\n jsonAttributes?: JsonAttributes;\n}\ntype Literal = ReturnType<typeof literal>;\n\ninterface FormattedPayload {\n query: Record<string, unknown>;\n\n externalQueryValues?: Record<string, unknown>;\n attributes: (SequelizeOrder | (string | Literal)[])[] | {\n include: (SequelizeOrder | (string | Literal)[])[];\n };\n order: (SequelizeOrder | Literal)[];\n page: number;\n perPage: number;\n include: any;\n scopes: (string | {\n method: (string | {\n replacementsMap: Record<string, string>;\n scopeValue: any;\n })[];\n })[];\n}\n\nconst formatPayload = ({\n order = [],\n page = PAGE_DEFAULT,\n perPage = PER_PAGE_DEFAULT,\n include = [],\n query = {},\n attributes = null,\n searchTerm = null,\n jsonAttributes = {},\n}: FormatPayloadData, model?: any, options?: FormatPayloadOptions): FormattedPayload => {\n const replacementsMap = createReplacementMap(order, query as Record<string, ConditionValue>);\n const associationModels = Object.keys(model?.associations || {});\n const { formattedOrders, orderScopes } = formatOrder({\n order: [...order, DEFAULT_ORDER],\n associationModels,\n replacementsMap,\n });\n const [filteredFormattedOrder, filteredLiteralAttributes] = getAttributeFromOrder(\n formattedOrders,\n options,\n );\n\n const formattedJsonAttributes = buildJsonAttributes(jsonAttributes);\n const allAttributes = [...filteredLiteralAttributes, ...(attributes ?? []), ...formattedJsonAttributes];\n const formattedAttribute = attributes?.length ? allAttributes : { include: allAttributes };\n\n const formattedInclude = formatInclude(include, model?.associations);\n const formattedPage = formatPage(page);\n const formattedPerPage = formatPerPage(perPage);\n const result = formatQuery(query, associationModels, replacementsMap, options?.additionalAllowedAttributes);\n const { formattedScopes: queryScopes, externalQueryValues } = result;\n let { formattedQuery } = result;\n if (searchTerm && !options?.skipSearchTermFormat) {\n const attributesToSend = attributes?.length ? attributes : Object.keys(model.rawAttributes || {});\n const queryWithSearchTerm = formatSearchTerm(searchTerm, attributesToSend, model.rawAttributes);\n formattedQuery = !formattedQuery || Object.keys(formattedQuery).length === 0 ? queryWithSearchTerm : {\n $and: [\n formattedQuery,\n queryWithSearchTerm,\n ],\n };\n }\n return {\n query: formattedQuery,\n order: filteredFormattedOrder,\n page: formattedPage,\n perPage: formattedPerPage,\n include: formattedInclude,\n scopes: [...queryScopes, ...orderScopes],\n ...(formattedAttribute && { attributes: formattedAttribute }),\n ...(Object.keys(externalQueryValues).length > 0 && { externalQueryValues }),\n };\n};\n\nexport default formatPayload;\n","import {\n ORDER_PREFIX,\n extractAttributeNameFromOrder,\n isOrderDesc,\n throwBadRequestError,\n PAGE_DEFAULT,\n PER_PAGE_DEFAULT,\n PAGE_MIN,\n PER_PAGE_MAX_LIMIT,\n PER_PAGE_MIN_LIMIT,\n isAttributeByAssociation,\n ASSOCIATION_PREFIX,\n ASSOCIATION_PATH_WRAPPER,\n} from '../utils';\nimport { OPERATORS, OPERATOR_PREFIX } from '../operators';\nimport type { MiddlewareValidationOption } from '../middleware';\nimport type { JsonAttributes } from '../formatter/jsonAttributesFormater';\n\nconst validateOperator = (operator: string): boolean => OPERATORS.includes(operator.split(OPERATOR_PREFIX, 2)[1] as typeof OPERATORS[number]);\n\nconst validateQueryAttribute = (\n rawAttribute: string,\n modelAttributes: string[] = [],\n associationModels: string[] = [],\n additionalAllowedAttributes: string[] = [],\n): boolean => {\n const attribute = (rawAttribute.startsWith(\n ASSOCIATION_PATH_WRAPPER,\n ) && rawAttribute.endsWith(\n ASSOCIATION_PATH_WRAPPER,\n )) ? rawAttribute.slice(1, -1) : rawAttribute;\n return [...modelAttributes, ...associationModels].includes(attribute.includes(ASSOCIATION_PREFIX)\n ? attribute.split(ASSOCIATION_PREFIX, 1)[0] : attribute)\n || additionalAllowedAttributes.includes(attribute);\n};\n\nconst validateSingleOrder = (\n currentOrder: string,\n rawAttributes: string[],\n associationModels: string[],\n options: MiddlewareValidationOption = {},\n): void => {\n const isOrderDescOrder = isOrderDesc(currentOrder);\n if (isOrderDescOrder && !currentOrder.startsWith(ORDER_PREFIX)) {\n throwBadRequestError(`${ORDER_PREFIX} must be only at the beginning of the word`);\n }\n const orderStringWithoutDesc = isOrderDescOrder ? currentOrder.split(ORDER_PREFIX, 2)[1] : currentOrder;\n const isOrderAssociation = isAttributeByAssociation(orderStringWithoutDesc, associationModels);\n let formattedOrderString = extractAttributeNameFromOrder(currentOrder, associationModels);\n const isLiteralAttribute = options?.literalAttributes?.map(la => la.attribute)?.includes(orderStringWithoutDesc);\n\n if (!isOrderAssociation && formattedOrderString.includes(ASSOCIATION_PREFIX)) {\n [formattedOrderString] = formattedOrderString.split(ASSOCIATION_PREFIX, 1);\n }\n\n if (!(rawAttributes.includes(formattedOrderString) || isOrderAssociation || isLiteralAttribute)) {\n throwBadRequestError(`${currentOrder} is invalid. isLiteralAttribute: ${isLiteralAttribute}`);\n }\n};\n\nconst validateSingleAttribute = (currentAttribute: string, rawAttributes: string[]): void => {\n if (!rawAttributes.includes(currentAttribute)) {\n throwBadRequestError(`${currentAttribute} is invalid`);\n }\n};\n\nconst validateOrderAttributes = (\n order: string[],\n rawAttributes: string[],\n associationModels: string[] = [],\n options: MiddlewareValidationOption = {},\n): void => {\n order.forEach(o => validateSingleOrder(o, rawAttributes, associationModels, options));\n};\n\nconst validateAttributes = (attributes: string[], rawAttributes: string[]): void => {\n attributes.forEach(a => validateSingleAttribute(a, rawAttributes));\n};\n\nconst validateJsonAttributes = (jsonAttributes: JsonAttributes, rawAttributes: string[]): void => {\n const allAttributes = [\n ...(jsonAttributes.select?.map(s => s.columnName) ?? []),\n ...(jsonAttributes.computed?.map(c => c.columnName) ?? []),\n ];\n\n validateAttributes(allAttributes, rawAttributes);\n};\n\n// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style\nconst validateEnrichments = (enrichments: string[] | { [enrichmentName: string]: { exclude?: string[]; }; }, options?: MiddlewareValidationOption): void => {\n const enrichmentKeys = Array.isArray(enrichments) ? enrichments : Object.keys(enrichments);\n if (!enrichmentKeys?.length) {\n return;\n }\n const invalidEnrichment = enrichmentKeys.find(enrichment => !options?.enrichmentAttributes?.includes(enrichment));\n if (invalidEnrichment) {\n throwBadRequestError(`enrichment attribute ${invalidEnrichment} is invalid`);\n }\n};\n\nconst validateQueryPayload = (query: object, rawAttributes: string[], associationModels: string[] = [], additionalAllowedAttributes: string[] = []): void => {\n Object.entries(query).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n if (value[0] && typeof value[0] === 'object') {\n value.map(v => validateQueryPayload(v, rawAttributes, associationModels, additionalAllowedAttributes));\n }\n } else if (validateOperator(key) || validateQueryAttribute(key, rawAttributes, associationModels, additionalAllowedAttributes)) {\n if (value && typeof value === 'object') {\n validateQueryPayload(value, rawAttributes, [], additionalAllowedAttributes);\n }\n } else {\n throwBadRequestError(`invalid key: ${key}`);\n }\n });\n};\n\nconst validatePagination = ({\n page,\n perPage,\n}: { page: number; perPage: number; }) => {\n if (page < PAGE_MIN) {\n throwBadRequestError('Page must be greater than 0');\n }\n\n if (perPage > PER_PAGE_MAX_LIMIT || perPage < PER_PAGE_MIN_LIMIT) {\n throwBadRequestError(`PerPage must be between ${PER_PAGE_MIN_LIMIT} to ${PER_PAGE_MAX_LIMIT}`);\n }\n};\n\nconst validateIncludePayload = (include: any[], associations: Record<string, any>): void => {\n const associationsKeys = Object.keys(associations);\n include.forEach((i) => {\n validateQueryAttribute(i.model, associationsKeys);\n const target = associations[i.model]?.target;\n if (!target) {\n throwBadRequestError('model not found in associations');\n }\n\n const { rawAttributes } = target;\n const attributeKeys = Object.keys(rawAttributes);\n if (i.where) {\n validateQueryPayload(i.where, attributeKeys);\n }\n if (i.order) {\n validateOrderAttributes(i.order, attributeKeys);\n }\n if (i.attributes) {\n validateAttributes(i.attributes, attributeKeys);\n }\n if (![null, undefined, true, false].includes(i.required)) {\n throwBadRequestError('include.required must be a boolean');\n }\n });\n};\n\ninterface PayloadValidationData {\n query?: object;\n order?: string[];\n attributes?: string[];\n include?: any[];\n page?: number;\n perPage?: number;\n // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style\n enrichments?: string[] | { [enrichmentName: string]: { exclude?: string[]; }; };\n group?: string[];\n jsonAttributes?: {\n select?: { columnName: string; keys: string[]; alias?: string; }[];\n computed?: { columnName: string; action: 'length'; alias: string; }[];\n };\n}\n\nexport const validatePayload = (\n {\n query = {},\n order = [],\n attributes = [],\n include = [],\n page = PAGE_DEFAULT,\n perPage = PER_PAGE_DEFAULT,\n enrichments = [],\n group = [],\n jsonAttributes = {},\n }: PayloadValidationData,\n model?: any,\n options: MiddlewareValidationOption = {},\n): boolean => {\n const rawAttributes = Object.keys(model.rawAttributes);\n const associationModels = Object.keys(model?.associations || {});\n if (!attributes || attributes.length === 0) {\n attributes = rawAttributes;\n } else {\n validateAttributes(attributes, rawAttributes);\n }\n\n validateOrderAttributes(order, rawAttributes, associationModels, options);\n validateQueryPayload(query, rawAttributes, associationModels, options.additionalAllowedAttributes);\n validateEnrichments(enrichments, options);\n validateJsonAttributes(jsonAttributes, rawAttributes);\n\n if (!Array.isArray(group)) {\n throwBadRequestError('group must be an array');\n }\n if (include.length && typeof include === 'object') {\n validateIncludePayload(include, model?.associations);\n } else if (include && typeof include !== 'object') {\n throwBadRequestError('include must be an array');\n }\n\n validatePagination({\n page,\n perPage,\n });\n return true;\n};\n","import type { Handler } from 'express';\nimport Logger, { type LoggerInstanceManager } from '@autofleet/logger';\nimport { BadRequest, handleError } from '@autofleet/errors';\nimport Joi from 'joi';\nimport formatPayload, { type FormatPayloadOptions } from '../formatter';\nimport { validatePayload } from '../validations';\nimport { ComputedActions } from '../formatter/jsonAttributesFormater';\n\nconst {\n object, string, number, any, array, alternatives,\n} = Joi.types();\nconst fallbackLogger = Logger();\n\nconst querySchema = object.keys({\n query: object,\n attributes: array.items(string),\n order: array.items(string),\n page: number,\n perPage: number,\n include: array.items(any),\n searchTerm: string,\n group: array.items(string),\n enrichments: alternatives.try(array.items(string), object.pattern(string, { exclude: array.items(string) })),\n jsonAttributes: Joi.object({\n select: Joi.array().items(\n Joi.object({\n columnName: Joi.string().required(),\n keys: Joi.array().items(Joi.string().required()).required(),\n alias: Joi.string().optional(),\n }),\n ).default([]),\n computed: Joi.array().items(\n Joi.object({\n columnName: Joi.string().required(),\n action: Joi.string().valid(...Object.values(ComputedActions)).required(),\n alias: Joi.string().required(),\n }),\n ).default([]),\n }).default({}),\n});\n\ntype literal = any;\ntype LiteralQuery = (literal | string)[] | literal;\nexport interface LiteralAttribute { attribute: string; literal: LiteralQuery; }\nexport interface MiddlewareValidationOption {\n literalAttributes?: LiteralAttribute[];\n enrichmentAttributes?: string[];\n additionalAllowedAttributes?: string[];\n logger?: LoggerInstanceManager;\n}\n\ntype ReqKeys = 'body' | 'query';\n\nexport const queryValidation = (model: any, data: any, options: MiddlewareValidationOption = {}): void => {\n const {\n query,\n attributes,\n order,\n page,\n perPage,\n include,\n group,\n enrichments,\n jsonAttributes,\n } = data;\n\n const result = querySchema.validate(data);\n if (result.error) {\n throw new BadRequest([result.error]);\n }\n validatePayload({\n query,\n attributes,\n order,\n page,\n perPage,\n include,\n enrichments,\n group,\n jsonAttributes,\n }, model, options);\n};\n\n/** consider using @see {@link queryHandler} directly */\nexport const queryValidationMiddleware = (model: any, options: MiddlewareValidationOption = {}, inner: ReqKeys = 'body'): Handler => (req, res, next): void => {\n try {\n queryValidation(model, req[inner], options);\n next();\n } catch (error) {\n const { query, attributes, order } = req[inner];\n handleError(error as Error, res, {\n logger: options.logger ?? fallbackLogger,\n message: 'error in query middleware',\n payload: {\n error,\n query,\n attributes,\n order,\n },\n });\n }\n};\n\nexport const queryFormat = (model: any, data: any, options: FormatPayloadOptions = {}): void => {\n const {\n order, page, perPage, include, query, attributes, searchTerm, jsonAttributes,\n } = data;\n\n const {\n query: formattedQuery,\n externalQueryValues,\n order: formattedOrder,\n page: formattedPage,\n perPage: formattedPerPage,\n include: formattedInclude,\n scopes: formattedScopes,\n attributes: formattedAttribute,\n } = formatPayload({\n query, order, page, perPage, include, attributes, searchTerm, jsonAttributes,\n }, model, options);\n\n data.query = formattedQuery;\n data.externalQueryValues = externalQueryValues;\n data.order = formattedOrder;\n data.attributes = formattedAttribute;\n data.page = formattedPage;\n data.perPage = formattedPerPage;\n data.include = formattedInclude;\n data.scopes = formattedScopes;\n if (options.includeRawPayload) {\n data.rawPayload = {\n order,\n page,\n perPage,\n include,\n query,\n attributes,\n searchTerm,\n };\n }\n};\n\n/** consider using @see {@link queryHandler} directly */\nexport const queryFormatMiddleware = (model: any, options: FormatPayloadOptions = {}, inner: ReqKeys = 'body'): Handler => (req, _res, next): void => {\n queryFormat(model, req[inner], options);\n next();\n};\n","import type { Handler } from 'express';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport { UnexpectedError, handleError } from '@autofleet/errors';\nimport type formatPayload from '../formatter';\nimport type { FormatPayloadOptions } from '../formatter';\nimport { pick } from '../utils';\nimport { type MiddlewareValidationOption, queryFormat, queryValidation } from '../middleware';\n\ninterface QueryValues extends ReturnType<typeof formatPayload> {\n enrichments?: string[] | Record<string, { exclude: string[]; }>;\n distinct: boolean;\n searchTerm?: string;\n}\n\ninterface QueryHandlerOptions {\n /** The sequelize model too which querying abilities are added. */\n model: any;\n /** Optional settings for validation. */\n validationOptions?: Omit<MiddlewareValidationOption, 'logger'>;\n /** Optional settings for payload formatting */\n formatOptions?: FormatPayloadOptions;\n logger: LoggerInstanceManager;\n /** The name of model to be printed in logs. defaults to `model`s constructor name. */\n modelName?: string;\n /** Sequelize scopes of the model to be used within the query. @example ['userScope'] */\n additionalScopes?: string[];\n /** Callback to allow modifying the query values prior to querying the DB */\n modifyQueryValues?: (queryValues: QueryValues) => QueryValues;\n /** Optional callback to modify endpoint's response based on the DBs response. */\n onRowsRetrieved?: (data: { rows: any[]; count: number; }, queryValues: QueryValues) => any;\n}\n\ntype Asyncify<T extends (...a: any[]) => any> = (...a: Parameters<T>) => Promise<Awaited<ReturnType<T>>>;\n\nexport const queryHandler = ({\n model, logger, validationOptions, formatOptions, modelName = model.constructor?.name, additionalScopes = [], modifyQueryValues, onRowsRetrieved,\n}: QueryHandlerOptions): Asyncify<Handler> => async (req, res) => {\n try {\n queryValidation(model, req.body, { ...validationOptions, logger });\n } catch (error) {\n const payload = pick(req.body as QueryValues, ['query', 'order', 'attributes'] as const);\n handleError(error as Error, res, { logger, message: 'error in query endpoint', payload });\n return;\n }\n try {\n queryFormat(model, req.body, formatOptions);\n\n const queryValues = Object.assign(\n pick(req.body as QueryValues, ['query', 'externalQueryValues', 'order', 'attributes', 'page', 'perPage', 'include', 'scopes', 'enrichments'] as const),\n { distinct: true },\n );\n\n logger.info(`querying ${modelName}`, { queryValues });\n\n const modifiedQuery = modifyQueryValues?.(queryValues) ?? queryValues;\n const {\n scopes = [],\n query: where,\n perPage: limit,\n page,\n enrichments: _enrichments,\n externalQueryValues: _externalQueryValues,\n ...rest\n } = modifiedQuery;\n\n const result = await model.scope([...additionalScopes, ...scopes]).findAndCountAll({\n where,\n limit,\n offset: (page - 1) * limit,\n ...rest,\n });\n\n if (!result.rows.length || !onRowsRetrieved) {\n res.json(result);\n return;\n }\n\n const enrichmentResult = await onRowsRetrieved(result, modifiedQuery);\n\n res.json(enrichmentResult);\n } catch (error) {\n handleError(new UnexpectedError(error as Error), res, { logger, message: `Error while querying ${modelName}`, payload: { query: req.body } });\n }\n};\n","export class BulkerError extends Error {\n public readonly retryable: boolean;\n\n constructor(message: string, retryable = false) {\n super(message);\n this.name = 'BulkerError';\n this.retryable = retryable;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static isBulkerError(err: unknown): err is BulkerError {\n return err instanceof BulkerError;\n }\n\n static wrap(err: unknown, message?: string, retryable = false): BulkerError {\n if (err instanceof BulkerError) {\n return new BulkerError(message ?? err.message, retryable || err.retryable);\n }\n\n const msg = message ? `${message}: ${err instanceof Error ? err.message : String(err)}` : String(err);\n return new BulkerError(msg, retryable);\n }\n\n static retryable(message: string): BulkerError {\n return new BulkerError(message, true);\n }\n\n static nonRetryable(message: string): BulkerError {\n return new BulkerError(message, false);\n }\n}\n\nexport class ValidationError extends BulkerError {\n constructor(message: string, retryable = false) {\n super(message, retryable);\n this.name = 'ValidationError';\n }\n}\n","import { z } from 'zod';\n\nconst getErrorMessage = (keys: string[]) => {\n if (keys.length === 0) return 'No keys provided';\n if (keys.length === 1) return `Key \"${keys[0]}\" is required`;\n return `Exactly one of [${keys.join(', ')}] must be provided`;\n};\n\nexport function oneOfKeys<T extends z.ZodRawShape>(\n shape: T,\n keys: (keyof T)[],\n): z.ZodType<any> {\n return z.object(shape).refine(\n (data: any) => {\n const presentKeys = keys.filter(\n k => data[k as string] !== undefined && data[k as string] !== null,\n );\n return presentKeys.length === 1;\n },\n {\n message: getErrorMessage(keys as string[]),\n },\n );\n}\n\nconst uuidValidation = z.string().uuid();\n\nexport function buildIdentityScopeSchema(keys: readonly string[]): ReturnType<typeof oneOfKeys> {\n const shape = Object.fromEntries(\n keys.map(key => [key, z.union([uuidValidation, z.array(uuidValidation)]).optional()]),\n );\n return oneOfKeys(shape, keys as (keyof typeof shape)[]);\n}\n","import type { Model, ModelStatic } from 'sequelize';\nimport type RabbitMq from '@autofleet/rabbit';\nimport type { z } from 'zod';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\n\nexport type Id = string | number;\n\nexport interface JobMetadata {\n status: string;\n total: number;\n action: string;\n}\n\nexport interface ConsumeOptions {\n enableRabbitTrace?: boolean;\n [key: string]: any;\n}\n\nexport interface JobStatus {\n jobId: string;\n status: string;\n action: string;\n total: number;\n queued: number;\n processed: number;\n succeeded: number;\n failed: number;\n errors: any[];\n createdAt?: string;\n updatedAt?: string;\n duration: {\n startTime: string | null;\n endTime: string | null;\n durationMs: number | null;\n };\n}\n\nexport interface PartialAckResult<Id = string | number> {\n succeeded?: Id[];\n failed?: { id: Id; error?: string; }[];\n}\n\ntype MessageAck = () => Promise<void>;\ntype MessageNack = (err?: Error, requeue?: boolean) => Promise<void>;\ntype MessagePartialAck = (result: PartialAckResult) => Promise<void>;\n\nexport type BulkPerIdHandler<T = any> = (args: {\n jobId?: string; // optional, in case needed for logging\n ids: Id[]; // array of IDs - can be single ID [id] or multiple [id1, id2, ...]\n /* id will be available to use only if consumerBatchSize is 1 */\n id?: Id; // single ID being processed\n payload: T;\n}, ack: MessageAck, nack: MessageNack, partialAck: MessagePartialAck) => Promise<void>;\nexport interface AdditionalIdsHookData<T = any> {\n rawPayload: {\n query: Record<string, any>;\n include?: any[];\n searchTerm: string;\n };\n payload: T;\n}\n\nexport interface BulkRouteOptions<T = any> {\n /** e.g. \"change-state\" - the action identifier sent in request body */\n action: string;\n /** Sequelize model from which to scan IDs */\n model: Model<any, any> | ModelStatic<any> | any;\n modelScopes: string[]; // optional, Sequelize scopes to apply when scanning\n /** Consumer that processes IDs (can handle single ID [id] or multiple [id1, id2, ...]) */\n consumer: BulkPerIdHandler<T>; // required, worker function to process IDs\n /** Consumer batch size - how many IDs to send to consumer at once (default 1) */\n consumerBatchSize?: number; // optional, default 1 (single ID per call)\n consumerOptions?: ConsumeOptions; // optional, RabbitMQ consumer options\n rabbitQueueName?: string; // optional, if using RabbitMQ, the name of the queue to publish to and consume from\n\n payloadSchema?: z.ZodType<T>; // optional Zod schema to validate and type payload structure\n /** Identity Scopes to be provided and are used and validated from the query.query object */\n identityScopes?: string[]; // identity scope fields to validate in payload\n\n // Optional per-route overrides:\n idField?: string; // default: inherited from bulker.defaults.idField\n pageSize?: number; // default: inherited from bulker.defaults.pageSize\n workerConcurrency?: number; // default: inherited\n jobAttempts?: number; // default: inherited\n jobBackoffMs?: number; // default: inherited\n removeOnComplete?: boolean | number; // default: inherited\n removeOnFail?: boolean | number; // default: inherited\n\n /**\n * Hook to provide additional IDs that will be OR-ed with the main query.\n * This is useful when you need to manually fetch additional IDs based on the raw request body.\n * The returned IDs will be included in the where clause using an OR statement.\n *\n * @example\n * additionalIdsHook: async (data) => {\n * const { rawPayload, payload } = reqBody;\n * if (!rawPayload.searchTerm) return [];\n * const filters = rawPayload?.query?.fleetId ? { fleetId: query.query.fleetId } : {};\n * const labelIds = await searchDriversByLabelsValue(rawPayload.searchTerm, filters);\n * const vendorIds = await searchDriversByVendorName(rawPayload.searchTerm, filters);\n * return [...labelIds, ...vendorIds];\n * }\n */\n additionalIdsHook?: (data: AdditionalIdsHookData) => Promise<Id[]>;\n}\n\n// ============================================================================\n// Bulker Initialization Types\n// ============================================================================\n\nexport interface BulkerInit {\n sequelize: any; // Sequelize\n logger: LoggerInstanceManager;\n rabbit: RabbitMq;\n redis: any; // RedisClientType or config object\n getUserId?: () => string | null; // function that returns the user ID for the current request context, or null if not available\n emitEvents?: boolean; // opt-in to enable event emission (default: false)\n defaults?: {\n pageSize?: number; // default 1000\n maxJobsPerUser?: number; // default unlimited (no per-user rate limiting)\n idField?: string; // default \"id\"\n consumerBatchSize?: number; // default 1 (batch size for consumer)\n workerConcurrency?: number; // default 16\n jobTtlSeconds?: number; // default 7 days (for Redis status hash expiry)\n errorLogLimit?: number; // default 10 (max number of error logs to keep per job)\n };\n}\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\nexport interface BulkRouterEvents {\n // Job lifecycle events\n 'job:created': (data: { jobId: string; action: string; total: number; }) => void;\n 'job:started': (data: { jobId: string; action: string; }) => void;\n 'job:queued': (data: { jobId: string; action: string; queued: number; total: number; }) => void;\n 'job:completed': (data: { jobId: string; action: string; processed: number; failed: number; duration: number; }) => void;\n 'job:canceled': (data: { jobId: string; action: string; }) => void;\n 'job:failed': (data: { jobId: string; action: string; error: string; }) => void;\n\n // Item processing events\n 'item:processing': (data: { jobId: string; action: string; id: string | number; }) => void;\n 'item:processed': (data: { jobId: string; action: string; id: string | number; }) => void;\n 'item:failed': (data: { jobId: string; action: string; id: string | number; error: string; }) => void;\n 'item:retrying': (data: { jobId: string; action: string; id: string | number; }) => void;\n\n // Worker events\n 'worker:started': (data: { action: string; queueName: string; }) => void;\n 'worker:error': (data: { action: string; error: string; }) => void;\n\n // Scan events\n 'scan:page': (data: { jobId: string; action: string; pageNumber: number; itemsInPage: number; }) => void;\n}\n\n/**\n * Interface for event emitters with the emitEvent helper method\n */\nexport interface IBulkEventEmitter {\n emitEvent<K extends keyof BulkRouterEvents>(\n event: K,\n ...args: Parameters<BulkRouterEvents[K]>\n ): void;\n}\n\n// ============================================================================\n// Identity Scope Types\n// ============================================================================\n\nexport const DEFAULT_IDENTITY_SCOPES: readonly string[] = [\n 'businessModelId',\n 'fleetId',\n 'demandSourceId',\n 'contextId',\n 'userId',\n 'businessAccountId',\n 'activeBusinessModelId',\n];\n\nexport type DefaultIdentityScope = typeof DEFAULT_IDENTITY_SCOPES[number];\n","import type { Handler } from 'express';\nimport { randomUUID } from 'node:crypto';\nimport type { Model, ModelStatic, WhereOptions } from 'sequelize';\nimport { Op } from 'sequelize';\nimport type RabbitMq from '@autofleet/rabbit';\nimport { z } from 'zod';\nimport type { Bulker } from '.';\nimport { BulkerError } from './Errors';\nimport { queryValidation, queryFormat } from '../../middleware';\nimport { pick } from '../../utils';\nimport { buildIdentityScopeSchema } from './utils/identityScope';\nimport type {\n BulkPerIdHandler,\n BulkRouteOptions,\n Id,\n JobStatus,\n ConsumeOptions,\n PartialAckResult,\n} from './types';\nimport { DEFAULT_IDENTITY_SCOPES } from './types';\n\ntype Asyncify<T extends (...a: any[]) => any> = (...a: Parameters<T>) => Promise<Awaited<ReturnType<T>>>;\n\nexport class BulkRoute<T = any> {\n public readonly action: string;\n\n // TODO: understand why theres an issue with the model type\n private readonly model: ModelStatic<any> | Model<any, any> | any;\n\n private readonly consumer: BulkPerIdHandler<T>;\n\n private readonly idField: string;\n\n private readonly pageSize: number;\n\n private readonly consumerBatchSize: number;\n\n private readonly rabbitQueueName: string | null = null;\n\n private readonly rabbit: RabbitMq | null = null;\n\n private readonly consumerOptions: ConsumeOptions;\n\n private readonly payloadSchema?: z.ZodType<T>;\n\n private readonly identityScopeSchema: z.ZodType<any>;\n\n private readonly modelScopes: string[];\n\n constructor(\n private readonly bulker: Bulker,\n private readonly opts: BulkRouteOptions<T>,\n ) {\n this.action = opts.action;\n this.model = opts.model;\n this.modelScopes = opts.modelScopes ?? [];\n this.consumer = opts.consumer;\n this.rabbit = bulker.rabbit;\n this.rabbitQueueName = opts.rabbitQueueName ?? `bulk-${this.action}-queue`;\n this.consumerOptions = {\n enableRabbitTrace: true, // always enable user tracing to be able to do getUser()\n ...this.opts.consumerOptions,\n };\n this.payloadSchema = opts.payloadSchema;\n\n if (!/^[a-zA-Z0-9-_]+$/.test(this.action)) throw new Error('BulkRoute action must be alphanumeric');\n if (!this.model || typeof this.model.findAll !== 'function' || typeof this.model.count !== 'function') throw new Error('BulkRoute model must be a valid Sequelize model');\n if (typeof this.consumer !== 'function') throw new Error('BulkRoute consumer must be a function');\n if (this.payloadSchema && !(this.payloadSchema instanceof z.ZodType)) throw new Error('BulkRoute payloadSchema must be a Zod schema');\n if (this.modelScopes && !Array.isArray(this.modelScopes)) throw new Error('BulkRoute scopes must be an array of strings');\n if (this.opts.additionalIdsHook && typeof this.opts.additionalIdsHook !== 'function') throw new Error('BulkRoute additionalIdsHook must be a function');\n\n // resolve defaults\n this.idField = opts.idField ?? bulker.defaults.idField;\n this.pageSize = opts.pageSize ?? bulker.defaults.pageSize;\n this.consumerBatchSize = opts.consumerBatchSize ?? bulker.defaults.consumerBatchSize;\n\n const modelAttributes = this.model.rawAttributes || {};\n\n const identityScopes = opts.identityScopes && opts.identityScopes.length > 0 ? opts.identityScopes : DEFAULT_IDENTITY_SCOPES;\n const filteredIdentityScopes = identityScopes.filter(s => !!modelAttributes[s]);\n\n if (filteredIdentityScopes.length === 0) {\n this.bulker.logger.warn(`BulkRoute for action ${this.action} has no valid identityScopes configured - all records will be accessible`);\n }\n\n this.bulker.logger.info(`BulkRoute for action ${this.action} using idField ${this.idField}, pageSize ${this.pageSize},\n consumerBatchSize ${this.consumerBatchSize}, identityScopes: ${filteredIdentityScopes.join(', ')}`);\n\n this.identityScopeSchema = buildIdentityScopeSchema(filteredIdentityScopes);\n\n // Start worker immediately\n this.startRabbitWorker().catch((err) => {\n this.bulker.logger.error(`Failed to start RabbitMQ worker for queue ${this.rabbitQueueName}: ${err.message || err}`);\n });\n }\n\n public bulkHandler: Asyncify<Handler> = async (req, res, next): Promise<any> => {\n try {\n const { query, payload, preview } = req.body ?? {};\n let validatedPayload: T | undefined = payload;\n\n if (payload && this.payloadSchema) {\n try {\n validatedPayload = this.payloadSchema.parse(payload);\n } catch (err) {\n if (err instanceof z.ZodError) {\n return res.status(400).json({\n error: 'invalid_payload',\n details: err.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', '),\n });\n }\n throw err;\n }\n }\n\n try {\n queryValidation(this.model, query, { logger: this.bulker.logger as any });\n } catch (err) {\n return res.status(400).json({ error: 'invalid_query', details: (err as Error).message || err });\n }\n\n const identityScopeResult = this.identityScopeSchema.safeParse(query?.query || {});\n if (!identityScopeResult.success) {\n return res.status(400).json({\n error: 'invalid_identity_scope',\n details: identityScopeResult.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join(', '),\n });\n }\n\n queryFormat(this.model, query, { includeRawPayload: true });\n\n const queryValues = Object.assign(\n pick(query, ['query', 'externalQueryValues', 'order', 'include', 'scopes', 'enrichments'] as const),\n { distinct: true },\n );\n\n const {\n query: where,\n ...rest\n } = queryValues;\n\n let additionalIds: Id[] = [];\n if (this.opts.additionalIdsHook) {\n const { rawPayload } = query;\n try {\n additionalIds = await this.opts.additionalIdsHook({ rawPayload, payload: validatedPayload! });\n this.bulker.logger.info(`additionalIdsHook returned ${additionalIds.length} IDs for action ${this.action}`);\n } catch (err) {\n this.bulker.logger.error(`Error in additionalIdsHook for action ${this.action}: ${(err as Error).message || err}`, { err });\n return res.status(500).json({ error: 'additional_ids_hook_error', details: (err as Error).message || err });\n }\n }\n\n const orClauses: WhereOptions[] = [];\n\n if (where && typeof where === 'object' && Object.keys(where).length > 0) {\n orClauses.push(where as WhereOptions);\n }\n\n if (Array.isArray(additionalIds) && additionalIds.length > 0) {\n orClauses.push({ id: { $in: additionalIds } });\n }\n\n let finalWhere: WhereOptions;\n if (orClauses.length === 0) {\n return res.status(400).json({ error: 'no_query', details: 'No valid query provided to select records' });\n } else if (orClauses.length === 1) {\n const [onlyWhere] = orClauses;\n finalWhere = onlyWhere;\n } else {\n finalWhere = { $or: orClauses };\n }\n this.bulker.logger.info(`Constructed final where clause for action ${this.action}`, { where: finalWhere });\n\n const findData = {\n where: finalWhere,\n ...rest,\n };\n\n // Preview count\n const total = await this.model.scope(this.modelScopes).count({\n ...findData,\n col: this.idField,\n });\n if (preview) {\n return res.json({ estimatedCount: total });\n }\n\n // Create job status in Redis\n const jobId = randomUUID();\n await this.bulker.jobManager.initJob(jobId, {\n status: 'queued',\n total,\n action: this.action,\n });\n\n // Emit job:created event\n this.bulker.emitEvent('job:created', { jobId, action: this.action, total });\n\n // Kick off scanning + enqueuing (async)\n setImmediate(() => {\n this.rabbitScanAndEnqueue(jobId, findData, validatedPayload).catch(err => this.bulker.logger.error(err));\n });\n\n return res.status(202).json({ jobId, estimatedCount: total });\n } catch (e) {\n this.bulker.logger.error(`Error in bulkHandler for action ${this.action}: ${(e as Error).message || e}`, { err: e });\n return next(e);\n }\n };\n\n /** Get user jobs - public method for BulkRouter to use */\n public async getUserJobs(userId: string, limit = 20): Promise<(JobStatus | null)[]> {\n return this.bulker.jobManager.getUserJobs(userId, limit);\n }\n\n /** Helper method to batch IDs into chunks of consumerBatchSize */\n private batchIds(ids: Id[]): Id[][] {\n const batches: Id[][] = [];\n for (let i = 0; i < ids.length; i += this.consumerBatchSize) {\n batches.push(ids.slice(i, i + this.consumerBatchSize));\n }\n return batches;\n }\n\n private async rabbitScanAndEnqueue(jobId: string, findData: any, payload: T | undefined): Promise<void> {\n if (!this.rabbit) throw new Error('RabbitMQ not configured in Bulker');\n const startTime = Date.now().toString();\n if (this.bulker.getUserId) {\n const userId = this.bulker.getUserId();\n if (userId) await this.bulker.jobManager.addUserJob(userId, jobId);\n }\n\n // Batch the job start status and time update\n await this.bulker.jobManager.setJobFields(jobId, {\n status: 'running',\n startTime,\n });\n\n // Emit job:started event\n this.bulker.emitEvent('job:started', { jobId, action: this.action });\n\n let lastId: Id | null = null;\n let queued = 0;\n let pageNumber = 0;\n\n while (true) {\n // check for cancel before fetching the next page\n const st = await this.bulker.jobManager.getJobField(jobId, 'status');\n if (!st || st === 'canceled') {\n this.bulker.logger.info(`Job ${jobId} was canceled, stopping scan and enqueue`);\n break;\n }\n\n const pageWhere = lastId\n ? { [Op.and]: [findData.where, { [this.idField]: { [Op.gt]: lastId } }] }\n : findData.where;\n // add getRows function to overwrite\n const rows: Record<string, any>[] = await this.model.scope(this.modelScopes).findAll({\n where: pageWhere,\n ...pick(findData, ['include', 'order', 'scopes'] as const),\n attributes: [this.idField],\n order: [[this.idField, 'ASC']],\n limit: this.pageSize,\n raw: true,\n subQuery: false,\n });\n if (rows.length === 0) break;\n\n // Batch IDs and enqueue\n let id: Id | undefined;\n const allIds = rows.map(r => r[this.idField]);\n if (this.consumerBatchSize === 1 && allIds.length === 1) {\n id = allIds[0];\n }\n const batches = this.batchIds(allIds);\n const rabbitJobs = batches.map(ids => ({\n jobId, ids, payload, id,\n }));\n this.bulker.logger.info(`Enqueuing ${rabbitJobs.length} batched messages (${allIds.length} total IDs) to RabbitMQ queue ${this.rabbitQueueName}`);\n const publishToRabbitRes = await Promise.allSettled(rabbitJobs.map(j => this.rabbit!.sendToQueue(this.rabbitQueueName!, j)));\n const rejected = publishToRabbitRes.filter(r => r.status === 'rejected');\n if (rejected.length > 0) {\n this.bulker.logger.error(`Failed to enqueue ${rejected.length} messages to RabbitMQ`, { rejected });\n throw new Error(`Failed to enqueue ${rejected.length} messages to RabbitMQ`);\n }\n\n queued += rows.length;\n await this.bulker.jobManager.incrJobField(jobId, 'queued', rows.length);\n lastId = rows[rows.length - 1][this.idField];\n\n // Emit scan:page event\n pageNumber += 1;\n this.bulker.emitEvent('scan:page', {\n jobId,\n action: this.action,\n pageNumber,\n itemsInPage: rows.length,\n });\n\n // Emit job:queued event with progress\n const job = await this.bulker.jobManager.getJob(jobId);\n if (job) {\n this.bulker.emitEvent('job:queued', {\n jobId,\n action: this.action,\n queued,\n total: job.total,\n });\n }\n }\n\n // If no items were queued, mark job as completed immediately\n if (queued === 0) {\n await this.bulker.jobManager.completeEmptyJob(jobId);\n }\n }\n\n public async startRabbitWorker(): Promise<void> {\n this.bulker.logger.info(`Starting RabbitMQ consumer for queue ${this.rabbitQueueName}`);\n\n // Emit worker:started event\n this.bulker.emitEvent('worker:started', {\n action: this.action,\n queueName: this.rabbitQueueName!,\n });\n\n return this.rabbit?.consume(this.rabbitQueueName!, async (msg, ack, nack) => {\n if (!msg) return;\n const { jobId, ids, payload } = msg.content as { jobId: string; ids: Id[]; payload: T; };\n let messageAcked = false;\n const wrappedAck = async () => {\n if (!messageAcked) {\n messageAcked = true;\n await Promise.all([\n this.bulker.jobManager.ack(jobId, ids.length),\n ack(),\n ]);\n // Emit item:processed event for each ID (keep existing behavior)\n for (const id of ids) {\n this.bulker.emitEvent('item:processed', { jobId, action: this.action, id });\n }\n }\n };\n\n const wrappedNack = async (err?: Error, requeue?: boolean) => {\n if (messageAcked) return;\n messageAcked = true;\n\n let requeueFinal = requeue;\n if (requeueFinal === undefined) {\n if (BulkerError.isBulkerError(err)) {\n requeueFinal = err.retryable;\n } else {\n requeueFinal = false; // non-BulkerErrors are considered non-retryable by default\n }\n }\n\n await Promise.all([\n this.bulker.jobManager.nack(jobId, err ? (err.message || String(err)) : 'nacked', { ids }, ids.length),\n nack(null, { skipRetry: !requeueFinal }),\n ]);\n\n // Emit item events for each ID (keep existing behavior)\n for (const id of ids) {\n if (requeueFinal) {\n this.bulker.emitEvent('item:retrying', {\n jobId,\n action: this.action,\n id,\n });\n } else {\n this.bulker.emitEvent('item:failed', {\n jobId,\n action: this.action,\n id,\n error: err ? (err.message || String(err)) : 'nacked',\n });\n }\n }\n };\n\n const wrappedPartialAck = async (result: PartialAckResult) => {\n if (messageAcked) return;\n messageAcked = true;\n\n const succeededIds = result.succeeded ?? [];\n const failedResults = result.failed ?? [];\n\n await Promise.all([\n this.bulker.jobManager.partialAck(jobId, succeededIds.length, failedResults),\n ack(), // Always ack the RabbitMQ message since we've fully processed the batch\n ]);\n\n // Emit item:processed event for succeeded IDs\n for (const id of succeededIds) {\n this.bulker.emitEvent('item:processed', { jobId, action: this.action, id });\n }\n\n // Emit item:failed event for failed IDs\n for (const failedItem of failedResults) {\n this.bulker.emitEvent('item:failed', {\n jobId,\n action: this.action,\n id: failedItem.id,\n error: failedItem.error || 'Item failed',\n });\n }\n };\n\n // If canceled, count as processed but do nothing\n const st = await this.bulker.jobManager.getJobField(jobId, 'status');\n if (!st || st === 'canceled') {\n await this.bulker.jobManager.incrJobField(jobId, 'processed', ids.length);\n return;\n }\n\n try {\n this.bulker.logger.info(`Started processing job ${jobId} action ${this.action} ids amount: ${ids.length}`);\n\n // Emit item:processing event for each ID\n for (const id of ids) {\n this.bulker.emitEvent('item:processing', { jobId, action: this.action, id });\n }\n\n await this.consumer(\n {\n jobId,\n ids,\n id: this.consumerBatchSize === 1 ? ids[0] : undefined,\n payload,\n },\n wrappedAck,\n wrappedNack,\n wrappedPartialAck,\n );\n // If consumer did not ack/nack, we will ack here\n await wrappedAck();\n } catch (err: any) {\n this.bulker.logger.error(`Error processing job ${jobId} action ${this.action}: ${err.message || err}`, { err });\n\n // Emit worker:error event\n this.bulker.emitEvent('worker:error', {\n action: this.action,\n error: err.message || String(err),\n });\n\n // If consumer did not ack/nack, we will nack here\n await wrappedNack(err, false);\n }\n }, this.consumerOptions);\n }\n}\n","import { Router } from 'express';\nimport type { Bulker } from '.';\nimport { BulkRoute } from './BulkRoute';\nimport type { BulkRouteOptions } from './types';\n\nexport default class BulkRouter {\n private readonly bulker: Bulker;\n\n private readonly router: Router;\n\n private routes: BulkRoute<any>[] = [];\n\n private actionsToRouteMap = new Map<string, BulkRoute<any>>();\n\n private staticRoute: string;\n\n constructor(bulker: Bulker, router?: Router, staticRoute = '/bulk-actions') {\n this.bulker = bulker;\n this.router = router ?? Router();\n this.staticRoute = staticRoute;\n this.registerStaticRoutes();\n }\n\n private registerStaticRoutes() {\n // POST /bulk-actions - action-based routing\n this.router.post(this.staticRoute, this.bulkHandler);\n\n // GET /jobs/:id - get job status\n this.router.get('/jobs/:id', this.getJobHandler);\n\n // GET /jobs - get user's recent jobs\n this.router.get('/jobs', this.getMyJobsHandler);\n\n // POST /jobs/:id/cancel - cancel a job\n this.router.post('/jobs/:id/cancel', this.cancelJobHandler);\n }\n\n addAction<T = any>(actionName: string, opts: Omit<BulkRouteOptions<T>, 'action'>): BulkRoute<T> {\n const route = new BulkRoute<T>(this.bulker, {\n action: actionName,\n ...opts,\n });\n this.bulker.logger.info(`Registering action handler: ${actionName}`);\n\n // Map action to route for action-based routing\n if (this.actionsToRouteMap.has(actionName)) {\n throw new Error(`Action \"${actionName}\" is already registered`);\n }\n this.actionsToRouteMap.set(actionName, route);\n this.routes.push(route);\n return route;\n }\n\n private bulkHandler = async (req: any, res: any, next: any) => {\n try {\n const { action } = req.body ?? {};\n\n if (!action) {\n return res.status(400).json({\n error: 'missing_action',\n message: 'Request body must include an \"action\" field',\n });\n }\n\n const route = this.actionsToRouteMap.get(action);\n if (!route) {\n return res.status(404).json({\n error: 'unknown_action',\n message: `Action \"${action}\" is not registered`,\n availableActions: Array.from(this.actionsToRouteMap.keys()),\n });\n }\n\n // Delegate to the specific route handler\n return route.bulkHandler(req, res, next);\n } catch (e) {\n this.bulker.logger.error(`Error in BulkRouter bulkHandler: ${(e as Error).message || e}`, { err: e });\n next(e);\n return null;\n }\n };\n\n private getJobHandler = async (req: any, res: any, next: any) => {\n try {\n if (!req.params.id) return res.status(400).json({ error: 'missing_id' });\n\n const status = await this.bulker.jobManager.getJob(req.params.id);\n if (!status) {\n return res.status(404).json({ error: 'not_found' });\n }\n\n return res.json(status);\n } catch (e) {\n return next(e);\n }\n };\n\n private cancelJobHandler = async (req: any, res: any, next: any) => {\n try {\n if (!req.params.id) return res.status(400).json({ error: 'missing_id' });\n\n const ok = await this.bulker.jobManager.cancelJob(req.params.id);\n if (!ok) {\n return res.status(404).json({ error: 'not_found' });\n }\n\n this.bulker.logger.info(`Job ${req.params.id} cancel requested`);\n return res.json({ ok: true });\n } catch (e) {\n return next(e);\n }\n };\n\n private getMyJobsHandler = async (req: any, res: any, next: any) => {\n try {\n if (!this.bulker.getUserId) {\n return res.status(400).json({\n error: 'user_id_function_not_configured',\n message: 'Bulker instance does not have a getUserId function configured',\n });\n }\n\n const userId = this.bulker.getUserId();\n\n if (!userId) {\n return res.json([]);\n }\n\n const jobs = await this.bulker.jobManager.getUserJobs(userId);\n const filteredJobs = jobs.filter(j => j !== null);\n\n // Sort by creation date (most recent first)\n filteredJobs.sort((a, b) => {\n const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return dateB - dateA;\n });\n\n return res.json(filteredJobs);\n } catch (e) {\n return next(e);\n }\n };\n\n /** Get the configured router instance */\n getRouter(): Router {\n return this.router;\n }\n}\n","// Unified Lua script for atomically updating job status in Redis\n// Handles ack (all succeeded), nack (all failed), and partial ack (mixed results)\n// Takes the job key as KEYS[1]\n// Arguments: succeededCount, failedCount, failedResults (JSON), errorLogLimit, updatedAt\n// Returns the updated processed count\nexport const localPartialAckScript = `\nlocal jobKey = KEYS[1]\nlocal succeededCount = tonumber(ARGV[1]) or 0\nlocal failedCount = tonumber(ARGV[2]) or 0\nlocal failedResults = ARGV[3]\nlocal errorLogLimit = tonumber(ARGV[4])\nlocal updatedAt = ARGV[5]\n\nlocal total = tonumber(redis.call('HGET', jobKey, 'total'))\nif not total then\n return 0\nend\n\n-- Update counters\nlocal totalCount = succeededCount + failedCount\nredis.call('HINCRBY', jobKey, 'succeeded', succeededCount)\nredis.call('HINCRBY', jobKey, 'failed', failedCount)\nlocal processed = redis.call('HINCRBY', jobKey, 'processed', totalCount)\nredis.call('HSET', jobKey, 'updatedAt', updatedAt)\n\n-- Process failed results and add errors\nif failedCount > 0 then\n local errorsJson = redis.call('HGET', jobKey, 'errors') or '[]'\n local errors = cjson.decode(errorsJson)\n local failedData = cjson.decode(failedResults)\n\n for i = 1, #failedData do\n if #errors >= errorLogLimit then\n -- Keep only the last (errorLogLimit - 1) entries\n local newErrors = {}\n for j = #errors - errorLogLimit + 2, #errors do\n table.insert(newErrors, errors[j])\n end\n errors = newErrors\n end\n\n local failedItem = failedData[i]\n local errorData\n -- If id is a table (object), store it as-is (for backwards compat with nack)\n -- Otherwise, wrap simple values in { id: value } structure (for partialAck)\n if type(failedItem.id) == \"table\" then\n errorData = failedItem.id\n else\n errorData = { id = failedItem.id }\n end\n\n local errorEntry = {\n message = failedItem.error or 'Item failed',\n data = errorData\n }\n table.insert(errors, errorEntry)\n end\n\n redis.call('HSET', jobKey, 'errors', cjson.encode(errors))\nend\n\n-- Check if job is complete\nif processed >= total then\n redis.call('HSET', jobKey, 'status', 'completed')\n redis.call('HSET', jobKey, 'endTime', updatedAt)\nend\n\nreturn processed\n`;\n","import type { RedisClientType } from 'redis';\nimport { localPartialAckScript } from './redis-scripts';\nimport type { EmitsBulkEvents } from './events';\nimport type { Id, JobMetadata, JobStatus } from './types';\n\n/**\n * Manages all job-related operations in Redis\n * Centralizes job creation, status updates, cancellation, and user job tracking\n */\nexport class JobManager {\n private eventEmitter?: EmitsBulkEvents;\n\n private readonly redis: RedisClientType;\n\n private readonly defaults: {\n maxJobsPerUser: number;\n jobTtlSeconds: number;\n errorLogLimit: number;\n };\n\n constructor(\n redis: RedisClientType,\n defaults?: {\n maxJobsPerUser?: number;\n jobTtlSeconds?: number;\n errorLogLimit?: number;\n },\n ) {\n this.redis = redis;\n this.defaults = {\n maxJobsPerUser: defaults?.maxJobsPerUser ?? -1, // -1 means unlimited\n jobTtlSeconds: defaults?.jobTtlSeconds ?? 7 * 24 * 3600, // default 7 days\n errorLogLimit: defaults?.errorLogLimit ?? 10, // default 10 errors\n };\n }\n\n /** Set event emitter for emitting job events */\n setEventEmitter(emitter: EmitsBulkEvents): void {\n this.eventEmitter = emitter;\n }\n\n /** Generate Redis key for job */\n static jobKey(jobId: string): string {\n return `bulker:job:${jobId}`;\n }\n\n /** Generate Redis key for user jobs list */\n static userJobsKey(userId: string): string {\n return `bulker:user:${userId}:jobs`;\n }\n\n /**\n * Initialize a new job in Redis\n */\n async initJob(jobId: string, meta: JobMetadata): Promise<void> {\n const now = Date.now().toString();\n const jobKey = JobManager.jobKey(jobId);\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, {\n status: meta.status,\n total: String(meta.total),\n queued: '0',\n processed: '0',\n succeeded: '0',\n failed: '0',\n action: meta.action,\n errors: JSON.stringify([]),\n createdAt: now,\n updatedAt: now,\n startTime: now,\n endTime: '',\n });\n multi.expire(jobKey, this.defaults.jobTtlSeconds);\n\n await multi.exec();\n }\n\n /**\n * Get job status from Redis\n */\n async getJob(jobId: string): Promise<JobStatus | null> {\n const jobKey = JobManager.jobKey(jobId);\n const obj = await this.redis.hGetAll(jobKey);\n\n if (!obj || Object.keys(obj).length === 0) {\n return null;\n }\n\n // Calculate duration\n const startTime = obj.startTime ? Number(obj.startTime) : null;\n const endTime = obj.endTime && obj.endTime !== '' ? Number(obj.endTime) : null;\n const currentTime = Date.now();\n\n const duration = {\n startTime: startTime ? new Date(startTime).toISOString() : null,\n endTime: endTime ? new Date(endTime).toISOString() : null,\n durationMs: startTime ? (endTime || currentTime) - startTime : null,\n };\n\n return {\n jobId,\n status: obj.status,\n action: obj.action,\n total: Number(obj.total ?? 0),\n queued: Number(obj.queued ?? 0),\n processed: Number(obj.processed ?? 0),\n succeeded: Number(obj.succeeded ?? 0),\n failed: Number(obj.failed ?? 0),\n errors: obj.errors ? JSON.parse(obj.errors) : [],\n createdAt: obj.createdAt ? new Date(Number(obj.createdAt)).toISOString() : undefined,\n updatedAt: obj.updatedAt ? new Date(Number(obj.updatedAt)).toISOString() : undefined,\n duration,\n };\n }\n\n /**\n * Set a single field on a job\n */\n async setJobField(jobId: string, field: string, value: string): Promise<boolean> {\n const jobKey = JobManager.jobKey(jobId);\n const exists = await this.redis.exists(jobKey);\n if (!exists) return false;\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, field, value);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n await multi.exec();\n return true;\n }\n\n /**\n * Set multiple fields on a job\n */\n async setJobFields(jobId: string, fields: Record<string, string>): Promise<boolean> {\n const jobKey = JobManager.jobKey(jobId);\n const exists = await this.redis.exists(jobKey);\n if (!exists) return false;\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, fields);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n await multi.exec();\n return true;\n }\n\n /**\n * Increment a counter field on a job\n */\n async incrJobField(jobId: string, field: 'queued' | 'processed' | 'succeeded' | 'failed', by = 1): Promise<number> {\n const jobKey = JobManager.jobKey(jobId);\n\n const multi = this.redis.multi();\n multi.hIncrBy(jobKey, field, by);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n const results = await multi.exec() as [number | null, number][];\n if (results?.[0]?.[0] === null) {\n return results[0][1];\n }\n return 0;\n }\n\n /**\n * Get a single field from a job\n */\n async getJobField(jobId: string, field: string): Promise<string | undefined> {\n const jobKey = JobManager.jobKey(jobId);\n return this.redis.hGet(jobKey, field);\n }\n\n /**\n * Cancel a job\n */\n async cancelJob(jobId: string): Promise<boolean> {\n return this.setJobField(jobId, 'status', 'canceled');\n }\n\n /**\n * Mark job as completed (for jobs with no items to process)\n */\n async completeEmptyJob(jobId: string): Promise<void> {\n const endTime = Date.now().toString();\n await this.setJobFields(jobId, {\n status: 'completed',\n endTime,\n });\n }\n\n /**\n * Handle successful message processing (ack)\n * Internally calls partialAck with all items succeeded\n */\n async ack(jobId: string, count = 1): Promise<void> {\n // All succeeded, no failures\n await this.partialAck(jobId, count, []);\n }\n\n /**\n * Handle failed message processing (nack)\n * Internally calls partialAck with all items failed\n */\n async nack(jobId: string, errorMsg: string, data: { ids: Id[]; }, count = 1): Promise<void> {\n // All failed, no successes\n // Create failed results array with one entry per failed item\n const failedResults: { id: any; error?: string; }[] = [];\n\n const getId = (index: number): any => {\n if (data.ids && Array.isArray(data.ids)) {\n return data.ids[index] ?? `unknown-${index}`;\n }\n return `unknown-${index}`;\n };\n\n for (let i = 0; i < count; i++) {\n failedResults.push({\n id: getId(i),\n error: errorMsg,\n });\n }\n\n await this.partialAck(jobId, 0, failedResults);\n }\n\n /**\n * Handle partial success/failure for a batch of items\n * Uses Redis Lua script for atomic operation\n */\n async partialAck(\n jobId: string,\n succeededCount: number,\n failedResults: { id: any; error?: string; }[],\n ): Promise<void> {\n const jobKey = JobManager.jobKey(jobId);\n await this.redis.eval(localPartialAckScript, {\n keys: [jobKey],\n arguments: [\n String(succeededCount),\n String(failedResults.length),\n JSON.stringify(failedResults),\n String(this.defaults.errorLogLimit),\n Date.now().toString(),\n ],\n });\n\n // Check if job completed and emit event\n const job = await this.getJob(jobId);\n if (job?.status === 'completed') {\n const duration = job.duration.durationMs || 0;\n this.eventEmitter?.emitEvent('job:completed', {\n jobId,\n action: job.action,\n processed: job.processed,\n failed: job.failed,\n duration,\n });\n }\n }\n\n /**\n * Add a job to user's job list\n */\n async addUserJob(userId: string, jobId: string): Promise<void> {\n const userJobsKey = JobManager.userJobsKey(userId);\n const USER_JOB_TTL_SECONDS = 3600; // 1 hour\n\n const multi = this.redis.multi();\n multi.lPush(userJobsKey, jobId);\n if (this.defaults.maxJobsPerUser > 0) {\n multi.lTrim(userJobsKey, 0, this.defaults.maxJobsPerUser - 1);\n }\n multi.expire(userJobsKey, USER_JOB_TTL_SECONDS);\n\n await multi.exec();\n }\n\n /**\n * Remove a job from user's job list\n */\n async removeUserJob(userId: string, jobId: string): Promise<void> {\n const userJobsKey = JobManager.userJobsKey(userId);\n await this.redis.lRem(userJobsKey, 0, jobId);\n }\n\n /**\n * Get all jobs for a user\n */\n async getUserJobs(userId: string, limit = 20): Promise<(JobStatus | null)[]> {\n const userJobsKey = JobManager.userJobsKey(userId);\n const jobIds = await this.redis.lRange(userJobsKey, 0, limit - 1);\n\n const jobs = await Promise.all(\n jobIds.map(jobId => this.getJob(jobId)),\n );\n\n return jobs;\n }\n}\n","import type { Sequelize } from 'sequelize';\nimport { createClient, type RedisClientType } from 'redis';\nimport type { Router } from 'express';\nimport type rabbit from '@autofleet/rabbit';\nimport BulkRouter from './BulkRouter';\nimport { JobManager } from './JobManager';\nimport type { BulkEventEmitter, BulkRouterEvents, EmitsBulkEvents } from './events';\nimport { EventEmitter } from 'node:events';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\n\nconst NOOP_EMITTER: EmitsBulkEvents = {\n emitEvent: () => {\n // no-op\n },\n};\n\nexport interface BulkerInit {\n sequelize: Sequelize;\n logger: LoggerInstanceManager;\n rabbit: rabbit;\n redis: RedisClientType | ReturnType<typeof createClient> | { host: string; port?: number; password?: string; db?: number; };\n getUserId?: () => string | null; // function that returns the user ID for the current request context, or null if not available\n emitEvents?: boolean; // opt-in to enable event emission (default: false)\n defaults?: {\n pageSize?: number; // default 1000\n maxJobsPerUser?: number; // default unlimited (no per-user rate limiting)\n idField?: string; // default \"id\"\n consumerBatchSize?: number; // default 1 (batch size for consumer)\n workerConcurrency?: number; // default 16\n jobTtlSeconds?: number; // default 7 days (for Redis status hash expiry)\n errorLogLimit?: number; // default 10 (max number of error logs to keep per job)\n };\n}\n\nexport class Bulker extends EventEmitter implements BulkEventEmitter {\n public readonly sequelize: Sequelize;\n\n public readonly logger: BulkerInit['logger'];\n\n public readonly redis: RedisClientType;\n\n public readonly rabbit: rabbit;\n\n public readonly defaults: Required<NonNullable<BulkerInit['defaults']>>;\n\n public readonly jobManager: JobManager;\n\n private readonly bulkRouters: BulkRouter[] = [];\n\n public getUserId: BulkerInit['getUserId'];\n\n public readonly eventsEnabled: boolean;\n\n constructor(init: BulkerInit) {\n super();\n this.sequelize = init.sequelize;\n this.logger = init.logger;\n this.rabbit = init.rabbit;\n this.eventsEnabled = init.emitEvents ?? false;\n if (init.redis && typeof (init.redis as any).connect === 'function') {\n this.redis = init.redis as RedisClientType;\n if (!(this.redis.isOpen)) {\n this.redis.connect().catch((err) => {\n this.logger.error('Error connecting to Redis:', err);\n });\n }\n } else {\n const config = init.redis as { host: string; port?: number; password?: string; db?: number; };\n this.redis = createClient({\n socket: {\n host: config.host,\n port: config.port,\n },\n commandsQueueMaxLength: 5000,\n disableOfflineQueue: false,\n password: config.password,\n database: config.db,\n });\n this.redis.connect().catch((err) => {\n this.logger.error('Error connecting to Redis:', err);\n });\n }\n this.getUserId = init.getUserId ?? (() => null);\n\n this.defaults = {\n maxJobsPerUser: init.defaults?.maxJobsPerUser ?? -1,\n pageSize: init.defaults?.pageSize ?? 1000,\n idField: init.defaults?.idField ?? 'id',\n consumerBatchSize: init.defaults?.consumerBatchSize ?? 1,\n workerConcurrency: init.defaults?.workerConcurrency ?? 16,\n jobTtlSeconds: init.defaults?.jobTtlSeconds ?? 7 * 24 * 3600,\n errorLogLimit: init.defaults?.errorLogLimit ?? 10,\n };\n\n // Initialize JobManager\n this.jobManager = new JobManager(this.redis, {\n maxJobsPerUser: this.defaults.maxJobsPerUser,\n jobTtlSeconds: this.defaults.jobTtlSeconds,\n errorLogLimit: this.defaults.errorLogLimit,\n });\n\n // Set event emitter reference in JobManager\n this.jobManager.setEventEmitter(this.eventsEnabled ? {\n emitEvent: this.emitEvent.bind(this),\n } : NOOP_EMITTER);\n }\n\n pingRedis(): Promise<string> {\n return this.redis.ping();\n }\n\n createBulkRouter(router?: Router, staticRoute?: string): BulkRouter {\n const bulkRouter = new BulkRouter(this, router, staticRoute);\n this.bulkRouters.push(bulkRouter);\n return bulkRouter;\n }\n\n /**\n * Emit event only if events are enabled.\n * Centralizes the eventsEnabled check to avoid repetition.\n */\n emitEvent<K extends keyof BulkRouterEvents>(\n event: K,\n ...args: Parameters<BulkRouterEvents[K]>\n ): void {\n if (this.eventsEnabled) {\n this.emit(event, ...args);\n }\n }\n}\n\nexport { BulkerError } from './Errors';\nexport { BulkRoute } from './BulkRoute';\nexport { JobManager } from './JobManager';\nexport { type BulkEventEmitter } from './events';\nexport { z } from 'zod';\n\nexport {\n type BulkRouteOptions,\n type JobMetadata,\n type JobStatus,\n type BulkRouterEvents,\n type IBulkEventEmitter,\n type AdditionalIdsHookData,\n type BulkPerIdHandler,\n} from './types';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["DefaultOp","Op","order","replacements: Record<string, string>","replacementMap: Record<string, string>","formattedOrders: SequelizeOrder[]","formattedQuery: Record<string, unknown>","externalQueryValues: Record<string, unknown>","formatPayload","z","DEFAULT_IDENTITY_SCOPES: readonly string[]","bulker: Bulker","opts: BulkRouteOptions<T>","validatedPayload: T | undefined","z","additionalIds: Id[]","orClauses: WhereOptions[]","finalWhere: WhereOptions","batches: Id[][]","lastId: Id | null","rows: Record<string, any>[]","id: Id | undefined","err: any","failedResults: { id: any; error?: string; }[]","NOOP_EMITTER: EmitsBulkEvents"],"sources":["../src/operators/index.ts","../src/utils.ts","../src/formatter/jsonAttributesFormater.ts","../src/formatter/index.ts","../src/validations/index.ts","../src/middleware/index.ts","../src/handler/index.ts","../src/bulker/src/Errors.ts","../src/bulker/src/utils/identityScope.ts","../src/bulker/src/types.ts","../src/bulker/src/BulkRoute.ts","../src/bulker/src/BulkRouter.ts","../src/bulker/src/redis-scripts.ts","../src/bulker/src/JobManager.ts","../src/bulker/src/index.ts"],"sourcesContent":["import { Op as DefaultOp } from 'sequelize';\n\nexport const OPERATORS = [\n 'eq',\n 'ne',\n 'gte',\n 'gt',\n 'lte',\n 'lt',\n 'not',\n 'in',\n 'notIn',\n 'is',\n 'like',\n 'iLike',\n 'notLike',\n 'between',\n 'and',\n 'or',\n 'overlap',\n 'contains',\n] as const;\n\nexport const OPERATOR_PREFIX = '$';\n\nexport const OPERATORS_TO_SQL = {\n $eq: '=',\n $ne: '!=',\n $gte: '>=',\n $gt: '>',\n $lte: '<=',\n $lt: '<',\n $not: 'NOT',\n $in: 'IN',\n $notIn: 'NOT IN',\n $is: 'IS',\n $like: 'LIKE',\n $iLike: 'ILIKE',\n $notLike: 'NOT LIKE',\n $and: 'AND',\n $or: 'OR',\n};\n\nexport const formatOperators = (sequelize: { Op: typeof DefaultOp; } = { Op: DefaultOp }): Record<string, symbol> => {\n const { Op } = sequelize;\n return Object.fromEntries(OPERATORS.map(o => [`${OPERATOR_PREFIX + o}`, Op[o]]));\n};\n","import { randomInt } from 'node:crypto';\nimport { BadRequest } from '@autofleet/errors';\nimport { OPERATOR_PREFIX } from './operators';\n\nexport const ORDER_PREFIX = '-';\nexport const ASSOCIATION_PREFIX = '.';\nexport const ASSOCIATION_PATH_WRAPPER = '$';\nexport const PER_PAGE_DEFAULT = 20;\nexport const PAGE_DEFAULT = 1;\nexport const PER_PAGE_MAX_LIMIT = 100;\nexport const PER_PAGE_MIN_LIMIT = 1;\nexport const PAGE_MIN = 1;\n\nexport const wrapAttributeWithOperator = (attribute: string) => `${OPERATOR_PREFIX}${attribute}${OPERATOR_PREFIX}`;\nexport const isAttributeByAssociation = (attributeName: string, associatedModels: string[]): boolean => attributeName.includes(ASSOCIATION_PREFIX)\n && associatedModels.includes(attributeName.split(ASSOCIATION_PREFIX, 1)[0]);\n\nexport const extractAttributeNameFromOrder = (order: string, associationModels: string[]): string => {\n let formattedOrder = order;\n if (order.includes(ORDER_PREFIX)) {\n [, formattedOrder] = formattedOrder.split(ORDER_PREFIX, 2);\n }\n if (isAttributeByAssociation(formattedOrder, associationModels)) {\n [formattedOrder] = formattedOrder.split(ASSOCIATION_PREFIX, 1);\n }\n return formattedOrder;\n};\n\nexport const isOrderDesc = (order: string): boolean => order.includes(ORDER_PREFIX);\n\nexport const throwBadRequestError = (message: string): never => {\n throw new BadRequest([new Error(message)]);\n};\n\nexport const extractAssociatedAttributeNameFromOrder = (order: string): string => order.split(ASSOCIATION_PREFIX, 2)[1];\n\nexport const generateRandomString = (length = 5): string => {\n const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n return Array.from({ length }, () => characters.charAt(randomInt(characters.length))).join('');\n};\n\nexport const pick = <T extends object, K extends keyof T = keyof T>(\n obj: T,\n keys: K[],\n): Pick<T, K> => Object.fromEntries(keys.map(key => [key, obj[key]])) as Pick<T, K>;\n","import { literal } from 'sequelize';\n\nexport const ComputedActions = {\n length: 'length',\n} as const;\n\ninterface JsonSelectAttribute {\n columnName: string;\n keys: string[];\n alias?: string;\n}\n\ninterface JsonComputedAttribute {\n columnName: string;\n action: keyof typeof ComputedActions;\n alias: string;\n}\n\nexport interface JsonAttributes {\n select?: JsonSelectAttribute[];\n computed?: JsonComputedAttribute[];\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/(?!^)[A-Z]/g, letter => `_${letter.toLowerCase()}`);\n}\n\n/**\n * Builds a computed attribute based on the action specified.\n * Currently, supports 'length' action which calculates the length of a JSON array.\n *\n * @param {JsonComputedAttribute} computedAttribute - The computed attribute definition.\n * @returns {Array} An array containing the SQL literal and the alias for the computed attribute.\n */\nfunction buildComputedAttribute(computedAttribute: JsonComputedAttribute) {\n switch (computedAttribute.action) {\n case ComputedActions.length: {\n // Generates: jsonb_array_length(column)\n // Returns the length of the JSON array (e.g., [\"a\", \"b\"] -> 2)\n // Note: Expects the column to be a JSON array\n const sql = `jsonb_array_length(${toSnakeCase(computedAttribute.columnName)})`;\n return [literal(sql), computedAttribute.alias];\n }\n default:\n computedAttribute.action satisfies never;\n return [];\n }\n}\n\nfunction buildComputedAttributes(computed: JsonComputedAttribute[]) {\n return computed.map(attr => buildComputedAttribute(attr));\n}\n\n/**\n * Builds a list of SQL select attributes using json_build_object\n * to extract specific keys from a JSONB column.\n *\n * Example output:\n * SELECT json_build_object('a', column -> 'a', 'b', column -> 'b') AS alias\n */\nfunction buildSelectAttributes(select: JsonSelectAttribute[]) {\n return select.map((attr) => {\n const columnNameSnake = toSnakeCase(attr.columnName);\n const sql = `json_build_object(${attr.keys\n .map(k => `'${k}', ${columnNameSnake} -> '${k}'`)\n .join(', ')})`;\n const alias = attr.alias || attr.columnName;\n return [literal(sql), alias];\n });\n}\n\n/**\n * Return a single array combining selected and computed attributes.\n * Format: [[literal, alias], [literal, alias], ...]\n */\nexport default function buildJsonAttributes({ select = [], computed = [] }: JsonAttributes = {}): (string | ReturnType<typeof literal>)[][] {\n const selectAttributes = buildSelectAttributes(select);\n const computedAttributes = buildComputedAttributes(computed);\n\n return [...selectAttributes, ...computedAttributes];\n}\n","import type { literal } from 'sequelize';\nimport { customFields } from '@autofleet/common-types';\nimport {\n extractAssociatedAttributeNameFromOrder,\n extractAttributeNameFromOrder, generateRandomString,\n isAttributeByAssociation,\n isOrderDesc, ORDER_PREFIX,\n PAGE_DEFAULT,\n PER_PAGE_DEFAULT, wrapAttributeWithOperator,\n} from '../utils';\nimport type { LiteralAttribute } from '../middleware';\nimport { OPERATORS_TO_SQL } from '../operators';\nimport buildJsonAttributes, { type JsonAttributes } from './jsonAttributesFormater';\n\nconst DEFAULT_ORDER = 'id';\nconst DESCENDING_KEY = 'DESC';\nconst ASCENDING_KEY = 'ASC';\nconst CUSTOM_FIELDS_QUERY_PREFIX = 'customFields.';\nconst { CUSTOM_FIELDS_FILTER_SCOPE, CUSTOM_FIELDS_SORT_SCOPE } = customFields;\ntype OrderItem = string | [string, string];\ntype SequelizeOrder = string | OrderItem[];\nexport interface FormatPayloadOptions {\n includeRawPayload?: boolean;\n literalAttributes?: LiteralAttribute[];\n DBFormatter?: typeof literal;\n skipSearchTermFormat?: boolean;\n additionalAllowedAttributes?: string[];\n}\n\ninterface ConditionWithOperator {\n operator: string;\n value: string;\n}\nexport type ConditionValue = ConditionWithOperator | ConditionWithOperator[] | string | string[];\n\nconst parseCustomFieldScopeQueryValue = (value: unknown) => {\n if (['string', 'number'].includes(typeof value) || Array.isArray(value)) {\n return value;\n }\n return Object.entries(value as object).map(([operator, conditionValue]) => ({\n operator: OPERATORS_TO_SQL[operator as keyof typeof OPERATORS_TO_SQL],\n value: conditionValue,\n }));\n};\n\nconst getAttributeFromOrder = (order: SequelizeOrder[], options: FormatPayloadOptions = {}): [(SequelizeOrder | Literal)[], SequelizeOrder[]] => {\n const { literalAttributes = [], DBFormatter = undefined } = options;\n const [formattedOrder, attributes] = order.reduce<[(SequelizeOrder | Literal)[], SequelizeOrder[]]>((acc, o) => {\n const [item, orderStyle = 'ASC'] = Array.isArray(o) ? o : [o];\n const found = literalAttributes?.find(obj => obj.attribute === item);\n if (found) {\n const order = DBFormatter ? DBFormatter(`\"${found.attribute}\" ${orderStyle as string}`) : `${found.attribute} ${orderStyle as string}`;\n acc[1].push(found.literal);\n acc[0].push([order as string]);\n } else {\n acc[0].push(o);\n }\n return acc;\n }, [[], []]);\n\n return [formattedOrder, attributes];\n};\n\n/**\n * Generates replacements for the given conditions.\n *\n * @param conditions - The conditions to generate replacements for.\n * @returns The replacements object.\n */\nexport const generateFilterReplacements = (conditions: Record<string, ConditionValue>): Record<string, string> => {\n const replacements: Record<string, string> = {};\n\n Object.entries(conditions).forEach(([key, condition]) => {\n const replacementKey = generateRandomString();\n replacements[replacementKey] = key.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n\n if (Array.isArray(condition)) {\n condition.forEach((value) => {\n const valueKey = generateRandomString();\n replacements[valueKey] = typeof value === 'string' ? value : value.value;\n });\n } else if (typeof condition === 'string' || typeof condition === 'number') {\n const conditionKey = generateRandomString();\n replacements[conditionKey] = condition;\n } else if (condition?.operator) {\n const operatorKey = generateRandomString();\n replacements[operatorKey] = condition.value;\n }\n });\n\n return replacements;\n};\n\n/**\n * Generates replacements for the given order array.\n *\n * @param order - The order array to generate replacements for.\n * @returns The replacements object.\n */\nexport const generateOrderReplacements = (order: string[]): Record<string, string> => {\n const replacementMap: Record<string, string> = {};\n order.forEach((o) => {\n if (o.startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n const rand = generateRandomString();\n replacementMap[rand] = o.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n } else if (o.substring(1).startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n const rand = generateRandomString();\n replacementMap[rand] = o.substring(1).split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n }\n });\n return replacementMap;\n};\n\n/**\n * Creates a combined replacement map from order and query.\n *\n * @param order - The order array.\n * @param query - The query object.\n * @returns The combined replacements object.\n */\nconst createReplacementMap = (order: string[], query: Record<string, ConditionValue>): Record<string, string> => ({\n ...generateOrderReplacements(order),\n ...generateFilterReplacements(query),\n});\n\nconst formatOrder = ({\n order,\n associationModels = [],\n replacementsMap = {},\n}: { order: string[]; associationModels?: string[]; replacementsMap?: Record<string, string>; }): {\n formattedOrders: SequelizeOrder[];\n orderScopes: (string | { method: [string, { replacementsMap: Record<string, string>; scopeValue: Record<string, 'DESC' | 'ASC'>; }]; })[];\n replacementsMap: Record<string, string>;\n} => {\n const formattedOrders: SequelizeOrder[] = [];\n const orderScopesMap = new Map<string, Record<string, 'DESC' | 'ASC'>>();\n order.forEach((o) => {\n if ([o, o.substring(1)].some(t => t.startsWith(CUSTOM_FIELDS_QUERY_PREFIX))) {\n if (!orderScopesMap.has(CUSTOM_FIELDS_SORT_SCOPE)) {\n orderScopesMap.set(CUSTOM_FIELDS_SORT_SCOPE, {});\n }\n const scopeKey = o.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n orderScopesMap.get(CUSTOM_FIELDS_SORT_SCOPE)![scopeKey] = (isOrderDesc(o) ? DESCENDING_KEY : ASCENDING_KEY);\n return;\n }\n const formattedOrder = [extractAttributeNameFromOrder(o, associationModels)];\n const isOrderDescOrder = isOrderDesc(o);\n const isOrderAssociation = isAttributeByAssociation(isOrderDescOrder\n ? o.split(ORDER_PREFIX, 2)[1]\n : o, associationModels);\n if (isOrderAssociation) {\n formattedOrder.push(extractAssociatedAttributeNameFromOrder(o));\n }\n if (isOrderDescOrder) {\n formattedOrder.push(DESCENDING_KEY);\n }\n formattedOrders.push(formattedOrder);\n });\n return {\n formattedOrders,\n replacementsMap,\n orderScopes: Array.from(orderScopesMap.entries()).map(([scopeName, scopeValue]) => {\n /* v8 ignore next 3 */\n if (!scopeValue) {\n return scopeName;\n }\n return {\n method: [scopeName, {\n replacementsMap,\n scopeValue,\n }],\n };\n }),\n };\n};\n\nconst formatPage = (page?: number) => page || PAGE_DEFAULT;\n\nconst formatPerPage = (perPage?: number) => perPage || PER_PAGE_DEFAULT;\n\ninterface Include {\n association?: string;\n model?: string;\n required?: boolean;\n include?: Include[];\n}\n\nconst formatInclude = (include: (string | Include)[], associationsMap: Record<string, any> = {}) => {\n let formattedInclude = include.map((i): any => {\n const includedAssociation = associationsMap[typeof i === 'string' ? i : (i.association || i.model!)];\n return {\n ...(typeof i !== 'string' && i),\n association: includedAssociation,\n required: typeof i === 'string' || i.required !== false,\n ...(typeof i !== 'string' && i.include && {\n include: formatInclude(i.include, includedAssociation?.target?.associations),\n }),\n };\n });\n formattedInclude = formattedInclude.map(({ model: _model, ...i }) => i);\n return formattedInclude;\n};\nconst formatQuery = (query: Record<string, unknown>, associationModels: string[], replacementsMap: Record<string, string>, additionalAllowedAttributes: string[] = []) => {\n const formattedQuery: Record<string, unknown> = {};\n const externalQueryValues: Record<string, unknown> = {};\n const formattedScopeMap = new Map<string, any>();\n\n Object.entries(query).forEach(([queryItemKey, queryItemValue]) => {\n if (queryItemKey.startsWith(CUSTOM_FIELDS_QUERY_PREFIX)) {\n if (!formattedScopeMap.has(CUSTOM_FIELDS_FILTER_SCOPE)) {\n formattedScopeMap.set(CUSTOM_FIELDS_FILTER_SCOPE, {});\n }\n const scopeKey = queryItemKey.split(CUSTOM_FIELDS_QUERY_PREFIX, 2)[1];\n formattedScopeMap.get(CUSTOM_FIELDS_FILTER_SCOPE)[scopeKey] = parseCustomFieldScopeQueryValue(queryItemValue);\n return;\n }\n if (additionalAllowedAttributes.includes(queryItemKey)) {\n externalQueryValues[queryItemKey] = queryItemValue;\n return;\n }\n const key = isAttributeByAssociation(queryItemKey, associationModels)\n ? wrapAttributeWithOperator(queryItemKey)\n : queryItemKey;\n formattedQuery[key] = queryItemValue;\n });\n\n const formattedScopes = Array.from(formattedScopeMap.entries()).map(([scopeName, scopeValue]) => {\n /* v8 ignore next 3 */\n if (!scopeValue) {\n return scopeName;\n }\n return {\n method: [scopeName, {\n replacementsMap,\n scopeValue,\n }],\n };\n });\n\n return {\n formattedQuery,\n externalQueryValues,\n formattedScopes,\n };\n};\n\nconst formatSearchTerm = (searchTerm: string, attributesToSend: string[], rawAttributes: Record<string, any>) => ({\n $and: searchTerm.split(' ').map(term => ({\n $or: attributesToSend.filter(attrKey => rawAttributes[attrKey].type.key === 'STRING').map(attr => ({\n [attr]: {\n $iLike: `%${term}%`,\n },\n })),\n })),\n});\n\ninterface FormatPayloadData {\n order?: string[];\n page?: number;\n perPage?: number;\n include?: (string | Include)[];\n query?: Record<string, unknown>;\n attributes?: string[] | null;\n searchTerm?: string | null;\n jsonAttributes?: JsonAttributes;\n}\ntype Literal = ReturnType<typeof literal>;\n\ninterface FormattedPayload {\n query: Record<string, unknown>;\n\n externalQueryValues?: Record<string, unknown>;\n attributes: (SequelizeOrder | (string | Literal)[])[] | {\n include: (SequelizeOrder | (string | Literal)[])[];\n };\n order: (SequelizeOrder | Literal)[];\n page: number;\n perPage: number;\n include: any;\n scopes: (string | {\n method: (string | {\n replacementsMap: Record<string, string>;\n scopeValue: any;\n })[];\n })[];\n}\n\nconst formatPayload = ({\n order = [],\n page = PAGE_DEFAULT,\n perPage = PER_PAGE_DEFAULT,\n include = [],\n query = {},\n attributes = null,\n searchTerm = null,\n jsonAttributes = {},\n}: FormatPayloadData, model?: any, options?: FormatPayloadOptions): FormattedPayload => {\n const replacementsMap = createReplacementMap(order, query as Record<string, ConditionValue>);\n const associationModels = Object.keys(model?.associations || {});\n const { formattedOrders, orderScopes } = formatOrder({\n order: [...order, DEFAULT_ORDER],\n associationModels,\n replacementsMap,\n });\n const [filteredFormattedOrder, filteredLiteralAttributes] = getAttributeFromOrder(\n formattedOrders,\n options,\n );\n\n const formattedJsonAttributes = buildJsonAttributes(jsonAttributes);\n const allAttributes = [...filteredLiteralAttributes, ...(attributes ?? []), ...formattedJsonAttributes];\n const formattedAttribute = attributes?.length ? allAttributes : { include: allAttributes };\n\n const formattedInclude = formatInclude(include, model?.associations);\n const formattedPage = formatPage(page);\n const formattedPerPage = formatPerPage(perPage);\n const result = formatQuery(query, associationModels, replacementsMap, options?.additionalAllowedAttributes);\n const { formattedScopes: queryScopes, externalQueryValues } = result;\n let { formattedQuery } = result;\n if (searchTerm && !options?.skipSearchTermFormat) {\n const attributesToSend = attributes?.length ? attributes : Object.keys(model.rawAttributes || {});\n const queryWithSearchTerm = formatSearchTerm(searchTerm, attributesToSend, model.rawAttributes);\n formattedQuery = !formattedQuery || Object.keys(formattedQuery).length === 0 ? queryWithSearchTerm : {\n $and: [\n formattedQuery,\n queryWithSearchTerm,\n ],\n };\n }\n return {\n query: formattedQuery,\n order: filteredFormattedOrder,\n page: formattedPage,\n perPage: formattedPerPage,\n include: formattedInclude,\n scopes: [...queryScopes, ...orderScopes],\n ...(formattedAttribute && { attributes: formattedAttribute }),\n ...(Object.keys(externalQueryValues).length > 0 && { externalQueryValues }),\n };\n};\n\nexport default formatPayload;\n","import {\n ORDER_PREFIX,\n extractAttributeNameFromOrder,\n isOrderDesc,\n throwBadRequestError,\n PAGE_DEFAULT,\n PER_PAGE_DEFAULT,\n PAGE_MIN,\n PER_PAGE_MAX_LIMIT,\n PER_PAGE_MIN_LIMIT,\n isAttributeByAssociation,\n ASSOCIATION_PREFIX,\n ASSOCIATION_PATH_WRAPPER,\n} from '../utils';\nimport { OPERATORS, OPERATOR_PREFIX } from '../operators';\nimport type { MiddlewareValidationOption } from '../middleware';\nimport type { JsonAttributes } from '../formatter/jsonAttributesFormater';\n\nconst validateOperator = (operator: string): boolean => OPERATORS.includes(operator.split(OPERATOR_PREFIX, 2)[1] as typeof OPERATORS[number]);\n\nconst validateQueryAttribute = (\n rawAttribute: string,\n modelAttributes: string[] = [],\n associationModels: string[] = [],\n additionalAllowedAttributes: string[] = [],\n): boolean => {\n const attribute = (rawAttribute.startsWith(\n ASSOCIATION_PATH_WRAPPER,\n ) && rawAttribute.endsWith(\n ASSOCIATION_PATH_WRAPPER,\n )) ? rawAttribute.slice(1, -1) : rawAttribute;\n return [...modelAttributes, ...associationModels].includes(attribute.includes(ASSOCIATION_PREFIX)\n ? attribute.split(ASSOCIATION_PREFIX, 1)[0] : attribute)\n || additionalAllowedAttributes.includes(attribute);\n};\n\nconst validateSingleOrder = (\n currentOrder: string,\n rawAttributes: string[],\n associationModels: string[],\n options: MiddlewareValidationOption = {},\n): void => {\n const isOrderDescOrder = isOrderDesc(currentOrder);\n if (isOrderDescOrder && !currentOrder.startsWith(ORDER_PREFIX)) {\n throwBadRequestError(`${ORDER_PREFIX} must be only at the beginning of the word`);\n }\n const orderStringWithoutDesc = isOrderDescOrder ? currentOrder.split(ORDER_PREFIX, 2)[1] : currentOrder;\n const isOrderAssociation = isAttributeByAssociation(orderStringWithoutDesc, associationModels);\n let formattedOrderString = extractAttributeNameFromOrder(currentOrder, associationModels);\n const isLiteralAttribute = options?.literalAttributes?.map(la => la.attribute)?.includes(orderStringWithoutDesc);\n\n if (!isOrderAssociation && formattedOrderString.includes(ASSOCIATION_PREFIX)) {\n [formattedOrderString] = formattedOrderString.split(ASSOCIATION_PREFIX, 1);\n }\n\n if (!(rawAttributes.includes(formattedOrderString) || isOrderAssociation || isLiteralAttribute)) {\n throwBadRequestError(`${currentOrder} is invalid. isLiteralAttribute: ${isLiteralAttribute}`);\n }\n};\n\nconst validateSingleAttribute = (currentAttribute: string, rawAttributes: string[]): void => {\n if (!rawAttributes.includes(currentAttribute)) {\n throwBadRequestError(`${currentAttribute} is invalid`);\n }\n};\n\nconst validateOrderAttributes = (\n order: string[],\n rawAttributes: string[],\n associationModels: string[] = [],\n options: MiddlewareValidationOption = {},\n): void => {\n order.forEach(o => validateSingleOrder(o, rawAttributes, associationModels, options));\n};\n\nconst validateAttributes = (attributes: string[], rawAttributes: string[]): void => {\n attributes.forEach(a => validateSingleAttribute(a, rawAttributes));\n};\n\nconst validateJsonAttributes = (jsonAttributes: JsonAttributes, rawAttributes: string[]): void => {\n const allAttributes = [\n ...(jsonAttributes.select?.map(s => s.columnName) ?? []),\n ...(jsonAttributes.computed?.map(c => c.columnName) ?? []),\n ];\n\n validateAttributes(allAttributes, rawAttributes);\n};\n\n// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style\nconst validateEnrichments = (enrichments: string[] | { [enrichmentName: string]: { exclude?: string[]; }; }, options?: MiddlewareValidationOption): void => {\n const enrichmentKeys = Array.isArray(enrichments) ? enrichments : Object.keys(enrichments);\n if (!enrichmentKeys?.length) {\n return;\n }\n const invalidEnrichment = enrichmentKeys.find(enrichment => !options?.enrichmentAttributes?.includes(enrichment));\n if (invalidEnrichment) {\n throwBadRequestError(`enrichment attribute ${invalidEnrichment} is invalid`);\n }\n};\n\nconst validateQueryPayload = (query: object, rawAttributes: string[], associationModels: string[] = [], additionalAllowedAttributes: string[] = []): void => {\n Object.entries(query).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n if (value[0] && typeof value[0] === 'object') {\n value.map(v => validateQueryPayload(v, rawAttributes, associationModels, additionalAllowedAttributes));\n }\n } else if (validateOperator(key) || validateQueryAttribute(key, rawAttributes, associationModels, additionalAllowedAttributes)) {\n if (value && typeof value === 'object') {\n validateQueryPayload(value, rawAttributes, [], additionalAllowedAttributes);\n }\n } else {\n throwBadRequestError(`invalid key: ${key}`);\n }\n });\n};\n\nconst validatePagination = ({\n page,\n perPage,\n}: { page: number; perPage: number; }) => {\n if (page < PAGE_MIN) {\n throwBadRequestError('Page must be greater than 0');\n }\n\n if (perPage > PER_PAGE_MAX_LIMIT || perPage < PER_PAGE_MIN_LIMIT) {\n throwBadRequestError(`PerPage must be between ${PER_PAGE_MIN_LIMIT} to ${PER_PAGE_MAX_LIMIT}`);\n }\n};\n\nconst validateIncludePayload = (include: any[], associations: Record<string, any>): void => {\n const associationsKeys = Object.keys(associations);\n include.forEach((i) => {\n validateQueryAttribute(i.model, associationsKeys);\n const target = associations[i.model]?.target;\n if (!target) {\n throwBadRequestError('model not found in associations');\n }\n\n const { rawAttributes } = target;\n const attributeKeys = Object.keys(rawAttributes);\n if (i.where) {\n validateQueryPayload(i.where, attributeKeys);\n }\n if (i.order) {\n validateOrderAttributes(i.order, attributeKeys);\n }\n if (i.attributes) {\n validateAttributes(i.attributes, attributeKeys);\n }\n if (![null, undefined, true, false].includes(i.required)) {\n throwBadRequestError('include.required must be a boolean');\n }\n });\n};\n\ninterface PayloadValidationData {\n query?: object;\n order?: string[];\n attributes?: string[];\n include?: any[];\n page?: number;\n perPage?: number;\n // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style\n enrichments?: string[] | { [enrichmentName: string]: { exclude?: string[]; }; };\n group?: string[];\n jsonAttributes?: {\n select?: { columnName: string; keys: string[]; alias?: string; }[];\n computed?: { columnName: string; action: 'length'; alias: string; }[];\n };\n}\n\nexport const validatePayload = (\n {\n query = {},\n order = [],\n attributes = [],\n include = [],\n page = PAGE_DEFAULT,\n perPage = PER_PAGE_DEFAULT,\n enrichments = [],\n group = [],\n jsonAttributes = {},\n }: PayloadValidationData,\n model?: any,\n options: MiddlewareValidationOption = {},\n): boolean => {\n const rawAttributes = Object.keys(model.rawAttributes);\n const associationModels = Object.keys(model?.associations || {});\n if (!attributes || attributes.length === 0) {\n attributes = rawAttributes;\n } else {\n validateAttributes(attributes, rawAttributes);\n }\n\n validateOrderAttributes(order, rawAttributes, associationModels, options);\n validateQueryPayload(query, rawAttributes, associationModels, options.additionalAllowedAttributes);\n validateEnrichments(enrichments, options);\n validateJsonAttributes(jsonAttributes, rawAttributes);\n\n if (!Array.isArray(group)) {\n throwBadRequestError('group must be an array');\n }\n if (include.length && typeof include === 'object') {\n validateIncludePayload(include, model?.associations);\n } else if (include && typeof include !== 'object') {\n throwBadRequestError('include must be an array');\n }\n\n validatePagination({\n page,\n perPage,\n });\n return true;\n};\n","import type { Handler } from 'express';\nimport Logger, { type LoggerInstanceManager } from '@autofleet/logger';\nimport { BadRequest, handleError } from '@autofleet/errors';\nimport Joi from 'joi';\nimport formatPayload, { type FormatPayloadOptions } from '../formatter';\nimport { validatePayload } from '../validations';\nimport { ComputedActions } from '../formatter/jsonAttributesFormater';\n\nconst {\n object, string, number, any, array, alternatives,\n} = Joi.types();\nconst fallbackLogger = Logger();\n\nconst querySchema = object.keys({\n query: object,\n attributes: array.items(string),\n order: array.items(string),\n page: number,\n perPage: number,\n include: array.items(any),\n searchTerm: string,\n group: array.items(string),\n enrichments: alternatives.try(array.items(string), object.pattern(string, { exclude: array.items(string) })),\n jsonAttributes: Joi.object({\n select: Joi.array().items(\n Joi.object({\n columnName: Joi.string().required(),\n keys: Joi.array().items(Joi.string().required()).required(),\n alias: Joi.string().optional(),\n }),\n ).default([]),\n computed: Joi.array().items(\n Joi.object({\n columnName: Joi.string().required(),\n action: Joi.string().valid(...Object.values(ComputedActions)).required(),\n alias: Joi.string().required(),\n }),\n ).default([]),\n }).default({}),\n});\n\ntype literal = any;\ntype LiteralQuery = (literal | string)[] | literal;\nexport interface LiteralAttribute { attribute: string; literal: LiteralQuery; }\nexport interface MiddlewareValidationOption {\n literalAttributes?: LiteralAttribute[];\n enrichmentAttributes?: string[];\n additionalAllowedAttributes?: string[];\n logger?: LoggerInstanceManager;\n}\n\ntype ReqKeys = 'body' | 'query';\n\nexport const queryValidation = (model: any, data: any, options: MiddlewareValidationOption = {}): void => {\n const {\n query,\n attributes,\n order,\n page,\n perPage,\n include,\n group,\n enrichments,\n jsonAttributes,\n } = data;\n\n const result = querySchema.validate(data);\n if (result.error) {\n throw new BadRequest([result.error]);\n }\n validatePayload({\n query,\n attributes,\n order,\n page,\n perPage,\n include,\n enrichments,\n group,\n jsonAttributes,\n }, model, options);\n};\n\n/** consider using @see {@link queryHandler} directly */\nexport const queryValidationMiddleware = (model: any, options: MiddlewareValidationOption = {}, inner: ReqKeys = 'body'): Handler => (req, res, next): void => {\n try {\n queryValidation(model, req[inner], options);\n next();\n } catch (error) {\n const { query, attributes, order } = req[inner];\n handleError(error as Error, res, {\n logger: options.logger ?? fallbackLogger,\n message: 'error in query middleware',\n payload: {\n error,\n query,\n attributes,\n order,\n },\n });\n }\n};\n\nexport const queryFormat = (model: any, data: any, options: FormatPayloadOptions = {}): void => {\n const {\n order, page, perPage, include, query, attributes, searchTerm, jsonAttributes,\n } = data;\n\n const {\n query: formattedQuery,\n externalQueryValues,\n order: formattedOrder,\n page: formattedPage,\n perPage: formattedPerPage,\n include: formattedInclude,\n scopes: formattedScopes,\n attributes: formattedAttribute,\n } = formatPayload({\n query, order, page, perPage, include, attributes, searchTerm, jsonAttributes,\n }, model, options);\n\n data.query = formattedQuery;\n data.externalQueryValues = externalQueryValues;\n data.order = formattedOrder;\n data.attributes = formattedAttribute;\n data.page = formattedPage;\n data.perPage = formattedPerPage;\n data.include = formattedInclude;\n data.scopes = formattedScopes;\n if (options.includeRawPayload) {\n data.rawPayload = {\n order,\n page,\n perPage,\n include,\n query,\n attributes,\n searchTerm,\n };\n }\n};\n\n/** consider using @see {@link queryHandler} directly */\nexport const queryFormatMiddleware = (model: any, options: FormatPayloadOptions = {}, inner: ReqKeys = 'body'): Handler => (req, _res, next): void => {\n queryFormat(model, req[inner], options);\n next();\n};\n","import type { Handler } from 'express';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\nimport { UnexpectedError, handleError } from '@autofleet/errors';\nimport type formatPayload from '../formatter';\nimport type { FormatPayloadOptions } from '../formatter';\nimport { pick } from '../utils';\nimport { type MiddlewareValidationOption, queryFormat, queryValidation } from '../middleware';\n\ninterface QueryValues extends ReturnType<typeof formatPayload> {\n enrichments?: string[] | Record<string, { exclude: string[]; }>;\n distinct: boolean;\n searchTerm?: string;\n}\n\ninterface QueryHandlerOptions {\n /** The sequelize model too which querying abilities are added. */\n model: any;\n /** Optional settings for validation. */\n validationOptions?: Omit<MiddlewareValidationOption, 'logger'>;\n /** Optional settings for payload formatting */\n formatOptions?: FormatPayloadOptions;\n logger: LoggerInstanceManager;\n /** The name of model to be printed in logs. defaults to `model`s constructor name. */\n modelName?: string;\n /** Sequelize scopes of the model to be used within the query. @example ['userScope'] */\n additionalScopes?: string[];\n /** Callback to allow modifying the query values prior to querying the DB */\n modifyQueryValues?: (queryValues: QueryValues) => QueryValues;\n /** Optional callback to modify endpoint's response based on the DBs response. */\n onRowsRetrieved?: (data: { rows: any[]; count: number; }, queryValues: QueryValues) => any;\n}\n\ntype Asyncify<T extends (...a: any[]) => any> = (...a: Parameters<T>) => Promise<Awaited<ReturnType<T>>>;\n\nexport const queryHandler = ({\n model, logger, validationOptions, formatOptions, modelName = model.constructor?.name, additionalScopes = [], modifyQueryValues, onRowsRetrieved,\n}: QueryHandlerOptions): Asyncify<Handler> => async (req, res) => {\n try {\n queryValidation(model, req.body, { ...validationOptions, logger });\n } catch (error) {\n const payload = pick(req.body as QueryValues, ['query', 'order', 'attributes'] as const);\n handleError(error as Error, res, { logger, message: 'error in query endpoint', payload });\n return;\n }\n try {\n queryFormat(model, req.body, formatOptions);\n\n const queryValues = Object.assign(\n pick(req.body as QueryValues, ['query', 'externalQueryValues', 'order', 'attributes', 'page', 'perPage', 'include', 'scopes', 'enrichments'] as const),\n { distinct: true },\n );\n\n logger.info(`querying ${modelName}`, { queryValues });\n\n const modifiedQuery = modifyQueryValues?.(queryValues) ?? queryValues;\n const {\n scopes = [],\n query: where,\n perPage: limit,\n page,\n enrichments: _enrichments,\n externalQueryValues: _externalQueryValues,\n ...rest\n } = modifiedQuery;\n\n const result = await model.scope([...additionalScopes, ...scopes]).findAndCountAll({\n where,\n limit,\n offset: (page - 1) * limit,\n ...rest,\n });\n\n if (!result.rows.length || !onRowsRetrieved) {\n res.json(result);\n return;\n }\n\n const enrichmentResult = await onRowsRetrieved(result, modifiedQuery);\n\n res.json(enrichmentResult);\n } catch (error) {\n handleError(new UnexpectedError(error as Error), res, { logger, message: `Error while querying ${modelName}`, payload: { query: req.body } });\n }\n};\n","export class BulkerError extends Error {\n public readonly retryable: boolean;\n\n constructor(message: string, retryable = false) {\n super(message);\n this.name = 'BulkerError';\n this.retryable = retryable;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static isBulkerError(err: unknown): err is BulkerError {\n return err instanceof BulkerError;\n }\n\n static wrap(err: unknown, message?: string, retryable = false): BulkerError {\n if (err instanceof BulkerError) {\n return new BulkerError(message ?? err.message, retryable || err.retryable);\n }\n\n const msg = message ? `${message}: ${err instanceof Error ? err.message : String(err)}` : String(err);\n return new BulkerError(msg, retryable);\n }\n\n static retryable(message: string): BulkerError {\n return new BulkerError(message, true);\n }\n\n static nonRetryable(message: string): BulkerError {\n return new BulkerError(message, false);\n }\n}\n\nexport class ValidationError extends BulkerError {\n constructor(message: string, retryable = false) {\n super(message, retryable);\n this.name = 'ValidationError';\n }\n}\n","import { z } from 'zod';\n\nconst getErrorMessage = (keys: string[]) => {\n if (keys.length === 0) return 'No keys provided';\n if (keys.length === 1) return `Key \"${keys[0]}\" is required`;\n return `Exactly one of [${keys.join(', ')}] must be provided`;\n};\n\nexport function oneOfKeys<T extends z.ZodRawShape>(\n shape: T,\n keys: (keyof T)[],\n): z.ZodType<any> {\n return z.object(shape).refine(\n (data: any) => {\n const presentKeys = keys.filter(\n k => data[k as string] !== undefined && data[k as string] !== null,\n );\n return presentKeys.length === 1;\n },\n {\n message: getErrorMessage(keys as string[]),\n },\n );\n}\n\nconst uuidValidation = z.string().uuid();\n\nexport function buildIdentityScopeSchema(keys: readonly string[]): ReturnType<typeof oneOfKeys> {\n const shape = Object.fromEntries(\n keys.map(key => [key, z.union([uuidValidation, z.array(uuidValidation)]).optional()]),\n );\n return oneOfKeys(shape, keys as (keyof typeof shape)[]);\n}\n","import type { Model, ModelStatic } from 'sequelize';\nimport type RabbitMq from '@autofleet/rabbit';\nimport type { z } from 'zod';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\n\nexport type Id = string | number;\n\nexport interface JobMetadata {\n status: string;\n total: number;\n action: string;\n}\n\nexport interface ConsumeOptions {\n enableRabbitTrace?: boolean;\n [key: string]: any;\n}\n\nexport interface JobStatus {\n jobId: string;\n status: string;\n action: string;\n total: number;\n queued: number;\n processed: number;\n succeeded: number;\n failed: number;\n errors: any[];\n createdAt?: string;\n updatedAt?: string;\n duration: {\n startTime: string | null;\n endTime: string | null;\n durationMs: number | null;\n };\n}\n\nexport interface PartialAckResult<Id = string | number> {\n succeeded?: Id[];\n failed?: { id: Id; error?: string; }[];\n}\n\ntype MessageAck = () => Promise<void>;\ntype MessageNack = (err?: Error, requeue?: boolean) => Promise<void>;\ntype MessagePartialAck = (result: PartialAckResult) => Promise<void>;\n\nexport type BulkPerIdHandler<T = any> = (args: {\n jobId?: string; // optional, in case needed for logging\n ids: Id[]; // array of IDs - can be single ID [id] or multiple [id1, id2, ...]\n /* id will be available to use only if consumerBatchSize is 1 */\n id?: Id; // single ID being processed\n payload: T;\n}, ack: MessageAck, nack: MessageNack, partialAck: MessagePartialAck) => Promise<void>;\nexport interface AdditionalIdsHookData<T = any> {\n rawPayload: {\n query: Record<string, any>;\n include?: any[];\n searchTerm: string;\n };\n payload: T;\n}\n\nexport interface BulkRouteOptions<T = any> {\n /** e.g. \"change-state\" - the action identifier sent in request body */\n action: string;\n /** Sequelize model from which to scan IDs */\n model: Model<any, any> | ModelStatic<any> | any;\n modelScopes: string[]; // optional, Sequelize scopes to apply when scanning\n /** Consumer that processes IDs (can handle single ID [id] or multiple [id1, id2, ...]) */\n consumer: BulkPerIdHandler<T>; // required, worker function to process IDs\n /** Consumer batch size - how many IDs to send to consumer at once (default 1) */\n consumerBatchSize?: number; // optional, default 1 (single ID per call)\n consumerOptions?: ConsumeOptions; // optional, RabbitMQ consumer options\n rabbitQueueName?: string; // optional, if using RabbitMQ, the name of the queue to publish to and consume from\n\n payloadSchema?: z.ZodType<T>; // optional Zod schema to validate and type payload structure\n /** Identity Scopes to be provided and are used and validated from the query.query object */\n identityScopes?: string[]; // identity scope fields to validate in payload\n\n // Optional per-route overrides:\n idField?: string; // default: inherited from bulker.defaults.idField\n pageSize?: number; // default: inherited from bulker.defaults.pageSize\n workerConcurrency?: number; // default: inherited\n jobAttempts?: number; // default: inherited\n jobBackoffMs?: number; // default: inherited\n removeOnComplete?: boolean | number; // default: inherited\n removeOnFail?: boolean | number; // default: inherited\n\n /**\n * Hook to provide additional IDs that will be OR-ed with the main query.\n * This is useful when you need to manually fetch additional IDs based on the raw request body.\n * The returned IDs will be included in the where clause using an OR statement.\n *\n * @example\n * additionalIdsHook: async (data) => {\n * const { rawPayload, payload } = reqBody;\n * if (!rawPayload.searchTerm) return [];\n * const filters = rawPayload?.query?.fleetId ? { fleetId: query.query.fleetId } : {};\n * const labelIds = await searchDriversByLabelsValue(rawPayload.searchTerm, filters);\n * const vendorIds = await searchDriversByVendorName(rawPayload.searchTerm, filters);\n * return [...labelIds, ...vendorIds];\n * }\n */\n additionalIdsHook?: (data: AdditionalIdsHookData) => Promise<Id[]>;\n}\n\n// ============================================================================\n// Bulker Initialization Types\n// ============================================================================\n\nexport interface BulkerInit {\n sequelize: any; // Sequelize\n logger: LoggerInstanceManager;\n rabbit: RabbitMq;\n redis: any; // RedisClientType or config object\n getUserId?: () => string | null; // function that returns the user ID for the current request context, or null if not available\n emitEvents?: boolean; // opt-in to enable event emission (default: false)\n defaults?: {\n pageSize?: number; // default 1000\n maxJobsPerUser?: number; // default unlimited (no per-user rate limiting)\n idField?: string; // default \"id\"\n consumerBatchSize?: number; // default 1 (batch size for consumer)\n workerConcurrency?: number; // default 16\n jobTtlSeconds?: number; // default 7 days (for Redis status hash expiry)\n errorLogLimit?: number; // default 10 (max number of error logs to keep per job)\n };\n}\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\nexport interface BulkRouterEvents {\n // Job lifecycle events\n 'job:created': (data: { jobId: string; action: string; total: number; }) => void;\n 'job:started': (data: { jobId: string; action: string; }) => void;\n 'job:queued': (data: { jobId: string; action: string; queued: number; total: number; }) => void;\n 'job:completed': (data: { jobId: string; action: string; processed: number; failed: number; duration: number; }) => void;\n 'job:canceled': (data: { jobId: string; action: string; }) => void;\n 'job:failed': (data: { jobId: string; action: string; error: string; }) => void;\n\n // Item processing events\n 'item:processing': (data: { jobId: string; action: string; id: string | number; }) => void;\n 'item:processed': (data: { jobId: string; action: string; id: string | number; }) => void;\n 'item:failed': (data: { jobId: string; action: string; id: string | number; error: string; }) => void;\n 'item:retrying': (data: { jobId: string; action: string; id: string | number; }) => void;\n\n // Worker events\n 'worker:started': (data: { action: string; queueName: string; }) => void;\n 'worker:error': (data: { action: string; error: string; }) => void;\n\n // Scan events\n 'scan:page': (data: { jobId: string; action: string; pageNumber: number; itemsInPage: number; }) => void;\n}\n\n/**\n * Interface for event emitters with the emitEvent helper method\n */\nexport interface IBulkEventEmitter {\n emitEvent<K extends keyof BulkRouterEvents>(\n event: K,\n ...args: Parameters<BulkRouterEvents[K]>\n ): void;\n}\n\n// ============================================================================\n// Identity Scope Types\n// ============================================================================\n\nexport const DEFAULT_IDENTITY_SCOPES: readonly string[] = [\n 'businessModelId',\n 'fleetId',\n 'demandSourceId',\n 'contextId',\n 'userId',\n 'businessAccountId',\n 'activeBusinessModelId',\n];\n\nexport type DefaultIdentityScope = typeof DEFAULT_IDENTITY_SCOPES[number];\n","import type { Handler } from 'express';\nimport { randomUUID } from 'node:crypto';\nimport type { Model, ModelStatic, WhereOptions } from 'sequelize';\nimport { Op } from 'sequelize';\nimport type RabbitMq from '@autofleet/rabbit';\nimport { z } from 'zod';\nimport type { Bulker } from '.';\nimport { BulkerError } from './Errors';\nimport { queryValidation, queryFormat } from '../../middleware';\nimport { pick } from '../../utils';\nimport { buildIdentityScopeSchema } from './utils/identityScope';\nimport type {\n BulkPerIdHandler,\n BulkRouteOptions,\n Id,\n JobStatus,\n ConsumeOptions,\n PartialAckResult,\n} from './types';\nimport { DEFAULT_IDENTITY_SCOPES } from './types';\n\ntype Asyncify<T extends (...a: any[]) => any> = (...a: Parameters<T>) => Promise<Awaited<ReturnType<T>>>;\n\nexport class BulkRoute<T = any> {\n public readonly action: string;\n\n // TODO: understand why theres an issue with the model type\n private readonly model: ModelStatic<any> | Model<any, any> | any;\n\n private readonly consumer: BulkPerIdHandler<T>;\n\n private readonly idField: string;\n\n private readonly pageSize: number;\n\n private readonly consumerBatchSize: number;\n\n private readonly rabbitQueueName: string | null = null;\n\n private readonly rabbit: RabbitMq | null = null;\n\n private readonly consumerOptions: ConsumeOptions;\n\n private readonly payloadSchema?: z.ZodType<T>;\n\n private readonly identityScopeSchema: z.ZodType<any>;\n\n private readonly modelScopes: string[];\n\n constructor(\n private readonly bulker: Bulker,\n private readonly opts: BulkRouteOptions<T>,\n ) {\n this.action = opts.action;\n this.model = opts.model;\n this.modelScopes = opts.modelScopes ?? [];\n this.consumer = opts.consumer;\n this.rabbit = bulker.rabbit;\n this.rabbitQueueName = opts.rabbitQueueName ?? `bulk-${this.action}-queue`;\n this.consumerOptions = {\n enableRabbitTrace: true, // always enable user tracing to be able to do getUser()\n ...this.opts.consumerOptions,\n };\n this.payloadSchema = opts.payloadSchema;\n\n if (!/^[a-zA-Z0-9-_]+$/.test(this.action)) throw new Error('BulkRoute action must be alphanumeric');\n if (!this.model || typeof this.model.findAll !== 'function' || typeof this.model.count !== 'function') throw new Error('BulkRoute model must be a valid Sequelize model');\n if (typeof this.consumer !== 'function') throw new Error('BulkRoute consumer must be a function');\n if (this.payloadSchema && !(this.payloadSchema instanceof z.ZodType)) throw new Error('BulkRoute payloadSchema must be a Zod schema');\n if (this.modelScopes && !Array.isArray(this.modelScopes)) throw new Error('BulkRoute scopes must be an array of strings');\n if (this.opts.additionalIdsHook && typeof this.opts.additionalIdsHook !== 'function') throw new Error('BulkRoute additionalIdsHook must be a function');\n\n // resolve defaults\n this.idField = opts.idField ?? bulker.defaults.idField;\n this.pageSize = opts.pageSize ?? bulker.defaults.pageSize;\n this.consumerBatchSize = opts.consumerBatchSize ?? bulker.defaults.consumerBatchSize;\n\n const modelAttributes = this.model.rawAttributes || {};\n\n const identityScopes = opts.identityScopes && opts.identityScopes.length > 0 ? opts.identityScopes : DEFAULT_IDENTITY_SCOPES;\n const filteredIdentityScopes = identityScopes.filter(s => !!modelAttributes[s]);\n\n if (filteredIdentityScopes.length === 0) {\n this.bulker.logger.warn(`BulkRoute for action ${this.action} has no valid identityScopes configured - all records will be accessible`);\n }\n\n this.bulker.logger.info(`BulkRoute for action ${this.action} using idField ${this.idField}, pageSize ${this.pageSize},\n consumerBatchSize ${this.consumerBatchSize}, identityScopes: ${filteredIdentityScopes.join(', ')}`);\n\n this.identityScopeSchema = buildIdentityScopeSchema(filteredIdentityScopes);\n\n // Start worker immediately\n this.startRabbitWorker().catch((err) => {\n this.bulker.logger.error(`Failed to start RabbitMQ worker for queue ${this.rabbitQueueName}: ${err.message || err}`);\n });\n }\n\n public bulkHandler: Asyncify<Handler> = async (req, res, next): Promise<any> => {\n try {\n const { query, payload, preview } = req.body ?? {};\n let validatedPayload: T | undefined = payload;\n\n if (payload && this.payloadSchema) {\n try {\n validatedPayload = this.payloadSchema.parse(payload);\n } catch (err) {\n if (err instanceof z.ZodError) {\n return res.status(400).json({\n error: 'invalid_payload',\n details: err.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', '),\n });\n }\n throw err;\n }\n }\n\n try {\n queryValidation(this.model, query, { logger: this.bulker.logger as any });\n } catch (err) {\n return res.status(400).json({ error: 'invalid_query', details: (err as Error).message || err });\n }\n\n const identityScopeResult = this.identityScopeSchema.safeParse(query?.query || {});\n if (!identityScopeResult.success) {\n return res.status(400).json({\n error: 'invalid_identity_scope',\n details: identityScopeResult.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join(', '),\n });\n }\n\n queryFormat(this.model, query, { includeRawPayload: true });\n\n const queryValues = Object.assign(\n pick(query, ['query', 'externalQueryValues', 'order', 'include', 'scopes', 'enrichments'] as const),\n { distinct: true },\n );\n\n const {\n query: where,\n ...rest\n } = queryValues;\n\n let additionalIds: Id[] = [];\n if (this.opts.additionalIdsHook) {\n const { rawPayload } = query;\n try {\n additionalIds = await this.opts.additionalIdsHook({ rawPayload, payload: validatedPayload! });\n this.bulker.logger.info(`additionalIdsHook returned ${additionalIds.length} IDs for action ${this.action}`);\n } catch (err) {\n this.bulker.logger.error(`Error in additionalIdsHook for action ${this.action}: ${(err as Error).message || err}`, { err });\n return res.status(500).json({ error: 'additional_ids_hook_error', details: (err as Error).message || err });\n }\n }\n\n const orClauses: WhereOptions[] = [];\n\n if (where && typeof where === 'object' && Object.keys(where).length > 0) {\n orClauses.push(where as WhereOptions);\n }\n\n if (Array.isArray(additionalIds) && additionalIds.length > 0) {\n orClauses.push({ id: { $in: additionalIds } });\n }\n\n let finalWhere: WhereOptions;\n if (orClauses.length === 0) {\n return res.status(400).json({ error: 'no_query', details: 'No valid query provided to select records' });\n } else if (orClauses.length === 1) {\n const [onlyWhere] = orClauses;\n finalWhere = onlyWhere;\n } else {\n finalWhere = { $or: orClauses };\n }\n this.bulker.logger.info(`Constructed final where clause for action ${this.action}`, { where: finalWhere });\n\n const findData = {\n where: finalWhere,\n ...rest,\n };\n\n // Preview count\n const total = await this.model.scope(this.modelScopes).count({\n ...findData,\n col: this.idField,\n });\n if (preview) {\n return res.json({ estimatedCount: total });\n }\n\n // Create job status in Redis\n const jobId = randomUUID();\n await this.bulker.jobManager.initJob(jobId, {\n status: 'queued',\n total,\n action: this.action,\n });\n\n // Emit job:created event\n this.bulker.emitEvent('job:created', { jobId, action: this.action, total });\n\n // Kick off scanning + enqueuing (async)\n setImmediate(() => {\n this.rabbitScanAndEnqueue(jobId, findData, validatedPayload).catch(err => this.bulker.logger.error(err));\n });\n\n return res.status(202).json({ jobId, estimatedCount: total });\n } catch (e) {\n this.bulker.logger.error(`Error in bulkHandler for action ${this.action}: ${(e as Error).message || e}`, { err: e });\n return next(e);\n }\n };\n\n /** Get user jobs - public method for BulkRouter to use */\n public async getUserJobs(userId: string, limit = 20): Promise<(JobStatus | null)[]> {\n return this.bulker.jobManager.getUserJobs(userId, limit);\n }\n\n /** Helper method to batch IDs into chunks of consumerBatchSize */\n private batchIds(ids: Id[]): Id[][] {\n const batches: Id[][] = [];\n for (let i = 0; i < ids.length; i += this.consumerBatchSize) {\n batches.push(ids.slice(i, i + this.consumerBatchSize));\n }\n return batches;\n }\n\n private async rabbitScanAndEnqueue(jobId: string, findData: any, payload: T | undefined): Promise<void> {\n if (!this.rabbit) throw new Error('RabbitMQ not configured in Bulker');\n const startTime = Date.now().toString();\n if (this.bulker.getUserId) {\n const userId = this.bulker.getUserId();\n if (userId) await this.bulker.jobManager.addUserJob(userId, jobId);\n }\n\n // Batch the job start status and time update\n await this.bulker.jobManager.setJobFields(jobId, {\n status: 'running',\n startTime,\n });\n\n // Emit job:started event\n this.bulker.emitEvent('job:started', { jobId, action: this.action });\n\n let lastId: Id | null = null;\n let queued = 0;\n let pageNumber = 0;\n\n while (true) {\n // check for cancel before fetching the next page\n const st = await this.bulker.jobManager.getJobField(jobId, 'status');\n if (!st || st === 'canceled') {\n this.bulker.logger.info(`Job ${jobId} was canceled, stopping scan and enqueue`);\n break;\n }\n\n const pageWhere = lastId\n ? { [Op.and]: [findData.where, { [this.idField]: { [Op.gt]: lastId } }] }\n : findData.where;\n // add getRows function to overwrite\n const rows: Record<string, any>[] = await this.model.scope(this.modelScopes).findAll({\n where: pageWhere,\n ...pick(findData, ['include', 'order', 'scopes'] as const),\n attributes: [this.idField],\n order: [[this.idField, 'ASC']],\n limit: this.pageSize,\n raw: true,\n subQuery: false,\n });\n if (rows.length === 0) break;\n\n // Batch IDs and enqueue\n let id: Id | undefined;\n const allIds = rows.map(r => r[this.idField]);\n if (this.consumerBatchSize === 1 && allIds.length === 1) {\n id = allIds[0];\n }\n const batches = this.batchIds(allIds);\n const rabbitJobs = batches.map(ids => ({\n jobId, ids, payload, id,\n }));\n this.bulker.logger.info(`Enqueuing ${rabbitJobs.length} batched messages (${allIds.length} total IDs) to RabbitMQ queue ${this.rabbitQueueName}`);\n const publishToRabbitRes = await Promise.allSettled(rabbitJobs.map(j => this.rabbit!.sendToQueue(this.rabbitQueueName!, j)));\n const rejected = publishToRabbitRes.filter(r => r.status === 'rejected');\n if (rejected.length > 0) {\n this.bulker.logger.error(`Failed to enqueue ${rejected.length} messages to RabbitMQ`, { rejected });\n throw new Error(`Failed to enqueue ${rejected.length} messages to RabbitMQ`);\n }\n\n queued += rows.length;\n await this.bulker.jobManager.incrJobField(jobId, 'queued', rows.length);\n lastId = rows[rows.length - 1][this.idField];\n\n // Emit scan:page event\n pageNumber += 1;\n this.bulker.emitEvent('scan:page', {\n jobId,\n action: this.action,\n pageNumber,\n itemsInPage: rows.length,\n });\n\n // Emit job:queued event with progress\n const job = await this.bulker.jobManager.getJob(jobId);\n if (job) {\n this.bulker.emitEvent('job:queued', {\n jobId,\n action: this.action,\n queued,\n total: job.total,\n });\n }\n }\n\n // If no items were queued, mark job as completed immediately\n if (queued === 0) {\n await this.bulker.jobManager.completeEmptyJob(jobId);\n }\n }\n\n public async startRabbitWorker(): Promise<void> {\n this.bulker.logger.info(`Starting RabbitMQ consumer for queue ${this.rabbitQueueName}`);\n\n // Emit worker:started event\n this.bulker.emitEvent('worker:started', {\n action: this.action,\n queueName: this.rabbitQueueName!,\n });\n\n return this.rabbit?.consume(this.rabbitQueueName!, async (msg, ack, nack) => {\n if (!msg) return;\n const { jobId, ids, payload } = msg.content as { jobId: string; ids: Id[]; payload: T; };\n let messageAcked = false;\n const wrappedAck = async () => {\n if (!messageAcked) {\n messageAcked = true;\n await Promise.all([\n this.bulker.jobManager.ack(jobId, ids.length),\n ack(),\n ]);\n // Emit item:processed event for each ID (keep existing behavior)\n for (const id of ids) {\n this.bulker.emitEvent('item:processed', { jobId, action: this.action, id });\n }\n }\n };\n\n const wrappedNack = async (err?: Error, requeue?: boolean) => {\n if (messageAcked) return;\n messageAcked = true;\n\n let requeueFinal = requeue;\n if (requeueFinal === undefined) {\n if (BulkerError.isBulkerError(err)) {\n requeueFinal = err.retryable;\n } else {\n requeueFinal = false; // non-BulkerErrors are considered non-retryable by default\n }\n }\n\n await Promise.all([\n this.bulker.jobManager.nack(jobId, err ? (err.message || String(err)) : 'nacked', { ids }, ids.length),\n nack(null, { skipRetry: !requeueFinal }),\n ]);\n\n // Emit item events for each ID (keep existing behavior)\n for (const id of ids) {\n if (requeueFinal) {\n this.bulker.emitEvent('item:retrying', {\n jobId,\n action: this.action,\n id,\n });\n } else {\n this.bulker.emitEvent('item:failed', {\n jobId,\n action: this.action,\n id,\n error: err ? (err.message || String(err)) : 'nacked',\n });\n }\n }\n };\n\n const wrappedPartialAck = async (result: PartialAckResult) => {\n if (messageAcked) return;\n messageAcked = true;\n\n const succeededIds = result.succeeded ?? [];\n const failedResults = result.failed ?? [];\n\n await Promise.all([\n this.bulker.jobManager.partialAck(jobId, succeededIds.length, failedResults),\n ack(), // Always ack the RabbitMQ message since we've fully processed the batch\n ]);\n\n // Emit item:processed event for succeeded IDs\n for (const id of succeededIds) {\n this.bulker.emitEvent('item:processed', { jobId, action: this.action, id });\n }\n\n // Emit item:failed event for failed IDs\n for (const failedItem of failedResults) {\n this.bulker.emitEvent('item:failed', {\n jobId,\n action: this.action,\n id: failedItem.id,\n error: failedItem.error || 'Item failed',\n });\n }\n };\n\n // If canceled, count as processed but do nothing\n const st = await this.bulker.jobManager.getJobField(jobId, 'status');\n if (!st || st === 'canceled') {\n await this.bulker.jobManager.incrJobField(jobId, 'processed', ids.length);\n return;\n }\n\n try {\n this.bulker.logger.info(`Started processing job ${jobId} action ${this.action} ids amount: ${ids.length}`);\n\n // Emit item:processing event for each ID\n for (const id of ids) {\n this.bulker.emitEvent('item:processing', { jobId, action: this.action, id });\n }\n\n await this.consumer(\n {\n jobId,\n ids,\n id: this.consumerBatchSize === 1 ? ids[0] : undefined,\n payload,\n },\n wrappedAck,\n wrappedNack,\n wrappedPartialAck,\n );\n // If consumer did not ack/nack, we will ack here\n await wrappedAck();\n } catch (err: any) {\n this.bulker.logger.error(`Error processing job ${jobId} action ${this.action}: ${err.message || err}`, { err });\n\n // Emit worker:error event\n this.bulker.emitEvent('worker:error', {\n action: this.action,\n error: err.message || String(err),\n });\n\n // If consumer did not ack/nack, we will nack here\n await wrappedNack(err, false);\n }\n }, this.consumerOptions);\n }\n}\n","import { Router } from 'express';\nimport type { Bulker } from '.';\nimport { BulkRoute } from './BulkRoute';\nimport type { BulkRouteOptions } from './types';\n\nexport default class BulkRouter {\n private readonly bulker: Bulker;\n\n private readonly router: Router;\n\n private routes: BulkRoute<any>[] = [];\n\n private actionsToRouteMap = new Map<string, BulkRoute<any>>();\n\n private staticRoute: string;\n\n constructor(bulker: Bulker, router?: Router, staticRoute = '/bulk-actions') {\n this.bulker = bulker;\n this.router = router ?? Router();\n this.staticRoute = staticRoute;\n this.registerStaticRoutes();\n }\n\n private registerStaticRoutes() {\n // POST /bulk-actions - action-based routing\n this.router.post(this.staticRoute, this.bulkHandler);\n\n // GET /jobs/:id - get job status\n this.router.get('/jobs/:id', this.getJobHandler);\n\n // GET /jobs - get user's recent jobs\n this.router.get('/jobs', this.getMyJobsHandler);\n\n // POST /jobs/:id/cancel - cancel a job\n this.router.post('/jobs/:id/cancel', this.cancelJobHandler);\n }\n\n addAction<T = any>(actionName: string, opts: Omit<BulkRouteOptions<T>, 'action'>): BulkRoute<T> {\n const route = new BulkRoute<T>(this.bulker, {\n action: actionName,\n ...opts,\n });\n this.bulker.logger.info(`Registering action handler: ${actionName}`);\n\n // Map action to route for action-based routing\n if (this.actionsToRouteMap.has(actionName)) {\n throw new Error(`Action \"${actionName}\" is already registered`);\n }\n this.actionsToRouteMap.set(actionName, route);\n this.routes.push(route);\n return route;\n }\n\n private bulkHandler = async (req: any, res: any, next: any) => {\n try {\n const { action } = req.body ?? {};\n\n if (!action) {\n return res.status(400).json({\n error: 'missing_action',\n message: 'Request body must include an \"action\" field',\n });\n }\n\n const route = this.actionsToRouteMap.get(action);\n if (!route) {\n return res.status(404).json({\n error: 'unknown_action',\n message: `Action \"${action}\" is not registered`,\n availableActions: Array.from(this.actionsToRouteMap.keys()),\n });\n }\n\n // Delegate to the specific route handler\n return route.bulkHandler(req, res, next);\n } catch (e) {\n this.bulker.logger.error(`Error in BulkRouter bulkHandler: ${(e as Error).message || e}`, { err: e });\n next(e);\n return null;\n }\n };\n\n private getJobHandler = async (req: any, res: any, next: any) => {\n try {\n if (!req.params.id) return res.status(400).json({ error: 'missing_id' });\n\n const status = await this.bulker.jobManager.getJob(req.params.id);\n if (!status) {\n return res.status(404).json({ error: 'not_found' });\n }\n\n return res.json(status);\n } catch (e) {\n return next(e);\n }\n };\n\n private cancelJobHandler = async (req: any, res: any, next: any) => {\n try {\n if (!req.params.id) return res.status(400).json({ error: 'missing_id' });\n\n const ok = await this.bulker.jobManager.cancelJob(req.params.id);\n if (!ok) {\n return res.status(404).json({ error: 'not_found' });\n }\n\n this.bulker.logger.info(`Job ${req.params.id} cancel requested`);\n return res.json({ ok: true });\n } catch (e) {\n return next(e);\n }\n };\n\n private getMyJobsHandler = async (req: any, res: any, next: any) => {\n try {\n if (!this.bulker.getUserId) {\n return res.status(400).json({\n error: 'user_id_function_not_configured',\n message: 'Bulker instance does not have a getUserId function configured',\n });\n }\n\n const userId = this.bulker.getUserId();\n\n if (!userId) {\n return res.json([]);\n }\n\n const jobs = await this.bulker.jobManager.getUserJobs(userId);\n const filteredJobs = jobs.filter(j => j !== null);\n\n // Sort by creation date (most recent first)\n filteredJobs.sort((a, b) => {\n const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;\n const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;\n return dateB - dateA;\n });\n\n return res.json(filteredJobs);\n } catch (e) {\n return next(e);\n }\n };\n\n /** Get the configured router instance */\n getRouter(): Router {\n return this.router;\n }\n}\n","// Unified Lua script for atomically updating job status in Redis\n// Handles ack (all succeeded), nack (all failed), and partial ack (mixed results)\n// Takes the job key as KEYS[1]\n// Arguments: succeededCount, failedCount, failedResults (JSON), errorLogLimit, updatedAt\n// Returns the updated processed count\nexport const localPartialAckScript = `\nlocal jobKey = KEYS[1]\nlocal succeededCount = tonumber(ARGV[1]) or 0\nlocal failedCount = tonumber(ARGV[2]) or 0\nlocal failedResults = ARGV[3]\nlocal errorLogLimit = tonumber(ARGV[4])\nlocal updatedAt = ARGV[5]\n\nlocal total = tonumber(redis.call('HGET', jobKey, 'total'))\nif not total then\n return 0\nend\n\n-- Update counters\nlocal totalCount = succeededCount + failedCount\nredis.call('HINCRBY', jobKey, 'succeeded', succeededCount)\nredis.call('HINCRBY', jobKey, 'failed', failedCount)\nlocal processed = redis.call('HINCRBY', jobKey, 'processed', totalCount)\nredis.call('HSET', jobKey, 'updatedAt', updatedAt)\n\n-- Process failed results and add errors\nif failedCount > 0 then\n local errorsJson = redis.call('HGET', jobKey, 'errors') or '[]'\n local errors = cjson.decode(errorsJson)\n local failedData = cjson.decode(failedResults)\n\n for i = 1, #failedData do\n if #errors >= errorLogLimit then\n -- Keep only the last (errorLogLimit - 1) entries\n local newErrors = {}\n for j = #errors - errorLogLimit + 2, #errors do\n table.insert(newErrors, errors[j])\n end\n errors = newErrors\n end\n\n local failedItem = failedData[i]\n local errorData\n -- If id is a table (object), store it as-is (for backwards compat with nack)\n -- Otherwise, wrap simple values in { id: value } structure (for partialAck)\n if type(failedItem.id) == \"table\" then\n errorData = failedItem.id\n else\n errorData = { id = failedItem.id }\n end\n\n local errorEntry = {\n message = failedItem.error or 'Item failed',\n data = errorData\n }\n table.insert(errors, errorEntry)\n end\n\n redis.call('HSET', jobKey, 'errors', cjson.encode(errors))\nend\n\n-- Check if job is complete\nif processed >= total then\n redis.call('HSET', jobKey, 'status', 'completed')\n redis.call('HSET', jobKey, 'endTime', updatedAt)\nend\n\nreturn processed\n`;\n","import type { RedisClientType } from 'redis';\nimport { localPartialAckScript } from './redis-scripts';\nimport type { EmitsBulkEvents } from './events';\nimport type { Id, JobMetadata, JobStatus } from './types';\n\n/**\n * Manages all job-related operations in Redis\n * Centralizes job creation, status updates, cancellation, and user job tracking\n */\nexport class JobManager {\n private eventEmitter?: EmitsBulkEvents;\n\n private readonly redis: RedisClientType;\n\n private readonly defaults: {\n maxJobsPerUser: number;\n jobTtlSeconds: number;\n errorLogLimit: number;\n };\n\n constructor(\n redis: RedisClientType,\n defaults?: {\n maxJobsPerUser?: number;\n jobTtlSeconds?: number;\n errorLogLimit?: number;\n },\n ) {\n this.redis = redis;\n this.defaults = {\n maxJobsPerUser: defaults?.maxJobsPerUser ?? -1, // -1 means unlimited\n jobTtlSeconds: defaults?.jobTtlSeconds ?? 7 * 24 * 3600, // default 7 days\n errorLogLimit: defaults?.errorLogLimit ?? 10, // default 10 errors\n };\n }\n\n /** Set event emitter for emitting job events */\n setEventEmitter(emitter: EmitsBulkEvents): void {\n this.eventEmitter = emitter;\n }\n\n /** Generate Redis key for job */\n static jobKey(jobId: string): string {\n return `bulker:job:${jobId}`;\n }\n\n /** Generate Redis key for user jobs list */\n static userJobsKey(userId: string): string {\n return `bulker:user:${userId}:jobs`;\n }\n\n /**\n * Initialize a new job in Redis\n */\n async initJob(jobId: string, meta: JobMetadata): Promise<void> {\n const now = Date.now().toString();\n const jobKey = JobManager.jobKey(jobId);\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, {\n status: meta.status,\n total: String(meta.total),\n queued: '0',\n processed: '0',\n succeeded: '0',\n failed: '0',\n action: meta.action,\n errors: JSON.stringify([]),\n createdAt: now,\n updatedAt: now,\n startTime: now,\n endTime: '',\n });\n multi.expire(jobKey, this.defaults.jobTtlSeconds);\n\n await multi.exec();\n }\n\n /**\n * Get job status from Redis\n */\n async getJob(jobId: string): Promise<JobStatus | null> {\n const jobKey = JobManager.jobKey(jobId);\n const obj = await this.redis.hGetAll(jobKey);\n\n if (!obj || Object.keys(obj).length === 0) {\n return null;\n }\n\n // Calculate duration\n const startTime = obj.startTime ? Number(obj.startTime) : null;\n const endTime = obj.endTime && obj.endTime !== '' ? Number(obj.endTime) : null;\n const currentTime = Date.now();\n\n const duration = {\n startTime: startTime ? new Date(startTime).toISOString() : null,\n endTime: endTime ? new Date(endTime).toISOString() : null,\n durationMs: startTime ? (endTime || currentTime) - startTime : null,\n };\n\n return {\n jobId,\n status: obj.status,\n action: obj.action,\n total: Number(obj.total ?? 0),\n queued: Number(obj.queued ?? 0),\n processed: Number(obj.processed ?? 0),\n succeeded: Number(obj.succeeded ?? 0),\n failed: Number(obj.failed ?? 0),\n errors: obj.errors ? JSON.parse(obj.errors) : [],\n createdAt: obj.createdAt ? new Date(Number(obj.createdAt)).toISOString() : undefined,\n updatedAt: obj.updatedAt ? new Date(Number(obj.updatedAt)).toISOString() : undefined,\n duration,\n };\n }\n\n /**\n * Set a single field on a job\n */\n async setJobField(jobId: string, field: string, value: string): Promise<boolean> {\n const jobKey = JobManager.jobKey(jobId);\n const exists = await this.redis.exists(jobKey);\n if (!exists) return false;\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, field, value);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n await multi.exec();\n return true;\n }\n\n /**\n * Set multiple fields on a job\n */\n async setJobFields(jobId: string, fields: Record<string, string>): Promise<boolean> {\n const jobKey = JobManager.jobKey(jobId);\n const exists = await this.redis.exists(jobKey);\n if (!exists) return false;\n\n const multi = this.redis.multi();\n multi.hSet(jobKey, fields);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n await multi.exec();\n return true;\n }\n\n /**\n * Increment a counter field on a job\n */\n async incrJobField(jobId: string, field: 'queued' | 'processed' | 'succeeded' | 'failed', by = 1): Promise<number> {\n const jobKey = JobManager.jobKey(jobId);\n\n const multi = this.redis.multi();\n multi.hIncrBy(jobKey, field, by);\n multi.hSet(jobKey, 'updatedAt', Date.now().toString());\n\n const results = await multi.exec() as [number | null, number][];\n if (results?.[0]?.[0] === null) {\n return results[0][1];\n }\n return 0;\n }\n\n /**\n * Get a single field from a job\n */\n async getJobField(jobId: string, field: string): Promise<string | undefined> {\n const jobKey = JobManager.jobKey(jobId);\n return this.redis.hGet(jobKey, field);\n }\n\n /**\n * Cancel a job\n */\n async cancelJob(jobId: string): Promise<boolean> {\n return this.setJobField(jobId, 'status', 'canceled');\n }\n\n /**\n * Mark job as completed (for jobs with no items to process)\n */\n async completeEmptyJob(jobId: string): Promise<void> {\n const endTime = Date.now().toString();\n await this.setJobFields(jobId, {\n status: 'completed',\n endTime,\n });\n }\n\n /**\n * Handle successful message processing (ack)\n * Internally calls partialAck with all items succeeded\n */\n async ack(jobId: string, count = 1): Promise<void> {\n // All succeeded, no failures\n await this.partialAck(jobId, count, []);\n }\n\n /**\n * Handle failed message processing (nack)\n * Internally calls partialAck with all items failed\n */\n async nack(jobId: string, errorMsg: string, data: { ids: Id[]; }, count = 1): Promise<void> {\n // All failed, no successes\n // Create failed results array with one entry per failed item\n const failedResults: { id: any; error?: string; }[] = [];\n\n const getId = (index: number): any => {\n if (data.ids && Array.isArray(data.ids)) {\n return data.ids[index] ?? `unknown-${index}`;\n }\n return `unknown-${index}`;\n };\n\n for (let i = 0; i < count; i++) {\n failedResults.push({\n id: getId(i),\n error: errorMsg,\n });\n }\n\n await this.partialAck(jobId, 0, failedResults);\n }\n\n /**\n * Handle partial success/failure for a batch of items\n * Uses Redis Lua script for atomic operation\n */\n async partialAck(\n jobId: string,\n succeededCount: number,\n failedResults: { id: any; error?: string; }[],\n ): Promise<void> {\n const jobKey = JobManager.jobKey(jobId);\n await this.redis.eval(localPartialAckScript, {\n keys: [jobKey],\n arguments: [\n String(succeededCount),\n String(failedResults.length),\n JSON.stringify(failedResults),\n String(this.defaults.errorLogLimit),\n Date.now().toString(),\n ],\n });\n\n // Check if job completed and emit event\n const job = await this.getJob(jobId);\n if (job?.status === 'completed') {\n const duration = job.duration.durationMs || 0;\n this.eventEmitter?.emitEvent('job:completed', {\n jobId,\n action: job.action,\n processed: job.processed,\n failed: job.failed,\n duration,\n });\n }\n }\n\n /**\n * Add a job to user's job list\n */\n async addUserJob(userId: string, jobId: string): Promise<void> {\n const userJobsKey = JobManager.userJobsKey(userId);\n const USER_JOB_TTL_SECONDS = 3600; // 1 hour\n\n const multi = this.redis.multi();\n multi.lPush(userJobsKey, jobId);\n if (this.defaults.maxJobsPerUser > 0) {\n multi.lTrim(userJobsKey, 0, this.defaults.maxJobsPerUser - 1);\n }\n multi.expire(userJobsKey, USER_JOB_TTL_SECONDS);\n\n await multi.exec();\n }\n\n /**\n * Remove a job from user's job list\n */\n async removeUserJob(userId: string, jobId: string): Promise<void> {\n const userJobsKey = JobManager.userJobsKey(userId);\n await this.redis.lRem(userJobsKey, 0, jobId);\n }\n\n /**\n * Get all jobs for a user\n */\n async getUserJobs(userId: string, limit = 20): Promise<(JobStatus | null)[]> {\n const userJobsKey = JobManager.userJobsKey(userId);\n const jobIds = await this.redis.lRange(userJobsKey, 0, limit - 1);\n\n const jobs = await Promise.all(\n jobIds.map(jobId => this.getJob(jobId)),\n );\n\n return jobs;\n }\n}\n","import type { Sequelize } from 'sequelize';\nimport { createClient, type RedisClientType } from 'redis';\nimport type { Router } from 'express';\nimport type rabbit from '@autofleet/rabbit';\nimport BulkRouter from './BulkRouter';\nimport { JobManager } from './JobManager';\nimport type { BulkEventEmitter, BulkRouterEvents, EmitsBulkEvents } from './events';\nimport { EventEmitter } from 'node:events';\nimport type { LoggerInstanceManager } from '@autofleet/logger';\n\nconst NOOP_EMITTER: EmitsBulkEvents = {\n emitEvent: () => {\n // no-op\n },\n};\n\nexport interface BulkerInit {\n sequelize: Sequelize;\n logger: LoggerInstanceManager;\n rabbit: rabbit;\n redis: RedisClientType | ReturnType<typeof createClient> | { host: string; port?: number; password?: string; db?: number; };\n getUserId?: () => string | null; // function that returns the user ID for the current request context, or null if not available\n emitEvents?: boolean; // opt-in to enable event emission (default: false)\n defaults?: {\n pageSize?: number; // default 1000\n maxJobsPerUser?: number; // default unlimited (no per-user rate limiting)\n idField?: string; // default \"id\"\n consumerBatchSize?: number; // default 1 (batch size for consumer)\n workerConcurrency?: number; // default 16\n jobTtlSeconds?: number; // default 7 days (for Redis status hash expiry)\n errorLogLimit?: number; // default 10 (max number of error logs to keep per job)\n };\n}\n\nexport class Bulker extends EventEmitter implements BulkEventEmitter {\n public readonly sequelize: Sequelize;\n\n public readonly logger: BulkerInit['logger'];\n\n public readonly redis: RedisClientType;\n\n public readonly rabbit: rabbit;\n\n public readonly defaults: Required<NonNullable<BulkerInit['defaults']>>;\n\n public readonly jobManager: JobManager;\n\n private readonly bulkRouters: BulkRouter[] = [];\n\n public getUserId: BulkerInit['getUserId'];\n\n public readonly eventsEnabled: boolean;\n\n constructor(init: BulkerInit) {\n super();\n this.sequelize = init.sequelize;\n this.logger = init.logger;\n this.rabbit = init.rabbit;\n this.eventsEnabled = init.emitEvents ?? false;\n if (init.redis && typeof (init.redis as any).connect === 'function') {\n this.redis = init.redis as RedisClientType;\n if (!(this.redis.isOpen)) {\n this.redis.connect().catch((err) => {\n this.logger.error('Error connecting to Redis:', err);\n });\n }\n } else {\n const config = init.redis as { host: string; port?: number; password?: string; db?: number; };\n this.redis = createClient({\n socket: {\n connectTimeout: 60_000, // 60 seconds\n host: config.host,\n port: config.port,\n reconnectStrategy(retries, cause) {\n init.logger.warn(`[BULKER Redis] Reconnecting to Redis (attempt ${retries}). Cause: ${cause?.message}`);\n const delay = Math.min(retries * 50, 2000); // Exponential backoff up to 2 seconds\n return delay;\n },\n },\n commandsQueueMaxLength: 5000,\n disableOfflineQueue: false,\n password: config.password,\n database: config.db,\n });\n this.redis.connect().catch((err) => {\n this.logger.error('Error connecting to Redis:', err);\n });\n }\n this.getUserId = init.getUserId ?? (() => null);\n\n this.defaults = {\n maxJobsPerUser: init.defaults?.maxJobsPerUser ?? -1,\n pageSize: init.defaults?.pageSize ?? 1000,\n idField: init.defaults?.idField ?? 'id',\n consumerBatchSize: init.defaults?.consumerBatchSize ?? 1,\n workerConcurrency: init.defaults?.workerConcurrency ?? 16,\n jobTtlSeconds: init.defaults?.jobTtlSeconds ?? 7 * 24 * 3600,\n errorLogLimit: init.defaults?.errorLogLimit ?? 10,\n };\n\n // Initialize JobManager\n this.jobManager = new JobManager(this.redis, {\n maxJobsPerUser: this.defaults.maxJobsPerUser,\n jobTtlSeconds: this.defaults.jobTtlSeconds,\n errorLogLimit: this.defaults.errorLogLimit,\n });\n\n // Set event emitter reference in JobManager\n this.jobManager.setEventEmitter(this.eventsEnabled ? {\n emitEvent: this.emitEvent.bind(this),\n } : NOOP_EMITTER);\n }\n\n pingRedis(): Promise<string> {\n return this.redis.ping();\n }\n\n createBulkRouter(router?: Router, staticRoute?: string): BulkRouter {\n const bulkRouter = new BulkRouter(this, router, staticRoute);\n this.bulkRouters.push(bulkRouter);\n return bulkRouter;\n }\n\n /**\n * Emit event only if events are enabled.\n * Centralizes the eventsEnabled check to avoid repetition.\n */\n emitEvent<K extends keyof BulkRouterEvents>(\n event: K,\n ...args: Parameters<BulkRouterEvents[K]>\n ): void {\n if (this.eventsEnabled) {\n this.emit(event, ...args);\n }\n }\n}\n\nexport { BulkerError } from './Errors';\nexport { BulkRoute } from './BulkRoute';\nexport { JobManager } from './JobManager';\nexport { type BulkEventEmitter } from './events';\nexport { z } from 'zod';\n\nexport {\n type BulkRouteOptions,\n type JobMetadata,\n type JobStatus,\n type BulkRouterEvents,\n type IBulkEventEmitter,\n type AdditionalIdsHookData,\n type BulkPerIdHandler,\n} from './types';\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sheilta",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"description": "Middlewares for validation and parsing of endpoints meant for data querying.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"@types/express": "^4.17.21",
|
|
48
48
|
"@types/node": "^18.19.76",
|
|
49
49
|
"sequelize": "^6.37.7",
|
|
50
|
-
"@autofleet/errors": "^3.1.
|
|
51
|
-
"@autofleet/logger": "^4.2.
|
|
50
|
+
"@autofleet/errors": "^3.1.39",
|
|
51
|
+
"@autofleet/logger": "^4.2.43"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"@autofleet/rabbit": {
|