@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/cjs/EventCoordinator.cjs +20 -1
- package/dist/cjs/KeyMaster.cjs +62 -24
- package/dist/cjs/Operations.cjs +2 -2
- package/dist/cjs/QueryBuilder.cjs +123 -21
- package/dist/cjs/RowProcessor.cjs +1 -1
- package/dist/cjs/ops/create.cjs +137 -6
- package/dist/cjs/ops/get.cjs +44 -8
- package/dist/cjs/ops/remove.cjs +48 -9
- package/dist/cjs/ops/update.cjs +4 -2
- package/dist/cjs/util/relationshipUtils.cjs +117 -0
- package/dist/es/EventCoordinator.js +20 -2
- package/dist/es/KeyMaster.js +62 -24
- package/dist/es/Operations.js +2 -2
- package/dist/es/QueryBuilder.js +123 -21
- package/dist/es/RowProcessor.js +1 -1
- package/dist/es/ops/create.js +137 -6
- package/dist/es/ops/get.js +44 -8
- package/dist/es/ops/remove.js +48 -9
- package/dist/es/ops/update.js +5 -3
- package/dist/es/util/relationshipUtils.js +112 -0
- package/dist/index.cjs +536 -67
- package/dist/index.cjs.map +1 -1
- package/dist/types/EventCoordinator.d.ts +1 -0
- package/dist/types/KeyMaster.d.ts +2 -1
- package/dist/types/QueryBuilder.d.ts +1 -0
- package/dist/types/ops/create.d.ts +1 -1
- package/dist/types/util/relationshipUtils.d.ts +21 -0
- package/package.json +7 -7
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
|
-
|
|
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
|
|
207
|
-
options.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
|
|
211
|
-
|
|
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
|
-
|
|
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
|
|
583
|
+
pk: item[primaryKeyAttr]
|
|
336
584
|
});
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
|
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
|
-
|
|
526
|
-
models,
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
|
|
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;
|