@fjell/lib-sequelize 4.4.4 → 4.4.8

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.cjs CHANGED
@@ -93,9 +93,9 @@ const createOptions = (libOptions)=>{
93
93
 
94
94
  const logger$1.default = Logging.getLogger('@fjell/lib-sequelize');
95
95
 
96
- const logger$c = logger$1.default.get('lib-sequelize', 'Definition');
96
+ const logger$f = logger$1.default.get('lib-sequelize', 'Definition');
97
97
  function createDefinition(kta, scopes, libOptions) {
98
- logger$c.debug('createDefinition', {
98
+ logger$f.debug('createDefinition', {
99
99
  kta,
100
100
  scopes,
101
101
  libOptions
@@ -109,9 +109,9 @@ function createDefinition(kta, scopes, libOptions) {
109
109
  };
110
110
  }
111
111
 
112
- const logger$b = logger$1.default.get('sequelize', 'QueryBuilder');
112
+ const logger$e = logger$1.default.get('sequelize', 'QueryBuilder');
113
113
  const addDeleteQuery = (options, model)=>{
114
- logger$b.default('Adding Delete Query', {
114
+ logger$e.default('Adding Delete Query', {
115
115
  options
116
116
  });
117
117
  if (model.getAttributes().deletedAt) {
@@ -126,7 +126,7 @@ const addDeleteQuery = (options, model)=>{
126
126
  return options;
127
127
  };
128
128
  const addEventQueries = (options, events, model)=>{
129
- logger$b.default('Adding Event Queries', {
129
+ logger$e.default('Adding Event Queries', {
130
130
  options,
131
131
  events
132
132
  });
@@ -163,12 +163,12 @@ const addEventQueries = (options, events, model)=>{
163
163
  };
164
164
  // Add the references to the query
165
165
  const addReferenceQueries = (options, references, model)=>{
166
- logger$b.default('Adding Reference Queries', {
166
+ logger$e.default('Adding Reference Queries', {
167
167
  options,
168
168
  references
169
169
  });
170
170
  Object.keys(references).forEach((key)=>{
171
- logger$b.default('Adding Reference Query', {
171
+ logger$e.default('Adding Reference Query', {
172
172
  key,
173
173
  references
174
174
  });
@@ -334,14 +334,14 @@ const addAssociationIncludes = (options, model)=>{
334
334
  return options;
335
335
  };
336
336
  const buildQuery = (itemQuery, model)=>{
337
- logger$b.default('build', {
337
+ logger$e.default('build', {
338
338
  itemQuery
339
339
  });
340
340
  let options = {
341
341
  where: {}
342
342
  };
343
343
  if (itemQuery.compoundCondition) {
344
- logger$b.default('Adding Conditions', {
344
+ logger$e.default('Adding Conditions', {
345
345
  compoundCondition: itemQuery.compoundCondition
346
346
  });
347
347
  options = addCompoundCondition(options, itemQuery.compoundCondition, model);
@@ -359,7 +359,7 @@ const buildQuery = (itemQuery, model)=>{
359
359
  // TODO: Once we start to support Aggs on the server-side, we'll need to parse agg queries
360
360
  // Apply a limit to the result set
361
361
  if (itemQuery.limit) {
362
- logger$b.default('Limiting to', {
362
+ logger$e.default('Limiting to', {
363
363
  limit: itemQuery.limit
364
364
  });
365
365
  options.limit = itemQuery.limit;
@@ -410,8 +410,8 @@ const buildQuery = (itemQuery, model)=>{
410
410
  modelChain.push(currentModel);
411
411
  }
412
412
  // Build the full association path for the target field
413
- const targetType = kta[targetIndex];
414
- const associationPath = `$${associationParts.join('.')}.${targetType}Id$`;
413
+ const targetPrimaryKey = currentModel.primaryKeyAttribute || 'id';
414
+ const associationPath = `$${associationParts.join('.')}.${targetPrimaryKey}$`;
415
415
  // Build nested includes structure iteratively (clearer than recursion)
416
416
  let deepestInclude = null;
417
417
  // Build from the deepest level back to the root
@@ -497,10 +497,10 @@ const buildQuery = (itemQuery, model)=>{
497
497
  return result;
498
498
  };
499
499
 
500
- const logger$a = logger$1.default.get('sequelize', 'KeyMaster');
500
+ const logger$d = logger$1.default.get('sequelize', 'KeyMaster');
501
501
  // Helper function to extract location key value from item
502
502
  const extractLocationKeyValue = (model, item, locatorType, kta)=>{
503
- logger$a.default('Extracting location key value', {
503
+ logger$d.default('Extracting location key value', {
504
504
  locatorType,
505
505
  kta
506
506
  });
@@ -517,23 +517,45 @@ const extractLocationKeyValue = (model, item, locatorType, kta)=>{
517
517
  }
518
518
  return value;
519
519
  } else {
520
- // Need to traverse relationship
521
- // Try to get the value from the loaded relationship object
522
- const relationshipObject = item[locatorType];
523
- if (relationshipObject && typeof relationshipObject.id !== 'undefined') {
524
- return relationshipObject.id;
520
+ // Need to traverse relationship hierarchy
521
+ // Find the path through the key type array
522
+ const locatorIndex = kta.indexOf(locatorType);
523
+ if (locatorIndex === -1) {
524
+ throw new Error(`Locator type '${locatorType}' not found in key type array`);
525
525
  }
526
- // If the relationship object isn't loaded, we might need to look at the foreign key field
527
- // This handles cases where we have the foreign key but not the full object
526
+ // Start from the current item (index 0 in kta)
527
+ let currentObject = item;
528
+ // Traverse through each intermediate relationship to reach the target
529
+ for(let i = 1; i < locatorIndex; i++){
530
+ const intermediateType = kta[i];
531
+ // Check if the intermediate relationship object is loaded
532
+ if (currentObject[intermediateType] && typeof currentObject[intermediateType] === 'object') {
533
+ currentObject = currentObject[intermediateType];
534
+ } else {
535
+ // Try the foreign key approach if the relationship object isn't loaded
536
+ const foreignKeyField = `${intermediateType}Id`;
537
+ if (typeof currentObject[foreignKeyField] !== 'undefined' && currentObject[foreignKeyField] !== null) {
538
+ // We have the foreign key but not the loaded object, we can't traverse further
539
+ throw new Error(`Intermediate relationship '${intermediateType}' is not loaded. Cannot traverse to '${locatorType}'. Either include the relationship in your query or ensure it's loaded.`);
540
+ }
541
+ throw new Error(`Intermediate relationship '${intermediateType}' is missing in the relationship chain. Expected path: ${kta.slice(0, locatorIndex + 1).join(' → ')}`);
542
+ }
543
+ }
544
+ // Now extract the target locator value from the current object
545
+ // First try to get it from the loaded relationship object
546
+ if (currentObject[locatorType] && typeof currentObject[locatorType] === 'object' && typeof currentObject[locatorType].id !== 'undefined') {
547
+ return currentObject[locatorType].id;
548
+ }
549
+ // If the relationship object isn't loaded, try the foreign key field
528
550
  const foreignKeyField = `${locatorType}Id`;
529
- if (typeof item[foreignKeyField] !== 'undefined' && item[foreignKeyField] !== null) {
530
- return item[foreignKeyField];
551
+ if (typeof currentObject[foreignKeyField] !== 'undefined' && currentObject[foreignKeyField] !== null) {
552
+ return currentObject[foreignKeyField];
531
553
  }
532
- throw new Error(`Unable to extract location key for '${locatorType}'. Neither the relationship object nor direct foreign key is available.`);
554
+ throw new Error(`Unable to extract location key for '${locatorType}'. Neither the relationship object nor direct foreign key is available. Traversal path: ${kta.slice(0, locatorIndex + 1).join(' → ')}`);
533
555
  }
534
556
  };
535
557
  const removeKey = (item)=>{
536
- logger$a.default('Removing Key', {
558
+ logger$d.default('Removing Key', {
537
559
  item
538
560
  });
539
561
  delete item.key;
@@ -567,7 +589,7 @@ const removeKey = (item)=>{
567
589
  // return item;
568
590
  // }
569
591
  const addKey = (model, item, keyTypes)=>{
570
- logger$a.default('Adding Key', {
592
+ logger$d.default('Adding Key', {
571
593
  item
572
594
  });
573
595
  const key = {};
@@ -593,7 +615,7 @@ const addKey = (model, item, keyTypes)=>{
593
615
  });
594
616
  } catch (error) {
595
617
  const errorMessage = error instanceof Error ? error.message : String(error);
596
- logger$a.error(`Failed to extract location key for '${locatorType}'`, {
618
+ logger$d.error(`Failed to extract location key for '${locatorType}'`, {
597
619
  error: errorMessage,
598
620
  item,
599
621
  keyTypes
@@ -616,7 +638,8 @@ const addKey = (model, item, keyTypes)=>{
616
638
  return item;
617
639
  };
618
640
 
619
- const buildReference = async (item, referenceDefinition, registry)=>{
641
+ const logger$c = logger$1.default.get('sequelize', 'ReferenceBuilder');
642
+ const buildReference = async (item, referenceDefinition, registry, context)=>{
620
643
  // Check if there is more than one key type
621
644
  if (referenceDefinition.kta.length > 1) {
622
645
  throw new Error("The ReferenceBuilder doesn't work with more than one key type yet");
@@ -630,13 +653,52 @@ const buildReference = async (item, referenceDefinition, registry)=>{
630
653
  if (!library) {
631
654
  throw new Error("This model definition has a reference definition, but the dependency is not present");
632
655
  }
656
+ // Check if the column value is null - if so, skip the reference
657
+ const columnValue = item[referenceDefinition.column];
658
+ if (columnValue == null) {
659
+ item[referenceDefinition.property] = null;
660
+ return item;
661
+ }
633
662
  // Create a PriKey using the column value from item
634
663
  const priKey = {
635
664
  kt: referenceDefinition.kta[0],
636
- pk: item[referenceDefinition.column]
665
+ pk: columnValue
637
666
  };
638
- // Get the referenced item using the Library.Operations get method
639
- const referencedItem = await library.operations.get(priKey);
667
+ let referencedItem;
668
+ if (context) {
669
+ // Check if we already have this item cached
670
+ if (context.isCached(priKey)) {
671
+ logger$c.default('Using cached reference', {
672
+ priKey,
673
+ property: referenceDefinition.property
674
+ });
675
+ referencedItem = context.getCached(priKey);
676
+ } else if (context.isInProgress(priKey)) {
677
+ logger$c.default('Circular dependency detected, creating reference placeholder', {
678
+ priKey,
679
+ property: referenceDefinition.property
680
+ });
681
+ // Create a minimal reference object with just the key to break the cycle
682
+ referencedItem = {
683
+ key: priKey
684
+ };
685
+ } else {
686
+ // Mark this key as in progress before loading
687
+ context.markInProgress(priKey);
688
+ try {
689
+ // Get the referenced item using the Library.Operations get method (context now managed internally)
690
+ referencedItem = await library.operations.get(priKey);
691
+ // Cache the result
692
+ context.setCached(priKey, referencedItem);
693
+ } finally{
694
+ // Always mark as complete, even if there was an error
695
+ context.markComplete(priKey);
696
+ }
697
+ }
698
+ } else {
699
+ // Fallback to original behavior if no context provided
700
+ referencedItem = await library.operations.get(priKey);
701
+ }
640
702
  // TODO: In a Fjell-compliant implementation, this value should be stored in the ref property
641
703
  // For now, we'll just populate the property directly
642
704
  // Store the result in the property on item
@@ -644,27 +706,210 @@ const buildReference = async (item, referenceDefinition, registry)=>{
644
706
  return item;
645
707
  };
646
708
 
647
- const buildAggregation = async (item, aggregationDefinition, registry)=>{
709
+ function _define_property(obj, key, value) {
710
+ if (key in obj) {
711
+ Object.defineProperty(obj, key, {
712
+ value: value,
713
+ enumerable: true,
714
+ configurable: true,
715
+ writable: true
716
+ });
717
+ } else {
718
+ obj[key] = value;
719
+ }
720
+ return obj;
721
+ }
722
+ const logger$b = logger$1.default.get('sequelize', 'OperationContext');
723
+ /**
724
+ * Serialize an ItemKey to a string for use in sets and maps
725
+ */ const OperationContext.serializeKey = (key)=>{
726
+ if ('pk' in key && 'kt' in key && !('loc' in key)) {
727
+ // PriKey
728
+ return `${key.kt}:${key.pk}`;
729
+ } else if ('pk' in key && 'kt' in key && 'loc' in key) {
730
+ // ComKey
731
+ const locStr = key.loc.map((l)=>`${l.kt}:${l.lk}`).join(',');
732
+ return `${key.kt}:${key.pk}|${locStr}`;
733
+ }
734
+ throw new Error(`Unsupported key type: ${JSON.stringify(key)}`);
735
+ };
736
+ /**
737
+ * Create a new OperationContext
738
+ */ const createOperationContext = ()=>{
739
+ const inProgress = new Set();
740
+ const cache = new Map();
741
+ return {
742
+ inProgress,
743
+ cache,
744
+ markInProgress (key) {
745
+ const serialized = OperationContext.serializeKey(key);
746
+ logger$b.default('Marking key as in progress', {
747
+ key,
748
+ serialized
749
+ });
750
+ inProgress.add(serialized);
751
+ },
752
+ markComplete (key) {
753
+ const serialized = OperationContext.serializeKey(key);
754
+ logger$b.default('Marking key as complete', {
755
+ key,
756
+ serialized
757
+ });
758
+ inProgress.delete(serialized);
759
+ },
760
+ isInProgress (key) {
761
+ const serialized = OperationContext.serializeKey(key);
762
+ const result = inProgress.has(serialized);
763
+ logger$b.default('Checking if key is in progress', {
764
+ key,
765
+ serialized,
766
+ result
767
+ });
768
+ return result;
769
+ },
770
+ getCached (key) {
771
+ const serialized = OperationContext.serializeKey(key);
772
+ const result = cache.get(serialized);
773
+ logger$b.default('Getting cached item', {
774
+ key,
775
+ serialized,
776
+ found: !!result
777
+ });
778
+ return result;
779
+ },
780
+ setCached (key, item) {
781
+ const serialized = OperationContext.serializeKey(key);
782
+ logger$b.default('Caching item', {
783
+ key,
784
+ serialized
785
+ });
786
+ cache.set(serialized, item);
787
+ },
788
+ isCached (key) {
789
+ const serialized = OperationContext.serializeKey(key);
790
+ const result = cache.has(serialized);
791
+ logger$b.default('Checking if key is cached', {
792
+ key,
793
+ serialized,
794
+ result
795
+ });
796
+ return result;
797
+ }
798
+ };
799
+ };
800
+ /**
801
+ * Context Manager for sharing context across operations without changing public interfaces
802
+ */ class ContextManager {
803
+ /**
804
+ * Set the current context for the current operation chain
805
+ */ setCurrentContext(context) {
806
+ const contextId = Math.random().toString(36).substring(7);
807
+ this.contexts.set(contextId, context);
808
+ this.currentContextId = contextId;
809
+ logger$b.default('Set current context', {
810
+ contextId
811
+ });
812
+ return contextId;
813
+ }
814
+ /**
815
+ * Get the current context if one is set
816
+ */ getCurrentContext() {
817
+ if (this.currentContextId) {
818
+ const context = this.contexts.get(this.currentContextId);
819
+ logger$b.default('Got current context', {
820
+ contextId: this.currentContextId,
821
+ found: !!context
822
+ });
823
+ return context;
824
+ }
825
+ return;
826
+ }
827
+ /**
828
+ * Clear the current context
829
+ */ clearCurrentContext() {
830
+ if (this.currentContextId) {
831
+ logger$b.default('Clearing current context', {
832
+ contextId: this.currentContextId
833
+ });
834
+ this.contexts.delete(this.currentContextId);
835
+ this.currentContextId = null;
836
+ }
837
+ }
838
+ /**
839
+ * Execute a function with a specific context set as current
840
+ */ async withContext(context, fn) {
841
+ const previousContextId = this.currentContextId;
842
+ this.setCurrentContext(context);
843
+ try {
844
+ return await fn();
845
+ } finally{
846
+ this.clearCurrentContext();
847
+ if (previousContextId) {
848
+ this.currentContextId = previousContextId;
849
+ }
850
+ }
851
+ }
852
+ constructor(){
853
+ _define_property(this, "contexts", new Map());
854
+ _define_property(this, "currentContextId", null);
855
+ }
856
+ }
857
+ // Global context manager instance
858
+ const OperationContext.contextManager = new ContextManager();
859
+
860
+ const logger$a = logger$1.default.get('sequelize', 'AggregationBuilder');
861
+ const buildAggregation = async (item, aggregationDefinition, registry, context)=>{
648
862
  const location = core.ikToLKA(item.key);
649
863
  // Get the library instance from the registry using the key type array
650
864
  const libraryInstance = registry.get(aggregationDefinition.kta);
651
865
  if (!libraryInstance) {
652
866
  throw new Error(`Library instance not found for key type array: ${aggregationDefinition.kta.join(', ')}`);
653
867
  }
654
- // Based on cardinality, use either one or all operation
655
- if (aggregationDefinition.cardinality === 'one') {
656
- // For one-to-one relationship, use the one operation
657
- return libraryInstance.operations.one({}, location).then((result)=>{
658
- item[aggregationDefinition.property] = result;
659
- return item;
660
- });
661
- } else {
662
- // For one-to-many relationship, use the all operation
663
- return libraryInstance.operations.all({}, location).then((results)=>{
664
- item[aggregationDefinition.property] = results;
868
+ // Create a cache key for this aggregation query
869
+ // This helps avoid running the same aggregation multiple times
870
+ const aggregationCacheKey = `${aggregationDefinition.kta.join('.')}_${aggregationDefinition.cardinality}_${OperationContext.serializeKey(item.key)}`;
871
+ if (context) {
872
+ // Check if this aggregation is already cached
873
+ if (context.cache.has(aggregationCacheKey)) {
874
+ const cachedResult = context.cache.get(aggregationCacheKey);
875
+ logger$a.default('Using cached aggregation result', {
876
+ aggregationCacheKey,
877
+ property: aggregationDefinition.property
878
+ });
879
+ item[aggregationDefinition.property] = cachedResult;
665
880
  return item;
666
- });
881
+ }
882
+ // Note: We don't check for circular dependencies here because:
883
+ // 1. Aggregations are location-based queries, not key-based references
884
+ // 2. They should be allowed to run during normal item processing
885
+ // 3. The main circular dependency concern is with references, not aggregations
667
886
  }
887
+ // Execute aggregation within the current context to ensure context sharing
888
+ return OperationContext.contextManager.withContext(context || OperationContext.contextManager.getCurrentContext() || {
889
+ inProgress: new Set(),
890
+ cache: new Map()
891
+ }, async ()=>{
892
+ // Based on cardinality, use either one or all operation
893
+ if (aggregationDefinition.cardinality === 'one') {
894
+ // For one-to-one relationship, use the one operation
895
+ return libraryInstance.operations.one({}, location).then((result)=>{
896
+ if (context) {
897
+ context.cache.set(aggregationCacheKey, result);
898
+ }
899
+ item[aggregationDefinition.property] = result;
900
+ return item;
901
+ });
902
+ } else {
903
+ // For one-to-many relationship, use the all operation
904
+ return libraryInstance.operations.all({}, location).then((results)=>{
905
+ if (context) {
906
+ context.cache.set(aggregationCacheKey, results);
907
+ }
908
+ item[aggregationDefinition.property] = results;
909
+ return item;
910
+ });
911
+ }
912
+ });
668
913
  };
669
914
 
670
915
  const logger$9 = logger$1.default.get("sequelize", "EventCoordinator");
@@ -711,34 +956,68 @@ const removeEvents = (item)=>{
711
956
  };
712
957
 
713
958
  const logger$8 = logger$1.default.get('sequelize', 'RowProcessor');
714
- const processRow = async (row, keyTypes, referenceDefinitions, aggregationDefinitions, registry)=>{
959
+ const processRow = async (row, keyTypes, referenceDefinitions, aggregationDefinitions, registry, context)=>{
715
960
  logger$8.default('Processing Row', {
716
961
  row
717
962
  });
718
- let item = row.get({
719
- plain: true
720
- });
721
- logger$8.default('Adding Key to Item with Key Types: %s', general.stringifyJSON(keyTypes));
722
- item = addKey(row, item, keyTypes);
723
- item = populateEvents(item);
724
- logger$8.default('Key Added to Item: %s', general.stringifyJSON(item.key));
725
- if (referenceDefinitions && referenceDefinitions.length > 0) {
726
- for (const referenceDefinition of referenceDefinitions){
727
- logger$8.default('Processing Reference for %s to %s', item.key.kt, general.stringifyJSON(referenceDefinition.kta));
728
- item = await buildReference(item, referenceDefinition, registry);
729
- }
730
- }
731
- if (aggregationDefinitions && aggregationDefinitions.length > 0) {
732
- for (const aggregationDefinition of aggregationDefinitions){
733
- logger$8.default('Processing Aggregation for %s from %s', item.key.kt, general.stringifyJSON(aggregationDefinition.kta));
734
- item = await buildAggregation(item, aggregationDefinition, registry);
963
+ // Use provided context or create new one
964
+ const operationContext = context || createOperationContext();
965
+ // Process the row within the context to ensure all operations share the same context
966
+ return OperationContext.contextManager.withContext(operationContext, async ()=>{
967
+ let item = row.get({
968
+ plain: true
969
+ });
970
+ logger$8.default('Adding Key to Item with Key Types: %s', general.stringifyJSON(keyTypes));
971
+ item = addKey(row, item, keyTypes);
972
+ item = populateEvents(item);
973
+ logger$8.default('Key Added to Item: %s', general.stringifyJSON(item.key));
974
+ // Mark this item as in progress to detect circular references
975
+ operationContext.markInProgress(item.key);
976
+ try {
977
+ if (referenceDefinitions && referenceDefinitions.length > 0) {
978
+ for (const referenceDefinition of referenceDefinitions){
979
+ logger$8.default('Processing Reference for %s to %s', item.key.kt, general.stringifyJSON(referenceDefinition.kta));
980
+ item = await buildReference(item, referenceDefinition, registry, operationContext);
981
+ }
982
+ }
983
+ if (aggregationDefinitions && aggregationDefinitions.length > 0) {
984
+ for (const aggregationDefinition of aggregationDefinitions){
985
+ logger$8.default('Processing Aggregation for %s from %s', item.key.kt, general.stringifyJSON(aggregationDefinition.kta));
986
+ item = await buildAggregation(item, aggregationDefinition, registry, operationContext);
987
+ }
988
+ }
989
+ // Cache the fully processed item
990
+ operationContext.setCached(item.key, item);
991
+ } finally{
992
+ // Mark this item as complete
993
+ operationContext.markComplete(item.key);
735
994
  }
736
- }
737
- logger$8.default('Processed Row: %j', general.stringifyJSON(item));
738
- return item;
995
+ logger$8.default('Processed Row: %j', general.stringifyJSON(item));
996
+ return item;
997
+ });
739
998
  };
740
999
 
741
1000
  const logger$7 = logger$1.default.get('sequelize', 'ops', 'all');
1001
+ // Helper function to merge includes avoiding duplicates
1002
+ const mergeIncludes$1 = (existingIncludes, newIncludes)=>{
1003
+ const mergedIncludes = [
1004
+ ...existingIncludes
1005
+ ];
1006
+ for (const newInclude of newIncludes){
1007
+ const existingIndex = mergedIncludes.findIndex((existing)=>existing.as === newInclude.as && existing.model === newInclude.model);
1008
+ if (existingIndex === -1) {
1009
+ mergedIncludes.push(newInclude);
1010
+ } else if (newInclude.include && mergedIncludes[existingIndex].include) {
1011
+ mergedIncludes[existingIndex].include = [
1012
+ ...mergedIncludes[existingIndex].include,
1013
+ ...newInclude.include
1014
+ ];
1015
+ } else if (newInclude.include) {
1016
+ mergedIncludes[existingIndex].include = newInclude.include;
1017
+ }
1018
+ }
1019
+ return mergedIncludes;
1020
+ };
742
1021
  const all.getAllOperation = (models, definition, registry)=>{
743
1022
  const { coordinate, options: { references, aggregations } } = definition;
744
1023
  //#region Query
@@ -748,29 +1027,64 @@ const all.getAllOperation = (models, definition, registry)=>{
748
1027
  locations
749
1028
  });
750
1029
  const loc = locations || [];
751
- // SQ Libs don't support locations
752
- if (loc.length > 1) {
753
- throw new Error('Not implemented for more than one location key');
754
- }
755
- // We have the model here?
756
1030
  // @ts-ignore
757
1031
  const model = models[0];
758
- // We have the model here?
1032
+ // Build base query from itemQuery
759
1033
  const options = buildQuery(itemQuery, model);
760
- // If this has a location array, we need to add a where clause
761
- if (loc.length === 1) {
762
- const locKeyType = loc[0].kt;
763
- if (model.associations[locKeyType]) {
764
- const association = model.associations[locKeyType];
1034
+ // Handle location keys if present
1035
+ if (loc.length > 0) {
1036
+ const { kta } = coordinate;
1037
+ const directLocations = [];
1038
+ const hierarchicalLocations = [];
1039
+ const additionalIncludes = [];
1040
+ // Categorize location keys as direct or hierarchical
1041
+ for (const locKey of loc){
1042
+ const relationshipInfo = relationshipUtils.buildRelationshipPath(model, locKey.kt, kta, true);
1043
+ if (!relationshipInfo.found) {
1044
+ const errorMessage = `Location key '${locKey.kt}' cannot be resolved on model '${model.name}' or through its relationships.`;
1045
+ logger$7.error(errorMessage, {
1046
+ locations: loc,
1047
+ kta
1048
+ });
1049
+ throw new Error(errorMessage);
1050
+ }
1051
+ if (relationshipInfo.isDirect) {
1052
+ directLocations.push(locKey);
1053
+ } else {
1054
+ hierarchicalLocations.push(locKey);
1055
+ }
1056
+ }
1057
+ // Handle direct location keys (simple foreign key constraints)
1058
+ for (const locKey of directLocations){
1059
+ const foreignKeyField = locKey.kt + 'Id';
765
1060
  options.where = {
766
1061
  ...options.where,
767
- [association.foreignKey]: {
768
- [sequelize.Op.eq]: loc[0].lk
1062
+ [foreignKeyField]: {
1063
+ [sequelize.Op.eq]: locKey.lk
769
1064
  }
770
1065
  };
771
- } else {
772
- logger$7.error('Location key type not found in sequelize model association for: %s', locKeyType);
773
- throw new Error('Location key type not found in model');
1066
+ }
1067
+ // Handle hierarchical location keys (requires relationship traversal)
1068
+ for (const locKey of hierarchicalLocations){
1069
+ const relationshipInfo = relationshipUtils.buildRelationshipPath(model, locKey.kt, kta);
1070
+ if (relationshipInfo.found && relationshipInfo.path) {
1071
+ // Add the relationship constraint using the path
1072
+ options.where = {
1073
+ ...options.where,
1074
+ [relationshipInfo.path]: {
1075
+ [sequelize.Op.eq]: locKey.lk
1076
+ }
1077
+ };
1078
+ // Add necessary includes for the relationship traversal
1079
+ if (relationshipInfo.includes) {
1080
+ additionalIncludes.push(...relationshipInfo.includes);
1081
+ }
1082
+ }
1083
+ }
1084
+ // Merge additional includes with existing includes
1085
+ if (additionalIncludes.length > 0) {
1086
+ const existingIncludes = options.include || [];
1087
+ options.include = mergeIncludes$1(existingIncludes, additionalIncludes);
774
1088
  }
775
1089
  }
776
1090
  logger$7.default('Configured this Item Query', {
@@ -779,9 +1093,11 @@ const all.getAllOperation = (models, definition, registry)=>{
779
1093
  });
780
1094
  const matchingItems = await model.findAll(options);
781
1095
  // this.logger.default('Matching Items', { matchingItems });
1096
+ // Get the current context from context manager
1097
+ const context = OperationContext.contextManager.getCurrentContext();
782
1098
  // TODO: Move this Up!
783
1099
  return await Promise.all(matchingItems.map(async (row)=>{
784
- const processedRow = await processRow(row, coordinate.kta, references, aggregations, registry);
1100
+ const processedRow = await processRow(row, coordinate.kta, references, aggregations, registry, context);
785
1101
  return core.validateKeys(processedRow, coordinate.kta);
786
1102
  }));
787
1103
  };
@@ -1032,7 +1348,9 @@ const getGetOperation = (models, definition, registry)=>{
1032
1348
  if (!item) {
1033
1349
  throw new Library.NotFoundError('get', coordinate, key);
1034
1350
  } else {
1035
- return core.validateKeys(await processRow(item, kta, references, aggregations, registry), kta);
1351
+ // Get the current context from context manager
1352
+ const context = OperationContext.contextManager.getCurrentContext();
1353
+ return core.validateKeys(await processRow(item, kta, references, aggregations, registry, context), kta);
1036
1354
  }
1037
1355
  };
1038
1356
  return get;
@@ -1155,6 +1473,26 @@ registry)=>{
1155
1473
  };
1156
1474
 
1157
1475
  const logger$1 = logger$1.default.get('sequelize', 'ops', 'update');
1476
+ // Helper function to merge includes avoiding duplicates
1477
+ const mergeIncludes = (existingIncludes, newIncludes)=>{
1478
+ const mergedIncludes = [
1479
+ ...existingIncludes
1480
+ ];
1481
+ for (const newInclude of newIncludes){
1482
+ const existingIndex = mergedIncludes.findIndex((existing)=>existing.as === newInclude.as && existing.model === newInclude.model);
1483
+ if (existingIndex === -1) {
1484
+ mergedIncludes.push(newInclude);
1485
+ } else if (newInclude.include && mergedIncludes[existingIndex].include) {
1486
+ mergedIncludes[existingIndex].include = [
1487
+ ...mergedIncludes[existingIndex].include,
1488
+ ...newInclude.include
1489
+ ];
1490
+ } else if (newInclude.include) {
1491
+ mergedIncludes[existingIndex].include = newInclude.include;
1492
+ }
1493
+ }
1494
+ return mergedIncludes;
1495
+ };
1158
1496
  const getUpdateOperation = (models, definition, registry)=>{
1159
1497
  const { options: { references, aggregations } } = definition;
1160
1498
  const update = async (key, item)=>{
@@ -1170,15 +1508,49 @@ const getUpdateOperation = (models, definition, registry)=>{
1170
1508
  const priKey = key;
1171
1509
  response = await model.findByPk(priKey.pk);
1172
1510
  } else if (core.isComKey(key)) {
1173
- var _comKey_loc_, _comKey_loc_1;
1174
1511
  const comKey = key;
1175
- // Find the model by using both of the identifiers.
1176
- response = await model.findOne({
1177
- where: {
1178
- [(comKey === null || comKey === void 0 ? void 0 : (_comKey_loc_ = comKey.loc[0]) === null || _comKey_loc_ === void 0 ? void 0 : _comKey_loc_.kt) + 'Id']: comKey === null || comKey === void 0 ? void 0 : (_comKey_loc_1 = comKey.loc[0]) === null || _comKey_loc_1 === void 0 ? void 0 : _comKey_loc_1.lk,
1179
- id: comKey === null || comKey === void 0 ? void 0 : comKey.pk
1512
+ // Build query options for composite key with multiple location keys
1513
+ const where = {
1514
+ id: comKey.pk
1515
+ };
1516
+ const additionalIncludes = [];
1517
+ // Process all location keys in the composite key
1518
+ for (const locator of comKey.loc){
1519
+ const relationshipInfo = relationshipUtils.buildRelationshipPath(model, locator.kt, kta, true);
1520
+ if (!relationshipInfo.found) {
1521
+ const errorMessage = `Composite key locator '${locator.kt}' cannot be resolved on model '${model.name}' or through its relationships.`;
1522
+ logger$1.error(errorMessage, {
1523
+ key: comKey,
1524
+ kta
1525
+ });
1526
+ throw new Error(errorMessage);
1180
1527
  }
1528
+ if (relationshipInfo.isDirect) {
1529
+ // Direct foreign key field
1530
+ const fieldName = `${locator.kt}Id`;
1531
+ where[fieldName] = locator.lk;
1532
+ } else if (relationshipInfo.path) {
1533
+ // Hierarchical relationship requiring traversal
1534
+ where[relationshipInfo.path] = {
1535
+ [sequelize.Op.eq]: locator.lk
1536
+ };
1537
+ // Add necessary includes for relationship traversal
1538
+ if (relationshipInfo.includes) {
1539
+ additionalIncludes.push(...relationshipInfo.includes);
1540
+ }
1541
+ }
1542
+ }
1543
+ // Build final query options
1544
+ const queryOptions = {
1545
+ where
1546
+ };
1547
+ if (additionalIncludes.length > 0) {
1548
+ queryOptions.include = mergeIncludes([], additionalIncludes);
1549
+ }
1550
+ logger$1.default('Composite key query for update', {
1551
+ queryOptions
1181
1552
  });
1553
+ response = await model.findOne(queryOptions);
1182
1554
  }
1183
1555
  if (response) {
1184
1556
  // Remove the key and events