@fjell/lib-sequelize 4.4.1 → 4.4.2

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
@@ -187,7 +187,8 @@ const addReferenceQueries = (options, references, model)=>{
187
187
  return options;
188
188
  };
189
189
  const addCompoundCondition = (options, compoundCondition, model)=>{
190
- const where = {};
190
+ // Ensure options.where exists
191
+ options.where = options.where || {};
191
192
  let compoundOp;
192
193
  const compoundType = compoundCondition.compoundType;
193
194
  if (compoundType === "AND") {
@@ -203,36 +204,135 @@ const addCompoundCondition = (options, compoundCondition, model)=>{
203
204
  throw new Error('Nest Compound conditions not supported');
204
205
  }
205
206
  });
206
- where[compoundOp] = conditions;
207
- options.where = where;
207
+ // Merge with existing where conditions instead of replacing
208
+ if (Object.keys(options.where).length > 0) {
209
+ // If there are existing conditions, wrap everything in an AND
210
+ options.where = {
211
+ [sequelize.Op.and]: [
212
+ options.where,
213
+ {
214
+ [compoundOp]: conditions
215
+ }
216
+ ]
217
+ };
218
+ } else {
219
+ // If no existing conditions, just set the compound condition
220
+ options.where[compoundOp] = conditions;
221
+ }
208
222
  return options;
209
223
  };
