@fjell/lib-sequelize 4.4.73 → 4.4.75

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.
@@ -9,5 +9,18 @@ export type QueryOptions = {
9
9
  };
10
10
  export declare const addCompoundCondition: (options: any, compoundCondition: CompoundCondition, model: ModelStatic<any>) => any;
11
11
  export declare const addCondition: (conditions: Record<string, any>, condition: Condition, model: ModelStatic<any>) => Record<string, any>;
12
+ /**
13
+ * Detects aggregations that can be loaded via Sequelize INCLUDE and adds them to options.
14
+ * This prevents N+1 queries by using JOIN instead of separate queries per item.
15
+ *
16
+ * @param options - Query options to modify
17
+ * @param model - Sequelize model to check for associations
18
+ * @param aggregationDefinitions - Aggregation definitions from library config
19
+ * @returns Modified options with includes added, plus array of property names that were included
20
+ */
21
+ export declare const addAggregationIncludes: (options: any, model: ModelStatic<any>, aggregationDefinitions: any[]) => {
22
+ options: any;
23
+ includedAggregations: string[];
24
+ };
12
25
  export declare const buildQuery: (itemQuery: ItemQuery, model: ModelStatic<any>) => any;
13
26
  //# sourceMappingURL=QueryBuilder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"QueryBuilder.d.ts","sourceRoot":"","sources":["../src/QueryBuilder.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EACjB,SAAS,EAKT,SAAS,EAIV,MAAM,aAAa,CAAC;AAErB,OAAO,EAAe,WAAW,EAAM,MAAM,WAAW,CAAC;AAMzD,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;CACtB,CAAA;AAkFD,eAAO,MAAM,oBAAoB,GAAI,SAAS,GAAG,EAAE,mBAAmB,iBAAiB,EAAE,OAAO,WAAW,CAAC,GAAG,CAAC,QAoC/G,CAAA;AAoHD,eAAO,MAAM,YAAY,GAAI,YAAY,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,WAAW,SAAS,EAAE,OAAO,WAAW,CAAC,GAAG,CAAC,wBAU1G,CAAA;AAuED,eAAO,MAAM,UAAU,GACrB,WAAW,SAAS,EACpB,OAAO,WAAW,CAAC,GAAG,CAAC,KACtB,GAqDF,CAAA"}
