@fjell/lib-sequelize 4.4.75 → 4.4.77

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/dist/index.js CHANGED
@@ -173,7 +173,7 @@ var addReferenceQueries = (options, references, model) => {
173
173
  });
174
174
  return options;
175
175
  };
176
- var addCompoundCondition = (options, compoundCondition, model) => {
176
+ var addCompoundCondition = (options, compoundCondition, model, references, allReferences, registry) => {
177
177
  options.where = options.where || {};
178
178
  let compoundOp;
179
179
  const compoundType = compoundCondition.compoundType;
@@ -186,7 +186,7 @@ var addCompoundCondition = (options, compoundCondition, model) => {
186
186
  let conditions = {};
187
187
  compoundCondition.conditions.forEach((condition) => {
188
188
  if (isCondition(condition)) {
189
- conditions = addCondition(conditions, condition, model);
189
+ conditions = addCondition(conditions, condition, model, options, references, allReferences, registry);
190
190
  } else {
191
191
  throw new Error(`Nested Compound conditions not supported. Model: '${model.name}', Compound condition: ${stringifyJSON(compoundCondition)}, Nested condition: ${stringifyJSON(condition)}`);
192
192
  }
@@ -216,12 +216,267 @@ var getSequelizeOperator = (operator) => {
216
216
  return Op.gte;
217
217
  } else if (operator === "in") {
218
218
  return Op.in;
219
+ } else if (operator === "!=") {
220
+ return Op.ne;
219
221
  } else {
220
222
  throw new Error(`Operator ${operator} not supported`);
221
223
  }
222
224
  };
223
- var addAssociationCondition = (conditions, condition, model) => {
224
- const [associationName, attributeName] = condition.column.split(".", 2);
225
+ var findReferenceByProperty = (propertyName, references) => {
226
+ if (!references || references.length === 0) {
227
+ return null;
228
+ }
229
+ return references.find((ref) => ref.property === propertyName) || null;
230
+ };
231
+ var getReferencesFromRegistry = (model, registry) => {
232
+ if (!registry) {
233
+ return;
234
+ }
235
+ const modelName = model.name.toLowerCase();
236
+ try {
237
+ const library = registry.get([modelName]);
238
+ if (library && library.options && library.options.references) {
239
+ return library.options.references;
240
+ }
241
+ } catch {
242
+ }
243
+ return;
244
+ };
245
+ var buildNestedInclude = (pathSegments, currentModel, references, allReferences, registry, isFirstSegment = true) => {
246
+ if (pathSegments.length === 0) {
247
+ return null;
248
+ }
249
+ const [firstSegment, ...remainingSegments] = pathSegments;
250
+ if (isFirstSegment) {
251
+ const refDef = findReferenceByProperty(firstSegment, references);
252
+ if (!refDef) {
253
+ logger3.debug(`[buildNestedInclude] No reference definition found for property '${firstSegment}'`, {
254
+ model: currentModel.name,
255
+ property: firstSegment,
256
+ availableReferences: references?.map((r) => r.property) || [],
257
+ referencesCount: references?.length || 0
258
+ });
259
+ return null;
260
+ }
261
+ logger3.debug(`[buildNestedInclude] Found reference definition for property '${firstSegment}'`, {
262
+ model: currentModel.name,
263
+ property: firstSegment,
264
+ refDef: { property: refDef.property, column: refDef.column, kta: refDef.kta }
265
+ });
266
+ }
267
+ const association = currentModel.associations?.[firstSegment];
268
+ if (!association) {
269
+ if (isFirstSegment) {
270
+ logger3.debug(`[buildNestedInclude] Reference property '${firstSegment}' found but no Sequelize association exists`, {
271
+ model: currentModel.name,
272
+ property: firstSegment,
273
+ availableAssociations: Object.keys(currentModel.associations || {}),
274
+ associationsObject: currentModel.associations ? Object.keys(currentModel.associations) : "undefined"
275
+ });
276
+ }
277
+ return null;
278
+ }
279
+ logger3.debug(`[buildNestedInclude] Found Sequelize association for '${firstSegment}'`, {
280
+ model: currentModel.name,
281
+ property: firstSegment,
282
+ associationType: association.associationType,
283
+ targetModel: association.target?.name
284
+ });
285
+ const targetModel = association.target;
286
+ const includeConfig = {
287
+ model: targetModel,
288
+ as: firstSegment,
289
+ required: true
290
+ // Use INNER JOIN for filtering conditions
291
+ };
292
+ if (remainingSegments.length === 0) {
293
+ return { include: includeConfig, associationName: firstSegment };
294
+ }
295
+ let nestedReferences = getReferencesFromRegistry(targetModel, registry);
296
+ if (!nestedReferences) {
297
+ nestedReferences = allReferences?.get(targetModel.name);
298
+ }
299
+ const nestedInclude = buildNestedInclude(
300
+ remainingSegments,
301
+ targetModel,
302
+ nestedReferences,
303
+ allReferences,
304
+ registry,
305
+ false
306
+ // Subsequent segments don't require reference definition match
307
+ );
308
+ if (nestedInclude) {
309
+ includeConfig.include = [nestedInclude.include];
310
+ return { include: includeConfig, associationName: firstSegment };
311
+ }
312
+ const [nextSegment, ...restSegments] = remainingSegments;
313
+ const nextAssociation = targetModel.associations?.[nextSegment];
314
+ if (!nextAssociation) {
315
+ return null;
316
+ }
317
+ if (restSegments.length === 0) {
318
+ includeConfig.include = [{
319
+ model: nextAssociation.target,
320
+ as: nextSegment,
321
+ required: true
322
+ }];
323
+ return { include: includeConfig, associationName: firstSegment };
324
+ }
325
+ const deeperInclude = buildNestedInclude(
326
+ remainingSegments,
327
+ targetModel,
328
+ nestedReferences,
329
+ allReferences,
330
+ registry,
331
+ false
332
+ );
333
+ if (!deeperInclude) {
334
+ return null;
335
+ }
336
+ includeConfig.include = [deeperInclude.include];
337
+ return { include: includeConfig, associationName: firstSegment };
338
+ };
339
+ var detectReferenceJoin = (fieldPath, model, references, allReferences, registry) => {
340
+ const pathSegments = fieldPath.split(".");
341
+ if (pathSegments.length < 2) {
342
+ return null;
343
+ }
344
+ for (let i = pathSegments.length - 1; i >= 1; i--) {
345
+ const referencePath = pathSegments.slice(0, i);
346
+ const attributePath = pathSegments.slice(i).join(".");
347
+ const includeResult = buildNestedInclude(referencePath, model, references, allReferences, registry);
348
+ if (!includeResult) {
349
+ continue;
350
+ }
351
+ let finalModel = model;
352
+ for (const segment of referencePath) {
353
+ const assoc = finalModel.associations?.[segment];
354
+ if (!assoc) {
355
+ break;
356
+ }
357
+ finalModel = assoc.target;
358
+ }
359
+ const attributeSegments = attributePath.split(".");
360
+ if (attributeSegments.length !== 1 || !finalModel.getAttributes()[attributePath]) {
361
+ continue;
362
+ }
363
+ logger3.debug(`Auto-detected reference join for condition '${fieldPath}'`, {
364
+ model: model.name,
365
+ referencePath: referencePath.join("."),
366
+ attributePath,
367
+ associationName: includeResult.associationName
368
+ });
369
+ return {
370
+ include: includeResult.include,
371
+ attributePath,
372
+ associationName: includeResult.associationName
373
+ };
374
+ }
375
+ return null;
376
+ };
377
+ var addIncludeIfNotExists = (options, newInclude) => {
378
+ options.include = options.include || [];
379
+ const exists = options.include.some((inc) => {
380
+ if (typeof inc === "string") {
381
+ return inc === newInclude.as;
382
+ }
383
+ if (inc.model === newInclude.model && inc.as === newInclude.as) {
384
+ if (newInclude.include && inc.include) {
385
+ newInclude.include.forEach((nested) => {
386
+ const nestedExists = inc.include.some(
387
+ (existingNested) => existingNested.model === nested.model && existingNested.as === nested.as
388
+ );
389
+ if (!nestedExists) {
390
+ inc.include.push(nested);
391
+ }
392
+ });
393
+ } else if (newInclude.include) {
394
+ inc.include = newInclude.include;
395
+ }
396
+ return true;
397
+ }
398
+ return false;
399
+ });
400
+ if (!exists) {
401
+ options.include.push(newInclude);
402
+ }
403
+ };
404
+ var addAssociationCondition = (conditions, condition, model, options, references, allReferences, registry) => {
405
+ const fieldPath = condition.column;
406
+ const pathSegments = fieldPath.split(".");
407
+ logger3.debug(`[addAssociationCondition] Processing condition '${fieldPath}'`, {
408
+ model: model.name,
409
+ hasReferences: !!references,
410
+ referencesCount: references?.length || 0,
411
+ pathSegmentsLength: pathSegments.length,
412
+ pathSegments,
413
+ willAttemptDetection: !!(references && pathSegments.length >= 2)
414
+ });
415
+ if (references && pathSegments.length >= 2) {
416
+ logger3.debug(`[addAssociationCondition] Attempting reference join detection for '${fieldPath}'`, {
417
+ model: model.name,
418
+ referencesCount: references.length,
419
+ references: references.map((r) => ({ property: r.property, column: r.column })),
420
+ pathSegments,
421
+ hasOptions: !!options
422
+ });
423
+ const referenceJoin = detectReferenceJoin(fieldPath, model, references, allReferences, registry);
424
+ if (!referenceJoin) {
425
+ logger3.debug(`[addAssociationCondition] Reference join detection returned null for '${fieldPath}'`, {
426
+ model: model.name,
427
+ fieldPath
428
+ });
429
+ }
430
+ if (referenceJoin && options) {
431
+ logger3.debug(`[addAssociationCondition] Reference join detected successfully for '${fieldPath}'`, {
432
+ model: model.name,
433
+ associationName: referenceJoin.associationName,
434
+ attributePath: referenceJoin.attributePath
435
+ });
436
+ addIncludeIfNotExists(options, referenceJoin.include);
437
+ const attributeName2 = referenceJoin.attributePath;
438
+ const associationPath = fieldPath.substring(0, fieldPath.length - attributeName2.length - 1);
439
+ const sequelizeAssociationColumn2 = `$${associationPath}.${attributeName2}$`;
440
+ const finalPathSegments = associationPath.split(".");
441
+ let currentModel = model;
442
+ let targetModel = model;
443
+ for (const segment of finalPathSegments) {
444
+ const assoc = currentModel.associations?.[segment];
445
+ if (!assoc) {
446
+ throw new Error(`Association ${segment} not found on model ${currentModel.name}`);
447
+ }
448
+ targetModel = assoc.target;
449
+ currentModel = targetModel;
450
+ }
451
+ if (!targetModel.getAttributes()[attributeName2]) {
452
+ throw new Error(`Attribute ${attributeName2} not found on associated model ${targetModel.name} for path ${associationPath}`);
453
+ }
454
+ if (condition.value === null) {
455
+ if (condition.operator === "==" || !condition.operator) {
456
+ logger3.trace(`[QueryBuilder] Setting reference condition: ${sequelizeAssociationColumn2} IS NULL`);
457
+ conditions[sequelizeAssociationColumn2] = {
458
+ [Op.is]: null
459
+ };
460
+ } else if (condition.operator === "!=") {
461
+ logger3.trace(`[QueryBuilder] Setting reference condition: ${sequelizeAssociationColumn2} IS NOT NULL`);
462
+ conditions[sequelizeAssociationColumn2] = {
463
+ [Op.not]: null
464
+ };
465
+ } else {
466
+ logger3.error(`Operator ${condition.operator} cannot be used with null value on reference`, { condition });
467
+ throw new Error(`Operator ${condition.operator} cannot be used with null value. Use '==' for IS NULL or '!=' for IS NOT NULL.`);
468
+ }
469
+ return conditions;
470
+ }
471
+ const conditionOp2 = getSequelizeOperator(condition.operator);
472
+ logger3.trace(`[QueryBuilder] Setting reference condition: ${sequelizeAssociationColumn2} = ${stringifyJSON(condition.value)} (type: ${typeof condition.value})`);
473
+ conditions[sequelizeAssociationColumn2] = {
474
+ [conditionOp2]: condition.value
475
+ };
476
+ return conditions;
477
+ }
478
+ }
479
+ const [associationName, attributeName] = fieldPath.split(".", 2);
225
480
  if (!model.associations || !model.associations[associationName]) {
226
481
  throw new Error(`Association ${associationName} not found on model ${model.name}`);
227
482
  }
@@ -284,10 +539,10 @@ var addAttributeCondition = (conditions, condition, model) => {
284
539
  };
285
540
  return conditions;
286
541
  };
287
- var addCondition = (conditions, condition, model) => {
542
+ var addCondition = (conditions, condition, model, options, references, allReferences, registry) => {
288
543
  const conditionColumn = condition.column;
289
544
  if (conditionColumn.includes(".")) {
290
- return addAssociationCondition(conditions, condition, model);
545
+ return addAssociationCondition(conditions, condition, model, options, references, allReferences, registry);
291
546
  }
292
547
  return addAttributeCondition(conditions, condition, model);
293
548
  };
@@ -341,6 +596,46 @@ var addAssociationIncludes = (options, model) => {
341
596
  }
342
597
  return options;
343
598
  };
599
+ var addReferenceIncludes = (options, model, referenceDefinitions) => {
600
+ if (!referenceDefinitions || referenceDefinitions.length === 0) {
601
+ return { options, includedReferences: [] };
602
+ }
603
+ const includedReferences = [];
604
+ options.include = options.include || [];
605
+ for (const refDef of referenceDefinitions) {
606
+ const association = model.associations && model.associations[refDef.property];
607
+ if (association) {
608
+ const alreadyIncluded = options.include.some(
609
+ (inc) => typeof inc === "string" && inc === refDef.property || typeof inc === "object" && inc.as === refDef.property
610
+ );
611
+ if (!alreadyIncluded) {
612
+ logger3.default(`Auto-detected association for reference '${refDef.property}' - using INCLUDE to prevent N+1`, {
613
+ property: refDef.property,
614
+ associationType: association.associationType,
615
+ targetModel: association.target.name
616
+ });
617
+ options.include.push({
618
+ model: association.target,
619
+ as: refDef.property,
620
+ required: false
621
+ // Use LEFT JOIN to preserve items without references
622
+ });
623
+ includedReferences.push(refDef.property);
624
+ } else {
625
+ logger3.debug(`Association '${refDef.property}' already included in query`, {
626
+ property: refDef.property
627
+ });
628
+ includedReferences.push(refDef.property);
629
+ }
630
+ } else {
631
+ logger3.debug(`No association found for reference '${refDef.property}' - will use separate query`, {
632
+ property: refDef.property,
633
+ availableAssociations: Object.keys(model.associations || {})
634
+ });
635
+ }
636
+ }
637
+ return { options, includedReferences };
638
+ };
344
639
  var addAggregationIncludes = (options, model, aggregationDefinitions) => {
345
640
  if (!aggregationDefinitions || aggregationDefinitions.length === 0) {
346
641
  return { options, includedAggregations: [] };
@@ -354,7 +649,7 @@ var addAggregationIncludes = (options, model, aggregationDefinitions) => {
354
649
  (inc) => typeof inc === "string" && inc === aggDef.property || typeof inc === "object" && inc.as === aggDef.property
355
650
  );
356
651
  if (!alreadyIncluded) {
357
- logger3.debug(`Auto-detected association for aggregation '${aggDef.property}' - using INCLUDE to prevent N+1`, {
652
+ logger3.default(`Auto-detected association for aggregation '${aggDef.property}' - using INCLUDE to prevent N+1`, {
358
653
  property: aggDef.property,
359
654
  associationType: association.associationType,
360
655
  targetModel: association.target.name
@@ -381,14 +676,25 @@ var addAggregationIncludes = (options, model, aggregationDefinitions) => {
381
676
  }
382
677
  return { options, includedAggregations };
383
678
  };
384
- var buildQuery = (itemQuery, model) => {
679
+ var buildQuery = (itemQuery, model, references, registry) => {
385
680
  logger3.default(`QueryBuilder build called with itemQuery: ${stringifyJSON(itemQuery)}`);
681
+ logger3.debug(`[buildQuery] Parameters:`, {
682
+ modelName: model.name,
683
+ referencesCount: references?.length || 0,
684
+ references: references?.map((r) => ({ property: r.property, column: r.column })) || [],
685
+ hasRegistry: !!registry,
686
+ hasCompoundCondition: !!itemQuery.compoundCondition
687
+ });
386
688
  let options = {
387
689
  where: {}
388
690
  };
691
+ const allReferences = /* @__PURE__ */ new Map();
692
+ if (references) {
693
+ allReferences.set(model.name, references);
694
+ }
389
695
  if (itemQuery.compoundCondition) {
390
696
  logger3.default(`QueryBuilder adding conditions: ${stringifyJSON(itemQuery.compoundCondition)}`);
391
- options = addCompoundCondition(options, itemQuery.compoundCondition, model);
697
+ options = addCompoundCondition(options, itemQuery.compoundCondition, model, references, allReferences, registry);
392
698
  }
393
699
  if (model.getAttributes().deletedAt || model.getAttributes().isDeleted) {
394
700
  options = addDeleteQuery(options, model);
@@ -699,12 +1005,12 @@ var buildSequelizeReference = async (item, referenceDefinition, registry, contex
699
1005
  pk: columnValue,
700
1006
  loc: []
701
1007
  };
702
- libLogger3.debug("Using empty loc array for composite item reference", {
1008
+ libLogger3.default("Using empty loc array for composite item reference", {
703
1009
  kta: referenceDefinition.kta,
704
1010
  property: referenceDefinition.property
705
1011
  });
706
1012
  }
707
- libLogger3.debug("Created reference key", {
1013
+ libLogger3.default("Created reference key", {
708
1014
  itemKey,
709
1015
  isCompositeItem,
710
1016
  hasLocationColumns: !!referenceDefinition.locationColumns,
@@ -716,7 +1022,7 @@ var buildSequelizeReference = async (item, referenceDefinition, registry, contex
716
1022
  libLogger3.debug("Using cached reference", { itemKey, property: referenceDefinition.property });
717
1023
  referencedItem = context.getCached(itemKey);
718
1024
  } else if (context.isInProgress(itemKey)) {
719
- libLogger3.debug("Circular dependency detected, creating reference placeholder", {
1025
+ libLogger3.default("Circular dependency detected, creating reference placeholder", {
720
1026
  itemKey,
721
1027
  property: referenceDefinition.property
722
1028
  });
@@ -879,7 +1185,7 @@ function addAggsToItem(item, aggregationDefinitions) {
879
1185
  if (typeof aggregationValue !== "undefined") {
880
1186
  aggs[aggDef.property] = aggregationValue;
881
1187
  delete result[aggDef.property];
882
- libLogger2.debug(`Moved aggregation '${aggDef.property}' to aggs structure`, {
1188
+ libLogger2.default(`Moved aggregation '${aggDef.property}' to aggs structure`, {
883
1189
  property: aggDef.property,
884
1190
  hasValue: typeof aggregationValue !== "undefined",
885
1191
  valueType: Array.isArray(aggregationValue) ? "array" : typeof aggregationValue
@@ -919,7 +1225,7 @@ function removeAggsFromItem(item, aggregationDefinitions) {
919
1225
 
920
1226
  // src/RowProcessor.ts
921
1227
  var logger6 = logger_default.get("sequelize", "RowProcessor");
922
- var processRow = async (row, keyTypes, referenceDefinitions, aggregationDefinitions, registry, context, includedAggregations) => {
1228
+ var processRow = async (row, keyTypes, referenceDefinitions, aggregationDefinitions, registry, context, includedAggregations, includedReferences) => {
923
1229
  logger6.default("Processing Row", { row });
924
1230
  const operationContext = context || createOperationContext();
925
1231
  return contextManager.withContext(operationContext, async () => {
@@ -932,9 +1238,22 @@ var processRow = async (row, keyTypes, referenceDefinitions, aggregationDefiniti
932
1238
  try {
933
1239
  if (referenceDefinitions && referenceDefinitions.length > 0) {
934
1240
  const referenceStartTime = typeof performance !== "undefined" ? performance.now() : Date.now();
935
- const referencePromises = referenceDefinitions.map((referenceDefinition) => {
936
- logger6.default("Processing Reference for %s to %s", item.key.kt, stringifyJSON(referenceDefinition.kta));
937
- return buildSequelizeReference(item, referenceDefinition, registry, operationContext);
1241
+ const referencePromises = referenceDefinitions.map(async (referenceDefinition) => {
1242
+ const alreadyLoaded = includedReferences && includedReferences.includes(referenceDefinition.property) && typeof item[referenceDefinition.property] !== "undefined";
1243
+ if (alreadyLoaded) {
1244
+ logger6.default(
1245
+ `Skipping buildSequelizeReference for '${referenceDefinition.property}' - already loaded via INCLUDE (N+1 prevention)`,
1246
+ {
1247
+ property: referenceDefinition.property,
1248
+ itemType: item.key.kt,
1249
+ hasData: item[referenceDefinition.property] ? "loaded" : "undefined"
1250
+ }
1251
+ );
1252
+ return item;
1253
+ } else {
1254
+ logger6.default("Processing Reference for %s to %s", item.key.kt, stringifyJSON(referenceDefinition.kta));
1255
+ return buildSequelizeReference(item, referenceDefinition, registry, operationContext);
1256
+ }
938
1257
  });
939
1258
  await Promise.all(referencePromises);
940
1259
  const referenceDuration = (typeof performance !== "undefined" ? performance.now() : Date.now()) - referenceStartTime;
@@ -946,7 +1265,7 @@ var processRow = async (row, keyTypes, referenceDefinitions, aggregationDefiniti
946
1265
  for (const aggregationDefinition of aggregationDefinitions) {
947
1266
  const alreadyLoaded = includedAggregations && includedAggregations.includes(aggregationDefinition.property) && typeof item[aggregationDefinition.property] !== "undefined";
948
1267
  if (alreadyLoaded) {
949
- logger6.debug(
1268
+ logger6.default(
950
1269
  `Skipping buildAggregation for '${aggregationDefinition.property}' - already loaded via INCLUDE (N+1 prevention)`,
951
1270
  {
952
1271
  property: aggregationDefinition.property,
@@ -966,11 +1285,11 @@ var processRow = async (row, keyTypes, referenceDefinitions, aggregationDefiniti
966
1285
  }
967
1286
  if (referenceDefinitions && referenceDefinitions.length > 0) {
968
1287
  item = addRefsToSequelizeItem(item, referenceDefinitions);
969
- logger6.debug("Added refs structure to item (transparent wrapper)", { key: item.key });
1288
+ logger6.default("Added refs structure to item (transparent wrapper)", { key: item.key });
970
1289
  }
971
1290
  if (aggregationDefinitions && aggregationDefinitions.length > 0) {
972
1291
  item = addAggsToItem(item, aggregationDefinitions);
973
- logger6.debug("Added aggs structure to item (transparent wrapper)", { key: item.key });
1292
+ logger6.default("Added aggs structure to item (transparent wrapper)", { key: item.key });
974
1293
  }
975
1294
  logger6.default("Processed Row: %j", stringifyJSON(item));
976
1295
  return item;
@@ -1169,8 +1488,57 @@ function transformSequelizeError(error, itemType, key, modelName, itemData) {
1169
1488
  return error;
1170
1489
  }
1171
1490
 
1491
+ // src/metrics/QueryMetrics.ts
1492
+ var logger7 = logger_default.get("sequelize", "metrics", "QueryMetrics");
1493
+ var QueryMetrics = class {
1494
+ totalQueryCount = 0;
1495
+ queriesByModel = /* @__PURE__ */ new Map();
1496
+ LOG_INTERVAL = 10;
1497
+ /**
1498
+ * Records a query execution for a given model
1499
+ * @param modelName - The name of the Sequelize model the query was executed against
1500
+ */
1501
+ recordQuery(modelName) {
1502
+ this.totalQueryCount++;
1503
+ const currentCount = this.queriesByModel.get(modelName) || 0;
1504
+ this.queriesByModel.set(modelName, currentCount + 1);
1505
+ if (this.totalQueryCount % this.LOG_INTERVAL === 0) {
1506
+ const modelBreakdown = Array.from(this.queriesByModel.entries()).map(([model, count]) => `${model}: ${count}`).join(", ");
1507
+ logger7.debug(
1508
+ `Query execution count: ${this.totalQueryCount} total queries. Breakdown by model: ${modelBreakdown || "none"}`
1509
+ );
1510
+ }
1511
+ }
1512
+ /**
1513
+ * Gets the total number of queries executed
1514
+ */
1515
+ getTotalQueryCount() {
1516
+ return this.totalQueryCount;
1517
+ }
1518
+ /**
1519
+ * Gets the number of queries executed for a specific model
1520
+ */
1521
+ getQueryCountForModel(modelName) {
1522
+ return this.queriesByModel.get(modelName) || 0;
1523
+ }
1524
+ /**
1525
+ * Gets a map of all model query counts
1526
+ */
1527
+ getQueriesByModel() {
1528
+ return new Map(this.queriesByModel);
1529
+ }
1530
+ /**
1531
+ * Resets all metrics (useful for testing)
1532
+ */
1533
+ reset() {
1534
+ this.totalQueryCount = 0;
1535
+ this.queriesByModel.clear();
1536
+ }
1537
+ };
1538
+ var queryMetrics = new QueryMetrics();
1539
+
1172
1540
  // src/ops/all.ts
1173
- var logger7 = logger_default.get("sequelize", "ops", "all");
1541
+ var logger8 = logger_default.get("sequelize", "ops", "all");
1174
1542
  var mergeIncludes = (existingIncludes, newIncludes) => {
1175
1543
  const mergedIncludes = [...existingIncludes];
1176
1544
  for (const newInclude of newIncludes) {
@@ -1197,10 +1565,16 @@ var getAllOperation = (models, definition, registry) => {
1197
1565
  async (itemQuery, locations, allOptions) => {
1198
1566
  try {
1199
1567
  const locs = locations ?? [];
1200
- logger7.debug(`ALL operation called on ${models[0].name} with ${locs.length} location filters: ${locs.map((loc2) => `${loc2.kt}=${loc2.lk}`).join(", ") || "none"}`);
1568
+ logger8.debug(`ALL operation called on ${models[0].name} with ${locs.length} location filters: ${locs.map((loc2) => `${loc2.kt}=${loc2.lk}`).join(", ") || "none"}`);
1201
1569
  const loc = locs;
1202
1570
  const model = models[0];
1203
- let options = buildQuery(itemQuery ?? {}, model);
1571
+ let options = buildQuery(itemQuery ?? {}, model, references, registry);
1572
+ const { options: optionsWithRefs, includedReferences } = addReferenceIncludes(
1573
+ options,
1574
+ model,
1575
+ references || []
1576
+ );
1577
+ options = optionsWithRefs;
1204
1578
  const { options: optionsWithAggs, includedAggregations } = addAggregationIncludes(
1205
1579
  options,
1206
1580
  model,
@@ -1216,7 +1590,7 @@ var getAllOperation = (models, definition, registry) => {
1216
1590
  const relationshipInfo = buildRelationshipPath(model, locKey.kt, kta, true);
1217
1591
  if (!relationshipInfo.found) {
1218
1592
  const errorMessage = `Location key '${locKey.kt}' cannot be resolved on model '${model.name}' or through its relationships.`;
1219
- logger7.error(errorMessage, { locations: loc, kta });
1593
+ logger8.error(errorMessage, { locations: loc, kta });
1220
1594
  throw new Error(errorMessage);
1221
1595
  }
1222
1596
  if (relationshipInfo.isDirect) {
@@ -1227,31 +1601,31 @@ var getAllOperation = (models, definition, registry) => {
1227
1601
  }
1228
1602
  for (const locKey of directLocations) {
1229
1603
  if (locKey.lk === void 0 || locKey.lk == null || locKey.lk === "" || typeof locKey.lk === "object" && Object.keys(locKey.lk).length === 0) {
1230
- logger7.error(`Location key '${locKey.kt}' has invalid lk value: ${stringifyJSON(locKey.lk)}`, { locKey, locations: loc });
1604
+ logger8.error(`Location key '${locKey.kt}' has invalid lk value: ${stringifyJSON(locKey.lk)}`, { locKey, locations: loc });
1231
1605
  throw new Error(`Location key '${locKey.kt}' has invalid lk value: ${stringifyJSON(locKey.lk)}`);
1232
1606
  }
1233
1607
  const foreignKeyField = locKey.kt + "Id";
1234
1608
  if (options.where[foreignKeyField]) {
1235
- logger7.debug(`[ALL] Field ${foreignKeyField} already constrained by itemQuery, skipping location constraint to avoid conflicts`);
1609
+ logger8.debug(`[ALL] Field ${foreignKeyField} already constrained by itemQuery, skipping location constraint to avoid conflicts`);
1236
1610
  continue;
1237
1611
  }
1238
- logger7.trace(`[ALL] Setting direct location where clause: ${foreignKeyField} = ${stringifyJSON(locKey.lk)} (type: ${typeof locKey.lk})`);
1612
+ logger8.trace(`[ALL] Setting direct location where clause: ${foreignKeyField} = ${stringifyJSON(locKey.lk)} (type: ${typeof locKey.lk})`);
1239
1613
  options.where[foreignKeyField] = {
1240
1614
  [Op2.eq]: locKey.lk
1241
1615
  };
1242
1616
  }
1243
1617
  for (const locKey of hierarchicalLocations) {
1244
1618
  if (locKey.lk === void 0 || locKey.lk == null || locKey.lk === "" || typeof locKey.lk === "object" && Object.keys(locKey.lk).length === 0) {
1245
- logger7.error(`Hierarchical location key '${locKey.kt}' has invalid lk value: ${stringifyJSON(locKey.lk)}`, { locKey, locations: loc });
1619
+ logger8.error(`Hierarchical location key '${locKey.kt}' has invalid lk value: ${stringifyJSON(locKey.lk)}`, { locKey, locations: loc });
1246
1620
  throw new Error(`Hierarchical location key '${locKey.kt}' has invalid lk value: ${stringifyJSON(locKey.lk)}`);
1247
1621
  }
1248
1622
  const relationshipInfo = buildRelationshipPath(model, locKey.kt, kta);
1249
1623
  if (relationshipInfo.found && relationshipInfo.path) {
1250
1624
  if (options.where[relationshipInfo.path]) {
1251
- logger7.debug(`[ALL] Field ${relationshipInfo.path} already constrained by itemQuery, skipping hierarchical location constraint to avoid conflicts`);
1625
+ logger8.debug(`[ALL] Field ${relationshipInfo.path} already constrained by itemQuery, skipping hierarchical location constraint to avoid conflicts`);
1252
1626
  continue;
1253
1627
  }
1254
- logger7.trace(`[ALL] Setting hierarchical location where clause: ${relationshipInfo.path} = ${stringifyJSON(locKey.lk)} (type: ${typeof locKey.lk})`);
1628
+ logger8.trace(`[ALL] Setting hierarchical location where clause: ${relationshipInfo.path} = ${stringifyJSON(locKey.lk)} (type: ${typeof locKey.lk})`);
1255
1629
  options.where[relationshipInfo.path] = {
1256
1630
  [Op2.eq]: locKey.lk
1257
1631
  };
@@ -1269,7 +1643,7 @@ var getAllOperation = (models, definition, registry) => {
1269
1643
  const effectiveOffset = allOptions?.offset ?? itemQuery?.offset ?? 0;
1270
1644
  const whereFields = options.where ? Object.keys(options.where).join(", ") : "none";
1271
1645
  const includeCount = options.include?.length || 0;
1272
- logger7.default(
1646
+ logger8.default(
1273
1647
  `All query configured for ${model.name} with where fields: ${whereFields}, includes: ${includeCount}, limit: ${effectiveLimit}, offset: ${effectiveOffset}`
1274
1648
  );
1275
1649
  const countOptions = {
@@ -1279,9 +1653,10 @@ var getAllOperation = (models, definition, registry) => {
1279
1653
  if (options.include) {
1280
1654
  countOptions.include = options.include;
1281
1655
  }
1656
+ queryMetrics.recordQuery(model.name);
1282
1657
  const countResult = await model.count(countOptions);
1283
1658
  const total = Array.isArray(countResult) ? countResult.length : countResult;
1284
- logger7.debug(`[ALL] Total count for ${model.name}: ${total}`);
1659
+ logger8.debug(`[ALL] Total count for ${model.name}: ${total}`);
1285
1660
  delete options.limit;
1286
1661
  delete options.offset;
1287
1662
  if (effectiveLimit !== void 0) {
@@ -1291,10 +1666,11 @@ var getAllOperation = (models, definition, registry) => {
1291
1666
  options.offset = effectiveOffset;
1292
1667
  }
1293
1668
  try {
1294
- logger7.trace(`[ALL] Executing ${model.name}.findAll() with options: ${JSON.stringify(options, null, 2)}`);
1669
+ logger8.trace(`[ALL] Executing ${model.name}.findAll() with options: ${JSON.stringify(options, null, 2)}`);
1295
1670
  } catch {
1296
- logger7.trace(`[ALL] Executing ${model.name}.findAll() with options containing non-serializable operators (${Object.keys(options.where || {}).length} where conditions)`);
1671
+ logger8.trace(`[ALL] Executing ${model.name}.findAll() with options containing non-serializable operators (${Object.keys(options.where || {}).length} where conditions)`);
1297
1672
  }
1673
+ queryMetrics.recordQuery(model.name);
1298
1674
  const matchingItems = await model.findAll(options);
1299
1675
  const currentContext = contextManager.getCurrentContext();
1300
1676
  const items = await Promise.all(matchingItems.map(async (row) => {
@@ -1305,11 +1681,12 @@ var getAllOperation = (models, definition, registry) => {
1305
1681
  aggregations || [],
1306
1682
  registry,
1307
1683
  currentContext,
1308
- includedAggregations
1684
+ includedAggregations,
1685
+ includedReferences
1309
1686
  );
1310
1687
  return validateKeys(processedRow, coordinate.kta);
1311
1688
  }));
1312
- logger7.debug(`[ALL] Returning ${items.length} of ${total} ${model.name} records`);
1689
+ logger8.debug(`[ALL] Returning ${items.length} of ${total} ${model.name} records`);
1313
1690
  return {
1314
1691
  items,
1315
1692
  metadata: {
@@ -1330,7 +1707,7 @@ var getAllOperation = (models, definition, registry) => {
1330
1707
  // src/ops/create.ts
1331
1708
  import { createCreateWrapper, isComKey as isComKey3, isPriKey as isPriKey3 } from "@fjell/core";
1332
1709
  import { validateKeys as validateKeys2 } from "@fjell/core/validation";
1333
- var logger8 = logger_default.get("sequelize", "ops", "create");
1710
+ var logger9 = logger_default.get("sequelize", "ops", "create");
1334
1711
  async function validateHierarchicalChain(models, locKey, kta) {
1335
1712
  const locatorIndex = kta.indexOf(locKey.kt);
1336
1713
  if (locatorIndex === -1) {
@@ -1340,6 +1717,7 @@ async function validateHierarchicalChain(models, locKey, kta) {
1340
1717
  try {
1341
1718
  const chainResult = buildRelationshipChain(locatorModel, kta, locatorIndex, kta.length - 1);
1342
1719
  if (!chainResult.success) {
1720
+ queryMetrics.recordQuery(locatorModel.name);
1343
1721
  const record2 = await locatorModel.findByPk(locKey.lk);
1344
1722
  if (!record2) {
1345
1723
  throw new Error(`Referenced ${locKey.kt} with id ${locKey.lk} does not exist`);
@@ -1352,6 +1730,7 @@ async function validateHierarchicalChain(models, locKey, kta) {
1352
1730
  if (chainResult.includes && chainResult.includes.length > 0) {
1353
1731
  queryOptions.include = chainResult.includes;
1354
1732
  }
1733
+ queryMetrics.recordQuery(locatorModel.name);
1355
1734
  const record = await locatorModel.findOne(queryOptions);
1356
1735
  if (!record) {
1357
1736
  throw new Error(`Referenced ${locKey.kt} with id ${locKey.lk} does not exist or chain is invalid`);
@@ -1370,8 +1749,8 @@ var getCreateOperation = (models, definition, registry) => {
1370
1749
  coordinate,
1371
1750
  async (item, options) => {
1372
1751
  const constraints = options?.key ? `key: pk=${options.key.pk}, loc=[${isComKey3(options.key) ? options.key.loc.map((l) => `${l.kt}=${l.lk}`).join(", ") : ""}]` : options?.locations ? `locations: ${options.locations.map((loc) => `${loc.kt}=${loc.lk}`).join(", ")}` : "no constraints";
1373
- logger8.debug(`CREATE operation called on ${models[0].name} with ${constraints}`);
1374
- logger8.default(`Create configured for ${models[0].name} with ${Object.keys(item).length} item fields`);
1752
+ logger9.debug(`CREATE operation called on ${models[0].name} with ${constraints}`);
1753
+ logger9.default(`Create configured for ${models[0].name} with ${Object.keys(item).length} item fields`);
1375
1754
  const model = models[0];
1376
1755
  const modelAttributes = model.getAttributes();
1377
1756
  let itemData = { ...item };
@@ -1409,7 +1788,7 @@ var getCreateOperation = (models, definition, registry) => {
1409
1788
  if (!relationshipInfo.found) {
1410
1789
  const associations = model.associations ? Object.keys(model.associations) : [];
1411
1790
  const errorMessage = `Composite key locator '${locKey.kt}' cannot be resolved on model '${model.name}' or through its relationships. Available associations: [${associations.join(", ")}]. KTA: [${kta.join(", ")}]. Composite key: ${JSON.stringify(comKey, null, 2)}`;
1412
- logger8.error(errorMessage, { key: comKey, kta, associations });
1791
+ logger9.error(errorMessage, { key: comKey, kta, associations });
1413
1792
  throw new Error(errorMessage);
1414
1793
  }
1415
1794
  if (relationshipInfo.isDirect) {
@@ -1420,7 +1799,7 @@ var getCreateOperation = (models, definition, registry) => {
1420
1799
  }
1421
1800
  for (const locKey of directLocations) {
1422
1801
  if (locKey.lk == null || locKey.lk === "") {
1423
- logger8.error(`Composite key location '${locKey.kt}' has undefined/null lk value`, { locKey, key: comKey });
1802
+ logger9.error(`Composite key location '${locKey.kt}' has undefined/null lk value`, { locKey, key: comKey });
1424
1803
  throw new Error(`Composite key location '${locKey.kt}' has undefined/null lk value`);
1425
1804
  }
1426
1805
  const foreignKeyField = locKey.kt + "Id";
@@ -1439,7 +1818,7 @@ var getCreateOperation = (models, definition, registry) => {
1439
1818
  if (!relationshipInfo.found) {
1440
1819
  const associations = model.associations ? Object.keys(model.associations) : [];
1441
1820
  const errorMessage = `Location key '${locKey.kt}' cannot be resolved on model '${model.name}' or through its relationships. Available associations: [${associations.join(", ")}]. KTA: [${kta.join(", ")}]. Locations: ${JSON.stringify(options.locations, null, 2)}`;
1442
- logger8.error(errorMessage, { locations: options.locations, kta, associations });
1821
+ logger9.error(errorMessage, { locations: options.locations, kta, associations });
1443
1822
  throw new Error(errorMessage);
1444
1823
  }
1445
1824
  if (relationshipInfo.isDirect) {
@@ -1450,7 +1829,7 @@ var getCreateOperation = (models, definition, registry) => {
1450
1829
  }
1451
1830
  for (const locKey of directLocations) {
1452
1831
  if (locKey.lk == null || locKey.lk === "") {
1453
- logger8.error(`Location option '${locKey.kt}' has undefined/null lk value`, { locKey, locations: options.locations });
1832
+ logger9.error(`Location option '${locKey.kt}' has undefined/null lk value`, { locKey, locations: options.locations });
1454
1833
  throw new Error(`Location option '${locKey.kt}' has undefined/null lk value`);
1455
1834
  }
1456
1835
  const foreignKeyField = locKey.kt + "Id";
@@ -1461,11 +1840,12 @@ var getCreateOperation = (models, definition, registry) => {
1461
1840
  }
1462
1841
  }
1463
1842
  try {
1464
- logger8.trace(`[CREATE] Executing ${model.name}.create() with data: ${stringifyJSON(itemData)}`);
1843
+ logger9.trace(`[CREATE] Executing ${model.name}.create() with data: ${stringifyJSON(itemData)}`);
1844
+ queryMetrics.recordQuery(model.name);
1465
1845
  const createdRecord = await model.create(itemData);
1466
1846
  const processedRecord = await processRow(createdRecord, kta, references || [], aggregations || [], registry, void 0, void 0);
1467
1847
  const result = validateKeys2(processedRecord, kta);
1468
- logger8.debug(`[CREATE] Created ${model.name} with key: ${result.key ? JSON.stringify(result.key) : `id=${createdRecord.id}`}`);
1848
+ logger9.debug(`[CREATE] Created ${model.name} with key: ${result.key ? JSON.stringify(result.key) : `id=${createdRecord.id}`}`);
1469
1849
  return result;
1470
1850
  } catch (error) {
1471
1851
  throw transformSequelizeError(error, kta[0], options?.key, model.name, itemData);
@@ -1477,7 +1857,7 @@ var getCreateOperation = (models, definition, registry) => {
1477
1857
  // src/ops/find.ts
1478
1858
  import { createFindWrapper } from "@fjell/core";
1479
1859
  import { validateKeys as validateKeys3 } from "@fjell/core/validation";
1480
- var logger9 = logger_default.get("sequelize", "ops", "find");
1860
+ var logger10 = logger_default.get("sequelize", "ops", "find");
1481
1861
  var getFindOperation = (models, definition, registry) => {
1482
1862
  const { options: { finders, references, aggregations } } = definition;
1483
1863
  return createFindWrapper(
@@ -1487,20 +1867,20 @@ var getFindOperation = (models, definition, registry) => {
1487
1867
  const locs = locations ?? [];
1488
1868
  const params = finderParams ?? {};
1489
1869
  const locationFilters = locs.map((loc) => `${loc.kt}=${loc.lk}`).join(", ") || "none";
1490
- logger9.debug(
1870
+ logger10.debug(
1491
1871
  `FIND operation called on ${models[0].name} with finder '${finder}' and ${locs.length} location filters: ${locationFilters}`
1492
1872
  );
1493
- logger9.default(`Find configured for ${models[0].name} using finder '${finder}' with ${Object.keys(params).length} params`);
1873
+ logger10.default(`Find configured for ${models[0].name} using finder '${finder}' with ${Object.keys(params).length} params`);
1494
1874
  if (!finders || !finders[finder]) {
1495
- logger9.error(`No finders have been defined for this lib`);
1875
+ logger10.error(`No finders have been defined for this lib`);
1496
1876
  throw new Error(`No finders found`);
1497
1877
  }
1498
1878
  const finderMethod = finders[finder];
1499
1879
  if (!finderMethod) {
1500
- logger9.error(`Finder %s not found`, finder);
1880
+ logger10.error(`Finder %s not found`, finder);
1501
1881
  throw new Error(`Finder ${finder} not found`);
1502
1882
  }
1503
- logger9.trace(`[FIND] Executing finder '${finder}' on ${models[0].name} with params: ${stringifyJSON(params)}, locations: ${stringifyJSON(locs)}, options: ${stringifyJSON(findOptions)}`);
1883
+ logger10.trace(`[FIND] Executing finder '${finder}' on ${models[0].name} with params: ${stringifyJSON(params)}, locations: ${stringifyJSON(locs)}, options: ${stringifyJSON(findOptions)}`);
1504
1884
  const finderResult = await finderMethod(params, locs, findOptions);
1505
1885
  const processItems = async (items) => {
1506
1886
  return await Promise.all(items.map(async (row) => {
@@ -1512,7 +1892,7 @@ var getFindOperation = (models, definition, registry) => {
1512
1892
  if (isOptInResult) {
1513
1893
  const optInResult = finderResult;
1514
1894
  const processedResults2 = optInResult.items && optInResult.items.length > 0 ? await processItems(optInResult.items) : [];
1515
- logger9.debug(`[FIND] Finder opted-in, found ${processedResults2.length} ${models[0].name} records using finder '${finder}' (total: ${optInResult.metadata.total})`);
1895
+ logger10.debug(`[FIND] Finder opted-in, found ${processedResults2.length} ${models[0].name} records using finder '${finder}' (total: ${optInResult.metadata.total})`);
1516
1896
  return {
1517
1897
  items: processedResults2,
1518
1898
  metadata: optInResult.metadata
@@ -1520,7 +1900,7 @@ var getFindOperation = (models, definition, registry) => {
1520
1900
  }
1521
1901
  const results = finderResult;
1522
1902
  const processedResults = results && results.length > 0 ? await processItems(results) : [];
1523
- logger9.debug(`[FIND] Legacy finder, found ${processedResults.length} ${models[0].name} records using finder '${finder}'`);
1903
+ logger10.debug(`[FIND] Legacy finder, found ${processedResults.length} ${models[0].name} records using finder '${finder}'`);
1524
1904
  return {
1525
1905
  items: processedResults,
1526
1906
  metadata: {
@@ -1531,7 +1911,7 @@ var getFindOperation = (models, definition, registry) => {
1531
1911
  }
1532
1912
  };
1533
1913
  } catch (error) {
1534
- logger9.error("Error in find operation", {
1914
+ logger10.error("Error in find operation", {
1535
1915
  finder,
1536
1916
  finderParams,
1537
1917
  locations,
@@ -1563,7 +1943,7 @@ import {
1563
1943
  validateKeys as validateKeys4
1564
1944
  } from "@fjell/core";
1565
1945
  import { NotFoundError } from "@fjell/core";
1566
- var logger10 = logger_default.get("sequelize", "ops", "get");
1946
+ var logger11 = logger_default.get("sequelize", "ops", "get");
1567
1947
  var processCompositeKey = (comKey, model, kta) => {
1568
1948
  const where = { id: comKey.pk };
1569
1949
  const includes = [];
@@ -1571,7 +1951,7 @@ var processCompositeKey = (comKey, model, kta) => {
1571
1951
  const relationshipInfo = buildRelationshipPath(model, locator.kt, kta);
1572
1952
  if (!relationshipInfo.found) {
1573
1953
  const errorMessage = `Composite key locator '${locator.kt}' cannot be resolved on model '${model.name}' or through its relationships. Key type array: [${kta.join(", ")}], Composite key: ${stringifyJSON(comKey)}, Available associations: [${Object.keys(model.associations || {}).join(", ")}]`;
1574
- logger10.error(errorMessage, { key: comKey, kta });
1954
+ logger11.error(errorMessage, { key: comKey, kta });
1575
1955
  throw new Error(errorMessage);
1576
1956
  }
1577
1957
  if (relationshipInfo.path) {
@@ -1598,40 +1978,53 @@ var getGetOperation = (models, definition, registry) => {
1598
1978
  async (key) => {
1599
1979
  try {
1600
1980
  if (!isValidItemKey(key)) {
1601
- logger10.error("Key for Get is not a valid ItemKey: %j", key);
1981
+ logger11.error("Key for Get is not a valid ItemKey: %j", key);
1602
1982
  throw new Error("Key for Get is not a valid ItemKey");
1603
1983
  }
1604
1984
  const keyDescription = isPriKey4(key) ? `primary key: pk=${key.pk}` : `composite key: pk=${key.pk}, loc=[${key.loc.map((l) => `${l.kt}=${l.lk}`).join(", ")}]`;
1605
- logger10.debug(`GET operation called on ${models[0].name} with ${keyDescription}`);
1606
- logger10.default(`Get configured for ${models[0].name} with ${isPriKey4(key) ? "primary" : "composite"} key`);
1985
+ logger11.debug(`GET operation called on ${models[0].name} with ${keyDescription}`);
1986
+ logger11.default(`Get configured for ${models[0].name} with ${isPriKey4(key) ? "primary" : "composite"} key`);
1607
1987
  const itemKey = key;
1608
1988
  const model = models[0];
1609
1989
  let item;
1610
1990
  let includedAggregations = [];
1991
+ let includedReferences = [];
1611
1992
  if (isPriKey4(itemKey)) {
1612
1993
  let options = {};
1994
+ const refResult = addReferenceIncludes(options, model, references || []);
1995
+ includedReferences = refResult.includedReferences;
1996
+ options = refResult.options;
1613
1997
  const aggResult = addAggregationIncludes(options, model, aggregations || []);
1614
1998
  includedAggregations = aggResult.includedAggregations;
1615
1999
  options = aggResult.options;
1616
- logger10.trace(`[GET] Executing ${model.name}.findByPk() with pk: ${itemKey.pk} and ${includedAggregations.length} aggregation includes`);
2000
+ logger11.trace(`[GET] Executing ${model.name}.findByPk() with pk: ${itemKey.pk}, ${includedReferences.length} reference includes, and ${includedAggregations.length} aggregation includes`);
2001
+ queryMetrics.recordQuery(model.name);
1617
2002
  item = options.include && options.include.length > 0 ? await model.findByPk(itemKey.pk, { include: options.include }) : await model.findByPk(itemKey.pk);
1618
2003
  } else if (isComKey4(itemKey)) {
1619
2004
  const comKey = itemKey;
1620
2005
  if (comKey.loc.length === 0) {
1621
2006
  let options = {};
2007
+ const refResult = addReferenceIncludes(options, model, references || []);
2008
+ includedReferences = refResult.includedReferences;
2009
+ options = refResult.options;
1622
2010
  const aggResult = addAggregationIncludes(options, model, aggregations || []);
1623
2011
  includedAggregations = aggResult.includedAggregations;
1624
2012
  options = aggResult.options;
1625
- logger10.debug(`[GET] Empty loc array detected - finding by primary key across all locations: ${comKey.pk}`);
1626
- logger10.trace(`[GET] Executing ${model.name}.findByPk() with pk: ${comKey.pk} and ${includedAggregations.length} aggregation includes`);
2013
+ logger11.debug(`[GET] Empty loc array detected - finding by primary key across all locations: ${comKey.pk}`);
2014
+ logger11.trace(`[GET] Executing ${model.name}.findByPk() with pk: ${comKey.pk}, ${includedReferences.length} reference includes, and ${includedAggregations.length} aggregation includes`);
2015
+ queryMetrics.recordQuery(model.name);
1627
2016
  item = options.include && options.include.length > 0 ? await model.findByPk(comKey.pk, { include: options.include }) : await model.findByPk(comKey.pk);
1628
2017
  } else {
1629
2018
  let queryOptions = processCompositeKey(comKey, model, kta);
2019
+ const refResult = addReferenceIncludes(queryOptions, model, references || []);
2020
+ includedReferences = refResult.includedReferences;
2021
+ queryOptions = refResult.options;
1630
2022
  const aggResult = addAggregationIncludes(queryOptions, model, aggregations || []);
1631
2023
  includedAggregations = aggResult.includedAggregations;
1632
2024
  queryOptions = aggResult.options;
1633
- logger10.default("Composite key query", { queryOptions });
1634
- logger10.trace(`[GET] Executing ${model.name}.findOne() with options: ${stringifyJSON(queryOptions)} and ${includedAggregations.length} aggregation includes`);
2025
+ logger11.default("Composite key query", { queryOptions });
2026
+ logger11.trace(`[GET] Executing ${model.name}.findOne() with options: ${stringifyJSON(queryOptions)}, ${includedReferences.length} reference includes, and ${includedAggregations.length} aggregation includes`);
2027
+ queryMetrics.recordQuery(model.name);
1635
2028
  item = await model.findOne(queryOptions);
1636
2029
  }
1637
2030
  }
@@ -1643,8 +2036,8 @@ var getGetOperation = (models, definition, registry) => {
1643
2036
  );
1644
2037
  }
1645
2038
  const currentContext = contextManager.getCurrentContext();
1646
- const result = validateKeys4(await processRow(item, kta, references || [], aggregations || [], registry, currentContext, includedAggregations), kta);
1647
- logger10.debug(`[GET] Retrieved ${model.name} with key: ${result.key ? JSON.stringify(result.key) : `id=${item.id}`}`);
2039
+ const result = validateKeys4(await processRow(item, kta, references || [], aggregations || [], registry, currentContext, includedAggregations, includedReferences), kta);
2040
+ logger11.debug(`[GET] Retrieved ${model.name} with key: ${result.key ? JSON.stringify(result.key) : `id=${item.id}`}`);
1648
2041
  return result;
1649
2042
  } catch (error) {
1650
2043
  if (error instanceof NotFoundError) throw error;
@@ -1656,21 +2049,21 @@ var getGetOperation = (models, definition, registry) => {
1656
2049
 
1657
2050
  // src/ops/one.ts
1658
2051
  import { createOneWrapper } from "@fjell/core";
1659
- var logger11 = logger_default.get("sequelize", "ops", "one");
2052
+ var logger12 = logger_default.get("sequelize", "ops", "one");
1660
2053
  var getOneOperation = (models, definition, registry) => {
1661
2054
  return createOneWrapper(
1662
2055
  definition.coordinate,
1663
2056
  async (itemQuery, locations) => {
1664
2057
  const locs = locations ?? [];
1665
- logger11.debug(`ONE operation called on ${models[0].name} with ${locs.length} location filters: ${locs.map((loc) => `${loc.kt}=${loc.lk}`).join(", ") || "none"}`);
1666
- logger11.default(`One configured for ${models[0].name} delegating to all operation`);
2058
+ logger12.debug(`ONE operation called on ${models[0].name} with ${locs.length} location filters: ${locs.map((loc) => `${loc.kt}=${loc.lk}`).join(", ") || "none"}`);
2059
+ logger12.default(`One configured for ${models[0].name} delegating to all operation`);
1667
2060
  const result = await getAllOperation(models, definition, registry)(itemQuery ?? {}, locs, { limit: 1 });
1668
2061
  if (result.items.length > 0) {
1669
2062
  const item = result.items[0];
1670
- logger11.debug(`[ONE] Found ${models[0].name} record with key: ${item.key ? JSON.stringify(item.key) : "unknown"}`);
2063
+ logger12.debug(`[ONE] Found ${models[0].name} record with key: ${item.key ? JSON.stringify(item.key) : "unknown"}`);
1671
2064
  return item;
1672
2065
  } else {
1673
- logger11.debug(`[ONE] No ${models[0].name} record found`);
2066
+ logger12.debug(`[ONE] No ${models[0].name} record found`);
1674
2067
  return null;
1675
2068
  }
1676
2069
  }
@@ -1680,7 +2073,7 @@ var getOneOperation = (models, definition, registry) => {
1680
2073
  // src/ops/remove.ts
1681
2074
  import { abbrevIK, isComKey as isComKey5, isPriKey as isPriKey5, isValidItemKey as isValidItemKey2, createRemoveWrapper } from "@fjell/core";
1682
2075
  import { NotFoundError as NotFoundError2 } from "@fjell/core";
1683
- var logger12 = logger_default.get("sequelize", "ops", "remove");
2076
+ var logger13 = logger_default.get("sequelize", "ops", "remove");
1684
2077
  var processCompositeKey2 = (comKey, model, kta) => {
1685
2078
  const where = { id: comKey.pk };
1686
2079
  const includes = [];
@@ -1688,7 +2081,7 @@ var processCompositeKey2 = (comKey, model, kta) => {
1688
2081
  const relationshipInfo = buildRelationshipPath(model, locator.kt, kta);
1689
2082
  if (!relationshipInfo.found) {
1690
2083
  const errorMessage = `Composite key locator '${locator.kt}' cannot be resolved on model '${model.name}' or through its relationships.`;
1691
- logger12.error(errorMessage, { key: comKey, kta });
2084
+ logger13.error(errorMessage, { key: comKey, kta });
1692
2085
  throw new Error(errorMessage);
1693
2086
  }
1694
2087
  if (relationshipInfo.path) {
@@ -1715,24 +2108,26 @@ var getRemoveOperation = (models, definition, _registry) => {
1715
2108
  async (key) => {
1716
2109
  try {
1717
2110
  if (!isValidItemKey2(key)) {
1718
- logger12.error("Key for Remove is not a valid ItemKey: %j", key);
2111
+ logger13.error("Key for Remove is not a valid ItemKey: %j", key);
1719
2112
  throw new Error("Key for Remove is not a valid ItemKey");
1720
2113
  }
1721
2114
  const keyDescription = isPriKey5(key) ? `primary key: pk=${key.pk}` : `composite key: pk=${key.pk}, loc=[${key.loc.map((l) => `${l.kt}=${l.lk}`).join(", ")}]`;
1722
- logger12.debug(`REMOVE operation called on ${models[0].name} with ${keyDescription}`);
1723
- logger12.default(`Remove configured for ${models[0].name} with ${isPriKey5(key) ? "primary" : "composite"} key`);
2115
+ logger13.debug(`REMOVE operation called on ${models[0].name} with ${keyDescription}`);
2116
+ logger13.default(`Remove configured for ${models[0].name} with ${isPriKey5(key) ? "primary" : "composite"} key`);
1724
2117
  const model = models[0];
1725
2118
  let item;
1726
2119
  let returnItem;
1727
- logger12.debug("remove: %s", abbrevIK(key));
2120
+ logger13.debug("remove: %s", abbrevIK(key));
1728
2121
  if (isPriKey5(key)) {
1729
- logger12.debug(`[REMOVE] Executing ${model.name}.findByPk() with pk: ${key.pk}`);
2122
+ logger13.debug(`[REMOVE] Executing ${model.name}.findByPk() with pk: ${key.pk}`);
2123
+ queryMetrics.recordQuery(model.name);
1730
2124
  item = await model.findByPk(key.pk);
1731
2125
  } else if (isComKey5(key)) {
1732
2126
  const comKey = key;
1733
2127
  const queryOptions = processCompositeKey2(comKey, model, kta);
1734
- logger12.default(`Remove composite key query for ${model.name} with where fields: ${queryOptions.where ? Object.keys(queryOptions.where).join(", ") : "none"}`);
1735
- logger12.debug(`[REMOVE] Executing ${model.name}.findOne() with options: ${stringifyJSON(queryOptions)}`);
2128
+ logger13.default(`Remove composite key query for ${model.name} with where fields: ${queryOptions.where ? Object.keys(queryOptions.where).join(", ") : "none"}`);
2129
+ logger13.debug(`[REMOVE] Executing ${model.name}.findOne() with options: ${stringifyJSON(queryOptions)}`);
2130
+ queryMetrics.recordQuery(model.name);
1736
2131
  item = await model.findOne(queryOptions);
1737
2132
  }
1738
2133
  if (!item) {
@@ -1751,13 +2146,15 @@ var getRemoveOperation = (models, definition, _registry) => {
1751
2146
  if (model.getAttributes().deletedAt) {
1752
2147
  item.deletedAt = /* @__PURE__ */ new Date();
1753
2148
  }
1754
- logger12.debug(`[REMOVE] Executing ${model.name}.save() for soft delete`);
2149
+ logger13.debug(`[REMOVE] Executing ${model.name}.save() for soft delete`);
2150
+ queryMetrics.recordQuery(model.name);
1755
2151
  await item?.save();
1756
2152
  returnItem = item?.get({ plain: true });
1757
2153
  returnItem = addKey(item, returnItem, kta);
1758
2154
  returnItem = populateEvents(returnItem);
1759
2155
  } else if (options.deleteOnRemove) {
1760
- logger12.debug(`[REMOVE] Executing ${model.name}.destroy() for hard delete`);
2156
+ logger13.debug(`[REMOVE] Executing ${model.name}.destroy() for hard delete`);
2157
+ queryMetrics.recordQuery(model.name);
1761
2158
  await item?.destroy();
1762
2159
  returnItem = item?.get({ plain: true });
1763
2160
  returnItem = addKey(item, returnItem, kta);
@@ -1765,7 +2162,7 @@ var getRemoveOperation = (models, definition, _registry) => {
1765
2162
  } else {
1766
2163
  throw new Error("No deletedAt or isDeleted attribute found in model, and deleteOnRemove is not set");
1767
2164
  }
1768
- logger12.debug(`[REMOVE] Removed ${model.name} with key: ${returnItem.key ? JSON.stringify(returnItem.key) : `id=${item.id}`}`);
2165
+ logger13.debug(`[REMOVE] Removed ${model.name} with key: ${returnItem.key ? JSON.stringify(returnItem.key) : `id=${item.id}`}`);
1769
2166
  const { references } = options;
1770
2167
  if (references && references.length > 0) {
1771
2168
  returnItem = addRefsToSequelizeItem(returnItem, references);
@@ -1789,7 +2186,7 @@ import {
1789
2186
  import { validateKeys as validateKeys5 } from "@fjell/core/validation";
1790
2187
  import { NotFoundError as NotFoundError3 } from "@fjell/core";
1791
2188
  import { Op as Op3 } from "sequelize";
1792
- var logger13 = logger_default.get("sequelize", "ops", "update");
2189
+ var logger14 = logger_default.get("sequelize", "ops", "update");
1793
2190
  var mergeIncludes2 = (existingIncludes, newIncludes) => {
1794
2191
  const mergedIncludes = [...existingIncludes];
1795
2192
  for (const newInclude of newIncludes) {
@@ -1821,15 +2218,16 @@ var getUpdateOperation = (models, definition, registry) => {
1821
2218
  );
1822
2219
  }
1823
2220
  const keyDescription = isPriKey6(key) ? `primary key: pk=${key.pk}` : `composite key: pk=${key.pk}, loc=[${key.loc.map((l) => `${l.kt}=${l.lk}`).join(", ")}]`;
1824
- logger13.debug(`UPDATE operation called on ${models[0].name} with ${keyDescription}`, { options });
2221
+ logger14.debug(`UPDATE operation called on ${models[0].name} with ${keyDescription}`, { options });
1825
2222
  const { coordinate } = definition;
1826
2223
  const { kta } = coordinate;
1827
- logger13.debug("update: %s, %j", abbrevIK2(key), item);
2224
+ logger14.debug("update: %s, %j", abbrevIK2(key), item);
1828
2225
  const model = models[0];
1829
2226
  let response;
1830
2227
  if (isPriKey6(key)) {
1831
2228
  const priKey = key;
1832
- logger13.trace(`[UPDATE] Executing ${model.name}.findByPk() with pk: ${priKey.pk}`);
2229
+ logger14.trace(`[UPDATE] Executing ${model.name}.findByPk() with pk: ${priKey.pk}`);
2230
+ queryMetrics.recordQuery(model.name);
1833
2231
  response = await model.findByPk(priKey.pk);
1834
2232
  } else if (isComKey6(key)) {
1835
2233
  const comKey = key;
@@ -1839,7 +2237,7 @@ var getUpdateOperation = (models, definition, registry) => {
1839
2237
  const relationshipInfo = buildRelationshipPath(model, locator.kt, kta, true);
1840
2238
  if (!relationshipInfo.found) {
1841
2239
  const errorMessage = `Composite key locator '${locator.kt}' cannot be resolved on model '${model.name}' or through its relationships.`;
1842
- logger13.error(errorMessage, { key: comKey, kta });
2240
+ logger14.error(errorMessage, { key: comKey, kta });
1843
2241
  throw new Error(errorMessage);
1844
2242
  }
1845
2243
  if (relationshipInfo.isDirect) {
@@ -1858,8 +2256,9 @@ var getUpdateOperation = (models, definition, registry) => {
1858
2256
  if (additionalIncludes.length > 0) {
1859
2257
  queryOptions.include = mergeIncludes2([], additionalIncludes);
1860
2258
  }
1861
- logger13.default(`Update composite key query for ${model.name} with where fields: ${queryOptions.where ? Object.keys(queryOptions.where).join(", ") : "none"}`);
1862
- logger13.trace(`[UPDATE] Executing ${model.name}.findOne() with options: ${stringifyJSON(queryOptions)}`);
2259
+ logger14.default(`Update composite key query for ${model.name} with where fields: ${queryOptions.where ? Object.keys(queryOptions.where).join(", ") : "none"}`);
2260
+ logger14.trace(`[UPDATE] Executing ${model.name}.findOne() with options: ${stringifyJSON(queryOptions)}`);
2261
+ queryMetrics.recordQuery(model.name);
1863
2262
  response = await model.findOne(queryOptions);
1864
2263
  }
1865
2264
  if (!response) {
@@ -1878,13 +2277,14 @@ var getUpdateOperation = (models, definition, registry) => {
1878
2277
  if (aggregations && aggregations.length > 0) {
1879
2278
  updateProps = removeAggsFromItem(updateProps, aggregations);
1880
2279
  }
1881
- logger13.default(`Update found ${model.name} record to modify`);
1882
- logger13.default(`Update properties configured: ${Object.keys(updateProps).join(", ")}`);
1883
- logger13.trace(`[UPDATE] Executing ${model.name}.update() with properties: ${stringifyJSON(updateProps)}`);
2280
+ logger14.default(`Update found ${model.name} record to modify`);
2281
+ logger14.default(`Update properties configured: ${Object.keys(updateProps).join(", ")}`);
2282
+ logger14.trace(`[UPDATE] Executing ${model.name}.update() with properties: ${stringifyJSON(updateProps)}`);
2283
+ queryMetrics.recordQuery(model.name);
1884
2284
  response = await response.update(updateProps);
1885
2285
  const processedItem = await processRow(response, kta, references || [], aggregations || [], registry, void 0, void 0);
1886
2286
  const returnItem = validateKeys5(processedItem, kta);
1887
- logger13.debug(`[UPDATE] Updated ${model.name} with key: ${returnItem.key ? JSON.stringify(returnItem.key) : `id=${response.id}`}`);
2287
+ logger14.debug(`[UPDATE] Updated ${model.name} with key: ${returnItem.key ? JSON.stringify(returnItem.key) : `id=${response.id}`}`);
1888
2288
  return returnItem;
1889
2289
  } catch (error) {
1890
2290
  if (error instanceof NotFoundError3) throw error;
@@ -1896,7 +2296,7 @@ var getUpdateOperation = (models, definition, registry) => {
1896
2296
 
1897
2297
  // src/ops/upsert.ts
1898
2298
  import { createUpsertWrapper, isValidItemKey as isValidItemKey3, NotFoundError as NotFoundError4 } from "@fjell/core";
1899
- var logger14 = logger_default.get("sequelize", "ops", "upsert");
2299
+ var logger15 = logger_default.get("sequelize", "ops", "upsert");
1900
2300
  var getUpsertOperation = (models, definition, registry) => {
1901
2301
  const get = getGetOperation(models, definition, registry);
1902
2302
  const update = getUpdateOperation(models, definition, registry);
@@ -1905,31 +2305,31 @@ var getUpsertOperation = (models, definition, registry) => {
1905
2305
  definition.coordinate,
1906
2306
  async (key, item, locations, options) => {
1907
2307
  if (!isValidItemKey3(key)) {
1908
- logger14.error("Key for Upsert is not a valid ItemKey: %j", key);
2308
+ logger15.error("Key for Upsert is not a valid ItemKey: %j", key);
1909
2309
  throw new Error(`Key for Upsert is not a valid ItemKey: ${stringifyJSON(key)}`);
1910
2310
  }
1911
- logger14.debug(`[UPSERT] Attempting upsert with key: ${stringifyJSON(key)}`, { options });
2311
+ logger15.debug(`[UPSERT] Attempting upsert with key: ${stringifyJSON(key)}`, { options });
1912
2312
  let resultItem = null;
1913
2313
  try {
1914
- logger14.debug(`[UPSERT] Retrieving item by key: ${stringifyJSON(key)}`);
2314
+ logger15.debug(`[UPSERT] Retrieving item by key: ${stringifyJSON(key)}`);
1915
2315
  resultItem = await get(key);
1916
2316
  } catch (error) {
1917
2317
  const isNotFound = error instanceof NotFoundError4 || error?.name === "NotFoundError" || error?.errorInfo?.code === "NOT_FOUND";
1918
2318
  if (isNotFound) {
1919
- logger14.debug(`[UPSERT] Item not found, creating new item with key: ${stringifyJSON(key)}, errorType: ${error?.name}, errorCode: ${error?.errorInfo?.code}`);
2319
+ logger15.debug(`[UPSERT] Item not found, creating new item with key: ${stringifyJSON(key)}, errorType: ${error?.name}, errorCode: ${error?.errorInfo?.code}`);
1920
2320
  const createOptions3 = locations ? { locations } : { key };
1921
2321
  resultItem = await create(item, createOptions3);
1922
2322
  } else {
1923
- logger14.error(`[UPSERT] Unexpected error during get operation`, { error: error?.message, name: error?.name, code: error?.errorInfo?.code });
2323
+ logger15.error(`[UPSERT] Unexpected error during get operation`, { error: error?.message, name: error?.name, code: error?.errorInfo?.code });
1924
2324
  throw error;
1925
2325
  }
1926
2326
  }
1927
2327
  if (!resultItem) {
1928
2328
  throw new Error(`Failed to retrieve or create item for key: ${stringifyJSON(key)}`);
1929
2329
  }
1930
- logger14.debug(`[UPSERT] Updating item with properties, key: ${stringifyJSON(key)}`, { options });
2330
+ logger15.debug(`[UPSERT] Updating item with properties, key: ${stringifyJSON(key)}`, { options });
1931
2331
  resultItem = await update(resultItem.key, item, options);
1932
- logger14.debug(`[UPSERT] Item upserted successfully: ${stringifyJSON(resultItem)}`);
2332
+ logger15.debug(`[UPSERT] Item upserted successfully: ${stringifyJSON(resultItem)}`);
1933
2333
  return resultItem;
1934
2334
  }
1935
2335
  );
@@ -1958,9 +2358,9 @@ var createOperations = (models, coordinate, registry, options) => {
1958
2358
  };
1959
2359
 
1960
2360
  // src/SequelizeLibrary.ts
1961
- var logger15 = logger_default.get("SequelizeLibrary");
2361
+ var logger16 = logger_default.get("SequelizeLibrary");
1962
2362
  var createSequelizeLibrary = (registry, coordinate, models, options) => {
1963
- logger15.debug("createSequelizeLibrary", { coordinate, models, registry, options });
2363
+ logger16.debug("createSequelizeLibrary", { coordinate, models, registry, options });
1964
2364
  const operations = createOperations(models, coordinate, registry, options);
1965
2365
  const wrappedOperations = Library3.wrapOperations(operations, options, coordinate, registry);
1966
2366
  const libLibrary = Library3.createLibrary(registry, coordinate, wrappedOperations, options);
@@ -1974,10 +2374,10 @@ var isSequelizeLibrary = (library) => {
1974
2374
  };
1975
2375
 
1976
2376
  // src/SequelizeLibraryFactory.ts
1977
- var logger16 = logger_default.get("InstanceFactory");
2377
+ var logger17 = logger_default.get("InstanceFactory");
1978
2378
  var createSequelizeLibraryFactory = (models, options) => {
1979
2379
  return (coordinate, context) => {
1980
- logger16.debug("Creating Sequelize instance", {
2380
+ logger17.debug("Creating Sequelize instance", {
1981
2381
  coordinate,
1982
2382
  registry: context.registry,
1983
2383
  models: models.map((m) => m.name),
@@ -2022,9 +2422,9 @@ __export(primary_exports, {
2022
2422
 
2023
2423
  // src/primary/SequelizeLibrary.ts
2024
2424
  import { Primary } from "@fjell/lib";
2025
- var logger17 = logger_default.get("lib-sequelize", "primary", "library");
2425
+ var logger18 = logger_default.get("lib-sequelize", "primary", "library");
2026
2426
  function createSequelizeLibrary3(keyType, models, libOptions = {}, scopes = [], registry) {
2027
- logger17.debug("createSequelizeLibrary", { keyType, models, libOptions, scopes });
2427
+ logger18.debug("createSequelizeLibrary", { keyType, models, libOptions, scopes });
2028
2428
  const coordinate = createCoordinate([keyType], scopes);
2029
2429
  const options = createOptions2(libOptions);
2030
2430
  const operations = createOperations(models, coordinate, registry, options);