210
- const addCondition = (conditions, condition, model)=>{
211
- let conditionOp;
224
+ const getSequelizeOperator = (operator)=>{
225
+ if (operator === '==') {
226
+ return sequelize.Op.eq;
227
+ } else if (operator === '<') {
228
+ return sequelize.Op.lt;
229
+ } else if (operator === '>') {
230
+ return sequelize.Op.gt;
231
+ } else if (operator === '<=') {
232
+ return sequelize.Op.lte;
233
+ } else if (operator === '>=') {
234
+ return sequelize.Op.gte;
235
+ } else if (operator === 'in') {
236
+ return sequelize.Op.in;
237
+ } else {
238
+ throw new Error(`Operator ${operator} not supported`);
239
+ }
240
+ };
241
+ const addAssociationCondition = (conditions, condition, model)=>{
242
+ const [associationName, attributeName] = condition.column.split('.', 2);
243
+ // Check if the association exists on the model
244
+ if (!model.associations || !model.associations[associationName]) {
245
+ throw new Error(`Association ${associationName} not found on model ${model.name}`);
246
+ }
247
+ const association = model.associations[associationName];
248
+ const associatedModel = association.target;
249
+ // Check if the attribute exists on the associated model
250
+ if (!associatedModel.getAttributes()[attributeName]) {
251
+ throw new Error(`Attribute ${attributeName} not found on associated model ${associatedModel.name} for association ${associationName}`);
252
+ }
253
+ // Use Sequelize's $association.attribute$ syntax for querying associated models
254
+ const sequelizeAssociationColumn = `$${associationName}.${attributeName}$`;
255
+ const conditionOp = getSequelizeOperator(condition.operator);
256
+ conditions[sequelizeAssociationColumn] = {
257
+ [conditionOp]: condition.value
258
+ };
259
+ return conditions;
260
+ };
261
+ const addAttributeCondition = (conditions, condition, model)=>{
212
262
  const conditionColumn = condition.column;
213
263
  if (!model.getAttributes()[conditionColumn]) {
214
264
  throw new Error(`Condition column ${conditionColumn} not found on model ${model.name}`);
215
265
  }
216
- if (condition.operator === '==') {
217
- conditionOp = sequelize.Op.eq;
218
- } else if (condition.operator === '<') {
219
- conditionOp = sequelize.Op.lt;
220
- } else if (condition.operator === '>') {
221
- conditionOp = sequelize.Op.gt;
222
- } else if (condition.operator === '<=') {
223
- conditionOp = sequelize.Op.lte;
224
- } else if (condition.operator === '>=') {
225
- conditionOp = sequelize.Op.gte;
226
- } else if (condition.operator === 'in') {
227
- conditionOp = sequelize.Op.in;
228
- } else {
229
- throw new Error(`Operator ${condition.operator} not supported`);
230
- }
266
+ const conditionOp = getSequelizeOperator(condition.operator);
231
267
  conditions[conditionColumn] = {
232
268
  [conditionOp]: condition.value
233
269
  };
234
270
  return conditions;
235
271
  };
272
+ const addCondition = (conditions, condition, model)=>{
273
+ const conditionColumn = condition.column;
274
+ // Check if this is an association query (contains a dot)
275
+ if (conditionColumn.includes('.')) {
276
+ return addAssociationCondition(conditions, condition, model);
277
+ }
278
+ // Handle regular column queries
279
+ return addAttributeCondition(conditions, condition, model);
280
+ };
281
+ const collectAssociationsFromConditions = (conditions)=>{
282
+ const associations = new Set();
283
+ const processObject = (obj)=>{
284
+ if (typeof obj === 'object' && obj !== null) {
285
+ // Check string keys
286
+ Object.keys(obj).forEach((key)=>{
287
+ // Check if this is an association reference ($association.attribute$)
288
+ if (typeof key === 'string' && key.startsWith('$') && key.endsWith('$') && key.includes('.')) {
289
+ const associationName = key.substring(1, key.indexOf('.'));
290
+ associations.add(associationName);
291
+ }
292
+ // Recursively process nested objects
293
+ if (typeof obj[key] === 'object') {
294
+ processObject(obj[key]);
295
+ }
296
+ });
297
+ // Also check Symbol keys (for compound conditions like Op.and, Op.or)
298
+ Object.getOwnPropertySymbols(obj).forEach((symbol)=>{
299
+ if (typeof obj[symbol] === 'object') {
300
+ processObject(obj[symbol]);
301
+ }
302
+ });
303
+ }
304
+ // Handle arrays (for compound conditions that might be arrays)
305
+ if (Array.isArray(obj)) {
306
+ obj.forEach((item)=>{
307
+ if (typeof item === 'object') {
308
+ processObject(item);
309
+ }
310
+ });
311
+ }
312
+ };
313
+ processObject(conditions);
314
+ return associations;
315
+ };
316
+ const addAssociationIncludes = (options, model)=>{
317
+ // Collect all association names used in conditions
318
+ const referencedAssociations = collectAssociationsFromConditions(options.where);
319
+ if (referencedAssociations.size > 0) {
320
+ options.include = options.include || [];
321
+ // Add each referenced association to the include array
322
+ referencedAssociations.forEach((associationName)=>{
323
+ // Check if this association is already included
324
+ const alreadyIncluded = options.include.some((inc)=>typeof inc === 'string' && inc === associationName || typeof inc === 'object' && inc.association === associationName);
325
+ if (!alreadyIncluded && model.associations && model.associations[associationName]) {
326
+ options.include.push({
327
+ model: model.associations[associationName].target,
328
+ as: associationName,
329
+ required: false // Use LEFT JOIN so records without associations are still returned
330
+ });
331
+ }
332
+ });
333
+ }
334
+ return options;
335
+ };
236
336
  const buildQuery = (itemQuery, model)=>{
237
337
  logger$b.default('build', {
238
338
  itemQuery
@@ -282,10 +382,156 @@ const buildQuery = (itemQuery, model)=>{
282
382
  ];
283
383
  });
284
384
  }
385
+ // Add includes for any associations referenced in conditions
386
+ options = addAssociationIncludes(options, model);
285
387
  return options;
286
388
  };
287
389
 
