rads-db 3.0.59 → 3.0.63
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 +178 -125
- package/dist/index.d.ts +11 -1
- package/dist/index.mjs +178 -124
- package/drivers/restApi.cjs +6 -6
- package/drivers/restApi.mjs +5 -5
- package/features/cache.cjs +2 -3
- package/features/cache.mjs +2 -2
- package/integrations/lib.cjs +36 -14
- package/integrations/lib.mjs +48 -14
- package/package.json +1 -3
package/dist/index.cjs
CHANGED
|
@@ -5,13 +5,11 @@ const _ = require('lodash');
|
|
|
5
5
|
const uuid = require('uuid');
|
|
6
6
|
const createMerge = require('@fastify/deepmerge');
|
|
7
7
|
const _radsDb = require('_rads-db');
|
|
8
|
-
const pluralize = require('pluralize');
|
|
9
8
|
|
|
10
9
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
11
10
|
|
|
12
11
|
const ___default = /*#__PURE__*/_interopDefaultCompat(_);
|
|
13
12
|
const createMerge__default = /*#__PURE__*/_interopDefaultCompat(createMerge);
|
|
14
|
-
const pluralize__default = /*#__PURE__*/_interopDefaultCompat(pluralize);
|
|
15
13
|
|
|
16
14
|
function generateValidators(schema) {
|
|
17
15
|
const zodSchemas = {};
|
|
@@ -58,7 +56,7 @@ function getZodSchema(zodSchemas, schema, key) {
|
|
|
58
56
|
const objectSchema = {};
|
|
59
57
|
for (const fieldName in type.fields) {
|
|
60
58
|
const f = type.fields[fieldName];
|
|
61
|
-
if (f.decorators?.computed || f.decorators?.precomputed) {
|
|
59
|
+
if (f.decorators?.computed || f.decorators?.precomputed || f.isInverseRelation) {
|
|
62
60
|
continue;
|
|
63
61
|
}
|
|
64
62
|
objectSchema[fieldName] = getFieldZodSchema(zodSchemas, schema, f);
|
|
@@ -510,6 +508,73 @@ async function handleEffectsAfterPut(context, docs, beforePutResults, ctx) {
|
|
|
510
508
|
);
|
|
511
509
|
}
|
|
512
510
|
|
|
511
|
+
function cleanUndefinedAndNull(obj, isChange = false) {
|
|
512
|
+
for (const key in obj) {
|
|
513
|
+
if (obj[key] == null && !isChange)
|
|
514
|
+
delete obj[key];
|
|
515
|
+
if (___default.isPlainObject(obj[key]))
|
|
516
|
+
cleanUndefinedAndNull(obj[key], isChange || key === "change");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const mergeFn = createMerge__default({
|
|
521
|
+
mergeArray(options) {
|
|
522
|
+
const clone = options.clone;
|
|
523
|
+
return function(target, source) {
|
|
524
|
+
return clone(source);
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
function merge(target, source) {
|
|
529
|
+
cleanUndefined(source);
|
|
530
|
+
const result = mergeFn(target, source);
|
|
531
|
+
return result;
|
|
532
|
+
}
|
|
533
|
+
function cleanUndefined(obj) {
|
|
534
|
+
if (!obj || !___default.isPlainObject(obj))
|
|
535
|
+
return;
|
|
536
|
+
cleanUndefinedInner(obj);
|
|
537
|
+
}
|
|
538
|
+
function cleanUndefinedInner(obj) {
|
|
539
|
+
for (const key in obj) {
|
|
540
|
+
if (obj[key] === void 0)
|
|
541
|
+
delete obj[key];
|
|
542
|
+
if (___default.isPlainObject(obj[key]))
|
|
543
|
+
cleanUndefinedInner(obj[key]);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function diff(object, base, includeDenormFields = false) {
|
|
548
|
+
if (!base)
|
|
549
|
+
return object;
|
|
550
|
+
return ___default.transform(object, (result, value, key) => {
|
|
551
|
+
const oldValue = base[key];
|
|
552
|
+
if (!___default.isEqual(value, oldValue)) {
|
|
553
|
+
if (mustUseDeepDiff(value, oldValue)) {
|
|
554
|
+
const isRelationField = (value.id || oldValue.id) && key !== "change";
|
|
555
|
+
if (isRelationField && !includeDenormFields) {
|
|
556
|
+
if (value.id !== oldValue.id)
|
|
557
|
+
result[key] = value;
|
|
558
|
+
} else {
|
|
559
|
+
const subDiff = diff(value, oldValue, includeDenormFields);
|
|
560
|
+
if (!___default.isEmpty(subDiff))
|
|
561
|
+
result[key] = subDiff;
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
result[key] = value;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
function mustUseDeepDiff(value, oldValue) {
|
|
570
|
+
return ___default.isObject(value) && ___default.isObject(oldValue) && !___default.isArray(value) && !___default.isArray(oldValue);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const radsDbRelations = {
|
|
574
|
+
verifyRelationsSetup,
|
|
575
|
+
fillDenormFieldsBeforePut,
|
|
576
|
+
handleInclude
|
|
577
|
+
};
|
|
513
578
|
function verifyRelationsSetup(schema, effects) {
|
|
514
579
|
for (const entityName in schema) {
|
|
515
580
|
const entity = schema[entityName];
|
|
@@ -589,72 +654,102 @@ async function fillDenormFieldsBeforePut(computedContext, docUpdates, ctx) {
|
|
|
589
654
|
});
|
|
590
655
|
await Promise.all(promises);
|
|
591
656
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
for (const key in obj) {
|
|
595
|
-
if (obj[key] == null && !isChange)
|
|
596
|
-
delete obj[key];
|
|
597
|
-
if (___default.isPlainObject(obj[key]))
|
|
598
|
-
cleanUndefinedAndNull(obj[key], isChange || key === "change");
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const mergeFn = createMerge__default({
|
|
603
|
-
mergeArray(options) {
|
|
604
|
-
const clone = options.clone;
|
|
605
|
-
return function(target, source) {
|
|
606
|
-
return clone(source);
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
function merge(target, source) {
|
|
611
|
-
cleanUndefined(source);
|
|
612
|
-
const result = mergeFn(target, source);
|
|
613
|
-
return result;
|
|
614
|
-
}
|
|
615
|
-
function cleanUndefined(obj) {
|
|
616
|
-
if (!obj || !___default.isPlainObject(obj))
|
|
657
|
+
async function handleInclude(computedContext, include, result, ctx) {
|
|
658
|
+
if (!result || !result.length || !include)
|
|
617
659
|
return;
|
|
618
|
-
|
|
660
|
+
const { schema, typeName } = computedContext;
|
|
661
|
+
const fields = schema[typeName].fields || {};
|
|
662
|
+
const fieldsToInclude = ___default.keys(include).filter((key) => include[key]);
|
|
663
|
+
const relationsToInclude = fieldsToInclude.filter((key) => fields[key].isRelation);
|
|
664
|
+
const args = {
|
|
665
|
+
entityTypeName: typeName,
|
|
666
|
+
computedContext,
|
|
667
|
+
ctx,
|
|
668
|
+
result,
|
|
669
|
+
include
|
|
670
|
+
};
|
|
671
|
+
const inverseRelationsToInclude = fieldsToInclude.filter((key) => fields[key].isInverseRelation);
|
|
672
|
+
const downloadRelationsPromises = relationsToInclude.map(
|
|
673
|
+
(fieldName) => downloadRelatedDocuments({ ...args, fieldName })
|
|
674
|
+
);
|
|
675
|
+
const downloadInverseRelationsPromises = inverseRelationsToInclude.map(
|
|
676
|
+
(fieldName) => downloadInverseRelationDocuments({ ...args, fieldName })
|
|
677
|
+
);
|
|
678
|
+
await Promise.all([...downloadRelationsPromises, ...downloadInverseRelationsPromises]);
|
|
619
679
|
}
|
|
620
|
-
function
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
680
|
+
async function downloadInverseRelationDocuments(args) {
|
|
681
|
+
const { computedContext, ctx, entityTypeName, fieldName, result, include } = args;
|
|
682
|
+
const { schema, db } = computedContext;
|
|
683
|
+
const field = schema[entityTypeName].fields?.[fieldName];
|
|
684
|
+
if (!field?.inverseRelationField)
|
|
685
|
+
throw new Error(`Cannot find field ${fieldName}`);
|
|
686
|
+
const typeName = field.type;
|
|
687
|
+
const type = schema[typeName];
|
|
688
|
+
if (!type)
|
|
689
|
+
throw new Error(`Cannot find entity ${typeName}`);
|
|
690
|
+
for (const item of result) {
|
|
691
|
+
if (!item?.id)
|
|
692
|
+
continue;
|
|
693
|
+
const entityHandle = schema[typeName].handle;
|
|
694
|
+
const documentsRelatedToThisDoc = await db[entityHandle].getMany(
|
|
695
|
+
{ where: { [field.inverseRelationField]: { id: item.id } }, include: include?.[fieldName], maxItemCount: 100 },
|
|
696
|
+
ctx
|
|
697
|
+
);
|
|
698
|
+
item[fieldName] = documentsRelatedToThisDoc.nodes;
|
|
626
699
|
}
|
|
627
700
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
701
|
+
async function downloadRelatedDocuments(args) {
|
|
702
|
+
const { computedContext, ctx, entityTypeName, fieldName, result, include } = args;
|
|
703
|
+
const { schema, db } = computedContext;
|
|
704
|
+
const typeName = schema[entityTypeName].fields?.[fieldName].type || "";
|
|
705
|
+
const type = schema[typeName];
|
|
706
|
+
if (!type)
|
|
707
|
+
throw new Error(`Cannot find entity ${typeName}`);
|
|
708
|
+
const idsToGet = /* @__PURE__ */ new Set();
|
|
709
|
+
for (const item of result) {
|
|
710
|
+
if (!item)
|
|
711
|
+
continue;
|
|
712
|
+
const fieldValue = item[fieldName];
|
|
713
|
+
if (!fieldValue)
|
|
714
|
+
continue;
|
|
715
|
+
const fieldValues = ___default.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
716
|
+
for (const fv of fieldValues) {
|
|
717
|
+
const id = fv?.id;
|
|
718
|
+
if (!id)
|
|
719
|
+
continue;
|
|
720
|
+
idsToGet.add(id);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
const entityHandle = schema[typeName].handle;
|
|
724
|
+
const relatedEntities = await db[entityHandle].getAll(
|
|
725
|
+
{ where: { id_in: [...idsToGet] }, include: include?.[fieldName] },
|
|
726
|
+
ctx
|
|
727
|
+
);
|
|
728
|
+
const relatedEntitiesById = ___default.keyBy(relatedEntities, "id");
|
|
729
|
+
for (const item of result) {
|
|
730
|
+
if (!item)
|
|
731
|
+
continue;
|
|
732
|
+
const fieldValue = item[fieldName];
|
|
733
|
+
if (!fieldValue)
|
|
734
|
+
continue;
|
|
735
|
+
const fieldValues = ___default.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
736
|
+
for (const fv of fieldValues) {
|
|
737
|
+
const id = fv?.id;
|
|
738
|
+
if (!id)
|
|
739
|
+
continue;
|
|
740
|
+
const relatedEntity = relatedEntitiesById[id];
|
|
741
|
+
if (!relatedEntity) {
|
|
742
|
+
console.warn(`Cannot find ${typeName} with id "${id}" (for ${typeName}.${fieldName} with id "${item.id}")`);
|
|
647
743
|
}
|
|
744
|
+
Object.assign(fv, relatedEntity);
|
|
648
745
|
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
function mustUseDeepDiff(value, oldValue) {
|
|
652
|
-
return ___default.isObject(value) && ___default.isObject(oldValue) && !___default.isArray(value) && !___default.isArray(oldValue);
|
|
746
|
+
}
|
|
653
747
|
}
|
|
654
748
|
|
|
655
749
|
function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
656
750
|
const { schema, options, validators } = computedContext;
|
|
657
|
-
const
|
|
751
|
+
const entitySchema = schema[key];
|
|
752
|
+
const { precomputedFields } = entitySchema;
|
|
658
753
|
async function getMany(args, ctx) {
|
|
659
754
|
args = { ...args, where: { ...args?.where } };
|
|
660
755
|
ctx = { ...options.context, ...ctx, method: "getMany" };
|
|
@@ -662,7 +757,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
662
757
|
if (!result2)
|
|
663
758
|
result2 = await driverInstance.getMany(args, ctx);
|
|
664
759
|
if (args.include)
|
|
665
|
-
await handleInclude(computedContext, args.include, result2.nodes, ctx);
|
|
760
|
+
await radsDbRelations.handleInclude(computedContext, args.include, result2.nodes, ctx);
|
|
666
761
|
await handleComputed(computedContext, result2.nodes, ctx);
|
|
667
762
|
await afterGet(result2.nodes, args, ctx, computedContext);
|
|
668
763
|
return result2;
|
|
@@ -699,7 +794,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
699
794
|
}
|
|
700
795
|
d.doc = validatedDoc;
|
|
701
796
|
}
|
|
702
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
797
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
703
798
|
const docsToSave = docArgsToSave.map((x) => x.doc);
|
|
704
799
|
await driverInstance.putMany(docsToSave, ctx);
|
|
705
800
|
await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
|
|
@@ -730,8 +825,19 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
730
825
|
}
|
|
731
826
|
if (!options.keepNulls)
|
|
732
827
|
cleanUndefinedAndNull(doc);
|
|
733
|
-
return { oldDoc, doc };
|
|
828
|
+
return { oldDoc, doc, events: [] };
|
|
734
829
|
});
|
|
830
|
+
if (entitySchema.decorators.precomputed?.preset === "eventSourcing") {
|
|
831
|
+
const eventEntity = `${key}Event`;
|
|
832
|
+
const aggregateRelationField = ___default.lowerFirst(key);
|
|
833
|
+
const eventDriver = computedContext.drivers[eventEntity];
|
|
834
|
+
for (const d of docArgsToSave) {
|
|
835
|
+
d.events = await eventDriver.getAll(
|
|
836
|
+
{ where: { [aggregateRelationField]: { id: d.doc.id } }, orderBy: "date_asc" },
|
|
837
|
+
ctx
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
735
841
|
await handlePrecomputed(computedContext, docArgsToSave, ctx);
|
|
736
842
|
const beforePutResults = await handleEffectsBeforePut(computedContext, docArgsToSave, ctx);
|
|
737
843
|
await beforePut(docArgsToSave, ctx, computedContext);
|
|
@@ -742,7 +848,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
742
848
|
}
|
|
743
849
|
d.doc = validatedDoc;
|
|
744
850
|
}
|
|
745
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
851
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
746
852
|
for (const { oldDoc, doc } of docArgsToSave) {
|
|
747
853
|
const d = diff(doc, oldDoc);
|
|
748
854
|
if (___default.isEmpty(d))
|
|
@@ -801,7 +907,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
801
907
|
result2 = await driverInstance.get(args, ctx);
|
|
802
908
|
const resultArray = result2 ? [result2] : [];
|
|
803
909
|
if (result2 && args.include)
|
|
804
|
-
await handleInclude(computedContext, args.include, resultArray, ctx);
|
|
910
|
+
await radsDbRelations.handleInclude(computedContext, args.include, resultArray, ctx);
|
|
805
911
|
if (result2)
|
|
806
912
|
await handleComputed(computedContext, resultArray, ctx);
|
|
807
913
|
await afterGet(resultArray, args, ctx, computedContext);
|
|
@@ -814,7 +920,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
814
920
|
if (!result2)
|
|
815
921
|
result2 = await driverInstance.getAll(args, ctx);
|
|
816
922
|
if (args.include)
|
|
817
|
-
await handleInclude(computedContext, args.include, result2, ctx);
|
|
923
|
+
await radsDbRelations.handleInclude(computedContext, args.include, result2, ctx);
|
|
818
924
|
await handleComputed(computedContext, result2, ctx);
|
|
819
925
|
await afterGet(result2, args, ctx, computedContext);
|
|
820
926
|
return result2;
|
|
@@ -857,7 +963,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
857
963
|
}
|
|
858
964
|
}
|
|
859
965
|
doc = validatedDoc;
|
|
860
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
966
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
861
967
|
await driverInstance.put(doc, ctx);
|
|
862
968
|
await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
|
|
863
969
|
await afterPut(docArgsToSave, ctx, computedContext);
|
|
@@ -892,61 +998,6 @@ async function afterPut(items, ctx, computedContext) {
|
|
|
892
998
|
await f.afterPut?.(items, ctx, computedContext);
|
|
893
999
|
}
|
|
894
1000
|
}
|
|
895
|
-
async function handleInclude(computedContext, include, result, ctx) {
|
|
896
|
-
if (!result || !result.length || !include)
|
|
897
|
-
return;
|
|
898
|
-
const { schema, typeName, drivers } = computedContext;
|
|
899
|
-
const fields = schema[typeName].fields || {};
|
|
900
|
-
const relationsToInclude = ___default.keys(include).filter((key) => include[key] && fields[key].isRelation);
|
|
901
|
-
const downloadRelationsPromises = relationsToInclude.map(async (fieldName) => {
|
|
902
|
-
const typeName2 = fields[fieldName].type;
|
|
903
|
-
const type = schema[typeName2];
|
|
904
|
-
if (!type)
|
|
905
|
-
throw new Error(`Cannot find entity ${typeName2}`);
|
|
906
|
-
const idsToGet = /* @__PURE__ */ new Set();
|
|
907
|
-
for (const item of result) {
|
|
908
|
-
if (!item)
|
|
909
|
-
continue;
|
|
910
|
-
const fieldValue = item[fieldName];
|
|
911
|
-
if (!fieldValue)
|
|
912
|
-
continue;
|
|
913
|
-
const fieldValues = ___default.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
914
|
-
for (const fv of fieldValues) {
|
|
915
|
-
const id = fv?.id;
|
|
916
|
-
if (!id)
|
|
917
|
-
continue;
|
|
918
|
-
idsToGet.add(id);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
const driverInstance = getExistingDriverInstance(typeName2, drivers);
|
|
922
|
-
const relatedEntities = await driverInstance.getAll({ where: { id_in: [...idsToGet] } }, ctx);
|
|
923
|
-
const relatedEntitiesById = ___default.keyBy(relatedEntities, "id");
|
|
924
|
-
for (const item of result) {
|
|
925
|
-
if (!item)
|
|
926
|
-
continue;
|
|
927
|
-
const fieldValue = item[fieldName];
|
|
928
|
-
if (!fieldValue)
|
|
929
|
-
continue;
|
|
930
|
-
const fieldValues = ___default.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
931
|
-
for (const fv of fieldValues) {
|
|
932
|
-
const id = fv?.id;
|
|
933
|
-
if (!id)
|
|
934
|
-
continue;
|
|
935
|
-
const relatedEntity = relatedEntitiesById[id];
|
|
936
|
-
if (!relatedEntity)
|
|
937
|
-
console.warn(`Cannot find ${typeName2} with id "${id}" (for ${typeName2}.${fieldName} with id "${item.id}")`);
|
|
938
|
-
Object.assign(fv, relatedEntity);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
});
|
|
942
|
-
await Promise.all(downloadRelationsPromises);
|
|
943
|
-
}
|
|
944
|
-
function getExistingDriverInstance(entityName, drivers) {
|
|
945
|
-
if (!drivers[entityName]) {
|
|
946
|
-
throw new Error(`Driver for entity ${entityName} was not found!`);
|
|
947
|
-
}
|
|
948
|
-
return drivers[entityName];
|
|
949
|
-
}
|
|
950
1001
|
|
|
951
1002
|
function generateMethods(schema, validators, options) {
|
|
952
1003
|
const drivers = {};
|
|
@@ -976,7 +1027,7 @@ function generateMethods(schema, validators, options) {
|
|
|
976
1027
|
if (!opts.noComputed) {
|
|
977
1028
|
verifyComputedPresense(schema, opts.computed, effects);
|
|
978
1029
|
}
|
|
979
|
-
verifyRelationsSetup(schema, effects);
|
|
1030
|
+
radsDbRelations.verifyRelationsSetup(schema, effects);
|
|
980
1031
|
const computedContextGlobal = {
|
|
981
1032
|
schema,
|
|
982
1033
|
validators,
|
|
@@ -1144,12 +1195,14 @@ const restApi = (options) => (schema, entity) => {
|
|
|
1144
1195
|
}
|
|
1145
1196
|
return responseJson;
|
|
1146
1197
|
}
|
|
1147
|
-
const
|
|
1198
|
+
const { handlePlural } = schema[entity] || {};
|
|
1199
|
+
if (!handlePlural)
|
|
1200
|
+
throw new Error(`Entity ${entity} was not found in schema`);
|
|
1148
1201
|
const instance = {
|
|
1149
1202
|
driverName: "restApi",
|
|
1150
1203
|
async getMany(args) {
|
|
1151
1204
|
args = args || {};
|
|
1152
|
-
const url = `${opts.baseUrl}/${
|
|
1205
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
1153
1206
|
const fetchOptions = {
|
|
1154
1207
|
method: "POST",
|
|
1155
1208
|
body: JSON.stringify(args),
|
|
@@ -1159,7 +1212,7 @@ const restApi = (options) => (schema, entity) => {
|
|
|
1159
1212
|
return await fetchInner(url, fetchOptions);
|
|
1160
1213
|
},
|
|
1161
1214
|
async putMany(item) {
|
|
1162
|
-
const url = `${opts.baseUrl}/${
|
|
1215
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
1163
1216
|
const fetchOptions = {
|
|
1164
1217
|
method: "PUT",
|
|
1165
1218
|
body: JSON.stringify({ data: item }),
|
package/dist/index.d.ts
CHANGED
|
@@ -89,9 +89,17 @@ type DeepPartialWithNullsItem<T> = T extends {
|
|
|
89
89
|
type DeepPartial<T> = {
|
|
90
90
|
[K in keyof T]?: NonNullable<T[K]> extends any[] ? DeepPartial<NonNullable<T[K]>[number]>[] : NonNullable<T[K]> extends Record<string, any> ? DeepPartial<T[K]> : T[K];
|
|
91
91
|
};
|
|
92
|
+
/** Indicates that this field is a relation to another entity in the database.
|
|
93
|
+
* Only id will be stored in the database.
|
|
94
|
+
* If you want to store additional fields, please, pass them as the second type argument */
|
|
92
95
|
type Relation<T extends {
|
|
93
96
|
id: any;
|
|
94
97
|
}, K extends Exclude<keyof T, 'id'> = never> = Pick<T, K | 'id'>;
|
|
98
|
+
/** Indicates that this is computed field - all documents that point to this document via Relation<>.
|
|
99
|
+
* Note: this field is not stored in the database at all. Returns up to 100 items and doesn't support pagination
|
|
100
|
+
* If you need more control, please, use separate request instead.
|
|
101
|
+
*/
|
|
102
|
+
type InverseRelation<EN extends keyof EntityMeta, _Field extends keyof EntityMeta[EN]['relations']> = EntityMeta[EN]['type'];
|
|
95
103
|
type PutArgs<T> = {
|
|
96
104
|
id: string;
|
|
97
105
|
} & DeepPartialWithNulls<T>;
|
|
@@ -298,7 +306,9 @@ interface FieldDefinition {
|
|
|
298
306
|
isRequired?: boolean;
|
|
299
307
|
isArray?: boolean;
|
|
300
308
|
isRelation?: boolean;
|
|
309
|
+
isInverseRelation?: boolean;
|
|
301
310
|
isChange?: boolean;
|
|
311
|
+
inverseRelationField?: string;
|
|
302
312
|
relationDenormFields?: string[];
|
|
303
313
|
comment?: string;
|
|
304
314
|
decorators?: Record<string, Record<string, any>>;
|
|
@@ -448,4 +458,4 @@ declare function createRadsDb(args?: CreateRadsDbArgs): RadsDb;
|
|
|
448
458
|
*/
|
|
449
459
|
declare function createRadsDbClient(args?: CreateRadsDbClientArgs): RadsDb;
|
|
450
460
|
|
|
451
|
-
export { Change, ComputedContext, ComputedContextGlobal, ComputedDecoratorArgs, CreateRadsArgsDrivers, CreateRadsDbArgs, CreateRadsDbArgsNormalized, CreateRadsDbClientArgs, DeepKeys, DeepPartial, DeepPartialWithNulls, DeepPartialWithNullsItem, Driver, DriverConstructor, EntityDecoratorArgs, EntityMethods, EnumDefinition, FieldDecoratorArgs, FieldDefinition, FileSystemNode, FileUploadArgs, FileUploadDriver, FileUploadResult, GenerateClientNormalizedOptions, GenerateClientOptions, Get, GetAggArgs, GetAggArgsAgg, GetAggArgsAny, GetAggResponse, GetArgs, GetArgsAny, GetArgsInclude, GetManyArgs, GetManyArgsAny, GetManyResponse, GetResponse, GetResponseInclude, GetResponseIncludeSelect, GetResponseNoInclude, GetRestRoutesArgs, GetRestRoutesOptions, GetRestRoutesResponse, MinimalDriver, Put, PutArgs, PutEffect, RadsFeature, RadsHookDoc, RadsRequestContext, RadsUiSlotDefinition, RadsUiSlotName, RadsVitePluginOptions, Relation, RequiredFields, RestDriverOptions, RestFileUploadDriverOptions, Schema, SchemaValidators, TypeDefinition, UiDecoratorArgs, UiFieldDecoratorArgs, ValidateEntityDecoratorArgs, ValidateFieldDecoratorArgs, ValidateStringDecoratorArgs, VerifyManyArgs, VerifyManyArgsAny, VerifyManyResponse, Where, WhereJsonContains, cleanUndefinedAndNull, computed, createRadsDb, createRadsDbClient, diff, entity, field, getDriverInstance, handlePrecomputed, keepHistory, merge, precomputed, ui, validate };
|
|
461
|
+
export { Change, ComputedContext, ComputedContextGlobal, ComputedDecoratorArgs, CreateRadsArgsDrivers, CreateRadsDbArgs, CreateRadsDbArgsNormalized, CreateRadsDbClientArgs, DeepKeys, DeepPartial, DeepPartialWithNulls, DeepPartialWithNullsItem, Driver, DriverConstructor, EntityDecoratorArgs, EntityMethods, EnumDefinition, FieldDecoratorArgs, FieldDefinition, FileSystemNode, FileUploadArgs, FileUploadDriver, FileUploadResult, GenerateClientNormalizedOptions, GenerateClientOptions, Get, GetAggArgs, GetAggArgsAgg, GetAggArgsAny, GetAggResponse, GetArgs, GetArgsAny, GetArgsInclude, GetManyArgs, GetManyArgsAny, GetManyResponse, GetResponse, GetResponseInclude, GetResponseIncludeSelect, GetResponseNoInclude, GetRestRoutesArgs, GetRestRoutesOptions, GetRestRoutesResponse, InverseRelation, MinimalDriver, Put, PutArgs, PutEffect, RadsFeature, RadsHookDoc, RadsRequestContext, RadsUiSlotDefinition, RadsUiSlotName, RadsVitePluginOptions, Relation, RequiredFields, RestDriverOptions, RestFileUploadDriverOptions, Schema, SchemaValidators, TypeDefinition, UiDecoratorArgs, UiFieldDecoratorArgs, ValidateEntityDecoratorArgs, ValidateFieldDecoratorArgs, ValidateStringDecoratorArgs, VerifyManyArgs, VerifyManyArgsAny, VerifyManyResponse, Where, WhereJsonContains, cleanUndefinedAndNull, computed, createRadsDb, createRadsDbClient, diff, entity, field, getDriverInstance, handlePrecomputed, keepHistory, merge, precomputed, ui, validate };
|
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,6 @@ import _ from 'lodash';
|
|
|
3
3
|
import { v4 } from 'uuid';
|
|
4
4
|
import createMerge from '@fastify/deepmerge';
|
|
5
5
|
import { schema } from '_rads-db';
|
|
6
|
-
import pluralize from 'pluralize';
|
|
7
6
|
|
|
8
7
|
function generateValidators(schema) {
|
|
9
8
|
const zodSchemas = {};
|
|
@@ -50,7 +49,7 @@ function getZodSchema(zodSchemas, schema, key) {
|
|
|
50
49
|
const objectSchema = {};
|
|
51
50
|
for (const fieldName in type.fields) {
|
|
52
51
|
const f = type.fields[fieldName];
|
|
53
|
-
if (f.decorators?.computed || f.decorators?.precomputed) {
|
|
52
|
+
if (f.decorators?.computed || f.decorators?.precomputed || f.isInverseRelation) {
|
|
54
53
|
continue;
|
|
55
54
|
}
|
|
56
55
|
objectSchema[fieldName] = getFieldZodSchema(zodSchemas, schema, f);
|
|
@@ -502,6 +501,73 @@ async function handleEffectsAfterPut(context, docs, beforePutResults, ctx) {
|
|
|
502
501
|
);
|
|
503
502
|
}
|
|
504
503
|
|
|
504
|
+
function cleanUndefinedAndNull(obj, isChange = false) {
|
|
505
|
+
for (const key in obj) {
|
|
506
|
+
if (obj[key] == null && !isChange)
|
|
507
|
+
delete obj[key];
|
|
508
|
+
if (_.isPlainObject(obj[key]))
|
|
509
|
+
cleanUndefinedAndNull(obj[key], isChange || key === "change");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const mergeFn = createMerge({
|
|
514
|
+
mergeArray(options) {
|
|
515
|
+
const clone = options.clone;
|
|
516
|
+
return function(target, source) {
|
|
517
|
+
return clone(source);
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
function merge(target, source) {
|
|
522
|
+
cleanUndefined(source);
|
|
523
|
+
const result = mergeFn(target, source);
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
function cleanUndefined(obj) {
|
|
527
|
+
if (!obj || !_.isPlainObject(obj))
|
|
528
|
+
return;
|
|
529
|
+
cleanUndefinedInner(obj);
|
|
530
|
+
}
|
|
531
|
+
function cleanUndefinedInner(obj) {
|
|
532
|
+
for (const key in obj) {
|
|
533
|
+
if (obj[key] === void 0)
|
|
534
|
+
delete obj[key];
|
|
535
|
+
if (_.isPlainObject(obj[key]))
|
|
536
|
+
cleanUndefinedInner(obj[key]);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function diff(object, base, includeDenormFields = false) {
|
|
541
|
+
if (!base)
|
|
542
|
+
return object;
|
|
543
|
+
return _.transform(object, (result, value, key) => {
|
|
544
|
+
const oldValue = base[key];
|
|
545
|
+
if (!_.isEqual(value, oldValue)) {
|
|
546
|
+
if (mustUseDeepDiff(value, oldValue)) {
|
|
547
|
+
const isRelationField = (value.id || oldValue.id) && key !== "change";
|
|
548
|
+
if (isRelationField && !includeDenormFields) {
|
|
549
|
+
if (value.id !== oldValue.id)
|
|
550
|
+
result[key] = value;
|
|
551
|
+
} else {
|
|
552
|
+
const subDiff = diff(value, oldValue, includeDenormFields);
|
|
553
|
+
if (!_.isEmpty(subDiff))
|
|
554
|
+
result[key] = subDiff;
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
result[key] = value;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
function mustUseDeepDiff(value, oldValue) {
|
|
563
|
+
return _.isObject(value) && _.isObject(oldValue) && !_.isArray(value) && !_.isArray(oldValue);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const radsDbRelations = {
|
|
567
|
+
verifyRelationsSetup,
|
|
568
|
+
fillDenormFieldsBeforePut,
|
|
569
|
+
handleInclude
|
|
570
|
+
};
|
|
505
571
|
function verifyRelationsSetup(schema, effects) {
|
|
506
572
|
for (const entityName in schema) {
|
|
507
573
|
const entity = schema[entityName];
|
|
@@ -581,72 +647,102 @@ async function fillDenormFieldsBeforePut(computedContext, docUpdates, ctx) {
|
|
|
581
647
|
});
|
|
582
648
|
await Promise.all(promises);
|
|
583
649
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
for (const key in obj) {
|
|
587
|
-
if (obj[key] == null && !isChange)
|
|
588
|
-
delete obj[key];
|
|
589
|
-
if (_.isPlainObject(obj[key]))
|
|
590
|
-
cleanUndefinedAndNull(obj[key], isChange || key === "change");
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
const mergeFn = createMerge({
|
|
595
|
-
mergeArray(options) {
|
|
596
|
-
const clone = options.clone;
|
|
597
|
-
return function(target, source) {
|
|
598
|
-
return clone(source);
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
function merge(target, source) {
|
|
603
|
-
cleanUndefined(source);
|
|
604
|
-
const result = mergeFn(target, source);
|
|
605
|
-
return result;
|
|
606
|
-
}
|
|
607
|
-
function cleanUndefined(obj) {
|
|
608
|
-
if (!obj || !_.isPlainObject(obj))
|
|
650
|
+
async function handleInclude(computedContext, include, result, ctx) {
|
|
651
|
+
if (!result || !result.length || !include)
|
|
609
652
|
return;
|
|
610
|
-
|
|
653
|
+
const { schema, typeName } = computedContext;
|
|
654
|
+
const fields = schema[typeName].fields || {};
|
|
655
|
+
const fieldsToInclude = _.keys(include).filter((key) => include[key]);
|
|
656
|
+
const relationsToInclude = fieldsToInclude.filter((key) => fields[key].isRelation);
|
|
657
|
+
const args = {
|
|
658
|
+
entityTypeName: typeName,
|
|
659
|
+
computedContext,
|
|
660
|
+
ctx,
|
|
661
|
+
result,
|
|
662
|
+
include
|
|
663
|
+
};
|
|
664
|
+
const inverseRelationsToInclude = fieldsToInclude.filter((key) => fields[key].isInverseRelation);
|
|
665
|
+
const downloadRelationsPromises = relationsToInclude.map(
|
|
666
|
+
(fieldName) => downloadRelatedDocuments({ ...args, fieldName })
|
|
667
|
+
);
|
|
668
|
+
const downloadInverseRelationsPromises = inverseRelationsToInclude.map(
|
|
669
|
+
(fieldName) => downloadInverseRelationDocuments({ ...args, fieldName })
|
|
670
|
+
);
|
|
671
|
+
await Promise.all([...downloadRelationsPromises, ...downloadInverseRelationsPromises]);
|
|
611
672
|
}
|
|
612
|
-
function
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
673
|
+
async function downloadInverseRelationDocuments(args) {
|
|
674
|
+
const { computedContext, ctx, entityTypeName, fieldName, result, include } = args;
|
|
675
|
+
const { schema, db } = computedContext;
|
|
676
|
+
const field = schema[entityTypeName].fields?.[fieldName];
|
|
677
|
+
if (!field?.inverseRelationField)
|
|
678
|
+
throw new Error(`Cannot find field ${fieldName}`);
|
|
679
|
+
const typeName = field.type;
|
|
680
|
+
const type = schema[typeName];
|
|
681
|
+
if (!type)
|
|
682
|
+
throw new Error(`Cannot find entity ${typeName}`);
|
|
683
|
+
for (const item of result) {
|
|
684
|
+
if (!item?.id)
|
|
685
|
+
continue;
|
|
686
|
+
const entityHandle = schema[typeName].handle;
|
|
687
|
+
const documentsRelatedToThisDoc = await db[entityHandle].getMany(
|
|
688
|
+
{ where: { [field.inverseRelationField]: { id: item.id } }, include: include?.[fieldName], maxItemCount: 100 },
|
|
689
|
+
ctx
|
|
690
|
+
);
|
|
691
|
+
item[fieldName] = documentsRelatedToThisDoc.nodes;
|
|
618
692
|
}
|
|
619
693
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
694
|
+
async function downloadRelatedDocuments(args) {
|
|
695
|
+
const { computedContext, ctx, entityTypeName, fieldName, result, include } = args;
|
|
696
|
+
const { schema, db } = computedContext;
|
|
697
|
+
const typeName = schema[entityTypeName].fields?.[fieldName].type || "";
|
|
698
|
+
const type = schema[typeName];
|
|
699
|
+
if (!type)
|
|
700
|
+
throw new Error(`Cannot find entity ${typeName}`);
|
|
701
|
+
const idsToGet = /* @__PURE__ */ new Set();
|
|
702
|
+
for (const item of result) {
|
|
703
|
+
if (!item)
|
|
704
|
+
continue;
|
|
705
|
+
const fieldValue = item[fieldName];
|
|
706
|
+
if (!fieldValue)
|
|
707
|
+
continue;
|
|
708
|
+
const fieldValues = _.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
709
|
+
for (const fv of fieldValues) {
|
|
710
|
+
const id = fv?.id;
|
|
711
|
+
if (!id)
|
|
712
|
+
continue;
|
|
713
|
+
idsToGet.add(id);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
const entityHandle = schema[typeName].handle;
|
|
717
|
+
const relatedEntities = await db[entityHandle].getAll(
|
|
718
|
+
{ where: { id_in: [...idsToGet] }, include: include?.[fieldName] },
|
|
719
|
+
ctx
|
|
720
|
+
);
|
|
721
|
+
const relatedEntitiesById = _.keyBy(relatedEntities, "id");
|
|
722
|
+
for (const item of result) {
|
|
723
|
+
if (!item)
|
|
724
|
+
continue;
|
|
725
|
+
const fieldValue = item[fieldName];
|
|
726
|
+
if (!fieldValue)
|
|
727
|
+
continue;
|
|
728
|
+
const fieldValues = _.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
729
|
+
for (const fv of fieldValues) {
|
|
730
|
+
const id = fv?.id;
|
|
731
|
+
if (!id)
|
|
732
|
+
continue;
|
|
733
|
+
const relatedEntity = relatedEntitiesById[id];
|
|
734
|
+
if (!relatedEntity) {
|
|
735
|
+
console.warn(`Cannot find ${typeName} with id "${id}" (for ${typeName}.${fieldName} with id "${item.id}")`);
|
|
639
736
|
}
|
|
737
|
+
Object.assign(fv, relatedEntity);
|
|
640
738
|
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
function mustUseDeepDiff(value, oldValue) {
|
|
644
|
-
return _.isObject(value) && _.isObject(oldValue) && !_.isArray(value) && !_.isArray(oldValue);
|
|
739
|
+
}
|
|
645
740
|
}
|
|
646
741
|
|
|
647
742
|
function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
648
743
|
const { schema, options, validators } = computedContext;
|
|
649
|
-
const
|
|
744
|
+
const entitySchema = schema[key];
|
|
745
|
+
const { precomputedFields } = entitySchema;
|
|
650
746
|
async function getMany(args, ctx) {
|
|
651
747
|
args = { ...args, where: { ...args?.where } };
|
|
652
748
|
ctx = { ...options.context, ...ctx, method: "getMany" };
|
|
@@ -654,7 +750,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
654
750
|
if (!result2)
|
|
655
751
|
result2 = await driverInstance.getMany(args, ctx);
|
|
656
752
|
if (args.include)
|
|
657
|
-
await handleInclude(computedContext, args.include, result2.nodes, ctx);
|
|
753
|
+
await radsDbRelations.handleInclude(computedContext, args.include, result2.nodes, ctx);
|
|
658
754
|
await handleComputed(computedContext, result2.nodes, ctx);
|
|
659
755
|
await afterGet(result2.nodes, args, ctx, computedContext);
|
|
660
756
|
return result2;
|
|
@@ -691,7 +787,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
691
787
|
}
|
|
692
788
|
d.doc = validatedDoc;
|
|
693
789
|
}
|
|
694
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
790
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
695
791
|
const docsToSave = docArgsToSave.map((x) => x.doc);
|
|
696
792
|
await driverInstance.putMany(docsToSave, ctx);
|
|
697
793
|
await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
|
|
@@ -722,8 +818,19 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
722
818
|
}
|
|
723
819
|
if (!options.keepNulls)
|
|
724
820
|
cleanUndefinedAndNull(doc);
|
|
725
|
-
return { oldDoc, doc };
|
|
821
|
+
return { oldDoc, doc, events: [] };
|
|
726
822
|
});
|
|
823
|
+
if (entitySchema.decorators.precomputed?.preset === "eventSourcing") {
|
|
824
|
+
const eventEntity = `${key}Event`;
|
|
825
|
+
const aggregateRelationField = _.lowerFirst(key);
|
|
826
|
+
const eventDriver = computedContext.drivers[eventEntity];
|
|
827
|
+
for (const d of docArgsToSave) {
|
|
828
|
+
d.events = await eventDriver.getAll(
|
|
829
|
+
{ where: { [aggregateRelationField]: { id: d.doc.id } }, orderBy: "date_asc" },
|
|
830
|
+
ctx
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
727
834
|
await handlePrecomputed(computedContext, docArgsToSave, ctx);
|
|
728
835
|
const beforePutResults = await handleEffectsBeforePut(computedContext, docArgsToSave, ctx);
|
|
729
836
|
await beforePut(docArgsToSave, ctx, computedContext);
|
|
@@ -734,7 +841,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
734
841
|
}
|
|
735
842
|
d.doc = validatedDoc;
|
|
736
843
|
}
|
|
737
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
844
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
738
845
|
for (const { oldDoc, doc } of docArgsToSave) {
|
|
739
846
|
const d = diff(doc, oldDoc);
|
|
740
847
|
if (_.isEmpty(d))
|
|
@@ -793,7 +900,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
793
900
|
result2 = await driverInstance.get(args, ctx);
|
|
794
901
|
const resultArray = result2 ? [result2] : [];
|
|
795
902
|
if (result2 && args.include)
|
|
796
|
-
await handleInclude(computedContext, args.include, resultArray, ctx);
|
|
903
|
+
await radsDbRelations.handleInclude(computedContext, args.include, resultArray, ctx);
|
|
797
904
|
if (result2)
|
|
798
905
|
await handleComputed(computedContext, resultArray, ctx);
|
|
799
906
|
await afterGet(resultArray, args, ctx, computedContext);
|
|
@@ -806,7 +913,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
806
913
|
if (!result2)
|
|
807
914
|
result2 = await driverInstance.getAll(args, ctx);
|
|
808
915
|
if (args.include)
|
|
809
|
-
await handleInclude(computedContext, args.include, result2, ctx);
|
|
916
|
+
await radsDbRelations.handleInclude(computedContext, args.include, result2, ctx);
|
|
810
917
|
await handleComputed(computedContext, result2, ctx);
|
|
811
918
|
await afterGet(result2, args, ctx, computedContext);
|
|
812
919
|
return result2;
|
|
@@ -849,7 +956,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
849
956
|
}
|
|
850
957
|
}
|
|
851
958
|
doc = validatedDoc;
|
|
852
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
959
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
853
960
|
await driverInstance.put(doc, ctx);
|
|
854
961
|
await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
|
|
855
962
|
await afterPut(docArgsToSave, ctx, computedContext);
|
|
@@ -884,61 +991,6 @@ async function afterPut(items, ctx, computedContext) {
|
|
|
884
991
|
await f.afterPut?.(items, ctx, computedContext);
|
|
885
992
|
}
|
|
886
993
|
}
|
|
887
|
-
async function handleInclude(computedContext, include, result, ctx) {
|
|
888
|
-
if (!result || !result.length || !include)
|
|
889
|
-
return;
|
|
890
|
-
const { schema, typeName, drivers } = computedContext;
|
|
891
|
-
const fields = schema[typeName].fields || {};
|
|
892
|
-
const relationsToInclude = _.keys(include).filter((key) => include[key] && fields[key].isRelation);
|
|
893
|
-
const downloadRelationsPromises = relationsToInclude.map(async (fieldName) => {
|
|
894
|
-
const typeName2 = fields[fieldName].type;
|
|
895
|
-
const type = schema[typeName2];
|
|
896
|
-
if (!type)
|
|
897
|
-
throw new Error(`Cannot find entity ${typeName2}`);
|
|
898
|
-
const idsToGet = /* @__PURE__ */ new Set();
|
|
899
|
-
for (const item of result) {
|
|
900
|
-
if (!item)
|
|
901
|
-
continue;
|
|
902
|
-
const fieldValue = item[fieldName];
|
|
903
|
-
if (!fieldValue)
|
|
904
|
-
continue;
|
|
905
|
-
const fieldValues = _.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
906
|
-
for (const fv of fieldValues) {
|
|
907
|
-
const id = fv?.id;
|
|
908
|
-
if (!id)
|
|
909
|
-
continue;
|
|
910
|
-
idsToGet.add(id);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
const driverInstance = getExistingDriverInstance(typeName2, drivers);
|
|
914
|
-
const relatedEntities = await driverInstance.getAll({ where: { id_in: [...idsToGet] } }, ctx);
|
|
915
|
-
const relatedEntitiesById = _.keyBy(relatedEntities, "id");
|
|
916
|
-
for (const item of result) {
|
|
917
|
-
if (!item)
|
|
918
|
-
continue;
|
|
919
|
-
const fieldValue = item[fieldName];
|
|
920
|
-
if (!fieldValue)
|
|
921
|
-
continue;
|
|
922
|
-
const fieldValues = _.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
923
|
-
for (const fv of fieldValues) {
|
|
924
|
-
const id = fv?.id;
|
|
925
|
-
if (!id)
|
|
926
|
-
continue;
|
|
927
|
-
const relatedEntity = relatedEntitiesById[id];
|
|
928
|
-
if (!relatedEntity)
|
|
929
|
-
console.warn(`Cannot find ${typeName2} with id "${id}" (for ${typeName2}.${fieldName} with id "${item.id}")`);
|
|
930
|
-
Object.assign(fv, relatedEntity);
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
});
|
|
934
|
-
await Promise.all(downloadRelationsPromises);
|
|
935
|
-
}
|
|
936
|
-
function getExistingDriverInstance(entityName, drivers) {
|
|
937
|
-
if (!drivers[entityName]) {
|
|
938
|
-
throw new Error(`Driver for entity ${entityName} was not found!`);
|
|
939
|
-
}
|
|
940
|
-
return drivers[entityName];
|
|
941
|
-
}
|
|
942
994
|
|
|
943
995
|
function generateMethods(schema, validators, options) {
|
|
944
996
|
const drivers = {};
|
|
@@ -968,7 +1020,7 @@ function generateMethods(schema, validators, options) {
|
|
|
968
1020
|
if (!opts.noComputed) {
|
|
969
1021
|
verifyComputedPresense(schema, opts.computed, effects);
|
|
970
1022
|
}
|
|
971
|
-
verifyRelationsSetup(schema, effects);
|
|
1023
|
+
radsDbRelations.verifyRelationsSetup(schema, effects);
|
|
972
1024
|
const computedContextGlobal = {
|
|
973
1025
|
schema,
|
|
974
1026
|
validators,
|
|
@@ -1136,12 +1188,14 @@ const restApi = (options) => (schema, entity) => {
|
|
|
1136
1188
|
}
|
|
1137
1189
|
return responseJson;
|
|
1138
1190
|
}
|
|
1139
|
-
const
|
|
1191
|
+
const { handlePlural } = schema[entity] || {};
|
|
1192
|
+
if (!handlePlural)
|
|
1193
|
+
throw new Error(`Entity ${entity} was not found in schema`);
|
|
1140
1194
|
const instance = {
|
|
1141
1195
|
driverName: "restApi",
|
|
1142
1196
|
async getMany(args) {
|
|
1143
1197
|
args = args || {};
|
|
1144
|
-
const url = `${opts.baseUrl}/${
|
|
1198
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
1145
1199
|
const fetchOptions = {
|
|
1146
1200
|
method: "POST",
|
|
1147
1201
|
body: JSON.stringify(args),
|
|
@@ -1151,7 +1205,7 @@ const restApi = (options) => (schema, entity) => {
|
|
|
1151
1205
|
return await fetchInner(url, fetchOptions);
|
|
1152
1206
|
},
|
|
1153
1207
|
async putMany(item) {
|
|
1154
|
-
const url = `${opts.baseUrl}/${
|
|
1208
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
1155
1209
|
const fetchOptions = {
|
|
1156
1210
|
method: "PUT",
|
|
1157
1211
|
body: JSON.stringify({ data: item }),
|
package/drivers/restApi.cjs
CHANGED
|
@@ -4,9 +4,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
module.exports = void 0;
|
|
7
|
-
var _lodash = _interopRequireDefault(require("lodash"));
|
|
8
|
-
var _pluralize = _interopRequireDefault(require("pluralize"));
|
|
9
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
10
7
|
var _default = options => (schema, entity) => {
|
|
11
8
|
const opts = {
|
|
12
9
|
baseUrl: "/api",
|
|
@@ -30,12 +27,15 @@ var _default = options => (schema, entity) => {
|
|
|
30
27
|
}
|
|
31
28
|
return responseJson;
|
|
32
29
|
}
|
|
33
|
-
const
|
|
30
|
+
const {
|
|
31
|
+
handlePlural
|
|
32
|
+
} = schema[entity] || {};
|
|
33
|
+
if (!handlePlural) throw new Error(`Entity ${entity} was not found in schema`);
|
|
34
34
|
const instance = {
|
|
35
35
|
driverName: "restApi",
|
|
36
36
|
async getMany(args) {
|
|
37
37
|
args = args || {};
|
|
38
|
-
const url = `${opts.baseUrl}/${
|
|
38
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
39
39
|
const fetchOptions = {
|
|
40
40
|
method: "POST",
|
|
41
41
|
body: JSON.stringify(args),
|
|
@@ -50,7 +50,7 @@ var _default = options => (schema, entity) => {
|
|
|
50
50
|
return await fetchInner(url, fetchOptions);
|
|
51
51
|
},
|
|
52
52
|
async putMany(item) {
|
|
53
|
-
const url = `${opts.baseUrl}/${
|
|
53
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
54
54
|
const fetchOptions = {
|
|
55
55
|
method: "PUT",
|
|
56
56
|
body: JSON.stringify({
|
package/drivers/restApi.mjs
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import _ from "lodash";
|
|
2
|
-
import pluralize from "pluralize";
|
|
3
1
|
export default (options) => (schema, entity) => {
|
|
4
2
|
const opts = {
|
|
5
3
|
baseUrl: "/api",
|
|
@@ -24,12 +22,14 @@ export default (options) => (schema, entity) => {
|
|
|
24
22
|
}
|
|
25
23
|
return responseJson;
|
|
26
24
|
}
|
|
27
|
-
const
|
|
25
|
+
const { handlePlural } = schema[entity] || {};
|
|
26
|
+
if (!handlePlural)
|
|
27
|
+
throw new Error(`Entity ${entity} was not found in schema`);
|
|
28
28
|
const instance = {
|
|
29
29
|
driverName: "restApi",
|
|
30
30
|
async getMany(args) {
|
|
31
31
|
args = args || {};
|
|
32
|
-
const url = `${opts.baseUrl}/${
|
|
32
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
33
33
|
const fetchOptions = {
|
|
34
34
|
method: "POST",
|
|
35
35
|
body: JSON.stringify(args),
|
|
@@ -39,7 +39,7 @@ export default (options) => (schema, entity) => {
|
|
|
39
39
|
return await fetchInner(url, fetchOptions);
|
|
40
40
|
},
|
|
41
41
|
async putMany(item) {
|
|
42
|
-
const url = `${opts.baseUrl}/${
|
|
42
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
43
43
|
const fetchOptions = {
|
|
44
44
|
method: "PUT",
|
|
45
45
|
body: JSON.stringify({ data: item }),
|
package/features/cache.cjs
CHANGED
|
@@ -22,8 +22,7 @@ var _default = options => {
|
|
|
22
22
|
async function preloadEntities(entitiesToPreload) {
|
|
23
23
|
for (const typeName in entitiesToPreload) {
|
|
24
24
|
const {
|
|
25
|
-
where
|
|
26
|
-
revalidateInterval
|
|
25
|
+
where
|
|
27
26
|
} = entitiesToPreload[typeName];
|
|
28
27
|
const handle = schema[typeName]?.handle;
|
|
29
28
|
if (!handle) throw new Error(`Entity ${typeName} was not found`);
|
|
@@ -54,7 +53,7 @@ var _default = options => {
|
|
|
54
53
|
...schema[typeName],
|
|
55
54
|
name: cacheEntityName,
|
|
56
55
|
handle: cacheEntityName,
|
|
57
|
-
handlePlural: `${cacheEntityName}
|
|
56
|
+
handlePlural: `${cacheEntityName}/list`
|
|
58
57
|
};
|
|
59
58
|
cacheDrivers[handle] = (0, _radsDb.getDriverInstance)(schema, cacheEntityName, normalizedOptions[typeName].driver, drivers);
|
|
60
59
|
cacheStatus[handle] = {
|
package/features/cache.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export default (options) => {
|
|
|
11
11
|
const { schema, drivers } = context;
|
|
12
12
|
async function preloadEntities(entitiesToPreload) {
|
|
13
13
|
for (const typeName in entitiesToPreload) {
|
|
14
|
-
const { where
|
|
14
|
+
const { where } = entitiesToPreload[typeName];
|
|
15
15
|
const handle = schema[typeName]?.handle;
|
|
16
16
|
if (!handle)
|
|
17
17
|
throw new Error(`Entity ${typeName} was not found`);
|
|
@@ -36,7 +36,7 @@ export default (options) => {
|
|
|
36
36
|
...schema[typeName],
|
|
37
37
|
name: cacheEntityName,
|
|
38
38
|
handle: cacheEntityName,
|
|
39
|
-
handlePlural: `${cacheEntityName}
|
|
39
|
+
handlePlural: `${cacheEntityName}/list`
|
|
40
40
|
};
|
|
41
41
|
cacheDrivers[handle] = getDriverInstance(schema, cacheEntityName, normalizedOptions[typeName].driver, drivers);
|
|
42
42
|
cacheStatus[handle] = { preloadStatus: normalizedOptions[typeName].preload ? "neverLoaded" : void 0 };
|
package/integrations/lib.cjs
CHANGED
|
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.parseSchema = parseSchema;
|
|
7
7
|
var _typescript = require("typescript");
|
|
8
8
|
var _lodash = _interopRequireDefault(require("lodash"));
|
|
9
|
-
var _pluralize = _interopRequireDefault(require("pluralize"));
|
|
10
9
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
10
|
const supportedPrimitiveTypes = ["string", "number", "boolean", "Record<string, string>", "Record<string, any>"];
|
|
12
11
|
function parseSchema(typescriptFiles) {
|
|
@@ -186,7 +185,7 @@ function parseClassDeclaration(typeDeclaration, typeName, ctx) {
|
|
|
186
185
|
fields[field.name] = field;
|
|
187
186
|
}
|
|
188
187
|
const handle = _lodash.default.lowerFirst(name);
|
|
189
|
-
const handlePlural =
|
|
188
|
+
const handlePlural = `${handle}/list`;
|
|
190
189
|
const result = {
|
|
191
190
|
name,
|
|
192
191
|
handle,
|
|
@@ -268,23 +267,13 @@ function parseClassMember(node, parentFields, parentName, ctx) {
|
|
|
268
267
|
if (!parentField) throw new Error(`Cannot find field ${parentName}.${defaultValueCopyFrom}"`);
|
|
269
268
|
defaultValueType = parentField.type;
|
|
270
269
|
}
|
|
271
|
-
const
|
|
272
|
-
isArray,
|
|
273
|
-
isRelation,
|
|
274
|
-
isChange,
|
|
275
|
-
relationDenormFields,
|
|
276
|
-
type
|
|
277
|
-
} = parseFieldType(ctx, parentName, name, node, defaultValueType);
|
|
270
|
+
const parsedType = parseFieldType(ctx, parentName, name, node, defaultValueType);
|
|
278
271
|
const result = {
|
|
279
|
-
|
|
272
|
+
...parsedType,
|
|
280
273
|
defaultValue,
|
|
281
274
|
defaultValueCopyFrom,
|
|
282
275
|
name,
|
|
283
276
|
isRequired,
|
|
284
|
-
isArray,
|
|
285
|
-
isRelation,
|
|
286
|
-
isChange,
|
|
287
|
-
relationDenormFields,
|
|
288
277
|
comment
|
|
289
278
|
};
|
|
290
279
|
if (!_lodash.default.isEmpty(decorators)) result.decorators = decorators;
|
|
@@ -311,6 +300,7 @@ function parseFieldType(ctx, parentName, fieldName, node, defaultValueType) {
|
|
|
311
300
|
};
|
|
312
301
|
parseFieldTypeArray(parsedType, parentName);
|
|
313
302
|
parseFieldTypeRelation(parsedType, parentName, ctx);
|
|
303
|
+
parseFieldTypeInverseRelation(parsedType, parentName, ctx);
|
|
314
304
|
parseFieldTypeInlineEnum(parsedType, parentName, fieldName, ctx);
|
|
315
305
|
parseFieldTypeKeyofEnum(parsedType, parentName, fieldName, ctx);
|
|
316
306
|
parseFieldTypeRecordEnum(parsedType, parentName, fieldName, ctx);
|
|
@@ -328,8 +318,10 @@ function parseFieldType(ctx, parentName, fieldName, node, defaultValueType) {
|
|
|
328
318
|
return {
|
|
329
319
|
isArray: parsedType.isArray || void 0,
|
|
330
320
|
isRelation: parsedType.isRelation || void 0,
|
|
321
|
+
isInverseRelation: parsedType.isInverseRelation || void 0,
|
|
331
322
|
isChange: parsedType.isChange || void 0,
|
|
332
323
|
relationDenormFields: parsedType.relationDenormFields,
|
|
324
|
+
inverseRelationField: parsedType.inverseRelationField,
|
|
333
325
|
type: parsedType.type
|
|
334
326
|
};
|
|
335
327
|
}
|
|
@@ -382,6 +374,28 @@ function parseFieldTypeRelation(parsedType, parentName, ctx) {
|
|
|
382
374
|
}
|
|
383
375
|
}
|
|
384
376
|
}
|
|
377
|
+
function parseFieldTypeInverseRelation(parsedType, parentName, ctx) {
|
|
378
|
+
if (parsedType.nodeType?.kind !== _typescript.SyntaxKind.TypeReference) return;
|
|
379
|
+
const nt = parsedType.nodeType;
|
|
380
|
+
if (nt.typeName.getText(ctx.sourceFile) === "InverseRelation") {
|
|
381
|
+
if (!nt.typeArguments?.length) throw new Error(`Missing type argument for Relation<>: '${parentName}'`);
|
|
382
|
+
parsedType.type = parseStringField(ctx, nt.typeArguments[0]);
|
|
383
|
+
parsedType.isInverseRelation = true;
|
|
384
|
+
if (nt.typeArguments[1]) {
|
|
385
|
+
const taNode = nt.typeArguments[1];
|
|
386
|
+
parsedType.inverseRelationField = parseStringField(ctx, taNode);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function parseStringField(ctx, node) {
|
|
391
|
+
if (node.kind === _typescript.SyntaxKind.LiteralType) {
|
|
392
|
+
const literal = node.literal;
|
|
393
|
+
if (literal.kind === _typescript.SyntaxKind.StringLiteral) {
|
|
394
|
+
return literal.text;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
throw new Error(`Unexpected type - ${node.getText(ctx.sourceFile)}`);
|
|
398
|
+
}
|
|
385
399
|
function parseFieldTypeInlineEnum(parsedType, parentName, fieldName, ctx) {
|
|
386
400
|
if (parsedType.nodeType?.kind !== _typescript.SyntaxKind.UnionType) return;
|
|
387
401
|
const nt = parsedType.nodeType;
|
|
@@ -564,6 +578,14 @@ function verifyRelationFields(result) {
|
|
|
564
578
|
if (field.isRelation && !result[field.type]?.decorators?.entity) {
|
|
565
579
|
throw new Error(`${key}.${fieldName}: Relation must point to an entity. Got "${field.type}" instead.`);
|
|
566
580
|
}
|
|
581
|
+
if (field.isInverseRelation) {
|
|
582
|
+
if (!result[field.type]?.decorators?.entity) throw new Error(`${key}.${fieldName}: InverseRelation must point to an entity. Got "${field.type}" instead.`);
|
|
583
|
+
if (field.isRequired) throw new Error(`${key}.${fieldName}: InverseRelation cannot be required. Please, add "?" to the field name.`);
|
|
584
|
+
if (!field.isArray) throw new Error(`${key}.${fieldName}: InverseRelation must be an array.`);
|
|
585
|
+
if (!field.inverseRelationField) throw new Error(`${key}.${fieldName}: InverseRelation must have a field name.`);
|
|
586
|
+
if (!result[field.type]?.fields?.[field.inverseRelationField]?.isRelation) throw new Error(`${key}.${fieldName}: InverseRelation field must point to relation field. Got "${field.inverseRelationField}" instead.`);
|
|
587
|
+
if (result[field.type]?.fields?.[field.inverseRelationField]?.type !== key) throw new Error(`${key}.${fieldName}: InverseRelation field must point to the current entity. Got "${field.inverseRelationField}" instead.`);
|
|
588
|
+
}
|
|
567
589
|
}
|
|
568
590
|
}
|
|
569
591
|
}
|
package/integrations/lib.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ScriptTarget, SyntaxKind, createSourceFile } from "typescript";
|
|
2
2
|
import _ from "lodash";
|
|
3
|
-
import pluralize from "pluralize";
|
|
4
3
|
const supportedPrimitiveTypes = ["string", "number", "boolean", "Record<string, string>", "Record<string, any>"];
|
|
5
4
|
export function parseSchema(typescriptFiles) {
|
|
6
5
|
const typeNodesMap = getTypeNodesMap(typescriptFiles);
|
|
@@ -163,7 +162,7 @@ function parseClassDeclaration(typeDeclaration, typeName, ctx) {
|
|
|
163
162
|
fields[field.name] = field;
|
|
164
163
|
}
|
|
165
164
|
const handle = _.lowerFirst(name);
|
|
166
|
-
const handlePlural =
|
|
165
|
+
const handlePlural = `${handle}/list`;
|
|
167
166
|
const result = {
|
|
168
167
|
name,
|
|
169
168
|
handle,
|
|
@@ -232,23 +231,13 @@ function parseClassMember(node, parentFields, parentName, ctx) {
|
|
|
232
231
|
throw new Error(`Cannot find field ${parentName}.${defaultValueCopyFrom}"`);
|
|
233
232
|
defaultValueType = parentField.type;
|
|
234
233
|
}
|
|
235
|
-
const
|
|
236
|
-
ctx,
|
|
237
|
-
parentName,
|
|
238
|
-
name,
|
|
239
|
-
node,
|
|
240
|
-
defaultValueType
|
|
241
|
-
);
|
|
234
|
+
const parsedType = parseFieldType(ctx, parentName, name, node, defaultValueType);
|
|
242
235
|
const result = {
|
|
243
|
-
|
|
236
|
+
...parsedType,
|
|
244
237
|
defaultValue,
|
|
245
238
|
defaultValueCopyFrom,
|
|
246
239
|
name,
|
|
247
240
|
isRequired,
|
|
248
|
-
isArray,
|
|
249
|
-
isRelation,
|
|
250
|
-
isChange,
|
|
251
|
-
relationDenormFields,
|
|
252
241
|
comment
|
|
253
242
|
};
|
|
254
243
|
if (!_.isEmpty(decorators))
|
|
@@ -282,6 +271,7 @@ function parseFieldType(ctx, parentName, fieldName, node, defaultValueType) {
|
|
|
282
271
|
};
|
|
283
272
|
parseFieldTypeArray(parsedType, parentName);
|
|
284
273
|
parseFieldTypeRelation(parsedType, parentName, ctx);
|
|
274
|
+
parseFieldTypeInverseRelation(parsedType, parentName, ctx);
|
|
285
275
|
parseFieldTypeInlineEnum(parsedType, parentName, fieldName, ctx);
|
|
286
276
|
parseFieldTypeKeyofEnum(parsedType, parentName, fieldName, ctx);
|
|
287
277
|
parseFieldTypeRecordEnum(parsedType, parentName, fieldName, ctx);
|
|
@@ -301,8 +291,10 @@ function parseFieldType(ctx, parentName, fieldName, node, defaultValueType) {
|
|
|
301
291
|
return {
|
|
302
292
|
isArray: parsedType.isArray || void 0,
|
|
303
293
|
isRelation: parsedType.isRelation || void 0,
|
|
294
|
+
isInverseRelation: parsedType.isInverseRelation || void 0,
|
|
304
295
|
isChange: parsedType.isChange || void 0,
|
|
305
296
|
relationDenormFields: parsedType.relationDenormFields,
|
|
297
|
+
inverseRelationField: parsedType.inverseRelationField,
|
|
306
298
|
type: parsedType.type
|
|
307
299
|
};
|
|
308
300
|
}
|
|
@@ -360,6 +352,30 @@ function parseFieldTypeRelation(parsedType, parentName, ctx) {
|
|
|
360
352
|
}
|
|
361
353
|
}
|
|
362
354
|
}
|
|
355
|
+
function parseFieldTypeInverseRelation(parsedType, parentName, ctx) {
|
|
356
|
+
if (parsedType.nodeType?.kind !== SyntaxKind.TypeReference)
|
|
357
|
+
return;
|
|
358
|
+
const nt = parsedType.nodeType;
|
|
359
|
+
if (nt.typeName.getText(ctx.sourceFile) === "InverseRelation") {
|
|
360
|
+
if (!nt.typeArguments?.length)
|
|
361
|
+
throw new Error(`Missing type argument for Relation<>: '${parentName}'`);
|
|
362
|
+
parsedType.type = parseStringField(ctx, nt.typeArguments[0]);
|
|
363
|
+
parsedType.isInverseRelation = true;
|
|
364
|
+
if (nt.typeArguments[1]) {
|
|
365
|
+
const taNode = nt.typeArguments[1];
|
|
366
|
+
parsedType.inverseRelationField = parseStringField(ctx, taNode);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function parseStringField(ctx, node) {
|
|
371
|
+
if (node.kind === SyntaxKind.LiteralType) {
|
|
372
|
+
const literal = node.literal;
|
|
373
|
+
if (literal.kind === SyntaxKind.StringLiteral) {
|
|
374
|
+
return literal.text;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
throw new Error(`Unexpected type - ${node.getText(ctx.sourceFile)}`);
|
|
378
|
+
}
|
|
363
379
|
function parseFieldTypeInlineEnum(parsedType, parentName, fieldName, ctx) {
|
|
364
380
|
if (parsedType.nodeType?.kind !== SyntaxKind.UnionType)
|
|
365
381
|
return;
|
|
@@ -537,6 +553,24 @@ function verifyRelationFields(result) {
|
|
|
537
553
|
if (field.isRelation && !result[field.type]?.decorators?.entity) {
|
|
538
554
|
throw new Error(`${key}.${fieldName}: Relation must point to an entity. Got "${field.type}" instead.`);
|
|
539
555
|
}
|
|
556
|
+
if (field.isInverseRelation) {
|
|
557
|
+
if (!result[field.type]?.decorators?.entity)
|
|
558
|
+
throw new Error(`${key}.${fieldName}: InverseRelation must point to an entity. Got "${field.type}" instead.`);
|
|
559
|
+
if (field.isRequired)
|
|
560
|
+
throw new Error(`${key}.${fieldName}: InverseRelation cannot be required. Please, add "?" to the field name.`);
|
|
561
|
+
if (!field.isArray)
|
|
562
|
+
throw new Error(`${key}.${fieldName}: InverseRelation must be an array.`);
|
|
563
|
+
if (!field.inverseRelationField)
|
|
564
|
+
throw new Error(`${key}.${fieldName}: InverseRelation must have a field name.`);
|
|
565
|
+
if (!result[field.type]?.fields?.[field.inverseRelationField]?.isRelation)
|
|
566
|
+
throw new Error(
|
|
567
|
+
`${key}.${fieldName}: InverseRelation field must point to relation field. Got "${field.inverseRelationField}" instead.`
|
|
568
|
+
);
|
|
569
|
+
if (result[field.type]?.fields?.[field.inverseRelationField]?.type !== key)
|
|
570
|
+
throw new Error(
|
|
571
|
+
`${key}.${fieldName}: InverseRelation field must point to the current entity. Got "${field.inverseRelationField}" instead.`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
540
574
|
}
|
|
541
575
|
}
|
|
542
576
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rads-db",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.63",
|
|
4
4
|
"packageManager": "pnpm@8.6.1",
|
|
5
5
|
"description": "Say goodbye to boilerplate code and hello to efficient and elegant syntax.",
|
|
6
6
|
"author": "",
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"ignore": "^5.2.4",
|
|
78
78
|
"klaw": "^4.1.0",
|
|
79
79
|
"lodash": "^4.17.21",
|
|
80
|
-
"pluralize": "^8.0.0",
|
|
81
80
|
"uuid": ">=8",
|
|
82
81
|
"zod": "^3.21.4"
|
|
83
82
|
},
|
|
@@ -87,7 +86,6 @@
|
|
|
87
86
|
"@types/eslint": "^8.44.8",
|
|
88
87
|
"@types/klaw": "^3.0.3",
|
|
89
88
|
"@types/lodash": "4.14.191",
|
|
90
|
-
"@types/pluralize": "^0.0.29",
|
|
91
89
|
"@types/uuid": "^9.0.2",
|
|
92
90
|
"@vitest/ui": "^1.6.0",
|
|
93
91
|
"cross-env": "^7.0.3",
|