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 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
- function cleanUndefinedAndNull(obj, isChange = false) {
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
- cleanUndefinedInner(obj);
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 cleanUndefinedInner(obj) {
621
- for (const key in obj) {
622
- if (obj[key] === void 0)
623
- delete obj[key];
624
- if (___default.isPlainObject(obj[key]))
625
- cleanUndefinedInner(obj[key]);
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
- function diff(object, base, includeDenormFields = false) {
630
- if (!base)
631
- return object;
632
- return ___default.transform(object, (result, value, key) => {
633
- const oldValue = base[key];
634
- if (!___default.isEqual(value, oldValue)) {
635
- if (mustUseDeepDiff(value, oldValue)) {
636
- const isRelationField = (value.id || oldValue.id) && key !== "change";
637
- if (isRelationField && !includeDenormFields) {
638
- if (value.id !== oldValue.id)
639
- result[key] = value;
640
- } else {
641
- const subDiff = diff(value, oldValue, includeDenormFields);
642
- if (!___default.isEmpty(subDiff))
643
- result[key] = subDiff;
644
- }
645
- } else {
646
- result[key] = value;
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 { precomputedFields } = schema[key];
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 pluralEntityName = ___default.lowerFirst(pluralize__default(entity));
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}/${pluralEntityName}`;
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}/${pluralEntityName}`;
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
- function cleanUndefinedAndNull(obj, isChange = false) {
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
- cleanUndefinedInner(obj);
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 cleanUndefinedInner(obj) {
613
- for (const key in obj) {
614
- if (obj[key] === void 0)
615
- delete obj[key];
616
- if (_.isPlainObject(obj[key]))
617
- cleanUndefinedInner(obj[key]);
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
- function diff(object, base, includeDenormFields = false) {
622
- if (!base)
623
- return object;
624
- return _.transform(object, (result, value, key) => {
625
- const oldValue = base[key];
626
- if (!_.isEqual(value, oldValue)) {
627
- if (mustUseDeepDiff(value, oldValue)) {
628
- const isRelationField = (value.id || oldValue.id) && key !== "change";
629
- if (isRelationField && !includeDenormFields) {
630
- if (value.id !== oldValue.id)
631
- result[key] = value;
632
- } else {
633
- const subDiff = diff(value, oldValue, includeDenormFields);
634
- if (!_.isEmpty(subDiff))
635
- result[key] = subDiff;
636
- }
637
- } else {
638
- result[key] = value;
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 { precomputedFields } = schema[key];
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 pluralEntityName = _.lowerFirst(pluralize(entity));
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}/${pluralEntityName}`;
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}/${pluralEntityName}`;
1208
+ const url = `${opts.baseUrl}/${handlePlural}`;
1155
1209
  const fetchOptions = {
1156
1210
  method: "PUT",
1157
1211
  body: JSON.stringify({ data: item }),
@@ -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 pluralEntityName = _lodash.default.lowerFirst((0, _pluralize.default)(entity));
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}/${pluralEntityName}`;
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}/${pluralEntityName}`;
53
+ const url = `${opts.baseUrl}/${handlePlural}`;
54
54
  const fetchOptions = {
55
55
  method: "PUT",
56
56
  body: JSON.stringify({
@@ -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 pluralEntityName = _.lowerFirst(pluralize(entity));
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}/${pluralEntityName}`;
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}/${pluralEntityName}`;
42
+ const url = `${opts.baseUrl}/${handlePlural}`;
43
43
  const fetchOptions = {
44
44
  method: "PUT",
45
45
  body: JSON.stringify({ data: item }),
@@ -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}s`
56
+ handlePlural: `${cacheEntityName}/list`
58
57
  };
59
58
  cacheDrivers[handle] = (0, _radsDb.getDriverInstance)(schema, cacheEntityName, normalizedOptions[typeName].driver, drivers);
60
59
  cacheStatus[handle] = {
@@ -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, revalidateInterval } = entitiesToPreload[typeName];
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}s`
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 };
@@ -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 = (0, _pluralize.default)(handle);
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
- type,
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
  }
@@ -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 = pluralize(handle);
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 { isArray, isRelation, isChange, relationDenormFields, type } = parseFieldType(
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
- type,
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.59",
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",