390
+ /* eslint-disable indent */ /**
391
+ * Helper function to build relationship chain includes
392
+ */ const buildRelationshipChain = (targetModel, kta, currentIndex, targetIndex)=>{
393
+ // Build the association path and validate relationships exist
394
+ const associationParts = [];
395
+ const modelChain = [
396
+ targetModel
397
+ ];
398
+ let currentModel = targetModel;
399
+ // Validate that all associations exist and build model chain
400
+ for(let i = currentIndex + 1; i <= targetIndex; i++){
401
+ const intermediateType = kta[i];
402
+ const associationName = intermediateType;
403
+ if (!currentModel.associations || !currentModel.associations[associationName]) {
404
+ return {
405
+ success: false
406
+ };
407
+ }
408
+ associationParts.push(associationName);
409
+ currentModel = currentModel.associations[associationName].target;
410
+ modelChain.push(currentModel);
411
+ }
412
+ // Build the full association path for the target field
413
+ const targetType = kta[targetIndex];
414
+ const associationPath = `$${associationParts.join('.')}.${targetType}Id$`;
415
+ // Build nested includes structure iteratively (clearer than recursion)
416
+ let deepestInclude = null;
417
+ // Build from the deepest level back to the root
418
+ for(let i = targetIndex; i > currentIndex; i--){
419
+ const currentType = kta[i];
420
+ const modelIndex = i - currentIndex;
421
+ const includeObj = {
422
+ model: modelChain[modelIndex],
423
+ as: currentType,
424
+ required: true
425
+ };
426
+ if (deepestInclude) {
427
+ includeObj.include = [
428
+ deepestInclude
429
+ ];
430
+ }
431
+ deepestInclude = includeObj;
432
+ }
433
+ const includes = deepestInclude ? [
434
+ deepestInclude
435
+ ] : [];
436
+ return {
437
+ success: true,
438
+ path: associationPath,
439
+ includes
440
+ };
441
+ };
442
+ /**
443
+ * Helper function to build relationship path for a locator
444
+ * @param includeIsDirect Whether to include the isDirect flag in the result
445
+ */ const relationshipUtils.buildRelationshipPath = (targetModel, locatorType, kta, includeIsDirect = false)=>{
446
+ // First check if the field exists directly
447
+ const directFieldName = `${locatorType}Id`;
448
+ const attributes = targetModel.getAttributes();
449
+ if (attributes && attributes[directFieldName]) {
450
+ const result = {
451
+ found: true
452
+ };
453
+ if (includeIsDirect) {
454
+ result.isDirect = true;
455
+ }
456
+ return result;
457
+ }
458
+ // If not direct, look for relationship path
459
+ const targetIndex = kta.indexOf(locatorType);
460
+ if (targetIndex === -1) {
461
+ const result = {
462
+ found: false
463
+ };
464
+ if (includeIsDirect) {
465
+ result.isDirect = false;
466
+ }
467
+ return result;
468
+ }
469
+ const currentIndex = 0; // We're always looking from the base model
470
+ if (targetIndex <= currentIndex) {
471
+ const result = {
472
+ found: false
473
+ };
474
+ if (includeIsDirect) {
475
+ result.isDirect = false;
476
+ }
477
+ return result;
478
+ }
479
+ const chainResult = buildRelationshipChain(targetModel, kta, currentIndex, targetIndex);
480
+ if (chainResult.success) {
481
+ const result = {
482
+ found: true,
483
+ path: chainResult.path,
484
+ includes: chainResult.includes
485
+ };
486
+ if (includeIsDirect) {
487
+ result.isDirect = false;
488
+ }
489
+ return result;
490
+ }
491
+ const result = {
492
+ found: false
493
+ };
494
+ if (includeIsDirect) {
495
+ result.isDirect = false;
496
+ }
497
+ return result;
498
+ };
499
+
288
500
  const logger$a = logger$1.default.get('sequelize', 'KeyMaster');
