rads-db 3.0.58 → 3.0.62
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 +184 -124
- package/dist/index.d.ts +11 -1
- package/dist/index.mjs +185 -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);
|
|
@@ -82,7 +80,7 @@ function getFieldZodSchemaBase(zodSchemas, schema, field) {
|
|
|
82
80
|
const zodSchema = getZodSchema(zodSchemas, schema, field.type);
|
|
83
81
|
if (!zodSchema)
|
|
84
82
|
throw new Error(`Cannot find zod schema for type: ${field.type}`);
|
|
85
|
-
return
|
|
83
|
+
return deepPartial(zodSchema.omit({ id: true }));
|
|
86
84
|
}
|
|
87
85
|
if (field.type === "string")
|
|
88
86
|
return zod.z.string();
|
|
@@ -106,6 +104,25 @@ function getFieldZodSchemaBase(zodSchemas, schema, field) {
|
|
|
106
104
|
}
|
|
107
105
|
throw new Error(`Unknown type: ${field.type}`);
|
|
108
106
|
}
|
|
107
|
+
function deepPartial(zodSchema) {
|
|
108
|
+
const shape = zodSchema.shape;
|
|
109
|
+
const newShape = {};
|
|
110
|
+
for (const key in shape) {
|
|
111
|
+
let field = shape[key];
|
|
112
|
+
while (field instanceof zod.ZodNullable || field instanceof zod.ZodOptional) {
|
|
113
|
+
field = field.unwrap();
|
|
114
|
+
}
|
|
115
|
+
if (field instanceof zod.ZodLazy) {
|
|
116
|
+
field = field.schema;
|
|
117
|
+
}
|
|
118
|
+
if (field instanceof zod.ZodObject) {
|
|
119
|
+
newShape[key] = deepPartial(field).optional().nullable();
|
|
120
|
+
} else {
|
|
121
|
+
newShape[key] = field.optional().nullable();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return zod.z.object(newShape);
|
|
125
|
+
}
|
|
109
126
|
class ValidationError extends Error {
|
|
110
127
|
constructor(error) {
|
|
111
128
|
const firstError = error.errors[0];
|
|
@@ -491,6 +508,73 @@ async function handleEffectsAfterPut(context, docs, beforePutResults, ctx) {
|
|
|
491
508
|
);
|
|
492
509
|
}
|
|
493
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
|
+
};
|
|
494
578
|
function verifyRelationsSetup(schema, effects) {
|
|
495
579
|
for (const entityName in schema) {
|
|
496
580
|
const entity = schema[entityName];
|
|
@@ -570,67 +654,96 @@ async function fillDenormFieldsBeforePut(computedContext, docUpdates, ctx) {
|
|
|
570
654
|
});
|
|
571
655
|
await Promise.all(promises);
|
|
572
656
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
for (const key in obj) {
|
|
576
|
-
if (obj[key] == null && !isChange)
|
|
577
|
-
delete obj[key];
|
|
578
|
-
if (___default.isPlainObject(obj[key]))
|
|
579
|
-
cleanUndefinedAndNull(obj[key], key === "change");
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
const mergeFn = createMerge__default({
|
|
584
|
-
mergeArray(options) {
|
|
585
|
-
const clone = options.clone;
|
|
586
|
-
return function(target, source) {
|
|
587
|
-
return clone(source);
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
});
|
|
591
|
-
function merge(target, source) {
|
|
592
|
-
cleanUndefined(source);
|
|
593
|
-
const result = mergeFn(target, source);
|
|
594
|
-
return result;
|
|
595
|
-
}
|
|
596
|
-
function cleanUndefined(obj) {
|
|
597
|
-
if (!obj || !___default.isPlainObject(obj))
|
|
657
|
+
async function handleInclude(computedContext, include, result, ctx) {
|
|
658
|
+
if (!result || !result.length || !include)
|
|
598
659
|
return;
|
|
599
|
-
|
|
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]);
|
|
600
679
|
}
|
|
601
|
-
function
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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;
|
|
607
699
|
}
|
|
608
700
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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}")`);
|
|
628
743
|
}
|
|
744
|
+
Object.assign(fv, relatedEntity);
|
|
629
745
|
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
function mustUseDeepDiff(value, oldValue) {
|
|
633
|
-
return ___default.isObject(value) && ___default.isObject(oldValue) && !___default.isArray(value) && !___default.isArray(oldValue);
|
|
746
|
+
}
|
|
634
747
|
}
|
|
635
748
|
|
|
636
749
|
function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
@@ -643,7 +756,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
643
756
|
if (!result2)
|
|
644
757
|
result2 = await driverInstance.getMany(args, ctx);
|
|
645
758
|
if (args.include)
|
|
646
|
-
await handleInclude(computedContext, args.include, result2.nodes, ctx);
|
|
759
|
+
await radsDbRelations.handleInclude(computedContext, args.include, result2.nodes, ctx);
|
|
647
760
|
await handleComputed(computedContext, result2.nodes, ctx);
|
|
648
761
|
await afterGet(result2.nodes, args, ctx, computedContext);
|
|
649
762
|
return result2;
|
|
@@ -680,7 +793,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
680
793
|
}
|
|
681
794
|
d.doc = validatedDoc;
|
|
682
795
|
}
|
|
683
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
796
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
684
797
|
const docsToSave = docArgsToSave.map((x) => x.doc);
|
|
685
798
|
await driverInstance.putMany(docsToSave, ctx);
|
|
686
799
|
await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
|
|
@@ -723,7 +836,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
723
836
|
}
|
|
724
837
|
d.doc = validatedDoc;
|
|
725
838
|
}
|
|
726
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
839
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
727
840
|
for (const { oldDoc, doc } of docArgsToSave) {
|
|
728
841
|
const d = diff(doc, oldDoc);
|
|
729
842
|
if (___default.isEmpty(d))
|
|
@@ -782,7 +895,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
782
895
|
result2 = await driverInstance.get(args, ctx);
|
|
783
896
|
const resultArray = result2 ? [result2] : [];
|
|
784
897
|
if (result2 && args.include)
|
|
785
|
-
await handleInclude(computedContext, args.include, resultArray, ctx);
|
|
898
|
+
await radsDbRelations.handleInclude(computedContext, args.include, resultArray, ctx);
|
|
786
899
|
if (result2)
|
|
787
900
|
await handleComputed(computedContext, resultArray, ctx);
|
|
788
901
|
await afterGet(resultArray, args, ctx, computedContext);
|
|
@@ -795,7 +908,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
795
908
|
if (!result2)
|
|
796
909
|
result2 = await driverInstance.getAll(args, ctx);
|
|
797
910
|
if (args.include)
|
|
798
|
-
await handleInclude(computedContext, args.include, result2, ctx);
|
|
911
|
+
await radsDbRelations.handleInclude(computedContext, args.include, result2, ctx);
|
|
799
912
|
await handleComputed(computedContext, result2, ctx);
|
|
800
913
|
await afterGet(result2, args, ctx, computedContext);
|
|
801
914
|
return result2;
|
|
@@ -838,7 +951,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
838
951
|
}
|
|
839
952
|
}
|
|
840
953
|
doc = validatedDoc;
|
|
841
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
954
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
842
955
|
await driverInstance.put(doc, ctx);
|
|
843
956
|
await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
|
|
844
957
|
await afterPut(docArgsToSave, ctx, computedContext);
|
|
@@ -873,61 +986,6 @@ async function afterPut(items, ctx, computedContext) {
|
|
|
873
986
|
await f.afterPut?.(items, ctx, computedContext);
|
|
874
987
|
}
|
|
875
988
|
}
|
|
876
|
-
async function handleInclude(computedContext, include, result, ctx) {
|
|
877
|
-
if (!result || !result.length || !include)
|
|
878
|
-
return;
|
|
879
|
-
const { schema, typeName, drivers } = computedContext;
|
|
880
|
-
const fields = schema[typeName].fields || {};
|
|
881
|
-
const relationsToInclude = ___default.keys(include).filter((key) => include[key] && fields[key].isRelation);
|
|
882
|
-
const downloadRelationsPromises = relationsToInclude.map(async (fieldName) => {
|
|
883
|
-
const typeName2 = fields[fieldName].type;
|
|
884
|
-
const type = schema[typeName2];
|
|
885
|
-
if (!type)
|
|
886
|
-
throw new Error(`Cannot find entity ${typeName2}`);
|
|
887
|
-
const idsToGet = /* @__PURE__ */ new Set();
|
|
888
|
-
for (const item of result) {
|
|
889
|
-
if (!item)
|
|
890
|
-
continue;
|
|
891
|
-
const fieldValue = item[fieldName];
|
|
892
|
-
if (!fieldValue)
|
|
893
|
-
continue;
|
|
894
|
-
const fieldValues = ___default.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
895
|
-
for (const fv of fieldValues) {
|
|
896
|
-
const id = fv?.id;
|
|
897
|
-
if (!id)
|
|
898
|
-
continue;
|
|
899
|
-
idsToGet.add(id);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
const driverInstance = getExistingDriverInstance(typeName2, drivers);
|
|
903
|
-
const relatedEntities = await driverInstance.getAll({ where: { id_in: [...idsToGet] } }, ctx);
|
|
904
|
-
const relatedEntitiesById = ___default.keyBy(relatedEntities, "id");
|
|
905
|
-
for (const item of result) {
|
|
906
|
-
if (!item)
|
|
907
|
-
continue;
|
|
908
|
-
const fieldValue = item[fieldName];
|
|
909
|
-
if (!fieldValue)
|
|
910
|
-
continue;
|
|
911
|
-
const fieldValues = ___default.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
912
|
-
for (const fv of fieldValues) {
|
|
913
|
-
const id = fv?.id;
|
|
914
|
-
if (!id)
|
|
915
|
-
continue;
|
|
916
|
-
const relatedEntity = relatedEntitiesById[id];
|
|
917
|
-
if (!relatedEntity)
|
|
918
|
-
console.warn(`Cannot find ${typeName2} with id "${id}" (for ${typeName2}.${fieldName} with id "${item.id}")`);
|
|
919
|
-
Object.assign(fv, relatedEntity);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
});
|
|
923
|
-
await Promise.all(downloadRelationsPromises);
|
|
924
|
-
}
|
|
925
|
-
function getExistingDriverInstance(entityName, drivers) {
|
|
926
|
-
if (!drivers[entityName]) {
|
|
927
|
-
throw new Error(`Driver for entity ${entityName} was not found!`);
|
|
928
|
-
}
|
|
929
|
-
return drivers[entityName];
|
|
930
|
-
}
|
|
931
989
|
|
|
932
990
|
function generateMethods(schema, validators, options) {
|
|
933
991
|
const drivers = {};
|
|
@@ -957,7 +1015,7 @@ function generateMethods(schema, validators, options) {
|
|
|
957
1015
|
if (!opts.noComputed) {
|
|
958
1016
|
verifyComputedPresense(schema, opts.computed, effects);
|
|
959
1017
|
}
|
|
960
|
-
verifyRelationsSetup(schema, effects);
|
|
1018
|
+
radsDbRelations.verifyRelationsSetup(schema, effects);
|
|
961
1019
|
const computedContextGlobal = {
|
|
962
1020
|
schema,
|
|
963
1021
|
validators,
|
|
@@ -1125,12 +1183,14 @@ const restApi = (options) => (schema, entity) => {
|
|
|
1125
1183
|
}
|
|
1126
1184
|
return responseJson;
|
|
1127
1185
|
}
|
|
1128
|
-
const
|
|
1186
|
+
const { handlePlural } = schema[entity] || {};
|
|
1187
|
+
if (!handlePlural)
|
|
1188
|
+
throw new Error(`Entity ${entity} was not found in schema`);
|
|
1129
1189
|
const instance = {
|
|
1130
1190
|
driverName: "restApi",
|
|
1131
1191
|
async getMany(args) {
|
|
1132
1192
|
args = args || {};
|
|
1133
|
-
const url = `${opts.baseUrl}/${
|
|
1193
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
1134
1194
|
const fetchOptions = {
|
|
1135
1195
|
method: "POST",
|
|
1136
1196
|
body: JSON.stringify(args),
|
|
@@ -1140,7 +1200,7 @@ const restApi = (options) => (schema, entity) => {
|
|
|
1140
1200
|
return await fetchInner(url, fetchOptions);
|
|
1141
1201
|
},
|
|
1142
1202
|
async putMany(item) {
|
|
1143
|
-
const url = `${opts.baseUrl}/${
|
|
1203
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
1144
1204
|
const fetchOptions = {
|
|
1145
1205
|
method: "PUT",
|
|
1146
1206
|
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
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { z, ZodError } from 'zod';
|
|
1
|
+
import { z, ZodNullable, ZodOptional, ZodLazy, ZodObject, ZodError } from 'zod';
|
|
2
2
|
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);
|
|
@@ -74,7 +73,7 @@ function getFieldZodSchemaBase(zodSchemas, schema, field) {
|
|
|
74
73
|
const zodSchema = getZodSchema(zodSchemas, schema, field.type);
|
|
75
74
|
if (!zodSchema)
|
|
76
75
|
throw new Error(`Cannot find zod schema for type: ${field.type}`);
|
|
77
|
-
return
|
|
76
|
+
return deepPartial(zodSchema.omit({ id: true }));
|
|
78
77
|
}
|
|
79
78
|
if (field.type === "string")
|
|
80
79
|
return z.string();
|
|
@@ -98,6 +97,25 @@ function getFieldZodSchemaBase(zodSchemas, schema, field) {
|
|
|
98
97
|
}
|
|
99
98
|
throw new Error(`Unknown type: ${field.type}`);
|
|
100
99
|
}
|
|
100
|
+
function deepPartial(zodSchema) {
|
|
101
|
+
const shape = zodSchema.shape;
|
|
102
|
+
const newShape = {};
|
|
103
|
+
for (const key in shape) {
|
|
104
|
+
let field = shape[key];
|
|
105
|
+
while (field instanceof ZodNullable || field instanceof ZodOptional) {
|
|
106
|
+
field = field.unwrap();
|
|
107
|
+
}
|
|
108
|
+
if (field instanceof ZodLazy) {
|
|
109
|
+
field = field.schema;
|
|
110
|
+
}
|
|
111
|
+
if (field instanceof ZodObject) {
|
|
112
|
+
newShape[key] = deepPartial(field).optional().nullable();
|
|
113
|
+
} else {
|
|
114
|
+
newShape[key] = field.optional().nullable();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return z.object(newShape);
|
|
118
|
+
}
|
|
101
119
|
class ValidationError extends Error {
|
|
102
120
|
constructor(error) {
|
|
103
121
|
const firstError = error.errors[0];
|
|
@@ -483,6 +501,73 @@ async function handleEffectsAfterPut(context, docs, beforePutResults, ctx) {
|
|
|
483
501
|
);
|
|
484
502
|
}
|
|
485
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
|
+
};
|
|
486
571
|
function verifyRelationsSetup(schema, effects) {
|
|
487
572
|
for (const entityName in schema) {
|
|
488
573
|
const entity = schema[entityName];
|
|
@@ -562,67 +647,96 @@ async function fillDenormFieldsBeforePut(computedContext, docUpdates, ctx) {
|
|
|
562
647
|
});
|
|
563
648
|
await Promise.all(promises);
|
|
564
649
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
for (const key in obj) {
|
|
568
|
-
if (obj[key] == null && !isChange)
|
|
569
|
-
delete obj[key];
|
|
570
|
-
if (_.isPlainObject(obj[key]))
|
|
571
|
-
cleanUndefinedAndNull(obj[key], key === "change");
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
const mergeFn = createMerge({
|
|
576
|
-
mergeArray(options) {
|
|
577
|
-
const clone = options.clone;
|
|
578
|
-
return function(target, source) {
|
|
579
|
-
return clone(source);
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
});
|
|
583
|
-
function merge(target, source) {
|
|
584
|
-
cleanUndefined(source);
|
|
585
|
-
const result = mergeFn(target, source);
|
|
586
|
-
return result;
|
|
587
|
-
}
|
|
588
|
-
function cleanUndefined(obj) {
|
|
589
|
-
if (!obj || !_.isPlainObject(obj))
|
|
650
|
+
async function handleInclude(computedContext, include, result, ctx) {
|
|
651
|
+
if (!result || !result.length || !include)
|
|
590
652
|
return;
|
|
591
|
-
|
|
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]);
|
|
592
672
|
}
|
|
593
|
-
function
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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;
|
|
599
692
|
}
|
|
600
693
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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}")`);
|
|
620
736
|
}
|
|
737
|
+
Object.assign(fv, relatedEntity);
|
|
621
738
|
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
function mustUseDeepDiff(value, oldValue) {
|
|
625
|
-
return _.isObject(value) && _.isObject(oldValue) && !_.isArray(value) && !_.isArray(oldValue);
|
|
739
|
+
}
|
|
626
740
|
}
|
|
627
741
|
|
|
628
742
|
function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
@@ -635,7 +749,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
635
749
|
if (!result2)
|
|
636
750
|
result2 = await driverInstance.getMany(args, ctx);
|
|
637
751
|
if (args.include)
|
|
638
|
-
await handleInclude(computedContext, args.include, result2.nodes, ctx);
|
|
752
|
+
await radsDbRelations.handleInclude(computedContext, args.include, result2.nodes, ctx);
|
|
639
753
|
await handleComputed(computedContext, result2.nodes, ctx);
|
|
640
754
|
await afterGet(result2.nodes, args, ctx, computedContext);
|
|
641
755
|
return result2;
|
|
@@ -672,7 +786,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
672
786
|
}
|
|
673
787
|
d.doc = validatedDoc;
|
|
674
788
|
}
|
|
675
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
789
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
676
790
|
const docsToSave = docArgsToSave.map((x) => x.doc);
|
|
677
791
|
await driverInstance.putMany(docsToSave, ctx);
|
|
678
792
|
await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
|
|
@@ -715,7 +829,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
715
829
|
}
|
|
716
830
|
d.doc = validatedDoc;
|
|
717
831
|
}
|
|
718
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
832
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
719
833
|
for (const { oldDoc, doc } of docArgsToSave) {
|
|
720
834
|
const d = diff(doc, oldDoc);
|
|
721
835
|
if (_.isEmpty(d))
|
|
@@ -774,7 +888,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
774
888
|
result2 = await driverInstance.get(args, ctx);
|
|
775
889
|
const resultArray = result2 ? [result2] : [];
|
|
776
890
|
if (result2 && args.include)
|
|
777
|
-
await handleInclude(computedContext, args.include, resultArray, ctx);
|
|
891
|
+
await radsDbRelations.handleInclude(computedContext, args.include, resultArray, ctx);
|
|
778
892
|
if (result2)
|
|
779
893
|
await handleComputed(computedContext, resultArray, ctx);
|
|
780
894
|
await afterGet(resultArray, args, ctx, computedContext);
|
|
@@ -787,7 +901,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
787
901
|
if (!result2)
|
|
788
902
|
result2 = await driverInstance.getAll(args, ctx);
|
|
789
903
|
if (args.include)
|
|
790
|
-
await handleInclude(computedContext, args.include, result2, ctx);
|
|
904
|
+
await radsDbRelations.handleInclude(computedContext, args.include, result2, ctx);
|
|
791
905
|
await handleComputed(computedContext, result2, ctx);
|
|
792
906
|
await afterGet(result2, args, ctx, computedContext);
|
|
793
907
|
return result2;
|
|
@@ -830,7 +944,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
|
|
|
830
944
|
}
|
|
831
945
|
}
|
|
832
946
|
doc = validatedDoc;
|
|
833
|
-
await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
947
|
+
await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
|
|
834
948
|
await driverInstance.put(doc, ctx);
|
|
835
949
|
await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
|
|
836
950
|
await afterPut(docArgsToSave, ctx, computedContext);
|
|
@@ -865,61 +979,6 @@ async function afterPut(items, ctx, computedContext) {
|
|
|
865
979
|
await f.afterPut?.(items, ctx, computedContext);
|
|
866
980
|
}
|
|
867
981
|
}
|
|
868
|
-
async function handleInclude(computedContext, include, result, ctx) {
|
|
869
|
-
if (!result || !result.length || !include)
|
|
870
|
-
return;
|
|
871
|
-
const { schema, typeName, drivers } = computedContext;
|
|
872
|
-
const fields = schema[typeName].fields || {};
|
|
873
|
-
const relationsToInclude = _.keys(include).filter((key) => include[key] && fields[key].isRelation);
|
|
874
|
-
const downloadRelationsPromises = relationsToInclude.map(async (fieldName) => {
|
|
875
|
-
const typeName2 = fields[fieldName].type;
|
|
876
|
-
const type = schema[typeName2];
|
|
877
|
-
if (!type)
|
|
878
|
-
throw new Error(`Cannot find entity ${typeName2}`);
|
|
879
|
-
const idsToGet = /* @__PURE__ */ new Set();
|
|
880
|
-
for (const item of result) {
|
|
881
|
-
if (!item)
|
|
882
|
-
continue;
|
|
883
|
-
const fieldValue = item[fieldName];
|
|
884
|
-
if (!fieldValue)
|
|
885
|
-
continue;
|
|
886
|
-
const fieldValues = _.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
887
|
-
for (const fv of fieldValues) {
|
|
888
|
-
const id = fv?.id;
|
|
889
|
-
if (!id)
|
|
890
|
-
continue;
|
|
891
|
-
idsToGet.add(id);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
const driverInstance = getExistingDriverInstance(typeName2, drivers);
|
|
895
|
-
const relatedEntities = await driverInstance.getAll({ where: { id_in: [...idsToGet] } }, ctx);
|
|
896
|
-
const relatedEntitiesById = _.keyBy(relatedEntities, "id");
|
|
897
|
-
for (const item of result) {
|
|
898
|
-
if (!item)
|
|
899
|
-
continue;
|
|
900
|
-
const fieldValue = item[fieldName];
|
|
901
|
-
if (!fieldValue)
|
|
902
|
-
continue;
|
|
903
|
-
const fieldValues = _.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
904
|
-
for (const fv of fieldValues) {
|
|
905
|
-
const id = fv?.id;
|
|
906
|
-
if (!id)
|
|
907
|
-
continue;
|
|
908
|
-
const relatedEntity = relatedEntitiesById[id];
|
|
909
|
-
if (!relatedEntity)
|
|
910
|
-
console.warn(`Cannot find ${typeName2} with id "${id}" (for ${typeName2}.${fieldName} with id "${item.id}")`);
|
|
911
|
-
Object.assign(fv, relatedEntity);
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
});
|
|
915
|
-
await Promise.all(downloadRelationsPromises);
|
|
916
|
-
}
|
|
917
|
-
function getExistingDriverInstance(entityName, drivers) {
|
|
918
|
-
if (!drivers[entityName]) {
|
|
919
|
-
throw new Error(`Driver for entity ${entityName} was not found!`);
|
|
920
|
-
}
|
|
921
|
-
return drivers[entityName];
|
|
922
|
-
}
|
|
923
982
|
|
|
924
983
|
function generateMethods(schema, validators, options) {
|
|
925
984
|
const drivers = {};
|
|
@@ -949,7 +1008,7 @@ function generateMethods(schema, validators, options) {
|
|
|
949
1008
|
if (!opts.noComputed) {
|
|
950
1009
|
verifyComputedPresense(schema, opts.computed, effects);
|
|
951
1010
|
}
|
|
952
|
-
verifyRelationsSetup(schema, effects);
|
|
1011
|
+
radsDbRelations.verifyRelationsSetup(schema, effects);
|
|
953
1012
|
const computedContextGlobal = {
|
|
954
1013
|
schema,
|
|
955
1014
|
validators,
|
|
@@ -1117,12 +1176,14 @@ const restApi = (options) => (schema, entity) => {
|
|
|
1117
1176
|
}
|
|
1118
1177
|
return responseJson;
|
|
1119
1178
|
}
|
|
1120
|
-
const
|
|
1179
|
+
const { handlePlural } = schema[entity] || {};
|
|
1180
|
+
if (!handlePlural)
|
|
1181
|
+
throw new Error(`Entity ${entity} was not found in schema`);
|
|
1121
1182
|
const instance = {
|
|
1122
1183
|
driverName: "restApi",
|
|
1123
1184
|
async getMany(args) {
|
|
1124
1185
|
args = args || {};
|
|
1125
|
-
const url = `${opts.baseUrl}/${
|
|
1186
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
1126
1187
|
const fetchOptions = {
|
|
1127
1188
|
method: "POST",
|
|
1128
1189
|
body: JSON.stringify(args),
|
|
@@ -1132,7 +1193,7 @@ const restApi = (options) => (schema, entity) => {
|
|
|
1132
1193
|
return await fetchInner(url, fetchOptions);
|
|
1133
1194
|
},
|
|
1134
1195
|
async putMany(item) {
|
|
1135
|
-
const url = `${opts.baseUrl}/${
|
|
1196
|
+
const url = `${opts.baseUrl}/${handlePlural}`;
|
|
1136
1197
|
const fetchOptions = {
|
|
1137
1198
|
method: "PUT",
|
|
1138
1199
|
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.62",
|
|
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",
|