@fjell/lib-sequelize 4.4.4 → 4.4.5
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/cjs/AggregationBuilder.cjs +48 -14
- package/dist/cjs/KeyMaster.cjs +33 -11
- package/dist/cjs/OperationContext.cjs +161 -0
- package/dist/cjs/ReferenceBuilder.cjs +47 -5
- package/dist/cjs/RowProcessor.cjs +37 -22
- package/dist/cjs/ops/all.cjs +77 -18
- package/dist/cjs/ops/get.cjs +5 -2
- package/dist/cjs/ops/update.cjs +63 -7
- package/dist/cjs/util/relationshipUtils.cjs +2 -2
- package/dist/es/AggregationBuilder.js +48 -14
- package/dist/es/KeyMaster.js +33 -11
- package/dist/es/OperationContext.js +155 -0
- package/dist/es/ReferenceBuilder.js +47 -5
- package/dist/es/RowProcessor.js +37 -22
- package/dist/es/ops/all.js +77 -18
- package/dist/es/ops/get.js +5 -2
- package/dist/es/ops/update.js +63 -7
- package/dist/es/util/relationshipUtils.js +2 -2
- package/dist/index.cjs +461 -89
- package/dist/index.cjs.map +1 -1
- package/dist/types/AggregationBuilder.d.ts +2 -1
- package/dist/types/EventCoordinator.d.ts +6 -6
- package/dist/types/KeyMaster.d.ts +2 -2
- package/dist/types/OperationContext.d.ts +72 -0
- package/dist/types/ReferenceBuilder.d.ts +2 -1
- package/dist/types/RowProcessor.d.ts +2 -1
- package/dist/types/ops/all.d.ts +1 -1
- package/dist/types/ops/create.d.ts +2 -2
- package/dist/types/ops/update.d.ts +2 -2
- package/package.json +12 -12
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$
|
|
96
|
+
const logger$f = logger$1.default.get('lib-sequelize', 'Definition');
|
|
97
97
|
function createDefinition(kta, scopes, libOptions) {
|
|
98
|
-
logger$
|
|
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$
|
|
112
|
+
const logger$e = logger$1.default.get('sequelize', 'QueryBuilder');
|
|
113
113
|
const addDeleteQuery = (options, model)=>{
|
|
114
|
-
logger$
|
|
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$
|
|
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$
|
|
166
|
+
logger$e.default('Adding Reference Queries', {
|
|
167
167
|
options,
|
|
168
168
|
references
|
|
169
169
|
});
|
|
170
170
|
Object.keys(references).forEach((key)=>{
|
|
171
|
-
logger$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
414
|
-
const associationPath = `$${associationParts.join('.')}.${
|
|
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$
|
|
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$
|
|
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
|
-
//
|
|
522
|
-
const
|
|
523
|
-
if (
|
|
524
|
-
|
|
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
|
-
//
|
|
527
|
-
|
|
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
|
|
530
|
-
return
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
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:
|
|
665
|
+
pk: columnValue
|
|
637
666
|
};
|
|
638
|
-
|
|
639
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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
|
-
|
|
738
|
-
|
|
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
|
-
//
|
|
1032
|
+
// Build base query from itemQuery
|
|
759
1033
|
const options = buildQuery(itemQuery, model);
|
|
760
|
-
//
|
|
761
|
-
if (loc.length
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
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
|
-
[
|
|
768
|
-
[sequelize.Op.eq]:
|
|
1062
|
+
[foreignKeyField]: {
|
|
1063
|
+
[sequelize.Op.eq]: locKey.lk
|
|
769
1064
|
}
|
|
770
1065
|
};
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|