501
+ // Helper function to extract location key value from item
502
+ const extractLocationKeyValue = (model, item, locatorType, kta)=>{
503
+ logger$a.default('Extracting location key value', {
504
+ locatorType,
505
+ kta
506
+ });
507
+ const relationshipInfo = relationshipUtils.buildRelationshipPath(model, locatorType, kta, true);
508
+ if (!relationshipInfo.found) {
509
+ throw new Error(`Location key '${locatorType}' cannot be resolved on model '${model.name}' or through its relationships.`);
510
+ }
511
+ if (relationshipInfo.isDirect) {
512
+ // Direct foreign key field
513
+ const foreignKeyField = `${locatorType}Id`;
514
+ const value = item[foreignKeyField];
515
+ if (typeof value === 'undefined' || value === null) {
516
+ throw new Error(`Direct foreign key field '${foreignKeyField}' is missing or null in item`);
517
+ }
518
+ return value;
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;
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
528
+ const foreignKeyField = `${locatorType}Id`;
529
+ if (typeof item[foreignKeyField] !== 'undefined' && item[foreignKeyField] !== null) {
530
+ return item[foreignKeyField];
531
+ }
532
+ throw new Error(`Unable to extract location key for '${locatorType}'. Neither the relationship object nor direct foreign key is available.`);
533
+ }
534
+ };
289
535
  const removeKey = (item)=>{
290
536
  logger$a.default('Removing Key', {
291
537
  item
@@ -320,11 +566,13 @@ const removeKey = (item)=>{
320
566
  // }
321
567
  // return item;
322
568
  // }
323
- const addKey = (item, keyTypes)=>{
569
+ const addKey = (model, item, keyTypes)=>{
324
570
  logger$a.default('Adding Key', {
325
571
  item
326
572
  });
327
573
  const key = {};
574
+ const modelClass = model.constructor;
575
+ const primaryKeyAttr = modelClass.primaryKeyAttribute;
328
576
  if (Array.isArray(keyTypes) && keyTypes.length > 1) {
329
577
  const type = [
330
578
  ...keyTypes
@@ -332,33 +580,34 @@ const addKey = (item, keyTypes)=>{
332
580
  const pkType = type.shift();
333
581
  Object.assign(key, {
334
582
  kt: pkType,
335
- pk: item.id
583
+ pk: item[primaryKeyAttr]
336
584
  });
337
- // TODO: This is really just for primary items
338
- if (type.length === 1) {
339
- // TODO: This should be looking at the model to get the primary key of the reference item or association
340
- const locKeyTypeId = type[0] + 'Id';
341
- Object.assign(key, {
342
- loc: [
343
- {
344
- kt: type[0],
345
- lk: item[locKeyTypeId]
346
- }
347
- ]
348
- });
349
- } else if (type.length === 2) {
350
- throw new Error('Not implemented');
351
- } else if (type.length === 3) {
352
- throw new Error('Not implemented');
353
- } else if (type.length === 4) {
354
- throw new Error('Not implemented');
355
- } else if (type.length === 5) {
356
- throw new Error('Not implemented');
585
+ // Build location keys for composite key
586
+ const locationKeys = [];
587
+ for (const locatorType of type){
588
+ try {
589
+ const lk = extractLocationKeyValue(modelClass, item, locatorType, keyTypes);
590
+ locationKeys.push({
591
+ kt: locatorType,
592
+ lk
593
+ });
594
+ } catch (error) {
595
+ const errorMessage = error instanceof Error ? error.message : String(error);
596
+ logger$a.error(`Failed to extract location key for '${locatorType}'`, {
597
+ error: errorMessage,
598
+ item,
599
+ keyTypes
600
+ });
601
+ throw error;
602
+ }
357
603
  }
604
+ Object.assign(key, {
605
+ loc: locationKeys
606
+ });
358
607
  } else {
359
608
  Object.assign(key, {
360
609
  kt: keyTypes[0],
361
- pk: item.id
610
+ pk: item[primaryKeyAttr]
362
611
  });
363
612
  }
364
613
  Object.assign(item, {
@@ -435,6 +684,24 @@ const populateEvents = (item)=>{
435
684
  item.events = events;
436
685
  return item;
437
686
  };
687
+ const extractEvents = (item)=>{
688
+ logger$9.default('Extracting Events to database fields', {
689
+ item
690
+ });
691
+ if (item.events) {
692
+ var _item_events_created, _item_events_updated, _item_events_deleted;
693
+ if ((_item_events_created = item.events.created) === null || _item_events_created === void 0 ? void 0 : _item_events_created.at) {
694
+ item.createdAt = item.events.created.at;
695
+ }
696
+ if ((_item_events_updated = item.events.updated) === null || _item_events_updated === void 0 ? void 0 : _item_events_updated.at) {
697
+ item.updatedAt = item.events.updated.at;
698
+ }
699
+ if ((_item_events_deleted = item.events.deleted) === null || _item_events_deleted === void 0 ? void 0 : _item_events_deleted.at) {
700
+ item.deletedAt = item.events.deleted.at;
701
+ }
702
+ }
703
+ return item;
704
+ };
438
705
  const removeEvents = (item)=>{
439
706
  logger$9.default('Removing Events', {
440
707
  item
@@ -452,7 +719,7 @@ const processRow = async (row, keyTypes, referenceDefinitions, aggregationDefini
452
719
  plain: true
453
720
  });
454
721
  logger$8.default('Adding Key to Item with Key Types: %s', general.stringifyJSON(keyTypes));
455
- item = addKey(item, keyTypes);
722
+ item = addKey(row, item, keyTypes);
456
723
  item = populateEvents(item);
457
724
  logger$8.default('Key Added to Item: %s', general.stringifyJSON(item.key));
458
725
  if (referenceDefinitions && referenceDefinitions.length > 0) {
@@ -522,16 +789,143 @@ const all.getAllOperation = (models, definition, registry)=>{
522
789
  };
523
790
 
524
791
  const logger$6 = logger$1.default.get('sequelize', 'ops', 'create');
525
- const getCreateOperation = (// eslint-disable-next-line @typescript-eslint/no-unused-vars
526
- models, // eslint-disable-next-line @typescript-eslint/no-unused-vars
527
- definition, // eslint-disable-next-line @typescript-eslint/no-unused-vars
528
- registry)=>{
792
+ // Helper function to validate hierarchical chain exists
793
+ async function validateHierarchicalChain(models, locKey, kta) {
794
+ // Find the direct parent model that contains this locator
795
+ const locatorIndex = kta.indexOf(locKey.kt);
796
+ if (locatorIndex === -1) {
797
+ throw new Error(`Locator type '${locKey.kt}' not found in kta array`);
798
+ }
799
+ // Get the model for this locator
800
+ const locatorModel = models[locatorIndex] || models[0]; // Fallback to primary model
801
+ // Build a query to validate the chain exists
802
+ const chainResult = buildRelationshipChain(locatorModel, kta, locatorIndex, kta.length - 1);
803
+ if (!chainResult.success) {
804
+ // If we can't build a chain, just validate the record exists
805
+ const record = await locatorModel.findByPk(locKey.lk);
806
+ if (!record) {
807
+ throw new Error(`Referenced ${locKey.kt} with id ${locKey.lk} does not exist`);
808
+ }
809
+ return;
810
+ }
811
+ // Validate that the chain exists
812
+ const queryOptions = {
813
+ where: {
814
+ id: locKey.lk
815
+ }
816
+ };
817
+ if (chainResult.includes && chainResult.includes.length > 0) {
818
+ queryOptions.include = chainResult.includes;
819
+ }
820
+ const record = await locatorModel.findOne(queryOptions);
821
+ if (!record) {
822
+ throw new Error(`Referenced ${locKey.kt} with id ${locKey.lk} does not exist or chain is invalid`);
823
+ }
824
+ }
825
+ const getCreateOperation = (models, definition, registry)=>{
529
826
  const create = async (item, options)=>{
530
827
  logger$6.default('Create', {
531
828
  item,
532
829
  options
533
830
  });
534
- throw new Error('Not implemented');
831
+ const { coordinate, options: { references, aggregations } } = definition;
832
+ const { kta } = coordinate;
833
+ // Get the primary model (first model in array)
834
+ const model = models[0];
835
+ const modelAttributes = model.getAttributes();
836
+ // Validate that all item attributes exist on the model
837
+ let itemData = {
838
+ ...item
839
+ };
840
+ // TODO: We need the opposite of processRow, something to step down from fjell to database.
841
+ itemData = extractEvents(itemData);
842
+ itemData = removeEvents(itemData);
843
+ for (const key of Object.keys(itemData)){
844
+ if (!modelAttributes[key]) {
845
+ throw new Error(`Attribute '${key}' does not exist on model ${model.name}`);
846
+ }
847
+ }
848
+ // Handle key options
849
+ // If a key is supplied, assume its contents are to be assigned to the appropriate ids.
850
+ // For most cases this will be null as key generation is often through autoIncrement.
851
+ // If this is a CItem then the locations will be present.
852
+ if (options === null || options === void 0 ? void 0 : options.key) {
853
+ const key = options.key;
854
+ if (core.isPriKey(key)) {
855
+ // Set the primary key
856
+ itemData.id = key.pk;
857
+ } else if (core.isComKey(key)) {
858
+ // Set primary key
859
+ itemData.id = key.pk;
860
+ // Process location keys - only set direct foreign keys, validate hierarchical chains
861
+ const comKey = key;
862
+ const directLocations = [];
863
+ const hierarchicalLocations = [];
864
+ // Categorize location keys as direct or hierarchical
865
+ for (const locKey of comKey.loc){
866
+ const relationshipInfo = relationshipUtils.buildRelationshipPath(model, locKey.kt, kta, true);
867
+ if (!relationshipInfo.found) {
868
+ const errorMessage = `Composite key locator '${locKey.kt}' cannot be resolved on model '${model.name}' or through its relationships.`;
869
+ logger$6.error(errorMessage, {
870
+ key: comKey,
871
+ kta
872
+ });
873
+ throw new Error(errorMessage);
874
+ }
875
+ if (relationshipInfo.isDirect) {
876
+ directLocations.push(locKey);
877
+ } else {
878
+ hierarchicalLocations.push(locKey);
879
+ }
880
+ }
881
+ // Set direct foreign keys
882
+ for (const locKey of directLocations){
883
+ const foreignKeyField = locKey.kt + 'Id';
884
+ itemData[foreignKeyField] = locKey.lk;
885
+ }
886
+ // Validate hierarchical chains exist
887
+ for (const locKey of hierarchicalLocations){
888
+ await validateHierarchicalChain(models, locKey, kta);
889
+ }
890
+ }
891
+ }
892
+ // Handle locations options
893
+ // This is the most frequent way relationship ids will be set
894
+ if (options === null || options === void 0 ? void 0 : options.locations) {
895
+ const directLocations = [];
896
+ const hierarchicalLocations = [];
897
+ // Categorize location keys as direct or hierarchical
898
+ for (const locKey of options.locations){
899
+ const relationshipInfo = relationshipUtils.buildRelationshipPath(model, locKey.kt, kta, true);
900
+ if (!relationshipInfo.found) {
901
+ const errorMessage = `Location key '${locKey.kt}' cannot be resolved on model '${model.name}' or through its relationships.`;
902
+ logger$6.error(errorMessage, {
903
+ locations: options.locations,
904
+ kta
905
+ });
906
+ throw new Error(errorMessage);
907
+ }
908
+ if (relationshipInfo.isDirect) {
909
+ directLocations.push(locKey);
910
+ } else {
911
+ hierarchicalLocations.push(locKey);
912
+ }
913
+ }
914
+ // Set direct foreign keys
915
+ for (const locKey of directLocations){
916
+ const foreignKeyField = locKey.kt + 'Id';
917
+ itemData[foreignKeyField] = locKey.lk;
918
+ }
919
+ // Validate hierarchical chains exist
920
+ for (const locKey of hierarchicalLocations){
921
+ await validateHierarchicalChain(models, locKey, kta);
922
+ }
923
+ }
924
+ // Create the record
925
+ const createdRecord = await model.create(itemData);
926
+ // Add key and events
927
+ const processedRecord = await processRow(createdRecord, kta, references, aggregations, registry);
928
+ return core.validateKeys(processedRecord, kta);
535
929
  };
536
930
  return create;
537
931
  };
@@ -572,6 +966,42 @@ const getFindOperation = (models, definition, registry)=>{
572
966
  };
573
967
 
574
968
  const logger$4 = logger$1.default.get('sequelize', 'ops', 'get');
969
+ // Helper function to process composite key and build query options
970
+ const processCompositeKey$1 = (comKey, model, kta)=>{
971
+ const where = {
972
+ id: comKey.pk
973
+ };
974
+ const includes = [];
975
+ for (const locator of comKey.loc){
976
+ const relationshipInfo = relationshipUtils.buildRelationshipPath(model, locator.kt, kta);
977
+ if (!relationshipInfo.found) {
978
+ const errorMessage = `Composite key locator '${locator.kt}' cannot be resolved on model '${model.name}' or through its relationships.`;
979
+ logger$4.error(errorMessage, {
980
+ key: comKey,
981
+ kta
982
+ });
983
+ throw new Error(errorMessage);
984
+ }
985
+ if (relationshipInfo.path) {
986
+ // This requires a relationship traversal
987
+ where[relationshipInfo.path] = locator.lk;
988
+ if (relationshipInfo.includes) {
989
+ includes.push(...relationshipInfo.includes);
990
+ }
991
+ } else {
992
+ // This is a direct field
993
+ const fieldName = `${locator.kt}Id`;
994
+ where[fieldName] = locator.lk;
995
+ }
996
+ }
997
+ const result = {
998
+ where
999
+ };
1000
+ if (includes.length > 0) {
1001
+ result.include = includes;
1002
+ }
1003
+ return result;
1004
+ };
575
1005
  const getGetOperation = (models, definition, registry)=>{
576
1006
  const { coordinate, options: { references, aggregations } } = definition;
577
1007
  const { kta } = coordinate;
@@ -588,17 +1018,16 @@ const getGetOperation = (models, definition, registry)=>{
588
1018
  const model = models[0];
589
1019
  let item;
590
1020
  if (core.isPriKey(itemKey)) {
1021
+ // This is the easy case because we can just find the item by its primary key
591
1022
  item = await model.findByPk(itemKey.pk);
592
1023
  } else if (core.isComKey(itemKey)) {
593
- var _comKey_loc_, _comKey_loc_1;
1024
+ // This is a composite key, so we need to build a where clause based on the composite key's locators
594
1025
  const comKey = itemKey;
595
- // TODO: This should probably interrogate the model?
596
- item = await model.findOne({
597
- where: {
598
- id: comKey.pk,
599
- [(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
600
- }
1026
+ const queryOptions = processCompositeKey$1(comKey, model, kta);
1027
+ logger$4.default('Composite key query', {
1028
+ queryOptions
601
1029
  });
1030
+ item = await model.findOne(queryOptions);
602
1031
  }
603
1032
  if (!item) {
604
1033
  throw new Library.NotFoundError('get', coordinate, key);
@@ -627,6 +1056,42 @@ const getOneOperation = (models, definition, registry)=>{
627
1056
  };
628
1057
 
629
1058
  const logger$2 = logger$1.default.get('sequelize', 'ops', 'remove');
1059
+ // Helper function to process composite key and build query options
1060
+ const processCompositeKey = (comKey, model, kta)=>{
1061
+ const where = {
1062
+ id: comKey.pk
1063
+ };
1064
+ const includes = [];
1065
+ for (const locator of comKey.loc){
1066
+ const relationshipInfo = relationshipUtils.buildRelationshipPath(model, locator.kt, kta);
1067
+ if (!relationshipInfo.found) {
1068
+ const errorMessage = `Composite key locator '${locator.kt}' cannot be resolved on model '${model.name}' or through its relationships.`;
1069
+ logger$2.error(errorMessage, {
1070
+ key: comKey,
1071
+ kta
1072
+ });
1073
+ throw new Error(errorMessage);
1074
+ }
1075
+ if (relationshipInfo.path) {
1076
+ // This requires a relationship traversal
1077
+ where[relationshipInfo.path] = locator.lk;
1078
+ if (relationshipInfo.includes) {
1079
+ includes.push(...relationshipInfo.includes);
1080
+ }
1081
+ } else {
1082
+ // This is a direct field
1083
+ const fieldName = `${locator.kt}Id`;
1084
+ where[fieldName] = locator.lk;
1085
+ }
1086
+ }
1087
+ const result = {
1088
+ where
1089
+ };
1090
+ if (includes.length > 0) {
1091
+ result.include = includes;
1092
+ }
1093
+ return result;
1094
+ };
630
1095
  const getRemoveOperation = (models, definition, // eslint-disable-next-line @typescript-eslint/no-unused-vars
631
1096
  registry)=>{
632
1097
  const { coordinate, options } = definition;
@@ -647,14 +1112,16 @@ registry)=>{
647
1112
  if (core.isPriKey(key)) {
648
1113
  item = await model.findByPk(key.pk);
649
1114
  } else if (core.isComKey(key)) {
650
- var _comKey_loc_, _comKey_loc_1;
1115
+ // This is a composite key, so we need to build a where clause based on the composite key's locators
651
1116
  const comKey = key;
652
- item = await model.findOne({
653
- where: {
654
- id: comKey.pk,
655
- [(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
656
- }
1117
+ const queryOptions = processCompositeKey(comKey, model, kta);
1118
+ logger$2.default('Composite key query', {
1119
+ queryOptions
657
1120
  });
1121
+ item = await model.findOne(queryOptions);
1122
+ }
1123
+ if (!item) {
1124
+ throw new Error(`Item not found for removal with key: ${core.abbrevIK(key)}`);
658
1125
  }
659
1126
  const isDeletedAttribute = model.getAttributes().isDeleted;
660
1127
  const deletedAtAttribute = model.getAttributes().deletedAt;
@@ -670,14 +1137,14 @@ registry)=>{
670
1137
  returnItem = item === null || item === void 0 ? void 0 : item.get({
671
1138
  plain: true
672
1139
  });
673
- returnItem = addKey(returnItem, kta);
1140
+ returnItem = addKey(item, returnItem, kta);
674
1141
  returnItem = populateEvents(returnItem);
675
1142
  } else if (options.deleteOnRemove) {
676
1143
  await (item === null || item === void 0 ? void 0 : item.destroy());
677
1144
  returnItem = item === null || item === void 0 ? void 0 : item.get({
678
1145
  plain: true
679
1146
  });
680
- returnItem = addKey(returnItem, kta);
1147
+ returnItem = addKey(item, returnItem, kta);
681
1148
  returnItem = populateEvents(returnItem);
682
1149
  } else {
683
1150
  throw new Error('No deletedAt or isDeleted attribute found in model, and deleteOnRemove is not set');
@@ -716,7 +1183,9 @@ const getUpdateOperation = (models, definition, registry)=>{
716
1183
  if (response) {
717
1184
  // Remove the key and events
718
1185
  let updateProps = removeKey(item);
719
- updateProps = removeEvents(item);
1186
+ // TODO: We need the opposite of processRow, something to step down from fjell to database.
1187
+ updateProps = extractEvents(updateProps);
1188
+ updateProps = removeEvents(updateProps);
720
1189
  logger$1.default('Response: %s', general.stringifyJSON(response));
721
1190
  logger$1.default('Update Properties: %s', general.stringifyJSON(updateProps));
722
1191
  // Update the object
@@ -736,12 +1205,12 @@ const createOperations = (models, definition, registry)=>{
736
1205
  const operations = {};
737
1206
  operations.all = all.getAllOperation(models, definition, registry);
738
1207
  operations.one = getOneOperation(models, definition, registry);
739
- operations.create = getCreateOperation();
1208
+ operations.create = getCreateOperation(models, definition, registry);
740
1209
  operations.update = getUpdateOperation(models, definition, registry);
741
1210
  operations.get = getGetOperation(models, definition, registry);
742
1211
  operations.remove = getRemoveOperation(models, definition);
743
1212
  operations.find = getFindOperation(models, definition, registry);
744
- operations.upsert = ()=>{
1213
+ operations.upsert = async ()=>{
745
1214
  throw new Error('Not implemented');
746
1215
  };
747
1216
  return operations;