1
+ {"version":3,"file":"QueryBuilder.d.ts","sourceRoot":"","sources":["../src/QueryBuilder.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EACjB,SAAS,EAKT,SAAS,EAIV,MAAM,aAAa,CAAC;AAErB,OAAO,EAAe,WAAW,EAAM,MAAM,WAAW,CAAC;AAMzD,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;CACtB,CAAA;AAkFD,eAAO,MAAM,oBAAoB,GAAI,SAAS,GAAG,EAAE,mBAAmB,iBAAiB,EAAE,OAAO,WAAW,CAAC,GAAG,CAAC,QAoC/G,CAAA;AAoHD,eAAO,MAAM,YAAY,GAAI,YAAY,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,WAAW,SAAS,EAAE,OAAO,WAAW,CAAC,GAAG,CAAC,wBAU1G,CAAA;AAuED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GACjC,SAAS,GAAG,EACZ,OAAO,WAAW,CAAC,GAAG,CAAC,EACvB,wBAAwB,GAAG,EAAE,KAC5B;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,oBAAoB,EAAE,MAAM,EAAE,CAAA;CAiDhD,CAAC;AAEF,eAAO,MAAM,UAAU,GACrB,WAAW,SAAS,EACpB,OAAO,WAAW,CAAC,GAAG,CAAC,KACtB,GAqDF,CAAA"}
@@ -5,5 +5,5 @@ import * as Library from "@fjell/lib";
5
5
  import { contextManager, createOperationContext, OperationContext } from "@fjell/lib";
6
6
  export type { OperationContext };
7
7
  export { createOperationContext, contextManager };
8
- export declare const processRow: <S extends string, L1 extends string = never, L2 extends string = never, L3 extends string = never, L4 extends string = never, L5 extends string = never>(row: Model<any, any>, keyTypes: AllItemTypeArrays<S, L1, L2, L3, L4, L5>, referenceDefinitions: SequelizeReferenceDefinition[], aggregationDefinitions: AggregationDefinition[], registry: Library.Registry, context?: OperationContext) => Promise<Item<S, L1, L2, L3, L4, L5>>;
8
+ export declare const processRow: <S extends string, L1 extends string = never, L2 extends string = never, L3 extends string = never, L4 extends string = never, L5 extends string = never>(row: Model<any, any>, keyTypes: AllItemTypeArrays<S, L1, L2, L3, L4, L5>, referenceDefinitions: SequelizeReferenceDefinition[], aggregationDefinitions: AggregationDefinition[], registry: Library.Registry, context?: OperationContext, includedAggregations?: string[]) => Promise<Item<S, L1, L2, L3, L4, L5>>;
9
9
  //# sourceMappingURL=RowProcessor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RowProcessor.d.ts","sourceRoot":"","sources":["../src/RowProcessor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAIlC,OAAO,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AACtC,OAAO,EAEL,cAAc,EACd,sBAAsB,EACtB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAUpB,YAAY,EAAE,gBAAgB,EAAE,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,CAAC;AAElD,eAAO,MAAM,UAAU,GAAU,CAAC,SAAS,MAAM,EAC/C,EAAE,SAAS,MAAM,GAAG,KAAK,EACzB,EAAE,SAAS,MAAM,GAAG,KAAK,EACzB,EAAE,SAAS,MAAM,GAAG,KAAK,EACzB,EAAE,SAAS,MAAM,GAAG,KAAK,EACzB,EAAE,SAAS,MAAM,GAAG,KAAK,EACvB,KAAK,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,EACpB,UAAU,iBAAiB,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAClD,sBAAsB,4BAA4B,EAAE,EACpD,wBAAwB,qBAAqB,EAAE,EAC/C,UAAU,OAAO,CAAC,QAAQ,EAC1B,UAAU,gBAAgB,KACzB,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAqEvC,CAAC"}
1
+ {"version":3,"file":"RowProcessor.d.ts","sourceRoot":"","sources":["../src/RowProcessor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAIlC,OAAO,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,KAAK,OAAO,MAAM,YAAY,CAAC;AACtC,OAAO,EAEL,cAAc,EACd,sBAAsB,EACtB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAUpB,YAAY,EAAE,gBAAgB,EAAE,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,CAAC;AAElD,eAAO,MAAM,UAAU,GAAU,CAAC,SAAS,MAAM,EAC/C,EAAE,SAAS,MAAM,GAAG,KAAK,EACzB,EAAE,SAAS,MAAM,GAAG,KAAK,EACzB,EAAE,SAAS,MAAM,GAAG,KAAK,EACzB,EAAE,SAAS,MAAM,GAAG,KAAK,EACzB,EAAE,SAAS,MAAM,GAAG,KAAK,EACvB,KAAK,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,EACpB,UAAU,iBAAiB,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAClD,sBAAsB,4BAA4B,EAAE,EACpD,wBAAwB,qBAAqB,EAAE,EAC/C,UAAU,OAAO,CAAC,QAAQ,EAC1B,UAAU,gBAAgB,EAC1B,uBAAuB,MAAM,EAAE,KAE9B,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAwFvC,CAAC"}
package/dist/index.js CHANGED
@@ -341,6 +341,46 @@ var addAssociationIncludes = (options, model) => {
341
341
  }
342
342
  return options;
343
343
  };
344
+ var addAggregationIncludes = (options, model, aggregationDefinitions) => {
345
+ if (!aggregationDefinitions || aggregationDefinitions.length === 0) {
346
+ return { options, includedAggregations: [] };
347
+ }
348
+ const includedAggregations = [];
349
+ options.include = options.include || [];
350
+ for (const aggDef of aggregationDefinitions) {
351
+ const association = model.associations && model.associations[aggDef.property];
352
+ if (association) {
353
+ const alreadyIncluded = options.include.some(
354
+ (inc) => typeof inc === "string" && inc === aggDef.property || typeof inc === "object" && inc.as === aggDef.property
355
+ );
356
+ if (!alreadyIncluded) {
357
+ logger3.debug(`Auto-detected association for aggregation '${aggDef.property}' - using INCLUDE to prevent N+1`, {
358
+ property: aggDef.property,
359
+ associationType: association.associationType,
360
+ targetModel: association.target.name
361
+ });
362
+ options.include.push({
363
+ model: association.target,
364
+ as: aggDef.property,
365
+ required: false
366
+ // Use LEFT JOIN to preserve items without aggregations
367
+ });
368
+ includedAggregations.push(aggDef.property);
369
+ } else {
370
+ logger3.debug(`Association '${aggDef.property}' already included in query`, {
371
+ property: aggDef.property
372
+ });
373
+ includedAggregations.push(aggDef.property);
374
+ }
375
+ } else {
376
+ logger3.debug(`No association found for aggregation '${aggDef.property}' - will use separate query`, {
377
+ property: aggDef.property,
378
+ availableAssociations: Object.keys(model.associations || {})
379
+ });
380
+ }
381
+ }
382
+ return { options, includedAggregations };
383
+ };
344
384
  var buildQuery = (itemQuery, model) => {
345
385
  logger3.default(`QueryBuilder build called with itemQuery: ${stringifyJSON(itemQuery)}`);
346
386
  let options = {
@@ -879,7 +919,7 @@ function removeAggsFromItem(item, aggregationDefinitions) {
879
919
 
880
920
  // src/RowProcessor.ts
881
921
  var logger6 = logger_default.get("sequelize", "RowProcessor");
882
- var processRow = async (row, keyTypes, referenceDefinitions, aggregationDefinitions, registry, context) => {
922
+ var processRow = async (row, keyTypes, referenceDefinitions, aggregationDefinitions, registry, context, includedAggregations) => {
883
923
  logger6.default("Processing Row", { row });
884
924
  const operationContext = context || createOperationContext();
885
925
  return contextManager.withContext(operationContext, async () => {
@@ -904,8 +944,20 @@ var processRow = async (row, keyTypes, referenceDefinitions, aggregationDefiniti
904
944
  }
905
945
  if (aggregationDefinitions && aggregationDefinitions.length > 0) {
906
946
  for (const aggregationDefinition of aggregationDefinitions) {
907
- logger6.default("Processing Aggregation for %s from %s", item.key.kt, stringifyJSON(aggregationDefinition.kta));
908
- item = await buildAggregation(item, aggregationDefinition, registry, operationContext);
947
+ const alreadyLoaded = includedAggregations && includedAggregations.includes(aggregationDefinition.property) && typeof item[aggregationDefinition.property] !== "undefined";
948
+ if (alreadyLoaded) {
949
+ logger6.debug(
950
+ `Skipping buildAggregation for '${aggregationDefinition.property}' - already loaded via INCLUDE (N+1 prevention)`,
951
+ {
952
+ property: aggregationDefinition.property,
953
+ itemType: item.key.kt,
954
+ hasData: Array.isArray(item[aggregationDefinition.property]) ? `${item[aggregationDefinition.property].length} items` : "single item"
955
+ }
956
+ );
957
+ } else {
958
+ logger6.default("Processing Aggregation for %s from %s", item.key.kt, stringifyJSON(aggregationDefinition.kta));
959
+ item = await buildAggregation(item, aggregationDefinition, registry, operationContext);
960
+ }
909
961
  }
910
962
  }
911
963
  operationContext.setCached(item.key, item);
@@ -1148,7 +1200,13 @@ var getAllOperation = (models, definition, registry) => {
1148
1200
  logger7.debug(`ALL operation called on ${models[0].name} with ${locs.length} location filters: ${locs.map((loc2) => `${loc2.kt}=${loc2.lk}`).join(", ") || "none"}`);
1149
1201
  const loc = locs;
1150
1202
  const model = models[0];
1151
- const options = buildQuery(itemQuery ?? {}, model);
1203
+ let options = buildQuery(itemQuery ?? {}, model);
1204
+ const { options: optionsWithAggs, includedAggregations } = addAggregationIncludes(
1205
+ options,
1206
+ model,
1207
+ aggregations || []
1208
+ );
1209
+ options = optionsWithAggs;
1152
1210
  if (loc.length > 0) {
1153
1211
  const { kta } = coordinate;
1154
1212
  const directLocations = [];
@@ -1240,7 +1298,15 @@ var getAllOperation = (models, definition, registry) => {
1240
1298
  const matchingItems = await model.findAll(options);
1241
1299
  const currentContext = contextManager.getCurrentContext();
1242
1300
  const items = await Promise.all(matchingItems.map(async (row) => {
1243
- const processedRow = await processRow(row, coordinate.kta, references || [], aggregations || [], registry, currentContext);
1301
+ const processedRow = await processRow(
1302
+ row,
1303
+ coordinate.kta,
1304
+ references || [],
1305
+ aggregations || [],
1306
+ registry,
1307
+ currentContext,
1308
+ includedAggregations
1309
+ );
1244
1310
  return validateKeys(processedRow, coordinate.kta);
1245
1311
  }));
1246
1312
  logger7.debug(`[ALL] Returning ${items.length} of ${total} ${model.name} records`);
@@ -1397,7 +1463,7 @@ var getCreateOperation = (models, definition, registry) => {
1397
1463
  try {
1398
1464
  logger8.trace(`[CREATE] Executing ${model.name}.create() with data: ${stringifyJSON(itemData)}`);
1399
1465
  const createdRecord = await model.create(itemData);
1400
- const processedRecord = await processRow(createdRecord, kta, references || [], aggregations || [], registry);
1466
+ const processedRecord = await processRow(createdRecord, kta, references || [], aggregations || [], registry, void 0, void 0);
1401
1467
  const result = validateKeys2(processedRecord, kta);
1402
1468
  logger8.debug(`[CREATE] Created ${model.name} with key: ${result.key ? JSON.stringify(result.key) : `id=${createdRecord.id}`}`);
1403
1469
  return result;
@@ -1438,7 +1504,7 @@ var getFindOperation = (models, definition, registry) => {
1438
1504
  const finderResult = await finderMethod(params, locs, findOptions);
1439
1505
  const processItems = async (items) => {
1440
1506
  return await Promise.all(items.map(async (row) => {
1441
- const processedRow = await processRow(row, definition.coordinate.kta, references || [], aggregations || [], registry);
1507
+ const processedRow = await processRow(row, definition.coordinate.kta, references || [], aggregations || [], registry, void 0, void 0);
1442
1508
  return validateKeys3(processedRow, definition.coordinate.kta);
1443
1509
  }));
1444
1510
  };
@@ -1541,19 +1607,31 @@ var getGetOperation = (models, definition, registry) => {
1541
1607
  const itemKey = key;
1542
1608
  const model = models[0];
1543
1609
  let item;
1610
+ let includedAggregations = [];
1544
1611
  if (isPriKey4(itemKey)) {
1545
- logger10.trace(`[GET] Executing ${model.name}.findByPk() with pk: ${itemKey.pk}`);
1546
- item = await model.findByPk(itemKey.pk);
1612
+ let options = {};
1613
+ const aggResult = addAggregationIncludes(options, model, aggregations || []);
1614
+ includedAggregations = aggResult.includedAggregations;
1615
+ options = aggResult.options;
1616
+ logger10.trace(`[GET] Executing ${model.name}.findByPk() with pk: ${itemKey.pk} and ${includedAggregations.length} aggregation includes`);
1617
+ item = options.include && options.include.length > 0 ? await model.findByPk(itemKey.pk, { include: options.include }) : await model.findByPk(itemKey.pk);
1547
1618
  } else if (isComKey4(itemKey)) {
1548
1619
  const comKey = itemKey;
1549
1620
  if (comKey.loc.length === 0) {
1621
+ let options = {};
1622
+ const aggResult = addAggregationIncludes(options, model, aggregations || []);
1623
+ includedAggregations = aggResult.includedAggregations;
1624
+ options = aggResult.options;
1550
1625
  logger10.debug(`[GET] Empty loc array detected - finding by primary key across all locations: ${comKey.pk}`);
1551
- logger10.trace(`[GET] Executing ${model.name}.findByPk() with pk: ${comKey.pk}`);
1552
- item = await model.findByPk(comKey.pk);
1626
+ logger10.trace(`[GET] Executing ${model.name}.findByPk() with pk: ${comKey.pk} and ${includedAggregations.length} aggregation includes`);
1627
+ item = options.include && options.include.length > 0 ? await model.findByPk(comKey.pk, { include: options.include }) : await model.findByPk(comKey.pk);
1553
1628
  } else {
1554
- const queryOptions = processCompositeKey(comKey, model, kta);
1629
+ let queryOptions = processCompositeKey(comKey, model, kta);
1630
+ const aggResult = addAggregationIncludes(queryOptions, model, aggregations || []);
1631
+ includedAggregations = aggResult.includedAggregations;
1632
+ queryOptions = aggResult.options;
1555
1633
  logger10.default("Composite key query", { queryOptions });
1556
- logger10.trace(`[GET] Executing ${model.name}.findOne() with options: ${stringifyJSON(queryOptions)}`);
1634
+ logger10.trace(`[GET] Executing ${model.name}.findOne() with options: ${stringifyJSON(queryOptions)} and ${includedAggregations.length} aggregation includes`);
1557
1635
  item = await model.findOne(queryOptions);
1558
1636
  }
1559
1637
  }
@@ -1565,7 +1643,7 @@ var getGetOperation = (models, definition, registry) => {
1565
1643
  );
1566
1644
  }
1567
1645
  const currentContext = contextManager.getCurrentContext();
1568
- const result = validateKeys4(await processRow(item, kta, references || [], aggregations || [], registry, currentContext), kta);
1646
+ const result = validateKeys4(await processRow(item, kta, references || [], aggregations || [], registry, currentContext, includedAggregations), kta);
1569
1647
  logger10.debug(`[GET] Retrieved ${model.name} with key: ${result.key ? JSON.stringify(result.key) : `id=${item.id}`}`);
1570
1648
  return result;
1571
1649
  } catch (error) {
@@ -1804,7 +1882,7 @@ var getUpdateOperation = (models, definition, registry) => {
1804
1882
  logger13.default(`Update properties configured: ${Object.keys(updateProps).join(", ")}`);
1805
1883
  logger13.trace(`[UPDATE] Executing ${model.name}.update() with properties: ${stringifyJSON(updateProps)}`);
1806
1884
  response = await response.update(updateProps);
1807
- const processedItem = await processRow(response, kta, references || [], aggregations || [], registry);
1885
+ const processedItem = await processRow(response, kta, references || [], aggregations || [], registry, void 0, void 0);
1808
1886
  const returnItem = validateKeys5(processedItem, kta);
1809
1887
  logger13.debug(`[UPDATE] Updated ${model.name} with key: ${returnItem.key ? JSON.stringify(returnItem.key) : `id=${response.id}`}`);
1810
1888
  return returnItem;